From f621472964f88d444797bbd5f8df5b4bf2ce1bf2 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:24 +0000 Subject: [PATCH 01/17] chore(api): snapshot public export names before surface cleanup Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- packages/eve/public-api/README.md | 23 ++ packages/eve/public-api/eve.json | 27 ++ packages/eve/public-api/eve/agents/auth.json | 1 + packages/eve/public-api/eve/ai.json | 1 + packages/eve/public-api/eve/channels.json | 87 ++++++ .../eve/public-api/eve/channels/auth.json | 45 +++ .../eve/public-api/eve/channels/chat-sdk.json | 16 ++ .../eve/public-api/eve/channels/discord.json | 70 +++++ packages/eve/public-api/eve/channels/eve.json | 16 ++ .../eve/public-api/eve/channels/github.json | 48 ++++ .../eve/public-api/eve/channels/linear.json | 55 ++++ .../eve/public-api/eve/channels/linq.json | 11 + packages/eve/public-api/eve/channels/mcp.json | 1 + .../eve/public-api/eve/channels/photon.json | 10 + .../eve/public-api/eve/channels/slack.json | 158 +++++++++++ .../eve/public-api/eve/channels/teams.json | 91 ++++++ .../eve/public-api/eve/channels/telegram.json | 69 +++++ .../eve/public-api/eve/channels/twilio.json | 51 ++++ packages/eve/public-api/eve/client.json | 268 ++++++++++++++++++ packages/eve/public-api/eve/computer-use.json | 1 + .../public-api/eve/computer-use/sandbox.json | 1 + .../public-api/eve/computer-use/tools.json | 1 + packages/eve/public-api/eve/connections.json | 44 +++ packages/eve/public-api/eve/context.json | 11 + packages/eve/public-api/eve/evals.json | 71 +++++ packages/eve/public-api/eve/evals/expect.json | 11 + .../eve/public-api/eve/evals/loaders.json | 1 + .../eve/public-api/eve/evals/reporters.json | 14 + packages/eve/public-api/eve/events.json | 203 +++++++++++++ .../eve/experimental/schedules.json | 33 +++ .../eve/experimental/schedules/testing.json | 1 + .../eve/experimental/schedules/vercel.json | 1 + packages/eve/public-api/eve/extension.json | 1 + .../eve/public-api/eve/extensions/code.json | 1 + .../eve/extensions/code/prwatch.json | 29 ++ .../eve/extensions/code/sandbox.json | 15 + .../public-api/eve/extensions/code/tools.json | 1 + .../eve/public-api/eve/extensions/git.json | 1 + .../eve/extensions/git/sandbox.json | 16 ++ .../public-api/eve/extensions/git/tools.json | 1 + packages/eve/public-api/eve/hooks.json | 11 + packages/eve/public-api/eve/instructions.json | 9 + .../eve/public-api/eve/instrumentation.json | 32 +++ .../public-api/eve/instrumentation/otel.json | 23 ++ packages/eve/public-api/eve/local-dev.json | 1 + packages/eve/public-api/eve/memory.json | 28 ++ packages/eve/public-api/eve/memory/file.json | 10 + .../public-api/eve/memory/file/vercel.json | 1 + packages/eve/public-api/eve/memory/scope.json | 1 + packages/eve/public-api/eve/models.json | 1 + .../eve/public-api/eve/models/anthropic.json | 1 + .../eve/public-api/eve/models/openai.json | 1 + packages/eve/public-api/eve/next.json | 13 + packages/eve/public-api/eve/nuxt.json | 1 + packages/eve/public-api/eve/react.json | 21 ++ packages/eve/public-api/eve/sandbox.json | 25 ++ .../eve/public-api/eve/sandbox/docker.json | 8 + .../eve/public-api/eve/sandbox/just-bash.json | 1 + .../public-api/eve/sandbox/microsandbox.json | 6 + .../eve/public-api/eve/sandbox/provider.json | 17 ++ .../eve/public-api/eve/sandbox/vercel.json | 10 + packages/eve/public-api/eve/schedules.json | 9 + .../eve/public-api/eve/self-modification.json | 1 + .../eve/self-modification/agent.json | 1 + .../eve/self-modification/config.json | 8 + .../eve/self-modification/local.json | 1 + .../eve/self-modification/remote.json | 1 + .../eve/self-modification/sandbox.json | 6 + packages/eve/public-api/eve/server.json | 1 + packages/eve/public-api/eve/setup.json | 79 ++++++ .../eve/public-api/eve/setup/scaffold.json | 56 ++++ packages/eve/public-api/eve/skills.json | 10 + packages/eve/public-api/eve/svelte.json | 21 ++ packages/eve/public-api/eve/sveltekit.json | 1 + packages/eve/public-api/eve/tanstack.json | 1 + packages/eve/public-api/eve/tools.json | 53 ++++ .../public-api/eve/tools/agent-router.json | 1 + packages/eve/public-api/eve/tools/agent.json | 1 + .../eve/public-api/eve/tools/approval.json | 22 ++ .../public-api/eve/tools/ask_question.json | 1 + packages/eve/public-api/eve/tools/bash.json | 1 + packages/eve/public-api/eve/tools/glob.json | 1 + packages/eve/public-api/eve/tools/grep.json | 1 + .../eve/public-api/eve/tools/no_reply.json | 1 + .../eve/public-api/eve/tools/read_file.json | 8 + packages/eve/public-api/eve/tools/sleep.json | 1 + .../eve/public-api/eve/tools/web_fetch.json | 8 + .../eve/public-api/eve/tools/web_search.json | 10 + .../eve/public-api/eve/tools/workflow.json | 1 + .../eve/public-api/eve/tools/write_file.json | 8 + packages/eve/public-api/eve/vercel.json | 1 + packages/eve/public-api/eve/vue.json | 21 ++ .../eve/public-api/eve/workflow-modules.json | 1 + scripts/guard-invariants.mjs | 8 + scripts/public-exports.mjs | 109 +++++++ 95 files changed, 2181 insertions(+) create mode 100644 packages/eve/public-api/README.md create mode 100644 packages/eve/public-api/eve.json create mode 100644 packages/eve/public-api/eve/agents/auth.json create mode 100644 packages/eve/public-api/eve/ai.json create mode 100644 packages/eve/public-api/eve/channels.json create mode 100644 packages/eve/public-api/eve/channels/auth.json create mode 100644 packages/eve/public-api/eve/channels/chat-sdk.json create mode 100644 packages/eve/public-api/eve/channels/discord.json create mode 100644 packages/eve/public-api/eve/channels/eve.json create mode 100644 packages/eve/public-api/eve/channels/github.json create mode 100644 packages/eve/public-api/eve/channels/linear.json create mode 100644 packages/eve/public-api/eve/channels/linq.json create mode 100644 packages/eve/public-api/eve/channels/mcp.json create mode 100644 packages/eve/public-api/eve/channels/photon.json create mode 100644 packages/eve/public-api/eve/channels/slack.json create mode 100644 packages/eve/public-api/eve/channels/teams.json create mode 100644 packages/eve/public-api/eve/channels/telegram.json create mode 100644 packages/eve/public-api/eve/channels/twilio.json create mode 100644 packages/eve/public-api/eve/client.json create mode 100644 packages/eve/public-api/eve/computer-use.json create mode 100644 packages/eve/public-api/eve/computer-use/sandbox.json create mode 100644 packages/eve/public-api/eve/computer-use/tools.json create mode 100644 packages/eve/public-api/eve/connections.json create mode 100644 packages/eve/public-api/eve/context.json create mode 100644 packages/eve/public-api/eve/evals.json create mode 100644 packages/eve/public-api/eve/evals/expect.json create mode 100644 packages/eve/public-api/eve/evals/loaders.json create mode 100644 packages/eve/public-api/eve/evals/reporters.json create mode 100644 packages/eve/public-api/eve/events.json create mode 100644 packages/eve/public-api/eve/experimental/schedules.json create mode 100644 packages/eve/public-api/eve/experimental/schedules/testing.json create mode 100644 packages/eve/public-api/eve/experimental/schedules/vercel.json create mode 100644 packages/eve/public-api/eve/extension.json create mode 100644 packages/eve/public-api/eve/extensions/code.json create mode 100644 packages/eve/public-api/eve/extensions/code/prwatch.json create mode 100644 packages/eve/public-api/eve/extensions/code/sandbox.json create mode 100644 packages/eve/public-api/eve/extensions/code/tools.json create mode 100644 packages/eve/public-api/eve/extensions/git.json create mode 100644 packages/eve/public-api/eve/extensions/git/sandbox.json create mode 100644 packages/eve/public-api/eve/extensions/git/tools.json create mode 100644 packages/eve/public-api/eve/hooks.json create mode 100644 packages/eve/public-api/eve/instructions.json create mode 100644 packages/eve/public-api/eve/instrumentation.json create mode 100644 packages/eve/public-api/eve/instrumentation/otel.json create mode 100644 packages/eve/public-api/eve/local-dev.json create mode 100644 packages/eve/public-api/eve/memory.json create mode 100644 packages/eve/public-api/eve/memory/file.json create mode 100644 packages/eve/public-api/eve/memory/file/vercel.json create mode 100644 packages/eve/public-api/eve/memory/scope.json create mode 100644 packages/eve/public-api/eve/models.json create mode 100644 packages/eve/public-api/eve/models/anthropic.json create mode 100644 packages/eve/public-api/eve/models/openai.json create mode 100644 packages/eve/public-api/eve/next.json create mode 100644 packages/eve/public-api/eve/nuxt.json create mode 100644 packages/eve/public-api/eve/react.json create mode 100644 packages/eve/public-api/eve/sandbox.json create mode 100644 packages/eve/public-api/eve/sandbox/docker.json create mode 100644 packages/eve/public-api/eve/sandbox/just-bash.json create mode 100644 packages/eve/public-api/eve/sandbox/microsandbox.json create mode 100644 packages/eve/public-api/eve/sandbox/provider.json create mode 100644 packages/eve/public-api/eve/sandbox/vercel.json create mode 100644 packages/eve/public-api/eve/schedules.json create mode 100644 packages/eve/public-api/eve/self-modification.json create mode 100644 packages/eve/public-api/eve/self-modification/agent.json create mode 100644 packages/eve/public-api/eve/self-modification/config.json create mode 100644 packages/eve/public-api/eve/self-modification/local.json create mode 100644 packages/eve/public-api/eve/self-modification/remote.json create mode 100644 packages/eve/public-api/eve/self-modification/sandbox.json create mode 100644 packages/eve/public-api/eve/server.json create mode 100644 packages/eve/public-api/eve/setup.json create mode 100644 packages/eve/public-api/eve/setup/scaffold.json create mode 100644 packages/eve/public-api/eve/skills.json create mode 100644 packages/eve/public-api/eve/svelte.json create mode 100644 packages/eve/public-api/eve/sveltekit.json create mode 100644 packages/eve/public-api/eve/tanstack.json create mode 100644 packages/eve/public-api/eve/tools.json create mode 100644 packages/eve/public-api/eve/tools/agent-router.json create mode 100644 packages/eve/public-api/eve/tools/agent.json create mode 100644 packages/eve/public-api/eve/tools/approval.json create mode 100644 packages/eve/public-api/eve/tools/ask_question.json create mode 100644 packages/eve/public-api/eve/tools/bash.json create mode 100644 packages/eve/public-api/eve/tools/glob.json create mode 100644 packages/eve/public-api/eve/tools/grep.json create mode 100644 packages/eve/public-api/eve/tools/no_reply.json create mode 100644 packages/eve/public-api/eve/tools/read_file.json create mode 100644 packages/eve/public-api/eve/tools/sleep.json create mode 100644 packages/eve/public-api/eve/tools/web_fetch.json create mode 100644 packages/eve/public-api/eve/tools/web_search.json create mode 100644 packages/eve/public-api/eve/tools/workflow.json create mode 100644 packages/eve/public-api/eve/tools/write_file.json create mode 100644 packages/eve/public-api/eve/vercel.json create mode 100644 packages/eve/public-api/eve/vue.json create mode 100644 packages/eve/public-api/eve/workflow-modules.json create mode 100644 scripts/public-exports.mjs diff --git a/packages/eve/public-api/README.md b/packages/eve/public-api/README.md new file mode 100644 index 0000000000..5ed716b302 --- /dev/null +++ b/packages/eve/public-api/README.md @@ -0,0 +1,23 @@ +# Public export lists + +Each JSON file lists the named exports of one typed public `eve` package entry. +The TypeScript checker follows aliases and star re-exports from current source; +no declaration build is needed. The lists include experimental entries but do +not make them stable. `eve/internal/*` is excluded. + +Check the lists with: + +```sh +node scripts/public-exports.mjs +``` + +`pnpm guard:invariants` also checks them under rule 55. When an intentional API +change adds or removes names, update the lists in the same review: + +```sh +node scripts/public-exports.mjs --write +``` + +These are name inventories, not structural compatibility reports. A type's +fields remain contractual even when its name is not exported. Authoring +capability compatibility is checked separately by the extension contract guard. diff --git a/packages/eve/public-api/eve.json b/packages/eve/public-api/eve.json new file mode 100644 index 0000000000..45464e7101 --- /dev/null +++ b/packages/eve/public-api/eve.json @@ -0,0 +1,27 @@ +[ + "AgentCompactionDefinition", + "AgentDefinition", + "AgentExperimentalDefinition", + "AgentLimitsDefinition", + "AgentModelDefinition", + "AgentModelOptionsDefinition", + "AgentReasoningDefinition", + "AgentStaticModelDefinition", + "AgentWorkflowDefinition", + "AgentWorkflowRetentionDefinition", + "AgentWorkflowWorldDefinition", + "DefinedAgent", + "DynamicLocalSubagentDefinition", + "DynamicResolveContext", + "DynamicSentinel", + "DynamicSubagentDefinition", + "RemoteAgentDefinition", + "RemoteAgentDefinitionInput", + "RemoteAgentUrl", + "WorkspaceAgentDefinition", + "WorkspaceAgentTransport", + "defineAgent", + "defineDynamic", + "defineRemoteAgent", + "defineWorkspaceAgent" +] diff --git a/packages/eve/public-api/eve/agents/auth.json b/packages/eve/public-api/eve/agents/auth.json new file mode 100644 index 0000000000..11fabd8e4a --- /dev/null +++ b/packages/eve/public-api/eve/agents/auth.json @@ -0,0 +1 @@ +["OutboundAuthFn", "VercelOidcOptions", "basic", "bearer", "vercelOidc"] diff --git a/packages/eve/public-api/eve/ai.json b/packages/eve/public-api/eve/ai.json new file mode 100644 index 0000000000..9c76ae60f1 --- /dev/null +++ b/packages/eve/public-api/eve/ai.json @@ -0,0 +1 @@ +["decide"] diff --git a/packages/eve/public-api/eve/channels.json b/packages/eve/public-api/eve/channels.json new file mode 100644 index 0000000000..00c640508a --- /dev/null +++ b/packages/eve/public-api/eve/channels.json @@ -0,0 +1,87 @@ +[ + "AgentDescription", + "AgentSkillDescription", + "AgentSkillFileDescription", + "AgentToolDescription", + "AttachSessionFn", + "AudienceCaller", + "AudienceContext", + "AudienceInput", + "AudiencePrincipal", + "CancelTurnResult", + "Channel", + "ChannelAudience", + "ChannelCommitHandler", + "ChannelContinuationOps", + "ChannelCors", + "ChannelCorsOptions", + "ChannelDefinition", + "ChannelDelivery", + "ChannelEventContext", + "ChannelEvents", + "ChannelFrom", + "ChannelReceiveContext", + "ChannelResolveSession", + "ChannelRespondOptions", + "ChannelSendOptions", + "ChannelSource", + "ClearSessionResult", + "CommittedLine", + "CompactSessionResult", + "ConversationEnvironment", + "DELETE", + "Delivery", + "DeliveryResult", + "DisabledRouteSentinel", + "FetchFileContext", + "FetchFileFunction", + "FetchFileResult", + "GET", + "HEAD", + "HttpRouteDefinition", + "InferChannelMetadata", + "InstrumentationChannel", + "InstrumentationChannelForChannel", + "InstrumentationChannelForKind", + "InstrumentationChannelKind", + "InstrumentationChannelMetadata", + "InvokeToolFn", + "InvokeToolOptions", + "InvokeToolResult", + "MessageContent", + "MessageFilePart", + "MessagePart", + "MessageTextPart", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "ResetSessionResult", + "RouteDefinition", + "RouteHandlerArgs", + "Session", + "SessionCallback", + "SessionHandle", + "SessionNotActiveError", + "SessionOperations", + "SessionRespondOptions", + "SessionSendOptions", + "SessionStrandedError", + "TurnPolicy", + "WS", + "WebSocketMessage", + "WebSocketPeer", + "WebSocketRouteDefinition", + "WebSocketRouteHandler", + "WebSocketRouteHooks", + "WebSocketUpgradeRequest", + "WebSocketUpgradeResult", + "WebSocketUpgradeServerBridge", + "answerFromText", + "attachmentError", + "createWebSocketUpgradeServer", + "defineChannel", + "disableRoute", + "isChannel", + "isDisabledRouteSentinel" +] diff --git a/packages/eve/public-api/eve/channels/auth.json b/packages/eve/public-api/eve/channels/auth.json new file mode 100644 index 0000000000..665a7179d3 --- /dev/null +++ b/packages/eve/public-api/eve/channels/auth.json @@ -0,0 +1,45 @@ +[ + "AuthErrorOptions", + "AuthFn", + "AuthResult", + "ForbiddenError", + "HttpBasicAuthOptions", + "HttpBasicCredentials", + "IpAllowList", + "OAuthResourceAuth", + "OAuthResourceOptions", + "UnauthenticatedError", + "UnauthorizedChallenge", + "UnauthorizedResponseOptions", + "VercelOidcOptions", + "VercelSubjectEnvironment", + "VercelSubjectInput", + "VerifyJwtEcdsaConfig", + "VerifyJwtHmacConfig", + "VerifyOidcConfig", + "VerifyResult", + "VerifyVercelOidcOptions", + "createIpAllowList", + "createUnauthorizedResponse", + "escapeAuthChallengeParameter", + "extractBearerToken", + "httpBasic", + "isIpAllowed", + "jwtEcdsa", + "jwtHmac", + "localDev", + "none", + "oauthResource", + "oidc", + "placeholderAuth", + "readOAuthResourceOptions", + "routeAuth", + "vercelOidc", + "vercelSubject", + "verifyHttpBasic", + "verifyJwtEcdsa", + "verifyJwtHmac", + "verifyOidc", + "verifyVercelOidc", + "withAuthChallenges" +] diff --git a/packages/eve/public-api/eve/channels/chat-sdk.json b/packages/eve/public-api/eve/channels/chat-sdk.json new file mode 100644 index 0000000000..cdca7ed48c --- /dev/null +++ b/packages/eve/public-api/eve/channels/chat-sdk.json @@ -0,0 +1,16 @@ +[ + "ChatSdkChannel", + "ChatSdkChannelBridge", + "ChatSdkChannelConfig", + "ChatSdkChannelContext", + "ChatSdkChannelEvents", + "ChatSdkChannelState", + "ChatSdkEventContext", + "ChatSdkInstrumentationMetadata", + "ChatSdkReceiveTarget", + "ChatSdkRespondOptions", + "ChatSdkSendOptions", + "chatSdkChannel", + "isNotImplemented", + "messageContentOf" +] diff --git a/packages/eve/public-api/eve/channels/discord.json b/packages/eve/public-api/eve/channels/discord.json new file mode 100644 index 0000000000..bcc46c23a9 --- /dev/null +++ b/packages/eve/public-api/eve/channels/discord.json @@ -0,0 +1,70 @@ +[ + "DISCORD_COMPONENT_TYPE", + "DISCORD_EPHEMERAL_MESSAGE_FLAG", + "DISCORD_HITL_CUSTOM_ID_PREFIX", + "DISCORD_HITL_FREEFORM_CUSTOM_ID_PREFIX", + "DISCORD_HITL_FREEFORM_TEXT_INPUT_ID", + "DISCORD_INTERACTION_RESPONSE_TYPE", + "DISCORD_INTERACTION_TYPE", + "DISCORD_MESSAGE_CONTENT_MAX_LENGTH", + "DISCORD_NO_MENTIONS", + "DiscordAnswerContext", + "DiscordAnswerInteraction", + "DiscordAnswerResult", + "DiscordApiOptions", + "DiscordApiResponse", + "DiscordApplicationId", + "DiscordBotToken", + "DiscordChannel", + "DiscordChannelConfig", + "DiscordChannelCredentials", + "DiscordChannelEvents", + "DiscordChannelState", + "DiscordCommandInteraction", + "DiscordCommandOption", + "DiscordCommandResult", + "DiscordCommandResultOrPromise", + "DiscordComponentInteraction", + "DiscordContext", + "DiscordCredentials", + "DiscordEventContext", + "DiscordFetch", + "DiscordHandle", + "DiscordInboundContext", + "DiscordInstrumentationMetadata", + "DiscordInteraction", + "DiscordInteractionBase", + "DiscordMember", + "DiscordMessageBody", + "DiscordModalSubmitInteraction", + "DiscordPostedMessage", + "DiscordPublicKey", + "DiscordReceiveTarget", + "DiscordRequestOptions", + "DiscordUser", + "DiscordVerifyOptions", + "DiscordWebhookVerifier", + "ModelMessage", + "buildFreeformModalResponse", + "callDiscordApi", + "commandInteractionMessage", + "createDiscordFollowupMessage", + "defaultDiscordAuth", + "deriveComponentAnswers", + "deriveModalAnswers", + "discordChannel", + "discordContinuationToken", + "editDiscordOriginalResponse", + "formatDiscordContextBlock", + "isDiscordFreeformComponent", + "parseDiscordInteraction", + "renderInteractionComponents", + "resolveDiscordApplicationId", + "resolveDiscordBotToken", + "resolveDiscordPublicKey", + "sendDiscordChannelMessage", + "splitDiscordMessageContent", + "triggerDiscordTypingIndicator", + "verifyDiscordRequest", + "verifyDiscordSignature" +] diff --git a/packages/eve/public-api/eve/channels/eve.json b/packages/eve/public-api/eve/channels/eve.json new file mode 100644 index 0000000000..91586e4d61 --- /dev/null +++ b/packages/eve/public-api/eve/channels/eve.json @@ -0,0 +1,16 @@ +[ + "EveChannel", + "EveChannelCors", + "EveChannelCorsOptions", + "EveChannelEvents", + "EveChannelInput", + "EveEventContext", + "EveHandle", + "EveMessageContext", + "EveMessageResult", + "EveMessageResultOrPromise", + "ForwardedAssertion", + "TrustedForwarders", + "defaultEveAuth", + "eveChannel" +] diff --git a/packages/eve/public-api/eve/channels/github.json b/packages/eve/public-api/eve/channels/github.json new file mode 100644 index 0000000000..3a44687032 --- /dev/null +++ b/packages/eve/public-api/eve/channels/github.json @@ -0,0 +1,48 @@ +[ + "GITHUB_DEFAULT_EXCLUDED_DIFF_FILES", + "GitHubApiError", + "GitHubApiMethod", + "GitHubApiOptions", + "GitHubApiResponse", + "GitHubAppId", + "GitHubAppRef", + "GitHubBotName", + "GitHubChannel", + "GitHubChannelConfig", + "GitHubChannelCredentials", + "GitHubChannelEvents", + "GitHubChannelState", + "GitHubCheckRunEvent", + "GitHubCheckSuiteEvent", + "GitHubCiEvent", + "GitHubCiPayload", + "GitHubComment", + "GitHubConversationKind", + "GitHubConversationRef", + "GitHubDelivery", + "GitHubEventContext", + "GitHubHandle", + "GitHubInboundContext", + "GitHubInboundResult", + "GitHubInboundResultOrPromise", + "GitHubInstallationToken", + "GitHubIssueAction", + "GitHubIssueEvent", + "GitHubJsonObject", + "GitHubPostedComment", + "GitHubPrivateKey", + "GitHubProgressConfig", + "GitHubPullRequestAction", + "GitHubPullRequestContextConfig", + "GitHubPullRequestEvent", + "GitHubReactionContent", + "GitHubReceiveTarget", + "GitHubRepositoryRef", + "GitHubThread", + "GitHubUser", + "GitHubWebhookSecret", + "GitHubWebhookVerifier", + "GitHubWorkflowRunEvent", + "defaultGitHubAuth", + "githubChannel" +] diff --git a/packages/eve/public-api/eve/channels/linear.json b/packages/eve/public-api/eve/channels/linear.json new file mode 100644 index 0000000000..d9b6ddcf54 --- /dev/null +++ b/packages/eve/public-api/eve/channels/linear.json @@ -0,0 +1,55 @@ +[ + "LINEAR_CHANNEL_DEFAULT_ROUTE", + "LinearAccessToken", + "LinearAgentActivityContent", + "LinearAgentActivityCreateInput", + "LinearAgentActivityRecord", + "LinearAgentActivityRef", + "LinearAgentActivitySignal", + "LinearAgentSessionAction", + "LinearAgentSessionEvent", + "LinearAgentSessionRecord", + "LinearAgentSessionRef", + "LinearAgentSessionUpdateInput", + "LinearApiError", + "LinearApiOptions", + "LinearChannel", + "LinearChannelConfig", + "LinearChannelContext", + "LinearChannelCredentials", + "LinearChannelEvents", + "LinearChannelState", + "LinearDataWebhookEvent", + "LinearDelivery", + "LinearEventContext", + "LinearExternalUrl", + "LinearFetch", + "LinearHandle", + "LinearInboundEvent", + "LinearInboundResult", + "LinearInboundResultOrPromise", + "LinearInstrumentationMetadata", + "LinearIssueRef", + "LinearReceiveTarget", + "LinearSessionContext", + "LinearUser", + "LinearVerifyOptions", + "LinearWebhookSecret", + "LinearWebhookVerifier", + "callLinearGraphQL", + "createLinearAgentActivity", + "createLinearAgentSessionOnComment", + "createLinearAgentSessionOnIssue", + "defaultLinearAuth", + "defaultOnAgentSession", + "formatLinearContextBlock", + "linearChannel", + "linearContinuationToken", + "linearInteractionSignal", + "listLinearAgentSessionActivities", + "messageFromLinearAgentSessionEvent", + "parseLinearWebhookEvent", + "renderLinearInteractions", + "signLinearWebhookBody", + "updateLinearAgentSession" +] diff --git a/packages/eve/public-api/eve/channels/linq.json b/packages/eve/public-api/eve/channels/linq.json new file mode 100644 index 0000000000..98e3ec0ba4 --- /dev/null +++ b/packages/eve/public-api/eve/channels/linq.json @@ -0,0 +1,11 @@ +[ + "LinqChannel", + "LinqChannelConfig", + "LinqChannelCredentials", + "LinqCredentialValue", + "LinqInboundMessageContext", + "LinqInboundResult", + "LinqInboundResultOrPromise", + "defaultLinqAuth", + "linqChannel" +] diff --git a/packages/eve/public-api/eve/channels/mcp.json b/packages/eve/public-api/eve/channels/mcp.json new file mode 100644 index 0000000000..434df91696 --- /dev/null +++ b/packages/eve/public-api/eve/channels/mcp.json @@ -0,0 +1 @@ +["McpChannel", "McpChannelInput", "mcpChannel"] diff --git a/packages/eve/public-api/eve/channels/photon.json b/packages/eve/public-api/eve/channels/photon.json new file mode 100644 index 0000000000..365a33fa4a --- /dev/null +++ b/packages/eve/public-api/eve/channels/photon.json @@ -0,0 +1,10 @@ +[ + "PhotonIMessageChannel", + "PhotonIMessageChannelConfig", + "PhotonIMessageChannelCredentials", + "PhotonInboundMessageContext", + "PhotonInboundResult", + "PhotonInboundResultOrPromise", + "defaultPhotonAuth", + "photonIMessageChannel" +] diff --git a/packages/eve/public-api/eve/channels/slack.json b/packages/eve/public-api/eve/channels/slack.json new file mode 100644 index 0000000000..77c650086b --- /dev/null +++ b/packages/eve/public-api/eve/channels/slack.json @@ -0,0 +1,158 @@ +[ + "ANSWER_ACTION_PREFIX", + "Actions", + "ActionsElement", + "AdapterPostableMessage", + "Attachment", + "BlockKitBlock", + "Button", + "ButtonElement", + "ButtonOptions", + "ButtonProps", + "ButtonStyle", + "Card", + "CardChild", + "CardElement", + "CardJSXElement", + "CardJSXProps", + "CardLink", + "CardLinkProps", + "CardOptions", + "CardProps", + "CardText", + "ChatElement", + "ContainerProps", + "DerivedHitlResponse", + "Divider", + "DividerElement", + "DividerProps", + "ExternalSelect", + "ExternalSelectElement", + "ExternalSelectOptions", + "ExternalSelectProps", + "Field", + "FieldElement", + "FieldProps", + "Fields", + "FieldsElement", + "FileUpload", + "Image", + "ImageElement", + "ImageProps", + "LinkButton", + "LinkButtonElement", + "LinkButtonOptions", + "LinkButtonProps", + "LinkElement", + "LoadThreadContextMessagesOptions", + "Modal", + "ModalChild", + "ModalElement", + "ModalOptions", + "ModalProps", + "ModelMessage", + "PostableCard", + "RadioSelect", + "RadioSelectElement", + "RadioSelectOptions", + "SLACK_MARKDOWN_TEXT_MAX_LENGTH", + "Section", + "SectionElement", + "Select", + "SelectElement", + "SelectOption", + "SelectOptionElement", + "SelectOptionProps", + "SelectOptions", + "SelectProps", + "SlackAnswerContext", + "SlackAnswerResult", + "SlackAnswerSubmission", + "SlackApiResponse", + "SlackAttachment", + "SlackAuthor", + "SlackAuthorizationEventContext", + "SlackBotToken", + "SlackBotTokenContext", + "SlackChannel", + "SlackChannelConfig", + "SlackChannelCredentials", + "SlackChannelState", + "SlackCompletedReply", + "SlackContext", + "SlackEvent", + "SlackEventContext", + "SlackEventEnvelope", + "SlackEventRespondFn", + "SlackEventRespondOptions", + "SlackEventSendFn", + "SlackEventSendOptions", + "SlackFetch", + "SlackHandle", + "SlackHitlAction", + "SlackHitlRoute", + "SlackInboundContext", + "SlackInboundEventContext", + "SlackInboundMessageContext", + "SlackInboundResult", + "SlackInboundResultOrPromise", + "SlackInitialMessage", + "SlackInstrumentationMetadata", + "SlackInteractionAction", + "SlackInteractionContext", + "SlackMentionResult", + "SlackMentionResultOrPromise", + "SlackMessage", + "SlackPostInput", + "SlackPostedMessage", + "SlackReceiveTarget", + "SlackRenderHandler", + "SlackRenderNext", + "SlackRenderer", + "SlackRendererEvents", + "SlackRespondOptions", + "SlackSendOptions", + "SlackSessionOperations", + "SlackSessionTarget", + "SlackShortcut", + "SlackShortcutContext", + "SlackShortcutMessage", + "SlackSlashCommand", + "SlackSlashCommandContext", + "SlackTaskCard", + "SlackThread", + "SlackThreadMessage", + "SlackTransportOptions", + "SlackUploadFilesOptions", + "SlackUploadFilesResult", + "SlackWebhookVerifier", + "SlackWorkspaceHandle", + "Table", + "TableAlignment", + "TableElement", + "TableOptions", + "TaskCard", + "TextElement", + "TextInput", + "TextInputElement", + "TextInputOptions", + "TextInputProps", + "TextProps", + "TextStyle", + "ThreadContextSince", + "answerFromSlackAction", + "buildSlackAuthContext", + "callSlackApi", + "cardChildToFallbackText", + "cardToBlocks", + "cardToFallbackText", + "defaultSlackAuth", + "defineSlackRenderer", + "isCardElement", + "loadThreadContextMessages", + "postCompletedSlackReply", + "renderInteractionBlocks", + "resolveSlackBotToken", + "slackChannel", + "slackContinuationToken" +] diff --git a/packages/eve/public-api/eve/channels/teams.json b/packages/eve/public-api/eve/channels/teams.json new file mode 100644 index 0000000000..7090b0e8e0 --- /dev/null +++ b/packages/eve/public-api/eve/channels/teams.json @@ -0,0 +1,91 @@ +[ + "ModelMessage", + "TEAMS_ADAPTIVE_CARD_CONTENT_TYPE", + "TEAMS_HITL_CHOICE_INPUT_ID", + "TEAMS_HITL_DATA_KEY", + "TEAMS_HITL_FREEFORM_INPUT_ID", + "TEAMS_HITL_PROMPT_KEY", + "TEAMS_HITL_REQUEST_KIND_KEY", + "TEAMS_MESSAGE_TEXT_MAX_LENGTH", + "TeamsAccessTokenResult", + "TeamsActivity", + "TeamsActivityBase", + "TeamsAnswerResult", + "TeamsApiOptions", + "TeamsApiResponse", + "TeamsAppId", + "TeamsAppPassword", + "TeamsAttachment", + "TeamsChannel", + "TeamsChannelAccount", + "TeamsChannelConfig", + "TeamsChannelCredentials", + "TeamsChannelEvents", + "TeamsChannelState", + "TeamsContext", + "TeamsConversationAccount", + "TeamsConversationScope", + "TeamsConversationUpdateActivity", + "TeamsCredentials", + "TeamsEventContext", + "TeamsFetch", + "TeamsFilesConfig", + "TeamsFilesPolicy", + "TeamsHandle", + "TeamsInboundContext", + "TeamsInboundMessageContext", + "TeamsInboundResult", + "TeamsInboundResultOrPromise", + "TeamsInstrumentationMetadata", + "TeamsInvokeActivity", + "TeamsInvokeResult", + "TeamsInvokeResultOrPromise", + "TeamsJwtVerifyOptions", + "TeamsMention", + "TeamsMessageActivity", + "TeamsMessageBody", + "TeamsOutboundActivity", + "TeamsPostedActivity", + "TeamsReceiveTarget", + "TeamsRequestOptions", + "TeamsTenantId", + "TeamsThread", + "TeamsTokenProvider", + "TeamsVerifyOptions", + "TeamsWebhookVerifier", + "buildAuthCompletedText", + "buildTeamsTurnMessage", + "callTeamsConnectorApi", + "collectTeamsFileParts", + "createTeamsFetchFile", + "defaultTeamsAuth", + "deriveTeamsAnswers", + "formatConnectionDisplayName", + "formatTeamsContextBlock", + "isTeamsAnswerActivity", + "isTeamsPersonalMessage", + "isTeamsToolApprovalResponseActivity", + "normalizeTeamsFilesPolicy", + "normalizeTeamsPostInput", + "parseTeamsActivity", + "readTeamsToolApprovalPrompt", + "renderAnsweredInteractionMessage", + "renderInteractionAttachment", + "renderInteractionMessage", + "replyToTeamsActivity", + "resolveTeamsAccessToken", + "resolveTeamsAppId", + "resolveTeamsAppPassword", + "resolveTeamsTenantId", + "sendTeamsActivity", + "splitTeamsMessageText", + "teamsChannel", + "teamsContinuationToken", + "teamsInvokeResponse", + "teamsMentionUser", + "teamsThreadRootActivityId", + "triggerTeamsTypingIndicator", + "updateTeamsActivity", + "verifyTeamsJwt", + "verifyTeamsRequest" +] diff --git a/packages/eve/public-api/eve/channels/telegram.json b/packages/eve/public-api/eve/channels/telegram.json new file mode 100644 index 0000000000..28d66fb8ed --- /dev/null +++ b/packages/eve/public-api/eve/channels/telegram.json @@ -0,0 +1,69 @@ +[ + "ModelMessage", + "TELEGRAM_CALLBACK_RESPONSE_PREFIX", + "TELEGRAM_FILE_URL_PROTOCOL", + "TELEGRAM_HITL_CALLBACK_PREFIX", + "TELEGRAM_MESSAGE_TEXT_MAX_LENGTH", + "TELEGRAM_REPLY_RESPONSE_PREFIX", + "TelegramApiOptions", + "TelegramApiResponse", + "TelegramAttachment", + "TelegramBotToken", + "TelegramCallbackQuery", + "TelegramChannel", + "TelegramChannelConfig", + "TelegramChannelCredentials", + "TelegramChannelEvents", + "TelegramChannelState", + "TelegramChat", + "TelegramChatType", + "TelegramContext", + "TelegramCredentials", + "TelegramEphemeralMessageParameters", + "TelegramEventContext", + "TelegramFetch", + "TelegramHandle", + "TelegramHitlState", + "TelegramInboundContext", + "TelegramInboundResult", + "TelegramInboundResultOrPromise", + "TelegramInstrumentationMetadata", + "TelegramInteractionMessage", + "TelegramMessage", + "TelegramMessageBody", + "TelegramMessageReference", + "TelegramMessageResult", + "TelegramReceiveTarget", + "TelegramUpdate", + "TelegramUser", + "TelegramVerifyOptions", + "TelegramWebhookSecretToken", + "TelegramWebhookVerifier", + "answerTelegramCallbackQuery", + "buildTelegramTurnMessage", + "callTelegramApi", + "collectTelegramFileParts", + "createTelegramFetchFile", + "createTelegramFileUrl", + "defaultTelegramAuth", + "downloadTelegramFile", + "editTelegramMessageReplyMarkup", + "editTelegramMessageText", + "formatTelegramContextBlock", + "getTelegramFile", + "isTelegramSyntheticResponse", + "parseTelegramUpdate", + "registerTelegramFreeformPrompt", + "renderTelegramInteraction", + "resolveTelegramAnswers", + "resolveTelegramBotToken", + "resolveTelegramWebhookSecretToken", + "sendTelegramChatAction", + "sendTelegramMessage", + "splitTelegramMessageText", + "telegramCallbackAnswer", + "telegramChannel", + "telegramContinuationToken", + "telegramReplyAnswer", + "verifyTelegramRequest" +] diff --git a/packages/eve/public-api/eve/channels/twilio.json b/packages/eve/public-api/eve/channels/twilio.json new file mode 100644 index 0000000000..26c7ed4f4b --- /dev/null +++ b/packages/eve/public-api/eve/channels/twilio.json @@ -0,0 +1,51 @@ +[ + "TwilioAccountSid", + "TwilioAllowFrom", + "TwilioApiOptions", + "TwilioApiResponse", + "TwilioAuthToken", + "TwilioChannel", + "TwilioChannelConfig", + "TwilioChannelCredentials", + "TwilioChannelEvents", + "TwilioChannelState", + "TwilioContext", + "TwilioCredentials", + "TwilioEventContext", + "TwilioFetch", + "TwilioGatherTwimlOptions", + "TwilioHandle", + "TwilioInboundContext", + "TwilioInboundResult", + "TwilioInboundResultOrPromise", + "TwilioInstrumentationMetadata", + "TwilioMessagingConfig", + "TwilioReceiveTarget", + "TwilioSendMessageInput", + "TwilioSendMessageOptions", + "TwilioTextMessage", + "TwilioUpdateCallInput", + "TwilioVerifiedRequest", + "TwilioVerifyOptions", + "TwilioVoiceCall", + "TwilioVoiceConfig", + "TwilioVoiceResult", + "TwilioVoiceResultOrPromise", + "TwilioVoiceTranscription", + "TwilioWebhookUrl", + "buildTwilioSignatureBase", + "callTwilioApi", + "emptyTwilioResponse", + "escapeXml", + "gatherSpeechTwilioResponse", + "resolveTwilioAccountSid", + "resolveTwilioAuthToken", + "sayTwilioResponse", + "sendTwilioMessage", + "signTwilioRequest", + "twilioChannel", + "twilioContinuationToken", + "twimlResponse", + "updateTwilioCall", + "verifyTwilioRequest" +] diff --git a/packages/eve/public-api/eve/client.json b/packages/eve/public-api/eve/client.json new file mode 100644 index 0000000000..2fef34a36d --- /dev/null +++ b/packages/eve/public-api/eve/client.json @@ -0,0 +1,268 @@ +[ + "AgentInfoChannelEntry", + "AgentInfoChannels", + "AgentInfoConnectionEntry", + "AgentInfoDynamicResolverEntry", + "AgentInfoEntry", + "AgentInfoHookEntry", + "AgentInfoInstructions", + "AgentInfoInstructionsEntry", + "AgentInfoRemoteAgentEntry", + "AgentInfoResponseError", + "AgentInfoResult", + "AgentInfoSandboxEntry", + "AgentInfoScheduleEntry", + "AgentInfoSkillEntry", + "AgentInfoSource", + "AgentInfoSubagentEntry", + "AgentInfoToolEntry", + "AgentInfoTools", + "AgentObservation", + "Answer", + "AskedCall", + "CALL_OUTCOMES", + "CONTEXT_OUTCOMES", + "CallFact", + "CallInput", + "CallInputData", + "CallOutcome", + "CallOwner", + "CallProgress", + "CallProgressData", + "CallProgressRecord", + "CallRequested", + "CallRequestedData", + "CallSettled", + "CallSettledData", + "CallStarted", + "CallStartedData", + "CancelSessionResult", + "Capability", + "CapabilityKind", + "Cause", + "ChildFact", + "ChildOpened", + "ChildOpenedData", + "ClearResult", + "ClearedBy", + "Client", + "ClientAgentSession", + "ClientAuth", + "ClientError", + "ClientOptions", + "ClientRedirectPolicy", + "ClientSession", + "ClientSessionState", + "ClientSessionStrandedError", + "ClientSessions", + "CommitLine", + "CompactResult", + "ContentCompleted", + "ContentCompletedData", + "ContentDelta", + "ContentDeltaData", + "ContentFact", + "ContentKind", + "ContentPhase", + "ContentProgress", + "ContextFact", + "ContextKind", + "ContextOutcome", + "ContextSettled", + "ContextSettledData", + "ContextStarted", + "ContextStartedData", + "ConversationAgentSession", + "ConversationState", + "CreateSessionOptions", + "CreatedClientSession", + "CreatedIdleClientSession", + "DELIVERY_OUTCOMES", + "Delivery", + "DeliveryAdmitted", + "DeliveryAdmittedData", + "DeliveryConsumed", + "DeliveryConsumedData", + "DeliveryFact", + "DeliveryOutcome", + "DeliveryResult", + "DeliverySettled", + "DeliverySettledData", + "DeliverySource", + "Envelope", + "ErrorInfo", + "EveAgentStore", + "EveAgentStoreCallbacks", + "EveAgentStoreInit", + "EveAgentStoreSnapshot", + "EveAgentStoreStatus", + "EveInteractionPart", + "EveMessage", + "EveMessageMetadata", + "EveMessagePart", + "EveToolPart", + "FACT_CATALOG", + "Fact", + "FactDescriptor", + "FactOf", + "FactPosition", + "FactRole", + "FactType", + "Family", + "HEARTBEAT_RECORD", + "HeadersValue", + "HealthResponseError", + "HealthResult", + "INTERACTION_OUTCOMES", + "Interaction", + "InteractionDisplay", + "InteractionFact", + "InteractionKind", + "InteractionOpened", + "InteractionOpenedData", + "InteractionOption", + "InteractionOptionStyle", + "InteractionOrigin", + "InteractionOutcome", + "InteractionRequest", + "InteractionSettled", + "InteractionSettledData", + "InteractionSubject", + "JsonValue", + "LEASE_ENDED_RECORD", + "MAX_SESSION_LINE_BYTES", + "MODEL_OUTCOMES", + "MessageContent", + "MessageFilePart", + "MessagePart", + "MessageTextPart", + "ModelFact", + "ModelOutcome", + "ModelOwner", + "ModelRequested", + "ModelRequestedData", + "ModelRetried", + "ModelRetriedData", + "ModelSettled", + "ModelSettledData", + "ModelStarted", + "ModelStartedData", + "PROGRESS_CATALOG", + "PositionMarker", + "PrepareSend", + "Principal", + "Progress", + "ProgressDescriptor", + "ProgressLine", + "ProgressOf", + "ProgressType", + "RESPONSE_OUTCOMES", + "ReadRecord", + "ResetResult", + "ResolvedStreamReconnectPolicy", + "RespondTurnOptions", + "ResponseAdmitted", + "ResponseAdmittedData", + "ResponseFact", + "ResponseOutcome", + "ResponseSettled", + "ResponseSettledData", + "ResponseSubmitted", + "ResponseSubmittedData", + "ResponseValue", + "RuntimeIdentity", + "SESSION_OUTCOMES", + "STREAM_ENDED_RECORD", + "Scope", + "SendTurnInput", + "SendTurnOptions", + "SessionEnded", + "SessionEndedData", + "SessionEvent", + "SessionEventMeta", + "SessionFact", + "SessionLineTooLargeError", + "SessionNotActiveError", + "SessionOutcome", + "SessionPredecessor", + "SessionPreviews", + "SessionSnapshot", + "SessionStarted", + "SessionStartedData", + "SessionStreamEvent", + "SessionView", + "SignInChallenge", + "StoredLine", + "StreamOptions", + "StreamReconnectPolicy", + "StreamReconnectRetryPolicy", + "TASK_OUTCOMES", + "TERMINAL_OUTCOMES", + "TURN_OUTCOMES", + "TaskEnded", + "TaskEndedData", + "TaskFact", + "TaskOutcome", + "TaskStarted", + "TaskStartedData", + "TokenValue", + "ToolStub", + "ToolStubOutcome", + "TraceContext", + "TranscriptData", + "TranscriptMessage", + "TranscriptOptions", + "TransportRecord", + "TurnFact", + "TurnOutcome", + "TurnPaused", + "TurnPausedData", + "TurnSettled", + "TurnSettledData", + "TurnStarted", + "TurnStartedData", + "Usage", + "UsageFact", + "UsageOwner", + "UsageRecorded", + "UsageRecordedData", + "UserPart", + "ValueReference", + "callProgressSchemas", + "callSchemas", + "childSchemas", + "classifyRecord", + "clearedBySchema", + "contentPhase", + "contentProgressSchemas", + "contentSchemas", + "contextSchemas", + "conversationOf", + "createDataUrlFilePart", + "createTextWithFileContent", + "deliverySchemas", + "encodeLine", + "encodeLineBytes", + "endsTurn", + "failureOf", + "interactionRequestSchema", + "interactionSchemas", + "isAnswer", + "isCompleted", + "isFactType", + "isKnownOutcome", + "isProgressType", + "isStoredLine", + "isTerminal", + "isTerminalType", + "modelSchemas", + "parseAnswer", + "parseAnswers", + "responseSchemas", + "sessionSchemas", + "signInEnding", + "taskSchemas", + "transcriptOf", + "turnSchemas", + "usageSchemas" +] diff --git a/packages/eve/public-api/eve/computer-use.json b/packages/eve/public-api/eve/computer-use.json new file mode 100644 index 0000000000..8b30f8adcd --- /dev/null +++ b/packages/eve/public-api/eve/computer-use.json @@ -0,0 +1 @@ +["default"] diff --git a/packages/eve/public-api/eve/computer-use/sandbox.json b/packages/eve/public-api/eve/computer-use/sandbox.json new file mode 100644 index 0000000000..a12394a3c3 --- /dev/null +++ b/packages/eve/public-api/eve/computer-use/sandbox.json @@ -0,0 +1 @@ +["COMPUTER_USE_REVALIDATION_KEY", "installComputerUse", "startComputerUse"] diff --git a/packages/eve/public-api/eve/computer-use/tools.json b/packages/eve/public-api/eve/computer-use/tools.json new file mode 100644 index 0000000000..31bf90527c --- /dev/null +++ b/packages/eve/public-api/eve/computer-use/tools.json @@ -0,0 +1 @@ +["computer_use"] diff --git a/packages/eve/public-api/eve/connections.json b/packages/eve/public-api/eve/connections.json new file mode 100644 index 0000000000..964bb2e872 --- /dev/null +++ b/packages/eve/public-api/eve/connections.json @@ -0,0 +1,44 @@ +[ + "AuthorizationCallback", + "AuthorizationDefinition", + "ConnectionAuthDefinition", + "ConnectionAuthProvider", + "ConnectionAuthResolver", + "ConnectionAuthorizationChallenge", + "ConnectionAuthorizationFailedError", + "ConnectionAuthorizationFailedErrorOptions", + "ConnectionAuthorizationRequiredError", + "ConnectionAuthorizationRequiredErrorOptions", + "ConnectionPrincipal", + "ConnectionToolCallDefinition", + "CredentialOwner", + "DynamicConnectionDefinition", + "DynamicConnectionEvents", + "DynamicConnectionResolveContext", + "DynamicConnectionResult", + "DynamicConnectionSet", + "DynamicResolveContext", + "DynamicSentinel", + "Experimental_ConnectionEvent", + "Experimental_ConnectionEventContext", + "Experimental_ConnectionEventOrigin", + "Experimental_ConnectionEvents", + "HeadersDefinition", + "InteractiveAuthorizationDefinition", + "JsonValue", + "McpClientConnectionDefinition", + "NonInteractiveAuthorizationDefinition", + "OpenAPIConnectionDefinition", + "OpenAPISpecSource", + "ProvidedArgumentContext", + "ProvidedArgumentValue", + "ProvidedArgumentsDefinition", + "TokenResult", + "ToolFilterDefinition", + "defineDynamic", + "defineInteractiveAuthorization", + "defineMcpClientConnection", + "defineOpenAPIConnection", + "isConnectionAuthorizationFailedError", + "isConnectionAuthorizationRequiredError" +] diff --git a/packages/eve/public-api/eve/context.json b/packages/eve/public-api/eve/context.json new file mode 100644 index 0000000000..8a2a79ec7f --- /dev/null +++ b/packages/eve/public-api/eve/context.json @@ -0,0 +1,11 @@ +[ + "SessionAuth", + "SessionAuthContext", + "SessionContext", + "SessionInfo", + "SessionParent", + "SessionPredecessor", + "SessionTurn", + "StateHandle", + "defineState" +] diff --git a/packages/eve/public-api/eve/evals.json b/packages/eve/public-api/eve/evals.json new file mode 100644 index 0000000000..c1d30cb76e --- /dev/null +++ b/packages/eve/public-api/eve/evals.json @@ -0,0 +1,71 @@ +[ + "Assertion", + "AssertionEvaluation", + "AssertionHandle", + "AssertionResult", + "AssertionSeverity", + "CancelSessionResult", + "EveEval", + "EveEvalActionStatus", + "EveEvalAgentSession", + "EveEvalAssertions", + "EveEvalConfig", + "EveEvalConfigContext", + "EveEvalConfigInput", + "EveEvalContext", + "EveEvalCountMatcher", + "EveEvalDefinition", + "EveEvalDerivedFacts", + "EveEvalEventMatch", + "EveEvalInput", + "EveEvalInteractionMatchOptions", + "EveEvalJudgeConfig", + "EveEvalLiveTurn", + "EveEvalResult", + "EveEvalRunSummary", + "EveEvalScheduleDispatchResult", + "EveEvalSession", + "EveEvalSessionResult", + "EveEvalSkillLoad", + "EveEvalSkillLoadMatchOptions", + "EveEvalStreamEvent", + "EveEvalSubagentCall", + "EveEvalSubagentCallMatchOptions", + "EveEvalTarget", + "EveEvalTargetCapabilities", + "EveEvalTargetHandle", + "EveEvalTaskResult", + "EveEvalToolCall", + "EveEvalToolCallMatchOptions", + "EveEvalTraceContext", + "EveEvalTurn", + "EveEvalTurnFailedError", + "EveEvalValueMatcher", + "EveEvalVerdict", + "EveEvalWaitForEventOptions", + "EveEvalWatchOptions", + "Interaction", + "JudgeBatch", + "JudgeContext", + "JudgeInput", + "JudgeOpts", + "JudgeQuestion", + "MockModelMessage", + "MockModelOptions", + "MockModelRequest", + "MockModelResponder", + "MockModelResponse", + "MockModelTool", + "MockModelToolCall", + "MockModelToolResult", + "MockModelUsage", + "RuntimeIdentity", + "ScoreInput", + "ToolStub", + "ToolStubOutcome", + "TraceContext", + "defineEval", + "defineEvalConfig", + "mockModel", + "toolCallsOf" +] diff --git a/packages/eve/public-api/eve/evals/expect.json b/packages/eve/public-api/eve/evals/expect.json new file mode 100644 index 0000000000..a20b903812 --- /dev/null +++ b/packages/eve/public-api/eve/evals/expect.json @@ -0,0 +1,11 @@ +[ + "Assertion", + "AssertionEvaluation", + "AssertionHandle", + "AssertionSeverity", + "equals", + "includes", + "matches", + "satisfies", + "similarity" +] diff --git a/packages/eve/public-api/eve/evals/loaders.json b/packages/eve/public-api/eve/evals/loaders.json new file mode 100644 index 0000000000..a5fc7c7650 --- /dev/null +++ b/packages/eve/public-api/eve/evals/loaders.json @@ -0,0 +1 @@ +["loadJson", "loadYaml"] diff --git a/packages/eve/public-api/eve/evals/reporters.json b/packages/eve/public-api/eve/evals/reporters.json new file mode 100644 index 0000000000..380584c4c9 --- /dev/null +++ b/packages/eve/public-api/eve/evals/reporters.json @@ -0,0 +1,14 @@ +[ + "Braintrust", + "BraintrustReporterConfig", + "Console", + "ConsoleReporterConfig", + "Datadog", + "DatadogReporterConfig", + "EvalReporter", + "EveEvalCompleteContext", + "EveEvalSessionStartEvent", + "EveEvalStartEvent", + "JUnit", + "JUnitReporterConfig" +] diff --git a/packages/eve/public-api/eve/events.json b/packages/eve/public-api/eve/events.json new file mode 100644 index 0000000000..59f6bc162d --- /dev/null +++ b/packages/eve/public-api/eve/events.json @@ -0,0 +1,203 @@ +[ + "APPROVE_OPTION_ID", + "Activity", + "Answer", + "CALL_OUTCOMES", + "CONTEXT_OUTCOMES", + "CallFact", + "CallInput", + "CallInputData", + "CallOutcome", + "CallOwner", + "CallPreview", + "CallProgress", + "CallProgressData", + "CallProgressRecord", + "CallRequested", + "CallRequestedData", + "CallRow", + "CallSettled", + "CallSettledData", + "CallStarted", + "CallStartedData", + "CallState", + "Capability", + "CapabilityKind", + "Cause", + "ChangeRow", + "ChildFact", + "ChildOpened", + "ChildOpenedData", + "ChildRow", + "ClearedBy", + "CommitLine", + "ContentCompleted", + "ContentCompletedData", + "ContentDelta", + "ContentDeltaData", + "ContentFact", + "ContentKind", + "ContentPhase", + "ContentProgress", + "ContextFact", + "ContextKind", + "ContextOutcome", + "ContextSettled", + "ContextSettledData", + "ContextStarted", + "ContextStartedData", + "DELIVERY_OUTCOMES", + "DENY_OPTION_ID", + "DeliveryAdmitted", + "DeliveryAdmittedData", + "DeliveryConsumed", + "DeliveryConsumedData", + "DeliveryFact", + "DeliveryOutcome", + "DeliveryRow", + "DeliverySettled", + "DeliverySettledData", + "DeliverySource", + "Ended", + "Envelope", + "ErrorInfo", + "FACT_CATALOG", + "Fact", + "FactDescriptor", + "FactOf", + "FactPosition", + "FactRole", + "FactType", + "Family", + "HEARTBEAT_RECORD", + "INTERACTION_OUTCOMES", + "Interaction", + "InteractionDisplay", + "InteractionFact", + "InteractionKind", + "InteractionOpened", + "InteractionOpenedData", + "InteractionOption", + "InteractionOptionStyle", + "InteractionOrigin", + "InteractionOutcome", + "InteractionRequest", + "InteractionRow", + "InteractionSettled", + "InteractionSettledData", + "InteractionSubject", + "Introduced", + "JsonValue", + "LEASE_ENDED_RECORD", + "MODEL_OUTCOMES", + "ModelFact", + "ModelOutcome", + "ModelOwner", + "ModelRequested", + "ModelRequestedData", + "ModelRetried", + "ModelRetriedData", + "ModelSettled", + "ModelSettledData", + "ModelStarted", + "ModelStartedData", + "PROGRESS_CATALOG", + "PartPreview", + "PartRow", + "PositionMarker", + "Principal", + "Progress", + "ProgressDescriptor", + "ProgressLine", + "ProgressOf", + "ProgressType", + "RESPONSE_OUTCOMES", + "ReadRecord", + "ReceivedEvent", + "RequestStatus", + "ResponseAdmitted", + "ResponseAdmittedData", + "ResponseFact", + "ResponseOutcome", + "ResponseRow", + "ResponseSettled", + "ResponseSettledData", + "ResponseSubmitted", + "ResponseSubmittedData", + "ResponseValue", + "RunRow", + "RuntimeIdentity", + "SESSION_OUTCOMES", + "STREAM_ENDED_RECORD", + "Scope", + "SessionEnded", + "SessionEndedData", + "SessionFact", + "SessionOutcome", + "SessionPredecessor", + "SessionPreviews", + "SessionRow", + "SessionStarted", + "SessionStartedData", + "SessionView", + "SignInChallenge", + "StoredLine", + "TASK_OUTCOMES", + "TERMINAL_OUTCOMES", + "TURN_OUTCOMES", + "TaskEnded", + "TaskEndedData", + "TaskFact", + "TaskOutcome", + "TaskRow", + "TaskStarted", + "TaskStartedData", + "TraceContext", + "TransportRecord", + "TurnFact", + "TurnOutcome", + "TurnPaused", + "TurnPausedData", + "TurnRow", + "TurnSettled", + "TurnSettledData", + "TurnStarted", + "TurnStartedData", + "Usage", + "UsageFact", + "UsageOwner", + "UsageRecorded", + "UsageRecordedData", + "UsageTotals", + "UserPart", + "ValueReference", + "Waiting", + "activeTurn", + "activity", + "callLabel", + "callOutputSource", + "callState", + "classifyRecord", + "contentPhase", + "conversation", + "emptyPreviews", + "emptySessionView", + "failure", + "foldEvents", + "idle", + "interaction", + "isCompleted", + "isFactType", + "isKnownOutcome", + "isProgressType", + "isStoredLine", + "isTerminal", + "isTerminalType", + "openInteractions", + "queue", + "reply", + "requestStatus", + "signInEnding", + "tasks", + "waiting" +] diff --git a/packages/eve/public-api/eve/experimental/schedules.json b/packages/eve/public-api/eve/experimental/schedules.json new file mode 100644 index 0000000000..d1b853f69d --- /dev/null +++ b/packages/eve/public-api/eve/experimental/schedules.json @@ -0,0 +1,33 @@ +[ + "DefinedDynamicSchedules", + "DynamicSchedulesDefinition", + "DynamicSchedulesRunArgs", + "DynamicSchedulesToFn", + "ScheduleApprovals", + "ScheduleClient", + "ScheduleClientCreate", + "ScheduleClientUpdate", + "ScheduleCreate", + "ScheduleCreateApproval", + "ScheduleDelivery", + "ScheduleDeliveryTarget", + "ScheduleExpression", + "ScheduleList", + "ScheduleOccurrence", + "ScheduleOccurrenceEvent", + "ScheduleOccurrenceIdentity", + "ScheduleOperation", + "SchedulePage", + "SchedulePatch", + "SchedulePrincipalReference", + "ScheduleProvider", + "ScheduleProviderContext", + "ScheduleRecord", + "ScheduleScopeContext", + "ScheduleScopeResolverResult", + "ScheduleState", + "ScheduleTiming", + "byPrincipal", + "defineDynamicSchedules", + "schedules" +] diff --git a/packages/eve/public-api/eve/experimental/schedules/testing.json b/packages/eve/public-api/eve/experimental/schedules/testing.json new file mode 100644 index 0000000000..e275923ae5 --- /dev/null +++ b/packages/eve/public-api/eve/experimental/schedules/testing.json @@ -0,0 +1 @@ +["InMemoryScheduleProviderOptions", "inMemoryScheduleProvider"] diff --git a/packages/eve/public-api/eve/experimental/schedules/vercel.json b/packages/eve/public-api/eve/experimental/schedules/vercel.json new file mode 100644 index 0000000000..be75cbffbf --- /dev/null +++ b/packages/eve/public-api/eve/experimental/schedules/vercel.json @@ -0,0 +1 @@ +["VercelScheduleProviderOptions", "vercelScheduleProvider"] diff --git a/packages/eve/public-api/eve/extension.json b/packages/eve/public-api/eve/extension.json new file mode 100644 index 0000000000..c96c7d4745 --- /dev/null +++ b/packages/eve/public-api/eve/extension.json @@ -0,0 +1 @@ +["ExtensionHandle", "MountedExtension", "NoConfigExtensionHandle", "defineExtension"] diff --git a/packages/eve/public-api/eve/extensions/code.json b/packages/eve/public-api/eve/extensions/code.json new file mode 100644 index 0000000000..5dae1cdd7f --- /dev/null +++ b/packages/eve/public-api/eve/extensions/code.json @@ -0,0 +1 @@ +["CredentialPolicyBroker", "GitHubLeaseBroker", "GitHubLeaseRule", "default"] diff --git a/packages/eve/public-api/eve/extensions/code/prwatch.json b/packages/eve/public-api/eve/extensions/code/prwatch.json new file mode 100644 index 0000000000..6cc44a32da --- /dev/null +++ b/packages/eve/public-api/eve/extensions/code/prwatch.json @@ -0,0 +1,29 @@ +[ + "PrwatchDeleteInput", + "PrwatchDeleteInputSchema", + "PrwatchRegistration", + "PrwatchRegistryState", + "PrwatchTarget", + "PullRequestWatchInput", + "PullRequestWatchInputSchema", + "PullRequestWatchSnapshot", + "PullRequestWatchWakeReason", + "advancePullRequestWatchNotification", + "cancelPrwatchInState", + "completePrwatchInState", + "deletePrwatch", + "emptyPrwatchRegistry", + "finishPrwatch", + "framePullRequestWatchWake", + "initialPullRequestWatchNotificationState", + "isPrwatchCancelledInState", + "isRetryablePullRequestWatchError", + "prwatchKey", + "prwatchWasCancelled", + "pullRequestWatchSnapshot", + "readPullRequestWatchSnapshot", + "registerActivePrwatch", + "registerPrwatchInState", + "repoFullName", + "shouldRefreshPullRequestWatchToken" +] diff --git a/packages/eve/public-api/eve/extensions/code/sandbox.json b/packages/eve/public-api/eve/extensions/code/sandbox.json new file mode 100644 index 0000000000..ba8ac1e7c6 --- /dev/null +++ b/packages/eve/public-api/eve/extensions/code/sandbox.json @@ -0,0 +1,15 @@ +[ + "BrokeredCredentialOptions", + "CODE_TOOLING_REVALIDATION_KEY", + "COMPUTER_USE_REVALIDATION_KEY", + "GitHubLeaseRule", + "GitHubShellInput", + "GitHubShellOutput", + "authenticateGitHub", + "authenticateVercel", + "executeGitHubShell", + "githubShellApproval", + "installCodeTooling", + "installComputerUse", + "startComputerUse" +] diff --git a/packages/eve/public-api/eve/extensions/code/tools.json b/packages/eve/public-api/eve/extensions/code/tools.json new file mode 100644 index 0000000000..d01785acc5 --- /dev/null +++ b/packages/eve/public-api/eve/extensions/code/tools.json @@ -0,0 +1 @@ +["apply_patch", "computer_use", "gh", "grep"] diff --git a/packages/eve/public-api/eve/extensions/git.json b/packages/eve/public-api/eve/extensions/git.json new file mode 100644 index 0000000000..73b003aaa1 --- /dev/null +++ b/packages/eve/public-api/eve/extensions/git.json @@ -0,0 +1 @@ +["GitHubAuthorize", "GitHubAuthorizeRequest", "GitHubLeaseBroker", "GitHubLeaseRule", "default"] diff --git a/packages/eve/public-api/eve/extensions/git/sandbox.json b/packages/eve/public-api/eve/extensions/git/sandbox.json new file mode 100644 index 0000000000..ad1ab0d3d5 --- /dev/null +++ b/packages/eve/public-api/eve/extensions/git/sandbox.json @@ -0,0 +1,16 @@ +[ + "GH_SIGNED_COMMIT_SOURCE", + "GH_SIGNED_COMMIT_VERSION", + "GH_TOOL_DESCRIPTION", + "GITHUB_TOOLING_PATHS", + "GitHubConfig", + "GitHubLeaseRule", + "GitHubShellInput", + "GitHubShellOutput", + "defineGhTool", + "executeGitHubShell", + "githubInstructions", + "githubShellApproval", + "prSkillDescription", + "prSkillMarkdown" +] diff --git a/packages/eve/public-api/eve/extensions/git/tools.json b/packages/eve/public-api/eve/extensions/git/tools.json new file mode 100644 index 0000000000..8a1703bf6d --- /dev/null +++ b/packages/eve/public-api/eve/extensions/git/tools.json @@ -0,0 +1 @@ +["gh"] diff --git a/packages/eve/public-api/eve/hooks.json b/packages/eve/public-api/eve/hooks.json new file mode 100644 index 0000000000..1da2522075 --- /dev/null +++ b/packages/eve/public-api/eve/hooks.json @@ -0,0 +1,11 @@ +[ + "HookContext", + "HookDefinition", + "HookEvent", + "HookEventKey", + "HookEventMap", + "HookEventType", + "StreamEventHook", + "StreamEventHooks", + "defineHook" +] diff --git a/packages/eve/public-api/eve/instructions.json b/packages/eve/public-api/eve/instructions.json new file mode 100644 index 0000000000..e0beb18186 --- /dev/null +++ b/packages/eve/public-api/eve/instructions.json @@ -0,0 +1,9 @@ +[ + "DynamicInstructionsEvents", + "DynamicInstructionsResult", + "DynamicResolveContext", + "DynamicSentinel", + "InstructionsDefinition", + "defineDynamic", + "defineInstructions" +] diff --git a/packages/eve/public-api/eve/instrumentation.json b/packages/eve/public-api/eve/instrumentation.json new file mode 100644 index 0000000000..7ece5c73f9 --- /dev/null +++ b/packages/eve/public-api/eve/instrumentation.json @@ -0,0 +1,32 @@ +[ + "DISABLED", + "EvaluationRef", + "Handler", + "InstrumentationDisabled", + "InstrumentationEnvironment", + "InstrumentationEvent", + "InstrumentationModelInput", + "InstrumentationModelStartedInput", + "InstrumentationParentLineage", + "InstrumentationProvider", + "InstrumentationRun", + "InstrumentationRuntimeContext", + "InstrumentationScope", + "InstrumentationSession", + "InstrumentationTurn", + "InstrumentationUsage", + "JsonValue", + "PROVIDER", + "ProviderContext", + "ProviderDefinition", + "ProviderEvents", + "ProviderSetupContext", + "ProviderState", + "TraceCaptureContext", + "TraceCapturePolicy", + "TracePolicyDecision", + "defineInstrumentation", + "disableInstrumentation", + "isInstrumentationDisabled", + "isInstrumentationProvider" +] diff --git a/packages/eve/public-api/eve/instrumentation/otel.json b/packages/eve/public-api/eve/instrumentation/otel.json new file mode 100644 index 0000000000..adb22d89d8 --- /dev/null +++ b/packages/eve/public-api/eve/instrumentation/otel.json @@ -0,0 +1,23 @@ +[ + "ManagedTraceOptions", + "OtelDeclaration", + "OtelIntegration", + "OtelIntegrationOptions", + "OtelOptions", + "SpanAttributeDecision", + "SpanExportAttributeValue", + "SpanExportContext", + "SpanExportDecision", + "SpanExportPolicy", + "SpanExporter", + "SpanProcessor", + "TraceCaptureContext", + "TraceCapturePolicy", + "TracePolicyDecision", + "agentRuns", + "isOtelDeclaration", + "isOtelIntegration", + "localTraces", + "otel", + "otelIntegration" +] diff --git a/packages/eve/public-api/eve/local-dev.json b/packages/eve/public-api/eve/local-dev.json new file mode 100644 index 0000000000..92c63145ba --- /dev/null +++ b/packages/eve/public-api/eve/local-dev.json @@ -0,0 +1 @@ +["LocalDevCapability", "getLocalDevCapability"] diff --git a/packages/eve/public-api/eve/memory.json b/packages/eve/public-api/eve/memory.json new file mode 100644 index 0000000000..b0f13b29c4 --- /dev/null +++ b/packages/eve/public-api/eve/memory.json @@ -0,0 +1,28 @@ +[ + "DefinedMemory", + "MemoryCaptureHandler", + "MemoryCompactionCompletedContext", + "MemoryCompactionRequestedContext", + "MemoryDefinition", + "MemoryNamespaceContext", + "MemoryNamespaceDefinition", + "MemoryOperationContext", + "MemoryProvider", + "MemoryRecallHandler", + "MemoryRecallMessage", + "MemoryRecallResult", + "MemoryScope", + "MemoryScopeContext", + "MemoryScopeDefinition", + "MemoryScopeResolverResult", + "MemoryToolDefinition", + "MemoryToolSet", + "MemoryToolsContext", + "MemoryTurnCompletedContext", + "MemoryTurnContext", + "MemoryTurnStartedContext", + "MemoryVisibility", + "defaultNamespace", + "defineMemory", + "defineMemoryProvider" +] diff --git a/packages/eve/public-api/eve/memory/file.json b/packages/eve/public-api/eve/memory/file.json new file mode 100644 index 0000000000..fcc25664c5 --- /dev/null +++ b/packages/eve/public-api/eve/memory/file.json @@ -0,0 +1,10 @@ +[ + "FileMemoryOptions", + "MemoryDocument", + "MemoryDocumentBackend", + "MemoryDocumentConflictError", + "MemoryDocumentReadInput", + "MemoryDocumentWriteInput", + "fileMemory", + "inMemory" +] diff --git a/packages/eve/public-api/eve/memory/file/vercel.json b/packages/eve/public-api/eve/memory/file/vercel.json new file mode 100644 index 0000000000..1f750fd009 --- /dev/null +++ b/packages/eve/public-api/eve/memory/file/vercel.json @@ -0,0 +1 @@ +["VercelBlobBackendOptions", "vercelBlob"] diff --git a/packages/eve/public-api/eve/memory/scope.json b/packages/eve/public-api/eve/memory/scope.json new file mode 100644 index 0000000000..956adecbd4 --- /dev/null +++ b/packages/eve/public-api/eve/memory/scope.json @@ -0,0 +1 @@ +["byPrincipal"] diff --git a/packages/eve/public-api/eve/models.json b/packages/eve/public-api/eve/models.json new file mode 100644 index 0000000000..87615c66c2 --- /dev/null +++ b/packages/eve/public-api/eve/models.json @@ -0,0 +1 @@ +["auto"] diff --git a/packages/eve/public-api/eve/models/anthropic.json b/packages/eve/public-api/eve/models/anthropic.json new file mode 100644 index 0000000000..a5f146473c --- /dev/null +++ b/packages/eve/public-api/eve/models/anthropic.json @@ -0,0 +1 @@ +["anthropic"] diff --git a/packages/eve/public-api/eve/models/openai.json b/packages/eve/public-api/eve/models/openai.json new file mode 100644 index 0000000000..f7a05308de --- /dev/null +++ b/packages/eve/public-api/eve/models/openai.json @@ -0,0 +1 @@ +["chatgpt", "experimental_chatgpt", "openai"] diff --git a/packages/eve/public-api/eve/next.json b/packages/eve/public-api/eve/next.json new file mode 100644 index 0000000000..156296cdd8 --- /dev/null +++ b/packages/eve/public-api/eve/next.json @@ -0,0 +1,13 @@ +[ + "EVE_NEXT_SERVICE_PREFIX", + "EveNextConfig", + "EveNextConfigFunction", + "EveNextConfigInput", + "EveNextRewriteRule", + "EveNextRewriteSections", + "EveNextRewrites", + "WithEveAgentOptions", + "WithEveAgentsConfig", + "WithEveOptions", + "withEve" +] diff --git a/packages/eve/public-api/eve/nuxt.json b/packages/eve/public-api/eve/nuxt.json new file mode 100644 index 0000000000..b136f1bc67 --- /dev/null +++ b/packages/eve/public-api/eve/nuxt.json @@ -0,0 +1 @@ +["EveNuxtModuleOptions", "default"] diff --git a/packages/eve/public-api/eve/react.json b/packages/eve/public-api/eve/react.json new file mode 100644 index 0000000000..a2a8c566e7 --- /dev/null +++ b/packages/eve/public-api/eve/react.json @@ -0,0 +1,21 @@ +[ + "AgentObservation", + "AskedCall", + "ConversationAgentSession", + "ConversationState", + "Delivery", + "DeliveryResult", + "EveInteractionPart", + "EveMessage", + "EveMessageMetadata", + "EveMessagePart", + "EveToolPart", + "Interaction", + "PrepareSend", + "UseEveAgentHelpers", + "UseEveAgentOptions", + "UseEveAgentSnapshot", + "UseEveAgentStatus", + "signInEnding", + "useEveAgent" +] diff --git a/packages/eve/public-api/eve/sandbox.json b/packages/eve/public-api/eve/sandbox.json new file mode 100644 index 0000000000..ce8033fadb --- /dev/null +++ b/packages/eve/public-api/eve/sandbox.json @@ -0,0 +1,25 @@ +[ + "DefaultSandbox", + "DefaultSandboxEnvironmentOptions", + "RuntimeSandboxSession", + "RuntimeSandboxSessionFor", + "SandboxCommandResult", + "SandboxEnvironment", + "SandboxNetworkOptions", + "SandboxNetworkPolicy", + "SandboxOpenArguments", + "SandboxProcess", + "SandboxReadBinaryFileOptions", + "SandboxReadFileOptions", + "SandboxReadTextFileOptions", + "SandboxRunOptions", + "SandboxSelector", + "SandboxSelectorContext", + "SandboxSession", + "SandboxSpawnOptions", + "SandboxWriteBinaryFileOptions", + "SandboxWriteFileOptions", + "SandboxWriteTextFileOptions", + "defineParentSandbox", + "defineSandbox" +] diff --git a/packages/eve/public-api/eve/sandbox/docker.json b/packages/eve/public-api/eve/sandbox/docker.json new file mode 100644 index 0000000000..f1271435a3 --- /dev/null +++ b/packages/eve/public-api/eve/sandbox/docker.json @@ -0,0 +1,8 @@ +[ + "DockerSandbox", + "DockerSandboxEnvironmentOptions", + "DockerSandboxNetworkPolicy", + "DockerSandboxPullPolicy", + "DockerSandboxRuntimeOptions", + "DockerSandboxSession" +] diff --git a/packages/eve/public-api/eve/sandbox/just-bash.json b/packages/eve/public-api/eve/sandbox/just-bash.json new file mode 100644 index 0000000000..de1702ed83 --- /dev/null +++ b/packages/eve/public-api/eve/sandbox/just-bash.json @@ -0,0 +1 @@ +["JustBashEnvironmentOptions", "JustBashFilesystemContext", "JustBashSandbox"] diff --git a/packages/eve/public-api/eve/sandbox/microsandbox.json b/packages/eve/public-api/eve/sandbox/microsandbox.json new file mode 100644 index 0000000000..0bcc218ad2 --- /dev/null +++ b/packages/eve/public-api/eve/sandbox/microsandbox.json @@ -0,0 +1,6 @@ +[ + "MicrosandboxEnvironmentOptions", + "MicrosandboxSandbox", + "MicrosandboxSandboxRuntimeOptions", + "MicrosandboxSandboxSession" +] diff --git a/packages/eve/public-api/eve/sandbox/provider.json b/packages/eve/public-api/eve/sandbox/provider.json new file mode 100644 index 0000000000..122074aacf --- /dev/null +++ b/packages/eve/public-api/eve/sandbox/provider.json @@ -0,0 +1,17 @@ +[ + "SandboxDeleteOptions", + "SandboxPreparedArtifact", + "SandboxProvider", + "SandboxProviderDefinition", + "SandboxProviderFiles", + "SandboxProviderHandle", + "SandboxProviderImplementation", + "SandboxProviderPrepareContext", + "SandboxProviderResourceFile", + "SandboxProviderResourceSource", + "SandboxProviderResourceTree", + "SandboxProviderResources", + "SandboxProviderSessionContext", + "SandboxProviderTargetFile", + "defineSandboxProvider" +] diff --git a/packages/eve/public-api/eve/sandbox/vercel.json b/packages/eve/public-api/eve/sandbox/vercel.json new file mode 100644 index 0000000000..dc532b4d9e --- /dev/null +++ b/packages/eve/public-api/eve/sandbox/vercel.json @@ -0,0 +1,10 @@ +[ + "Drive", + "VercelSandbox", + "VercelSandboxEnvironmentOptions", + "VercelSandboxMount", + "VercelSandboxMountMode", + "VercelSandboxMounts", + "VercelSandboxRuntimeOptions", + "VercelSandboxSession" +] diff --git a/packages/eve/public-api/eve/schedules.json b/packages/eve/public-api/eve/schedules.json new file mode 100644 index 0000000000..575451711a --- /dev/null +++ b/packages/eve/public-api/eve/schedules.json @@ -0,0 +1,9 @@ +[ + "ScheduleDefinition", + "ScheduleHandlerArgs", + "ScheduleRunHandler", + "ScheduleToFn", + "TypedReceiveTarget", + "defineSchedule", + "isScheduleAuth" +] diff --git a/packages/eve/public-api/eve/self-modification.json b/packages/eve/public-api/eve/self-modification.json new file mode 100644 index 0000000000..8b30f8adcd --- /dev/null +++ b/packages/eve/public-api/eve/self-modification.json @@ -0,0 +1 @@ +["default"] diff --git a/packages/eve/public-api/eve/self-modification/agent.json b/packages/eve/public-api/eve/self-modification/agent.json new file mode 100644 index 0000000000..612a544262 --- /dev/null +++ b/packages/eve/public-api/eve/self-modification/agent.json @@ -0,0 +1 @@ +["SelfModificationAgentOptions", "default", "defineSelfModificationAgent"] diff --git a/packages/eve/public-api/eve/self-modification/config.json b/packages/eve/public-api/eve/self-modification/config.json new file mode 100644 index 0000000000..55c3114651 --- /dev/null +++ b/packages/eve/public-api/eve/self-modification/config.json @@ -0,0 +1,8 @@ +[ + "DEPLOYED_OPTION_MOVED_MESSAGE", + "ResolvedSelfModificationConfig", + "SelfModificationConfig", + "SelfModificationExtensionConfig", + "defineSelfModificationConfig", + "resolveSelfModificationConfig" +] diff --git a/packages/eve/public-api/eve/self-modification/local.json b/packages/eve/public-api/eve/self-modification/local.json new file mode 100644 index 0000000000..8b30f8adcd --- /dev/null +++ b/packages/eve/public-api/eve/self-modification/local.json @@ -0,0 +1 @@ +["default"] diff --git a/packages/eve/public-api/eve/self-modification/remote.json b/packages/eve/public-api/eve/self-modification/remote.json new file mode 100644 index 0000000000..8b30f8adcd --- /dev/null +++ b/packages/eve/public-api/eve/self-modification/remote.json @@ -0,0 +1 @@ +["default"] diff --git a/packages/eve/public-api/eve/self-modification/sandbox.json b/packages/eve/public-api/eve/self-modification/sandbox.json new file mode 100644 index 0000000000..c64038b8ae --- /dev/null +++ b/packages/eve/public-api/eve/self-modification/sandbox.json @@ -0,0 +1,6 @@ +[ + "SelfModificationSandbox", + "SelfModificationSandboxOptions", + "default", + "defineSelfModificationSandbox" +] diff --git a/packages/eve/public-api/eve/server.json b/packages/eve/public-api/eve/server.json new file mode 100644 index 0000000000..604642b360 --- /dev/null +++ b/packages/eve/public-api/eve/server.json @@ -0,0 +1 @@ +["SessionReader", "SessionStreamOptions", "sessions"] diff --git a/packages/eve/public-api/eve/setup.json b/packages/eve/public-api/eve/setup.json new file mode 100644 index 0000000000..4fd1d5cb73 --- /dev/null +++ b/packages/eve/public-api/eve/setup.json @@ -0,0 +1,79 @@ +[ + "AiGatewayEnvFile", + "AnySetupBox", + "ArgsHeadlessAiGateway", + "ArgsHeadlessProject", + "ChannelKind", + "ChatPreference", + "DeploymentInfo", + "DeploymentState", + "HeadlessEvent", + "HeadlessLogSink", + "HeadlessNextStep", + "HeadlessPromptError", + "InspectVerifiedRemoteAgentDeps", + "InteractionRequired", + "MultiSelectOptions", + "NoteTone", + "OutputSink", + "PackageManagerStrategy", + "ProjectResolution", + "PromptCommandLog", + "Prompter", + "PrompterValue", + "ProvisionSlackbotResult", + "ResolvedAiGateway", + "ResolvedAiGatewayCredentials", + "ResolvedProjectPath", + "ResolvedVercelProject", + "ResolvedVercelProjectSpec", + "RunPackageManagerOptions", + "RunPnpmOptions", + "RunResult", + "RunVercelOptions", + "RunnerOptions", + "SelectCommonOptions", + "SelectOption", + "SetupBox", + "SetupConnectionConnectorOptions", + "SetupConnectionConnectorResult", + "SetupMode", + "SetupState", + "SingleSelectOptions", + "SlackConnectorCandidate", + "SlackConnectorSelection", + "SlackbotConnectorInspection", + "VercelProjectIdentity", + "VerifiedRemoteAgentInspection", + "WiringMode", + "WizardCancelledError", + "createDefaultSetupState", + "createHeadlessPrompter", + "createPromptCommandOutput", + "createPrompter", + "detectDeployment", + "detectProjectResolution", + "formatHeadlessEvent", + "getPackageManagerStrategy", + "inspectSlackbotConnectors", + "inspectVerifiedRemoteAgent", + "linkProject", + "projectProductionUrlFromResolution", + "provisionSlackbot", + "reconcileSlackUid", + "requireAuth", + "requireProjectPath", + "resolveProjectByNameOrId", + "resolveTeam", + "runHeadless", + "runInteractive", + "runPackageManagerInstall", + "runPnpmInstall", + "runRemoteAuthFlow", + "runVercel", + "runVercelEnvPull", + "setupConnectionConnector", + "snapshotSetupState", + "spawnPackageManager", + "spawnPnpm" +] diff --git a/packages/eve/public-api/eve/setup/scaffold.json b/packages/eve/public-api/eve/setup/scaffold.json new file mode 100644 index 0000000000..c74421ea29 --- /dev/null +++ b/packages/eve/public-api/eve/setup/scaffold.json @@ -0,0 +1,56 @@ +[ + "CONNECTION_CATALOG", + "CURRENT_DIRECTORY_PROJECT_NAME", + "CUSTOM_CONNECTION_SLUG", + "ChannelKind", + "ChannelMutationResult", + "ConnectionAuthSpec", + "ConnectionCatalogEntry", + "ConnectionInput", + "ConnectionMutationAction", + "ConnectionMutationResult", + "ConnectionProtocol", + "CustomConnectionInput", + "DEFAULT_SLACK_CONNECTOR_SLUG", + "EnsureChannelOptions", + "EnsureConnectionDependenciesOptions", + "EnsureConnectionOptions", + "EnsurePackageDependenciesOptions", + "EnvHeader", + "EvePackageContract", + "HumanAction", + "HumanActionRequiredError", + "McpEndpoint", + "OpenApiEndpoint", + "PackageDependencyMutation", + "SCAFFOLDABLE_CHANNELS", + "SLACK_CHANNEL_DEFAULT_ROUTE", + "SUPPORTED_PROTOCOLS", + "ScaffoldBaseProjectOptions", + "ScaffoldExtensionProjectOptions", + "ScaffoldableChannel", + "SlackConnectorSlug", + "WebAuthentication", + "WebPackageVersions", + "WriteFileExistsError", + "byokProviderEnvVar", + "catalogSlugs", + "deriveSlackConnectorSlug", + "effectiveProtocols", + "endpointForProtocol", + "ensureChannel", + "ensureConnection", + "ensureConnectionDependencies", + "ensurePackageDependencies", + "getCatalogEntry", + "hasVercelHostFramework", + "isEveProject", + "isNextJsProject", + "isValidConnectionSlug", + "listAuthoredChannels", + "listAuthoredConnections", + "normalizeSlackConnectorSlug", + "resolveVercelHostFrameworkPreset", + "scaffoldBaseProject", + "scaffoldExtensionProject" +] diff --git a/packages/eve/public-api/eve/skills.json b/packages/eve/public-api/eve/skills.json new file mode 100644 index 0000000000..cfe9861b04 --- /dev/null +++ b/packages/eve/public-api/eve/skills.json @@ -0,0 +1,10 @@ +[ + "DynamicResolveContext", + "DynamicSentinel", + "NamedSkillDefinition", + "SkillDefinition", + "SkillFileContent", + "SkillPackageDefinition", + "defineDynamic", + "defineSkill" +] diff --git a/packages/eve/public-api/eve/svelte.json b/packages/eve/public-api/eve/svelte.json new file mode 100644 index 0000000000..1cc167eb33 --- /dev/null +++ b/packages/eve/public-api/eve/svelte.json @@ -0,0 +1,21 @@ +[ + "AgentObservation", + "AskedCall", + "ConversationAgentSession", + "ConversationState", + "Delivery", + "DeliveryResult", + "EveInteractionPart", + "EveMessage", + "EveMessageMetadata", + "EveMessagePart", + "EveToolPart", + "Interaction", + "PrepareSend", + "UseEveAgentOptions", + "UseEveAgentReturn", + "UseEveAgentSnapshot", + "UseEveAgentStatus", + "signInEnding", + "useEveAgent" +] diff --git a/packages/eve/public-api/eve/sveltekit.json b/packages/eve/public-api/eve/sveltekit.json new file mode 100644 index 0000000000..24d53075bf --- /dev/null +++ b/packages/eve/public-api/eve/sveltekit.json @@ -0,0 +1 @@ +["EveSvelteKitPluginOptions", "eveSvelteKit"] diff --git a/packages/eve/public-api/eve/tanstack.json b/packages/eve/public-api/eve/tanstack.json new file mode 100644 index 0000000000..5bd5f861ef --- /dev/null +++ b/packages/eve/public-api/eve/tanstack.json @@ -0,0 +1 @@ +["EveTanStackPluginOptions", "eveTanStack"] diff --git a/packages/eve/public-api/eve/tools.json b/packages/eve/public-api/eve/tools.json new file mode 100644 index 0000000000..e80ed7f497 --- /dev/null +++ b/packages/eve/public-api/eve/tools.json @@ -0,0 +1,53 @@ +[ + "AgentMessageResult", + "AgentResponse", + "AgentSendOptions", + "AgentSession", + "ApprovalResponder", + "ApprovedCall", + "DisabledToolSentinel", + "DynamicEvents", + "DynamicResolveContext", + "DynamicSentinel", + "DynamicToolEntry", + "DynamicToolEvents", + "DynamicToolResult", + "DynamicToolSet", + "MatchedConnectionResult", + "MatchedToolResult", + "SessionContext", + "SettledCallRow", + "ToolAnswer", + "ToolAskOptions", + "ToolAuthOptions", + "ToolAuthProvider", + "ToolContext", + "ToolDefinition", + "ToolLabelDefinition", + "ToolModelOutput", + "ToolModelOutputPart", + "ToolQuestion", + "ToolResultFromFn", + "ToolResultSource", + "WorkflowAgentMetadata", + "WorkflowExecuteToolDefinition", + "WorkflowServeCall", + "WorkflowServeContext", + "WorkflowServeReceive", + "WorkflowServeToolDefinition", + "WorkflowStepToolContext", + "WorkflowTaskToolDefinition", + "WorkflowToolContext", + "WorkflowToolDefinition", + "defineDurableCallback", + "defineDurableSchema", + "defineDynamic", + "defineTool", + "defineWorkflowTool", + "disableTool", + "isDisabledToolSentinel", + "serializeModelInputSchema", + "toolOutput", + "toolOutputPart", + "toolResultFrom" +] diff --git a/packages/eve/public-api/eve/tools/agent-router.json b/packages/eve/public-api/eve/tools/agent-router.json new file mode 100644 index 0000000000..18a6414443 --- /dev/null +++ b/packages/eve/public-api/eve/tools/agent-router.json @@ -0,0 +1 @@ +["AgentRouterInput", "AgentRouterTool", "agentRouter"] diff --git a/packages/eve/public-api/eve/tools/agent.json b/packages/eve/public-api/eve/tools/agent.json new file mode 100644 index 0000000000..b6698a5118 --- /dev/null +++ b/packages/eve/public-api/eve/tools/agent.json @@ -0,0 +1 @@ +["agent", "default"] diff --git a/packages/eve/public-api/eve/tools/approval.json b/packages/eve/public-api/eve/tools/approval.json new file mode 100644 index 0000000000..dc8adf2317 --- /dev/null +++ b/packages/eve/public-api/eve/tools/approval.json @@ -0,0 +1,22 @@ +[ + "Approval", + "ApprovalConfiguration", + "ApprovalContext", + "ApprovalPolicy", + "ApprovalPrompt", + "ApprovalPromptContext", + "ApprovalRequest", + "ApprovalResponse", + "ApprovalResponseAuth", + "ApprovalResponseContext", + "ApprovalResponseDecision", + "ApprovalResponsePolicy", + "ApprovalResponseSession", + "ApprovalStatus", + "AutoApprovalOptions", + "ConnectionToolAnnotations", + "always", + "auto", + "never", + "once" +] diff --git a/packages/eve/public-api/eve/tools/ask_question.json b/packages/eve/public-api/eve/tools/ask_question.json new file mode 100644 index 0000000000..785bb917de --- /dev/null +++ b/packages/eve/public-api/eve/tools/ask_question.json @@ -0,0 +1 @@ +["AskQuestionInput", "AskQuestionOutput", "askQuestion"] diff --git a/packages/eve/public-api/eve/tools/bash.json b/packages/eve/public-api/eve/tools/bash.json new file mode 100644 index 0000000000..c8b29e5bac --- /dev/null +++ b/packages/eve/public-api/eve/tools/bash.json @@ -0,0 +1 @@ +["BASH_INPUT_SCHEMA", "BASH_OUTPUT_SCHEMA", "BashToolInput", "BashToolOutput", "bash", "default"] diff --git a/packages/eve/public-api/eve/tools/glob.json b/packages/eve/public-api/eve/tools/glob.json new file mode 100644 index 0000000000..c3b8cc0529 --- /dev/null +++ b/packages/eve/public-api/eve/tools/glob.json @@ -0,0 +1 @@ +["GLOB_INPUT_SCHEMA", "GLOB_OUTPUT_SCHEMA", "GlobToolInput", "GlobToolOutput", "default", "glob"] diff --git a/packages/eve/public-api/eve/tools/grep.json b/packages/eve/public-api/eve/tools/grep.json new file mode 100644 index 0000000000..8c2ff72054 --- /dev/null +++ b/packages/eve/public-api/eve/tools/grep.json @@ -0,0 +1 @@ +["GREP_INPUT_SCHEMA", "GREP_OUTPUT_SCHEMA", "GrepToolInput", "GrepToolOutput", "default", "grep"] diff --git a/packages/eve/public-api/eve/tools/no_reply.json b/packages/eve/public-api/eve/tools/no_reply.json new file mode 100644 index 0000000000..fccad55b0e --- /dev/null +++ b/packages/eve/public-api/eve/tools/no_reply.json @@ -0,0 +1 @@ +["NoReplyToolInput", "noReply"] diff --git a/packages/eve/public-api/eve/tools/read_file.json b/packages/eve/public-api/eve/tools/read_file.json new file mode 100644 index 0000000000..134769b940 --- /dev/null +++ b/packages/eve/public-api/eve/tools/read_file.json @@ -0,0 +1,8 @@ +[ + "READ_FILE_INPUT_SCHEMA", + "READ_FILE_OUTPUT_SCHEMA", + "ReadFileToolInput", + "ReadFileToolOutput", + "default", + "readFile" +] diff --git a/packages/eve/public-api/eve/tools/sleep.json b/packages/eve/public-api/eve/tools/sleep.json new file mode 100644 index 0000000000..13a74535cc --- /dev/null +++ b/packages/eve/public-api/eve/tools/sleep.json @@ -0,0 +1 @@ +["SleepToolInput", "SleepToolOutput", "sleep"] diff --git a/packages/eve/public-api/eve/tools/web_fetch.json b/packages/eve/public-api/eve/tools/web_fetch.json new file mode 100644 index 0000000000..45c00980dc --- /dev/null +++ b/packages/eve/public-api/eve/tools/web_fetch.json @@ -0,0 +1,8 @@ +[ + "WEB_FETCH_INPUT_SCHEMA", + "WEB_FETCH_OUTPUT_SCHEMA", + "WebFetchToolInput", + "WebFetchToolOutput", + "default", + "webFetch" +] diff --git a/packages/eve/public-api/eve/tools/web_search.json b/packages/eve/public-api/eve/tools/web_search.json new file mode 100644 index 0000000000..78bad46957 --- /dev/null +++ b/packages/eve/public-api/eve/tools/web_search.json @@ -0,0 +1,10 @@ +[ + "WebSearchFallbackProvider", + "WebSearchProvider", + "WebSearchToolDefinition", + "WebSearchToolInput", + "default", + "defaultWebSearch", + "isWebSearchToolDefinition", + "webSearch" +] diff --git a/packages/eve/public-api/eve/tools/workflow.json b/packages/eve/public-api/eve/tools/workflow.json new file mode 100644 index 0000000000..fe973bf99d --- /dev/null +++ b/packages/eve/public-api/eve/tools/workflow.json @@ -0,0 +1 @@ +["WorkflowTool", "WorkflowToolInput", "WorkflowToolOptions", "workflow"] diff --git a/packages/eve/public-api/eve/tools/write_file.json b/packages/eve/public-api/eve/tools/write_file.json new file mode 100644 index 0000000000..12f5f6d0fc --- /dev/null +++ b/packages/eve/public-api/eve/tools/write_file.json @@ -0,0 +1,8 @@ +[ + "WRITE_FILE_INPUT_SCHEMA", + "WRITE_FILE_OUTPUT_SCHEMA", + "WriteFileToolInput", + "WriteFileToolOutput", + "default", + "writeFile" +] diff --git a/packages/eve/public-api/eve/vercel.json b/packages/eve/public-api/eve/vercel.json new file mode 100644 index 0000000000..d56e0f3287 --- /dev/null +++ b/packages/eve/public-api/eve/vercel.json @@ -0,0 +1 @@ +["EveVercelConfig", "EveVercelRouteConfig", "EveVercelServiceConfig", "WithEveOptions", "withEve"] diff --git a/packages/eve/public-api/eve/vue.json b/packages/eve/public-api/eve/vue.json new file mode 100644 index 0000000000..1cc167eb33 --- /dev/null +++ b/packages/eve/public-api/eve/vue.json @@ -0,0 +1,21 @@ +[ + "AgentObservation", + "AskedCall", + "ConversationAgentSession", + "ConversationState", + "Delivery", + "DeliveryResult", + "EveInteractionPart", + "EveMessage", + "EveMessageMetadata", + "EveMessagePart", + "EveToolPart", + "Interaction", + "PrepareSend", + "UseEveAgentOptions", + "UseEveAgentReturn", + "UseEveAgentSnapshot", + "UseEveAgentStatus", + "signInEnding", + "useEveAgent" +] diff --git a/packages/eve/public-api/eve/workflow-modules.json b/packages/eve/public-api/eve/workflow-modules.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/eve/public-api/eve/workflow-modules.json @@ -0,0 +1 @@ +[] diff --git a/scripts/guard-invariants.mjs b/scripts/guard-invariants.mjs index e652d3bd75..6287480b2e 100644 --- a/scripts/guard-invariants.mjs +++ b/scripts/guard-invariants.mjs @@ -152,6 +152,9 @@ * `eve/events`; the client) never import a schema module by * value: schemas serve tests and development only. * + * rule 55 — Public named exports match checker-generated per-entry lists. + * Additions and removals require an explicit, reviewable API diff. + * * Baselines for rules with pre-existing violations live in * `guard-invariants-baseline.json`. Counts and allowlists in that file * may only shrink (as offenders are removed) — they may never grow. @@ -162,6 +165,7 @@ import { dirname, join, relative, resolve, sep } from "node:path"; import { fileURLToPath } from "node:url"; import { load as loadYaml } from "js-yaml"; import { checkExtensionCapabilityContracts } from "./extension-capability-contracts.mjs"; +import { checkPublicExports } from "./public-exports.mjs"; const require = createRequire(import.meta.url); const extractorRequire = createRequire(require.resolve("@microsoft/api-extractor/package.json")); @@ -1926,6 +1930,10 @@ async function main() { violations.push(...state.rule52); violations.push(...state.rule53); + for (const issue of await checkPublicExports()) { + violations.push({ rule: 55, ...issue }); + } + if (violations.length === 0) { process.stdout.write("[eve:guard:invariants] ok — all mechanical lints passed.\n"); return; diff --git a/scripts/public-exports.mjs b/scripts/public-exports.mjs new file mode 100644 index 0000000000..0eb9f5db56 --- /dev/null +++ b/scripts/public-exports.mjs @@ -0,0 +1,109 @@ +import { execFileSync } from "node:child_process"; +import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { dirname, join, relative } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const require = createRequire(import.meta.url); +const ts = createRequire(require.resolve("@microsoft/api-extractor/package.json"))("typescript"); +const root = fileURLToPath(new URL("../", import.meta.url)); +const eve = join(root, "packages/eve"); +const snapshots = join(eve, "public-api"); + +export async function checkPublicExports({ write = false } = {}) { + execFileSync(process.execPath, [join(eve, "scripts/vendor-compiled.mjs")], { + cwd: eve, + stdio: ["ignore", "pipe", "pipe"], + }); + const manifest = JSON.parse(await readFile(join(eve, "package.json"), "utf8")); + const entries = new Map(); + for (const [key, value] of Object.entries(manifest.exports)) { + if (key === "./internal" || key.startsWith("./internal/")) continue; + const declaration = typeof value === "object" ? value.types : value; + if (typeof declaration !== "string" || !declaration.endsWith(".d.ts")) continue; + const source = join(eve, declaration.replace(/^\.\/dist\//, "")); + const implementation = source.replace(/\.d\.ts$/, ".ts"); + entries.set( + key === "." ? "eve" : `eve${key.slice(1)}`, + ts.sys.fileExists(implementation) ? implementation : source, + ); + } + const program = ts.createProgram([...entries.values()], { + module: ts.ModuleKind.NodeNext, + moduleResolution: ts.ModuleResolutionKind.NodeNext, + target: ts.ScriptTarget.ES2022, + customConditions: ["eve-source"], + allowImportingTsExtensions: true, + noEmit: true, + skipLibCheck: true, + }); + const checker = program.getTypeChecker(); + const issues = []; + const expectedPaths = new Set(); + for (const [entry, source] of entries) { + const file = join(snapshots, `${entry}.json`); + expectedPaths.add(file); + const sourceFile = program.getSourceFile(source); + if (sourceFile === undefined) + throw new Error(`Public entry ${entry} has no source at ${source}.`); + const symbol = checker.getSymbolAtLocation(sourceFile); + const names = + symbol === undefined + ? [] + : checker + .getExportsOfModule(symbol) + .map((item) => item.name) + .sort(); + const content = `${JSON.stringify(names, null, 2)}\n`; + if (write) { + await mkdir(dirname(file), { recursive: true }); + await writeFile(file, content); + continue; + } + const previous = await readFile(file, "utf8").catch((error) => { + if (error.code === "ENOENT") return undefined; + throw error; + }); + if (previous !== undefined && JSON.stringify(JSON.parse(previous)) === JSON.stringify(names)) + continue; + const before = new Set(previous === undefined ? [] : JSON.parse(previous)); + const after = new Set(names); + const added = names.filter((name) => !before.has(name)); + const removed = [...before].filter((name) => !after.has(name)); + issues.push({ + file: relative(root, file), + message: `${entry} export list changed (added: ${added.join(", ") || "none"}; removed: ${removed.join(", ") || "none"}). Review the API change and run node scripts/public-exports.mjs --write.`, + }); + } + const files = await readdir(snapshots, { recursive: true }).catch((error) => { + if (error.code === "ENOENT") return []; + throw error; + }); + for (const name of files) { + if (!name.endsWith(".json")) continue; + const file = join(snapshots, name); + if (expectedPaths.has(file)) continue; + if (write) await rm(file); + else + issues.push({ + file: relative(root, file), + message: + "Export list has no public package entry. Review its removal and run node scripts/public-exports.mjs --write.", + }); + } + return issues; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + if (process.argv.slice(2).some((arg) => arg !== "--write")) + throw new Error("Usage: node scripts/public-exports.mjs [--write]"); + const write = process.argv.includes("--write"); + const issues = await checkPublicExports({ write }); + if (issues.length > 0) { + for (const issue of issues) process.stderr.write(`${issue.file}: ${issue.message}\n`); + process.exitCode = 1; + } else + process.stdout.write( + `[eve:public-exports] ${write ? "updated" : "ok"} — public named export lists match.\n`, + ); +} From d69ecd8a9f4377582389c8bbfba2338e9d7bb038 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:25 +0000 Subject: [PATCH 02/17] refactor(api)!: curate exports and finalize v27 authoring contracts Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- .changeset/curated-public-api.md | 5 + .../eve/self-modification/extension.ts | 2 +- .../agent/channels/anchored.ts | 4 +- docs/channels/eve.mdx | 2 +- docs/concepts/upgrade-to-v27-events.md | 33 +- docs/guides/frontend/overview.mdx | 2 +- docs/reference/typescript-api.md | 6 +- .../agent/tools/invoke-self.ts | 2 +- .../agent/tools/call_child.ts | 4 +- .../evals/runtime/multi-turn-session.eval.ts | 2 +- .../agent/channels/threads.ts | 2 +- .../cancellation/cancel-channel-route.eval.ts | 19 +- .../cancellation/cancel-subagent.eval.ts | 2 +- .../evals/cancellation/cancel-turn.eval.ts | 7 +- .../clear-channel-session.eval.ts | 20 +- .../compact-channel-session.eval.ts | 20 +- .../queued-request-identity.eval.ts | 9 +- .../unified-http-session-operations.eval.ts | 32 +- .../agent-channels/agent/channels/delivery.ts | 4 +- .../agent-channels/agent/channels/webhook.ts | 2 +- .../cancelled-first-turn.eval.ts | 2 +- .../agent/channels/threads.ts | 2 +- .../evals/proxied-ask-text-response.eval.ts | 9 +- .../agent/tools/invoke-hidden.ts | 2 +- .../agent/tools/read-workspace-label.ts | 2 +- .../agent/tools/survey-through-tool.ts | 2 +- .../evals/remote-subagent-stream.eval.ts | 19 +- .../agent/tools/authorized-change.ts | 2 +- .../agent/tools/oauth-authorized-gate.ts | 2 +- .../agent/tools/release-grant.ts | 2 +- .../hitl/approval-response-settlement.eval.ts | 8 +- .../hitl/cancelled-approval-followup.eval.ts | 2 +- .../evals/sandbox/bash-background-job.eval.ts | 12 +- .../evals/sandbox/bash-curl-fanout.eval.ts | 12 +- .../agent/channels/action-narration.ts | 2 +- .../agent-tools/agent/channels/anchored.ts | 4 +- .../agent/channels/metadata-provider.ts | 2 +- .../static-tools/render-stripes-shared.ts | 11 +- .../evals/static-tools/web-search-exa.eval.ts | 10 +- .../static-tools/web-search-parallel.eval.ts | 10 +- .../evals/parallel-tool-calls.eval.ts | 12 +- .../evals/tool-stubs.eval.ts | 15 +- .../agent/channels/skill-context.ts | 2 +- .../agent/lib/agent-reply.ts | 2 +- .../agent/tools/research_brief.ts | 2 +- .../evals/hold-deploy.cancel.eval.ts | 2 +- packages/eve-self-modification/README.md | 6 +- .../extension/extension.ts | 2 +- packages/eve-self-modification/src/agent.ts | 4 +- packages/eve-self-modification/src/sandbox.ts | 2 +- .../compatibility/channel/v63.ts | 15 - .../compatibility/hook/v47.ts | 12 - .../compatibility/schedule/v34.ts | 24 - .../compatibility/state/v13.ts | 7 + .../extension-contracts/entrypoints/state.ts | 1 + .../extension-contracts/entrypoints/tool.ts | 6 +- .../reports/channel/v62.json | 3 +- .../reports/channel/v63.json | 23 - .../reports/channel/v64.json | 23 - .../reports/connection/v51.json | 2 +- .../reports/connection/v52.json | 16 - .../reports/dynamicInstructions/v40.json | 2 +- .../reports/dynamicSkill/v39.json | 2 +- .../reports/dynamicTool/v87.json | 2 +- .../reports/dynamicTool/v88.json | 16 - .../extension-contracts/reports/hook/v47.json | 2 +- .../extension-contracts/reports/hook/v48.json | 7 - .../reports/schedule/v34.json | 2 +- .../reports/schedule/v35.json | 15 - .../reports/state/v14.json | 16 + .../reports/subagent/v39.json | 2 +- .../extension-contracts/reports/tool/v93.json | 4 +- .../extension-contracts/reports/tool/v94.json | 29 - packages/eve/package.json | 55 +- packages/eve/public-api/eve/channels.json | 5 - .../eve/public-api/eve/channels/auth.json | 2 - .../eve/public-api/eve/channels/discord.json | 31 +- packages/eve/public-api/eve/channels/eve.json | 2 - .../eve/public-api/eve/channels/github.json | 1 - .../eve/public-api/eve/channels/linear.json | 13 +- .../eve/public-api/eve/channels/slack.json | 3 - .../eve/public-api/eve/channels/teams.json | 42 +- .../eve/public-api/eve/channels/telegram.json | 32 +- .../eve/public-api/eve/channels/twilio.json | 15 +- packages/eve/public-api/eve/client.json | 191 +-- packages/eve/public-api/eve/connections.json | 3 - packages/eve/public-api/eve/context.json | 1 + packages/eve/public-api/eve/evals.json | 7 +- packages/eve/public-api/eve/evals/expect.json | 12 +- .../eve/public-api/eve/instrumentation.json | 2 - packages/eve/public-api/eve/next.json | 1 - packages/eve/public-api/eve/sandbox.json | 1 - .../eve/public-api/eve/self-modification.json | 1 - .../eve/self-modification/agent.json | 1 - .../eve/self-modification/config.json | 8 - .../eve/self-modification/local.json | 2 +- .../eve/self-modification/sandbox.json | 6 - packages/eve/public-api/eve/setup.json | 79 - .../eve/public-api/eve/setup/scaffold.json | 56 - packages/eve/public-api/eve/tools.json | 1 - packages/eve/public-api/eve/tools/bash.json | 2 +- packages/eve/public-api/eve/tools/glob.json | 2 +- packages/eve/public-api/eve/tools/grep.json | 2 +- .../eve/public-api/eve/tools/read_file.json | 9 +- .../eve/public-api/eve/tools/web_fetch.json | 9 +- .../eve/public-api/eve/tools/web_search.json | 2 - .../eve/public-api/eve/tools/write_file.json | 9 +- packages/eve/src/channel/auth/definitions.ts | 1286 ++++++++++++++++ .../eve/src/channel/channel-operations.ts | 3 - packages/eve/src/channel/cors.test.ts | 13 + packages/eve/src/channel/cors.ts | 9 +- .../eve/src/channel/pending-authorization.ts | 4 +- packages/eve/src/channel/session.test.ts | 18 +- packages/eve/src/channel/session.ts | 34 +- packages/eve/src/cli/dev/tui/runner.ts | 12 +- .../eve/src/client/eve-agent-store-state.ts | 2 +- packages/eve/src/client/eve-agent-store.ts | 12 +- packages/eve/src/client/index.ts | 23 +- .../src/compiler/extension-compatibility.ts | 768 +++++----- .../src/context/dynamic-resolve-context.ts | 2 +- packages/eve/src/context/keys.ts | 3 +- packages/eve/src/context/session-context.ts | 8 +- packages/eve/src/discover/extensions.test.ts | 2 +- packages/eve/src/dynamic/definition.ts | 2 +- packages/eve/src/evals/expect/index.ts | 7 - packages/eve/src/evals/index.ts | 5 - packages/eve/src/evals/tool-calls.test.ts | 79 - packages/eve/src/evals/tool-calls.ts | 12 - packages/eve/src/eve-channel/index.ts | 7 +- .../src/eve-channel/request.stream.test.ts | 2 +- packages/eve/src/eve-channel/request.ts | 8 +- packages/eve/src/eve-channel/support.ts | 60 - packages/eve/src/eve-channel/types.ts | 30 +- .../execution/tool-auth.integration.test.ts | 2 +- .../authored-definition/connection.test.ts | 30 +- .../src/internal/mcp/protected-resource.ts | 2 +- .../dev-generation-artifacts.scenario.test.ts | 2 +- packages/eve/src/internal/scaffold.ts | 21 + .../testing/channel-conformance/harness.ts | 7 +- .../custom-channel-portability.ts | 13 +- packages/eve/src/public/channels/auth.ts | 1330 +---------------- .../eve/src/public/channels/discord/index.ts | 39 - .../channels/eve-session-routes.test.ts | 3 +- .../eve/src/public/channels/github/index.ts | 5 +- packages/eve/src/public/channels/index.ts | 5 - .../eve/src/public/channels/linear/index.ts | 12 +- packages/eve/src/public/channels/mcp.ts | 12 +- .../public/channels/oauth-resource.test.ts | 8 +- .../eve/src/public/channels/slack/index.ts | 11 - .../eve/src/public/channels/teams/index.ts | 48 +- .../eve/src/public/channels/telegram/index.ts | 34 - .../eve/src/public/channels/twilio/index.ts | 17 +- packages/eve/src/public/connections/index.ts | 3 - packages/eve/src/public/context/index.ts | 1 + .../definitions/connections/mcp.test.ts | 4 +- .../definitions/connections/openapi.test.ts | 4 +- .../eve/src/public/definitions/sandbox.ts | 1 - .../src/public/instrumentation/provider.ts | 26 +- packages/eve/src/public/memory/index.ts | 2 +- packages/eve/src/public/next/index.test.ts | 9 +- packages/eve/src/public/next/index.ts | 6 +- packages/eve/src/public/sandbox/index.ts | 1 - .../eve/src/public/sandbox/vercel.test.ts | 4 +- packages/eve/src/public/tools/bash.ts | 8 +- packages/eve/src/public/tools/glob.ts | 8 +- packages/eve/src/public/tools/grep.ts | 8 +- packages/eve/src/public/tools/index.ts | 1 - packages/eve/src/public/tools/read-file.ts | 2 - packages/eve/src/public/tools/web-fetch.ts | 2 - packages/eve/src/public/tools/web-search.ts | 2 - packages/eve/src/public/tools/write-file.ts | 2 - packages/eve/src/react/use-eve-agent.ts | 4 +- .../validate-authorization.test.ts | 52 - packages/eve/src/sandbox/providers.test.ts | 4 +- ...authored-runtime-boundary.scenario.test.ts | 2 +- .../self-modification/extension/extension.ts | 2 + .../extension/subagents/agent/sandbox.ts | 4 +- .../migration.integration.test.ts | 154 +- .../eve/src/self-modification/migration.ts | 103 -- .../eve/src/self-modification/setup.test.ts | 6 +- packages/eve/src/self-modification/setup.ts | 14 +- .../integrations/self-modification/setup.ts | 68 +- packages/eve/src/shared/connection-types.ts | 38 +- packages/eve/src/shared/history-message.ts | 235 +++ .../eve/src/shared/sandbox-environment.ts | 10 +- packages/eve/src/shared/sandbox-session.ts | 203 +-- .../eve/src/shared/validate-authorization.ts | 25 +- packages/eve/src/svelte/use-eve-agent.ts | 4 +- packages/eve/src/tools/auth.ts | 6 +- packages/eve/src/tools/definition.ts | 3 +- .../eve/src/tracing/eve/otel-declaration.ts | 2 +- packages/eve/src/vue/use-eve-agent.ts | 4 +- .../tui-client/tui-connection-auth-user.ts | 8 +- scripts/guard-invariants-baseline.json | 4 +- 194 files changed, 2601 insertions(+), 3644 deletions(-) create mode 100644 .changeset/curated-public-api.md delete mode 100644 packages/eve/extension-contracts/compatibility/channel/v63.ts delete mode 100644 packages/eve/extension-contracts/compatibility/hook/v47.ts delete mode 100644 packages/eve/extension-contracts/compatibility/schedule/v34.ts create mode 100644 packages/eve/extension-contracts/compatibility/state/v13.ts delete mode 100644 packages/eve/extension-contracts/reports/channel/v63.json delete mode 100644 packages/eve/extension-contracts/reports/channel/v64.json delete mode 100644 packages/eve/extension-contracts/reports/connection/v52.json delete mode 100644 packages/eve/extension-contracts/reports/dynamicTool/v88.json delete mode 100644 packages/eve/extension-contracts/reports/hook/v48.json delete mode 100644 packages/eve/extension-contracts/reports/schedule/v35.json create mode 100644 packages/eve/extension-contracts/reports/state/v14.json delete mode 100644 packages/eve/extension-contracts/reports/tool/v94.json delete mode 100644 packages/eve/public-api/eve/self-modification.json delete mode 100644 packages/eve/public-api/eve/self-modification/agent.json delete mode 100644 packages/eve/public-api/eve/self-modification/config.json delete mode 100644 packages/eve/public-api/eve/self-modification/sandbox.json delete mode 100644 packages/eve/public-api/eve/setup.json delete mode 100644 packages/eve/public-api/eve/setup/scaffold.json create mode 100644 packages/eve/src/channel/auth/definitions.ts create mode 100644 packages/eve/src/channel/cors.test.ts delete mode 100644 packages/eve/src/evals/tool-calls.test.ts delete mode 100644 packages/eve/src/evals/tool-calls.ts create mode 100644 packages/eve/src/internal/scaffold.ts delete mode 100644 packages/eve/src/self-modification/migration.ts create mode 100644 packages/eve/src/shared/history-message.ts diff --git a/.changeset/curated-public-api.md b/.changeset/curated-public-api.md new file mode 100644 index 0000000000..ac8f126b8e --- /dev/null +++ b/.changeset/curated-public-api.md @@ -0,0 +1,5 @@ +--- +"eve": minor +--- + +Curate public exports and give history and sandbox APIs eve-owned types while retaining reusable authoring types. Use canonical import paths, `principalType` for connection authorization, `ChannelCors` across channels, and explicit local or remote self-modification mounts; scaffold integrations now live under `eve/internal/*`. diff --git a/apps/docs/registry/eve/self-modification/extension.ts b/apps/docs/registry/eve/self-modification/extension.ts index 1bb436a3fb..8932a091c1 100644 --- a/apps/docs/registry/eve/self-modification/extension.ts +++ b/apps/docs/registry/eve/self-modification/extension.ts @@ -1,4 +1,4 @@ -import selfModification from "eve/self-modification"; +import selfModification from "eve/self-modification/local"; export default selfModification({ // model: "provider/model", diff --git a/apps/fixtures/agent-tui-client/agent/channels/anchored.ts b/apps/fixtures/agent-tui-client/agent/channels/anchored.ts index b5fdc4e51c..be01c61f38 100644 --- a/apps/fixtures/agent-tui-client/agent/channels/anchored.ts +++ b/apps/fixtures/agent-tui-client/agent/channels/anchored.ts @@ -63,7 +63,7 @@ export default defineChannel({ ok: true, anchorToken, initialContinuationToken: `pending:${body.threadId}`, - sessionId: session.id, + sessionId: session.sessionId, }); }), @@ -75,7 +75,7 @@ export default defineChannel({ state: initialState(anchorToken), }); - return Response.json({ ok: true, anchorToken, sessionId: session.id }); + return Response.json({ ok: true, anchorToken, sessionId: session.sessionId }); }), ], events: { diff --git a/docs/channels/eve.mdx b/docs/channels/eve.mdx index 086e218b41..511f0ae56c 100644 --- a/docs/channels/eve.mdx +++ b/docs/channels/eve.mdx @@ -255,7 +255,7 @@ export default eveChannel({ cors: { origin: "https://app.example.com", methods: ["GET", "POST"], - allowedHeaders: ["authorization", "content-type"], + allowHeaders: ["authorization", "content-type"], }, }); ``` diff --git a/docs/concepts/upgrade-to-v27-events.md b/docs/concepts/upgrade-to-v27-events.md index 35c77193a6..ac436f0ee7 100644 --- a/docs/concepts/upgrade-to-v27-events.md +++ b/docs/concepts/upgrade-to-v27-events.md @@ -343,13 +343,42 @@ are type-only exports. Obtain them from `new Client()` and its methods instead o - Turn results expose `view`, `reply`, `data`, `sessionEnded`, and the shared request status. - Match v27 facts in `t.event(...)`, `eventOrder(...)`, and `waitForEvent(...)`. Event matchers can match a fact's `scope`. -- `turn.waitForToolCall(name, options)` waits for a settled call to a tool, and `toolCallsOf(events)` - derives tool calls from events. Eval tool calls always carry their `callId`. +- `turn.waitForToolCall(name, options)` waits for a settled call to a tool. Replace `toolCallsOf` + with `Object.values(turn.view.calls)`; names are `call.capability.name`, and settled results use + `call.outcome`. Use `callOutputSource(turn.view, call.callId)` from `eve/events` when a call shares + another call's output. Eval tool calls still carry their `callId`. - The subagent matcher's `remoteUrl` is removed, and a subagent call's `status` is `pending` until it settles, then its outcome (`completed`, `failed`, `rejected`, `interrupted`, `abandoned`). - Interactions are `interactions`, `pendingInteractions`, and `requireInteraction(filter)`, sign-ins included; match on `kind`. +## Public imports and authoring types + +- Import fact and view types from `eve/events`, `SessionContext` and `HistoryMessage` from + `eve/context`, and assertion types and `ToolStub` from `eve/evals`. The client no longer + re-exports the event catalog's types. Named judge, mock-model, approval, and disabled-marker + types remain available. +- Connection authorization uses `principalType: "app" | "user"`; omitted getToken-only + ownership defaults to `"app"`. Replace `credentialOwner` and use `AuthorizationDefinition` + instead of `ConnectionAuthProvider`. +- All channels use `ChannelCors`. Use `allowHeaders`, `exposeHeaders`, and + `preflight: { statusCode }`; `origin` accepts a single string or an exact-origin list. +- History and sandbox types are eve-owned. Replace `RuntimeSandboxSessionFor` with + `RuntimeSandboxSession`; the eight sandbox method option types remain exported. +- Session handles read with `stream()`. `getEventStream`, `getLineStream`, and `getChildBinding` + are no longer public handle methods. Callback routing and forwarded initiator auth are + framework plumbing, not authored send options. +- Platform entries keep channel factories, default auth, continuation-token helpers, and authoring + types. Raw platform calls, verification, rendering, and wire constants are internal, except for + the documented Slack, Linear, and Chat SDK helpers. Built-in tool schemas remain on the tool + definitions; separate schema constants are no longer exported. +- `eve/setup` is no longer public. Scaffold integrations move to `eve/internal/scaffold`. + `eve/internal/*` and `eve/experimental/*` have no compatibility promise. +- Mount `eve/self-modification/local` or `/remote` explicitly. The bare entry and `/config` are + removed. Preserve authored changes before removing the retired + `agent/subagents/self-modification/` layout; setup reports the replacement mount rather than + deleting those files. + ## MCP and `eve invoke` - **MCP channel.** `input_required` remains the wire spelling for `awaiting-input`; sign-ins now diff --git a/docs/guides/frontend/overview.mdx b/docs/guides/frontend/overview.mdx index d506ded694..67e97d6fb8 100644 --- a/docs/guides/frontend/overview.mdx +++ b/docs/guides/frontend/overview.mdx @@ -205,7 +205,7 @@ A call your stream stopped following stays open; decide yourself what an open ca When the root agent delegates, its stream emits `task.started` for the task, `call.started` with the task's `taskId` for the call, `child.opened` with the child's `sessionId`, and `call.settled` with the call's result, failure, or cancellation. The subagent's tool part stays running until its `call.settled` arrives and then settles with the call's outcome; the start receipt never settles it. `view.calls[callId]` records each call's `taskId`, `status`, `outcome`, `output`, and `error`, and `agents[sessionId]` records each session a call opened, with its `taskId` and opening `callId`. These records come from the root stream alone. -Detailed child progress lives on the child session's stream, not in the root `messages`. Pass `followSubagents: true` to follow the session each subagent call reaches while the component is mounted; following is off by default. Narrow `agents[sessionId].observation.status` before reading its `conversation`: `"not-followed"` has no child detail, `"following"` has live detail, and `"idle"` has everything the calls so far produced and resumes when another call reaches the task. `"unavailable"` means the stream failed, not that the child failed. Calls that continue a task with its `taskId` reach the same session, so one session's `conversation.messages` and its native `view` tables span all of them. Sessions that a custom task opens with `ctx.agent` are recorded but not followed. +Detailed child progress lives on the child session's stream, not in the root `messages`. Pass `followAgents: true` to follow the session each subagent call reaches while the component is mounted; following is off by default. Narrow `agents[sessionId].observation.status` before reading its `conversation`: `"not-followed"` has no child detail, `"following"` has live detail, and `"idle"` has everything the calls so far produced and resumes when another call reaches the task. `"unavailable"` means the stream failed, not that the child failed. Calls that continue a task with its `taskId` reach the same session, so one session's `conversation.messages` and its native `view` tables span all of them. Sessions that a custom task opens with `ctx.agent` are recorded but not followed. If you manage your own session rather than using the hook, pass a `child.opened` event from `events` to [`session.agent()`](../client/streaming#follow-a-subagent) and follow the child with `stream()`. It follows local and remote children through the parent, so the browser never calls a remote deployment directly. See [What the parent sees](../../subagents#what-the-parent-sees) for the complete contract. diff --git a/docs/reference/typescript-api.md b/docs/reference/typescript-api.md index 89b2f2821a..930a13e9ce 100644 --- a/docs/reference/typescript-api.md +++ b/docs/reference/typescript-api.md @@ -157,7 +157,11 @@ Tool definitions accept `availableInSubagents: false` to restrict the tool to to | [`eve/client`](../guides/client/overview) | `Client`, `ClientSession`, health and agent-info schemas, response errors | | [`eve/server`](../concepts/sessions-runs-and-streaming#read-a-session-in-process) | `sessions`, for reading session streams in process | -Exported types ship from the same entrypoint as the helper they describe (for example `ToolDefinition` and `ToolContext` from `eve/tools`). The `exports` field in `packages/eve/package.json` lists every public entrypoint. +Exported types ship from the entry point that owns them (for example `ToolDefinition` and `ToolContext` from `eve/tools`). Import fact and view types from `eve/events`, `SessionContext` and `HistoryMessage` from `eve/context`, and assertion types and `ToolStub` from `eve/evals`. Frontend hook entries also export the types their hooks return. + +`HistoryMessage` and the sandbox session, process, result, and method-option types are eve-owned. Their declarations do not change with your app's AI SDK peer version. Named judge, mock-model, approval, and disabled-marker types remain public for reusable helpers and declaration emission. + +The `exports` field in `packages/eve/package.json` lists every package entry point. `eve/experimental/*` and `eve/internal/*` have no compatibility promise. The CLI setup wizard is not a public authoring API; Agent Builder's scaffold integration lives at `eve/internal/scaffold`. ## Direct provider models diff --git a/e2e/fixtures/agent-agent-tool-controls/agent/tools/invoke-self.ts b/e2e/fixtures/agent-agent-tool-controls/agent/tools/invoke-self.ts index d7b46b3386..8c6884d4ed 100644 --- a/e2e/fixtures/agent-agent-tool-controls/agent/tools/invoke-self.ts +++ b/e2e/fixtures/agent-agent-tool-controls/agent/tools/invoke-self.ts @@ -11,7 +11,7 @@ async function execute( "use workflow"; const response = await ctx.agent("agent").send("E2E_INTERNAL_ROOT_COPY"); - const { message } = await response.result(); + const { reply: message } = await response.result(); return message ?? null; } diff --git a/e2e/fixtures/agent-basic-runtime/agent/tools/call_child.ts b/e2e/fixtures/agent-basic-runtime/agent/tools/call_child.ts index 2f4d22bf20..0c357ba732 100644 --- a/e2e/fixtures/agent-basic-runtime/agent/tools/call_child.ts +++ b/e2e/fixtures/agent-basic-runtime/agent/tools/call_child.ts @@ -1,4 +1,5 @@ import { Client } from "eve/client"; +import type { RequestStatus } from "eve/events"; import { defineDynamic, defineTool } from "eve/tools"; import { z } from "zod"; @@ -45,7 +46,8 @@ export default defineDynamic({ }); const result = await response.result(); signal.throwIfAborted(); - return { data: result.data, status: result.status, sessionId: result.sessionId }; + const status: RequestStatus = result.status; + return { data: result.data, status, sessionId: result.sessionId }; }, }); }, diff --git a/e2e/fixtures/agent-basic-runtime/evals/runtime/multi-turn-session.eval.ts b/e2e/fixtures/agent-basic-runtime/evals/runtime/multi-turn-session.eval.ts index eccfb434de..1795b716e3 100644 --- a/e2e/fixtures/agent-basic-runtime/evals/runtime/multi-turn-session.eval.ts +++ b/e2e/fixtures/agent-basic-runtime/evals/runtime/multi-turn-session.eval.ts @@ -29,7 +29,7 @@ export default defineEval({ cancel, satisfies( (value: typeof cancel) => - value.status === "accepted" && value.sessionId === first.sessionId, + typeof value.deliveryId === "string" && value.sessionId === first.sessionId, "the parked session accepts cancellation through its stable address", ), ); diff --git a/e2e/fixtures/agent-cancellation/agent/channels/threads.ts b/e2e/fixtures/agent-cancellation/agent/channels/threads.ts index 7015b0c094..0e86059d54 100644 --- a/e2e/fixtures/agent-cancellation/agent/channels/threads.ts +++ b/e2e/fixtures/agent-cancellation/agent/channels/threads.ts @@ -27,7 +27,7 @@ export default defineChannel({ auth: body.actor === null ? null : { ...AUTH, principalId: body.actor ?? AUTH.principalId }, turnPolicy: body.turnPolicy, }); - return Response.json({ ok: true, sessionId: session.id }); + return Response.json({ ok: true, sessionId: session.sessionId }); }), POST("/threads/:threadId/stop", async (_request, { from, params }) => { const result = await from(params.threadId ?? "").cancel(); diff --git a/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-channel-route.eval.ts b/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-channel-route.eval.ts index ee5c46fd7a..bc88e635fe 100644 --- a/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-channel-route.eval.ts +++ b/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-channel-route.eval.ts @@ -9,17 +9,23 @@ interface Delivery { } interface StopResponse { - readonly status?: "accepted" | "no_active_turn"; + readonly deliveryId?: string; + readonly code?: string; } -async function postJson(target: EveEvalTargetHandle, path: string, body: unknown): Promise { +async function postJson( + target: EveEvalTargetHandle, + path: string, + body: unknown, + expectedStatus = 200, +): Promise { const response = await target.fetch(path, { body: JSON.stringify(body), headers: { "content-type": "application/json" }, method: "POST", }); const text = await response.text(); - if (!response.ok) { + if (response.status !== expectedStatus) { throw new Error(`POST ${path} failed (${response.status}): ${text}`); } return JSON.parse(text) as T; @@ -46,12 +52,13 @@ export default defineEval({ t.target, `/threads/${crypto.randomUUID()}/stop`, {}, + 409, ); await t.require( unknownThread, satisfies( - (value: StopResponse) => value.status === "no_active_turn", - "stopping an unknown thread reports no_active_turn", + (value: StopResponse) => value.code === "session_not_active", + "stopping an unknown thread reports session_not_active", ), ); @@ -75,7 +82,7 @@ export default defineEval({ await t.require( stopped, satisfies( - (value: StopResponse) => value.status === "accepted", + (value: StopResponse) => typeof value.deliveryId === "string", "the stop request is accepted", ), ); diff --git a/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-subagent.eval.ts b/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-subagent.eval.ts index b972f24abd..633003be18 100644 --- a/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-subagent.eval.ts +++ b/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-subagent.eval.ts @@ -26,7 +26,7 @@ export default defineEval({ await t.require( cancelled, satisfies( - (value: typeof cancelled) => value.status === "accepted", + (value: typeof cancelled) => typeof value.deliveryId === "string", "parent cancel request is accepted", ), ); diff --git a/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-turn.eval.ts b/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-turn.eval.ts index 4a9be8c4db..5a09adc039 100644 --- a/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-turn.eval.ts +++ b/e2e/fixtures/agent-cancellation/evals/cancellation/cancel-turn.eval.ts @@ -27,8 +27,8 @@ export default defineEval({ cancelled, satisfies( (value: typeof cancelled) => - value.status === "accepted" && value.sessionId === live.sessionId, - "cancel request is accepted with status 'accepted'", + typeof value.deliveryId === "string" && value.sessionId === live.sessionId, + "cancel request is accepted with a delivery receipt", ), ); @@ -50,7 +50,8 @@ export default defineEval({ await t.require( late, satisfies( - (value: typeof late) => value.status === "accepted" && value.sessionId === live.sessionId, + (value: typeof late) => + typeof value.deliveryId === "string" && value.sessionId === live.sessionId, "a live parked session accepts a late cancel as a no-op", ), ); diff --git a/e2e/fixtures/agent-cancellation/evals/cancellation/clear-channel-session.eval.ts b/e2e/fixtures/agent-cancellation/evals/cancellation/clear-channel-session.eval.ts index 34b267ffde..779124be88 100644 --- a/e2e/fixtures/agent-cancellation/evals/cancellation/clear-channel-session.eval.ts +++ b/e2e/fixtures/agent-cancellation/evals/cancellation/clear-channel-session.eval.ts @@ -8,7 +8,8 @@ interface Delivery { interface ClearResponse { readonly sessionId?: string; - readonly status?: "accepted" | "no_active_session"; + readonly deliveryId?: string; + readonly code?: string; } /** The line after the last event read: where the next read starts. */ @@ -18,14 +19,19 @@ function nextLine( return (events.at(-1)?.meta.position.line ?? -1) + 1; } -async function postJson(target: EveEvalTargetHandle, path: string, body: unknown): Promise { +async function postJson( + target: EveEvalTargetHandle, + path: string, + body: unknown, + expectedStatus = 200, +): Promise { const response = await target.fetch(path, { body: JSON.stringify(body), headers: { "content-type": "application/json" }, method: "POST", }); const text = await response.text(); - if (!response.ok) { + if (response.status !== expectedStatus) { throw new Error(`POST ${path} failed (${response.status}): ${text}`); } return JSON.parse(text) as T; @@ -41,12 +47,13 @@ export default defineEval({ t.target, `/threads/${crypto.randomUUID()}/clear`, {}, + 409, ); await t.require( unknownThread, satisfies( - (value: ClearResponse) => value.status === "no_active_session", - "clearing an unknown thread reports no_active_session", + (value: ClearResponse) => value.code === "session_not_active", + "clearing an unknown thread reports session_not_active", ), ); @@ -80,7 +87,8 @@ export default defineEval({ await t.require( clearedResponse, satisfies( - (value: ClearResponse) => value.status === "accepted" && value.sessionId === sessionId, + (value: ClearResponse) => + typeof value.deliveryId === "string" && value.sessionId === sessionId, "the channel accepts a clear for the active session", ), ); diff --git a/e2e/fixtures/agent-cancellation/evals/cancellation/compact-channel-session.eval.ts b/e2e/fixtures/agent-cancellation/evals/cancellation/compact-channel-session.eval.ts index 45759f4beb..fdb8b059a9 100644 --- a/e2e/fixtures/agent-cancellation/evals/cancellation/compact-channel-session.eval.ts +++ b/e2e/fixtures/agent-cancellation/evals/cancellation/compact-channel-session.eval.ts @@ -8,7 +8,8 @@ interface Delivery { interface CompactResponse { readonly sessionId?: string; - readonly status?: "accepted" | "no_active_session"; + readonly deliveryId?: string; + readonly code?: string; } /** The line after the last event read: where the next read starts. */ @@ -18,14 +19,19 @@ function nextLine( return (events.at(-1)?.meta.position.line ?? -1) + 1; } -async function postJson(target: EveEvalTargetHandle, path: string, body: unknown): Promise { +async function postJson( + target: EveEvalTargetHandle, + path: string, + body: unknown, + expectedStatus = 200, +): Promise { const response = await target.fetch(path, { body: JSON.stringify(body), headers: { "content-type": "application/json" }, method: "POST", }); const text = await response.text(); - if (!response.ok) { + if (response.status !== expectedStatus) { throw new Error(`POST ${path} failed (${response.status}): ${text}`); } return JSON.parse(text) as T; @@ -47,12 +53,13 @@ export default defineEval({ t.target, `/threads/${crypto.randomUUID()}/compact`, {}, + 409, ); await t.require( unknownThread, satisfies( - (value: CompactResponse) => value.status === "no_active_session", - "compacting an unknown thread reports no_active_session", + (value: CompactResponse) => value.code === "session_not_active", + "compacting an unknown thread reports session_not_active", ), ); @@ -86,7 +93,8 @@ export default defineEval({ await t.require( compactedResponse, satisfies( - (value: CompactResponse) => value.status === "accepted" && value.sessionId === sessionId, + (value: CompactResponse) => + typeof value.deliveryId === "string" && value.sessionId === sessionId, "the channel accepts compaction for the active session", ), ); diff --git a/e2e/fixtures/agent-cancellation/evals/cancellation/queued-request-identity.eval.ts b/e2e/fixtures/agent-cancellation/evals/cancellation/queued-request-identity.eval.ts index 4db99f0a48..42da1d598a 100644 --- a/e2e/fixtures/agent-cancellation/evals/cancellation/queued-request-identity.eval.ts +++ b/e2e/fixtures/agent-cancellation/evals/cancellation/queued-request-identity.eval.ts @@ -1,4 +1,5 @@ -import { defineEval, toolCallsOf } from "eve/evals"; +import { callOutputSource } from "eve/events"; +import { defineEval } from "eve/evals"; import { equals } from "eve/evals/expect"; export default defineEval({ @@ -55,8 +56,10 @@ export default defineEval({ const turn = await live.result(); turn.expectOk(); turn.calledTool("record-request", { count: markers.length, status: "completed" }); - const outputs = toolCallsOf(turn.events).flatMap((call) => - call.name === "record-request" && call.status === "completed" ? [call.output] : [], + const outputs = Object.values(turn.view.calls).flatMap((call) => + call.capability.name === "record-request" && call.outcome === "completed" + ? [callOutputSource(turn.view, call.callId)?.output] + : [], ); t.check( outputs, diff --git a/e2e/fixtures/agent-cancellation/evals/cancellation/unified-http-session-operations.eval.ts b/e2e/fixtures/agent-cancellation/evals/cancellation/unified-http-session-operations.eval.ts index 60d670dd8e..fc5ef3dff6 100644 --- a/e2e/fixtures/agent-cancellation/evals/cancellation/unified-http-session-operations.eval.ts +++ b/e2e/fixtures/agent-cancellation/evals/cancellation/unified-http-session-operations.eval.ts @@ -6,13 +6,13 @@ const TOOL_NAME = "wait-for-cancellation"; interface AcceptedResponse { readonly ok?: boolean; readonly sessionId?: string; - readonly status?: string; + readonly deliveryId?: string; } interface ResetResponse { readonly ok?: boolean; - readonly previousSessionId?: string; - readonly status?: string; + readonly sessionId?: string; + readonly deliveryId?: string; } async function postJson( @@ -52,7 +52,9 @@ export default defineEval({ created, satisfies( (value: AcceptedResponse) => - value.ok === true && value.status === "accepted" && typeof value.sessionId === "string", + value.ok === true && + typeof value.deliveryId === "string" && + typeof value.sessionId === "string", "the HTTP session route creates a session without a continuation token", ), ); @@ -75,7 +77,8 @@ export default defineEval({ await t.require( sent, satisfies( - (value: AcceptedResponse) => value.status === "accepted" && value.sessionId === sessionId, + (value: AcceptedResponse) => + typeof value.deliveryId === "string" && value.sessionId === sessionId, "the message route sends to the exact session ID", ), ); @@ -92,7 +95,8 @@ export default defineEval({ await t.require( cancelled, satisfies( - (value: AcceptedResponse) => value.status === "accepted" && value.sessionId === sessionId, + (value: AcceptedResponse) => + typeof value.deliveryId === "string" && value.sessionId === sessionId, "cancel targets the exact session ID", ), ); @@ -118,7 +122,8 @@ export default defineEval({ await t.require( compacted, satisfies( - (value: AcceptedResponse) => value.status === "accepted" && value.sessionId === sessionId, + (value: AcceptedResponse) => + typeof value.deliveryId === "string" && value.sessionId === sessionId, "compact targets the exact session ID", ), ); @@ -141,7 +146,8 @@ export default defineEval({ await t.require( cleared, satisfies( - (value: AcceptedResponse) => value.status === "accepted" && value.sessionId === sessionId, + (value: AcceptedResponse) => + typeof value.deliveryId === "string" && value.sessionId === sessionId, "clear targets the exact session ID", ), ); @@ -167,7 +173,8 @@ export default defineEval({ await t.require( followUpResponse, satisfies( - (value: AcceptedResponse) => value.status === "accepted" && value.sessionId === sessionId, + (value: AcceptedResponse) => + typeof value.deliveryId === "string" && value.sessionId === sessionId, "the same session accepts a message after compact and clear", ), ); @@ -180,12 +187,13 @@ export default defineEval({ t.target, `/eve/v1/session/${sessionId}/reset`, { reason: "Verify immutable HTTP session identity" }, - 200, + 202, ); await t.require( reset, satisfies( - (value: ResetResponse) => value.status === "reset" && value.previousSessionId === sessionId, + (value: ResetResponse) => + typeof value.deliveryId === "string" && value.sessionId === sessionId, "reset terminally retires the exact session ID", ), ); @@ -215,7 +223,7 @@ export default defineEval({ replacement, satisfies( (value: AcceptedResponse) => - value.status === "accepted" && + typeof value.deliveryId === "string" && typeof value.sessionId === "string" && value.sessionId !== sessionId, "a replacement session requires explicit creation", diff --git a/e2e/fixtures/agent-channels/agent/channels/delivery.ts b/e2e/fixtures/agent-channels/agent/channels/delivery.ts index ea19a06b02..041860e7c1 100644 --- a/e2e/fixtures/agent-channels/agent/channels/delivery.ts +++ b/e2e/fixtures/agent-channels/agent/channels/delivery.ts @@ -1,6 +1,6 @@ import { defineChannel, POST } from "eve/channels"; -export default defineChannel({ +export default defineChannel<{ author: string }>({ state: { author: "" }, deliver(payload) { return { ...payload, message: `${payload.state?.author}: ${payload.message}` }; @@ -11,7 +11,7 @@ export default defineChannel({ "Please acknowledge the meeting notes.", { auth: null, state: { author: "Alice" } }, ); - return Response.json({ sessionId: session.id }); + return Response.json({ sessionId: session.sessionId }); }), ], }); diff --git a/e2e/fixtures/agent-channels/agent/channels/webhook.ts b/e2e/fixtures/agent-channels/agent/channels/webhook.ts index 908b040285..42e378d7ff 100644 --- a/e2e/fixtures/agent-channels/agent/channels/webhook.ts +++ b/e2e/fixtures/agent-channels/agent/channels/webhook.ts @@ -26,7 +26,7 @@ export default defineChannel({ }, turnPolicy: body.turnPolicy, }); - return Response.json({ ok: true, sessionId: session.id }); + return Response.json({ ok: true, sessionId: session.sessionId }); }), ], }); diff --git a/e2e/fixtures/agent-dynamic-model-session/evals/dynamic-model/cancelled-first-turn.eval.ts b/e2e/fixtures/agent-dynamic-model-session/evals/dynamic-model/cancelled-first-turn.eval.ts index 8b20333835..69149ad033 100644 --- a/e2e/fixtures/agent-dynamic-model-session/evals/dynamic-model/cancelled-first-turn.eval.ts +++ b/e2e/fixtures/agent-dynamic-model-session/evals/dynamic-model/cancelled-first-turn.eval.ts @@ -25,7 +25,7 @@ export default defineEval({ cancelled, satisfies( (value: typeof cancelled) => - value.status === "accepted" && value.sessionId === live.sessionId, + typeof value.deliveryId === "string" && value.sessionId === live.sessionId, "the first-turn cancellation is accepted", ), ); diff --git a/e2e/fixtures/agent-session-timeout/agent/channels/threads.ts b/e2e/fixtures/agent-session-timeout/agent/channels/threads.ts index 5820a212f3..0f47d5f2dc 100644 --- a/e2e/fixtures/agent-session-timeout/agent/channels/threads.ts +++ b/e2e/fixtures/agent-session-timeout/agent/channels/threads.ts @@ -14,7 +14,7 @@ export default defineChannel({ const session = await from(params.threadId ?? "").send(body.message ?? "", { auth: AUTH, }); - return Response.json({ sessionId: session.id }); + return Response.json({ sessionId: session.sessionId }); }), POST("/threads/:threadId/owner", async (_request, { params, resolveSession }) => { const session = await resolveSession(params.threadId ?? ""); diff --git a/e2e/fixtures/agent-subagents-hitl/evals/proxied-ask-text-response.eval.ts b/e2e/fixtures/agent-subagents-hitl/evals/proxied-ask-text-response.eval.ts index 72bde0198e..c18cc45787 100644 --- a/e2e/fixtures/agent-subagents-hitl/evals/proxied-ask-text-response.eval.ts +++ b/e2e/fixtures/agent-subagents-hitl/evals/proxied-ask-text-response.eval.ts @@ -1,4 +1,9 @@ -import { defineEval, type EveEvalContext, type EveEvalSession } from "eve/evals"; +import { + defineEval, + type EveEvalContext, + type EveEvalLiveTurn, + type EveEvalSession, +} from "eve/evals"; /** A human text answer resolves the child's proxied request at the parent by request ID. */ export default defineEval({ @@ -57,7 +62,7 @@ async function waitForInput(t: EveEvalContext, initial: EveEvalSession): Promise } async function waitForObservedEvent( - turn: ReturnType, + turn: EveEvalLiveTurn, type: TType, ) { for (let attempt = 0; attempt < 1_500; attempt += 1) { diff --git a/e2e/fixtures/agent-subagents/agent/tools/invoke-hidden.ts b/e2e/fixtures/agent-subagents/agent/tools/invoke-hidden.ts index eca9869463..0f585a11af 100644 --- a/e2e/fixtures/agent-subagents/agent/tools/invoke-hidden.ts +++ b/e2e/fixtures/agent-subagents/agent/tools/invoke-hidden.ts @@ -19,7 +19,7 @@ async function execute({ target }: Input, ctx: WorkflowToolContext) { throw new Error(`Missing workflow metadata for internal subagent ${target}.`); } const response = await ctx.agent(target).send("Return your fixed marker."); - const { message } = await response.result(); + const { reply: message } = await response.result(); return { description, result: message ?? null }; } diff --git a/e2e/fixtures/agent-subagents/agent/tools/read-workspace-label.ts b/e2e/fixtures/agent-subagents/agent/tools/read-workspace-label.ts index e36ef9fd8f..75178ae72d 100644 --- a/e2e/fixtures/agent-subagents/agent/tools/read-workspace-label.ts +++ b/e2e/fixtures/agent-subagents/agent/tools/read-workspace-label.ts @@ -13,7 +13,7 @@ const membershipByPrincipal: Readonly> = { // Models a provider-side workspace membership store selected by eve for the current principal. const userGrant: ToolAuthProvider = { - credentialOwner: "user", + principalType: "user", async getToken({ principal }) { if (principal.type !== "user") throw new Error("A user workspace membership is required."); const token = membershipByPrincipal[principal.id]; diff --git a/e2e/fixtures/agent-subagents/agent/tools/survey-through-tool.ts b/e2e/fixtures/agent-subagents/agent/tools/survey-through-tool.ts index 930418e8a3..3ec87f7ff2 100644 --- a/e2e/fixtures/agent-subagents/agent/tools/survey-through-tool.ts +++ b/e2e/fixtures/agent-subagents/agent/tools/survey-through-tool.ts @@ -14,7 +14,7 @@ async function execute(_input: Input, ctx: WorkflowToolContext) { const response = await ctx .agent("survey-worker") .send("Please count the tide survey stations for Alice."); - const { message } = await response.result(); + const { reply: message } = await response.result(); return { survey: message ?? null }; } diff --git a/e2e/fixtures/agent-subagents/evals/remote-subagent-stream.eval.ts b/e2e/fixtures/agent-subagents/evals/remote-subagent-stream.eval.ts index a4873fc83d..6bdbbcd561 100644 --- a/e2e/fixtures/agent-subagents/evals/remote-subagent-stream.eval.ts +++ b/e2e/fixtures/agent-subagents/evals/remote-subagent-stream.eval.ts @@ -1,5 +1,6 @@ -import { endsTurn, type ChildOpened, type SessionStreamEvent } from "eve/client"; -import { toolCallsOf } from "eve/evals"; +import { emptySessionView, foldEvents } from "eve/events"; +import { endsTurn, type SessionStreamEvent } from "eve/client"; +import { type ChildOpened } from "eve/events"; import { defineEval, type EveEvalContext, type EveEvalTurn } from "eve/evals"; import { satisfies } from "eve/evals/expect"; @@ -37,13 +38,13 @@ export default defineEval({ await t.require( childEvents, - satisfies( - (events: readonly SessionStreamEvent[]) => - toolCallsOf(events).some( - (call) => call.name === "read-workspace-label" && call.status === "completed", - ), - "the proxied child stream carries the child's completed workspace lookup", - ), + satisfies((events: readonly SessionStreamEvent[]) => { + const view = emptySessionView(); + foldEvents(view, events); + return Object.values(view.calls).some( + (call) => call.capability.name === "read-workspace-label" && call.outcome === "completed", + ); + }, "the proxied child stream carries the child's completed workspace lookup"), ); const parentTrace = turn.events.find((event) => event.type === "turn.started")?.data.trace; const childTrace = childEvents.find((event) => event.type === "turn.started")?.data.trace; diff --git a/e2e/fixtures/agent-tools-hitl/agent/tools/authorized-change.ts b/e2e/fixtures/agent-tools-hitl/agent/tools/authorized-change.ts index 6e5de6c67a..63eb8471df 100644 --- a/e2e/fixtures/agent-tools-hitl/agent/tools/authorized-change.ts +++ b/e2e/fixtures/agent-tools-hitl/agent/tools/authorized-change.ts @@ -12,7 +12,7 @@ export default defineTool({ request: always(), response: ({ request, response }) => { // The person who asked for the change may withdraw it; only the fixture approver approves it. - if (response.decision === "cancel") { + if (response.decision === "deny") { return response.principal.principalId === request.principal?.principalId ? { status: "allowed" } : { status: "rejected", reason: "Only the requester can cancel this change." }; diff --git a/e2e/fixtures/agent-tools-hitl/agent/tools/oauth-authorized-gate.ts b/e2e/fixtures/agent-tools-hitl/agent/tools/oauth-authorized-gate.ts index 53e6238c5c..8418ca8bef 100644 --- a/e2e/fixtures/agent-tools-hitl/agent/tools/oauth-authorized-gate.ts +++ b/e2e/fixtures/agent-tools-hitl/agent/tools/oauth-authorized-gate.ts @@ -40,7 +40,7 @@ export default defineTool({ request: always(), async response({ auth, response }) { // Anyone may cancel; only an approval needs the responder's OAuth identity. - if (response.decision === "cancel") return { status: "allowed" }; + if (response.decision === "deny") return { status: "allowed" }; const credential = await auth.getToken(fakeOAuth, { authKey: "fixture-oauth" }); return credential.providerSubject === response.principal.principalId ? { status: "allowed" } diff --git a/e2e/fixtures/agent-tools-hitl/agent/tools/release-grant.ts b/e2e/fixtures/agent-tools-hitl/agent/tools/release-grant.ts index b5d6e9ca9f..a0b730bdf6 100644 --- a/e2e/fixtures/agent-tools-hitl/agent/tools/release-grant.ts +++ b/e2e/fixtures/agent-tools-hitl/agent/tools/release-grant.ts @@ -4,7 +4,7 @@ import { z } from "zod"; /** A per-user credential: each person's token names them, so the result shows whose was used. */ const releaseAuth: ToolAuthProvider = { - credentialOwner: "user", + principalType: "user", async getToken({ principal }) { return { token: `release-token:${principal.type === "user" ? principal.id : "app"}` }; }, diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts index 5762a6cbdf..1c4a5861ef 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts @@ -12,7 +12,7 @@ const TOOL = "authorized-change"; type Store = EveAgentStore; function toolPart(store: Store, callId: string): EveToolPart { - const parts = store.snapshot.data.messages.flatMap((message) => message.parts); + const parts = store.snapshot.messages.flatMap((message) => message.parts); const matches = parts.filter( (part): part is EveToolPart => part.type === "tool" && part.callId === callId, ); @@ -41,12 +41,12 @@ async function expectAnswerable( interactionId: string, ) { // The approval holds its turn open, and the store keeps following it live. - await t.require(store.snapshot.status, equals("streaming")); + await t.require(store.snapshot.status, equals("idle")); await t.require(store.snapshot.error, equals(undefined)); const part = toolPart(store, callId); - await t.require(part.status, equals("requested")); + await t.require(part.state, equals("approval")); await t.require(part.interaction?.status, equals("open")); - await t.require(part.interaction?.request.interactionId, equals(interactionId)); + await t.require(part.interaction?.interactionId, equals(interactionId)); await t.require(part.interaction?.answer, equals(undefined)); } diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/cancelled-approval-followup.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/cancelled-approval-followup.eval.ts index 3e8acab4f9..8aa88ebbd3 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/cancelled-approval-followup.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/cancelled-approval-followup.eval.ts @@ -21,7 +21,7 @@ export default defineEval({ await t.require( accepted, satisfies( - (value: typeof accepted) => value.status === "accepted", + (value: typeof accepted) => typeof value.deliveryId === "string", "the approval turn cancellation is accepted", ), ); diff --git a/e2e/fixtures/agent-tools-sandbox/evals/sandbox/bash-background-job.eval.ts b/e2e/fixtures/agent-tools-sandbox/evals/sandbox/bash-background-job.eval.ts index 3bf0df27cd..4b8b9ca0a1 100644 --- a/e2e/fixtures/agent-tools-sandbox/evals/sandbox/bash-background-job.eval.ts +++ b/e2e/fixtures/agent-tools-sandbox/evals/sandbox/bash-background-job.eval.ts @@ -1,5 +1,6 @@ +import { emptySessionView, foldEvents, callOutputSource } from "eve/events"; import type { SessionStreamEvent } from "eve/client"; -import { defineEval, toolCallsOf } from "eve/evals"; +import { defineEval } from "eve/evals"; const BASH_TOOL = "bash"; const INDEX_COMMAND = "for i in $(seq 1 120); do echo indexed batch $i; sleep 1; done"; @@ -55,9 +56,12 @@ function jobWasObservedAndStopped(outputs: readonly BashOutput[]): boolean { } function bashOutputs(events: readonly SessionStreamEvent[]): readonly BashOutput[] { - return toolCallsOf(events).flatMap((call) => { - if (call.name !== BASH_TOOL || call.status !== "completed") return []; - const output = call.output; + const view = emptySessionView(); + foldEvents(view, events); + + return Object.values(view.calls).flatMap((call) => { + if (call.capability.name !== BASH_TOOL || call.outcome !== "completed") return []; + const output = callOutputSource(view, call.callId)?.output; if (typeof output !== "object" || output === null || Array.isArray(output)) return []; return [output as BashOutput]; }); diff --git a/e2e/fixtures/agent-tools-sandbox/evals/sandbox/bash-curl-fanout.eval.ts b/e2e/fixtures/agent-tools-sandbox/evals/sandbox/bash-curl-fanout.eval.ts index 02cc41a3d3..6e8e2d4781 100644 --- a/e2e/fixtures/agent-tools-sandbox/evals/sandbox/bash-curl-fanout.eval.ts +++ b/e2e/fixtures/agent-tools-sandbox/evals/sandbox/bash-curl-fanout.eval.ts @@ -1,5 +1,6 @@ +import { emptySessionView, foldEvents, callOutputSource } from "eve/events"; import type { SessionStreamEvent } from "eve/client"; -import { defineEval, toolCallsOf } from "eve/evals"; +import { defineEval } from "eve/evals"; import { FANOUT_BARRIER_SERVER_URL } from "./shared"; @@ -87,10 +88,13 @@ function curlCallsReachBarrier(input: { } function curlBarrierResults(events: readonly SessionStreamEvent[]): readonly CurlBarrierResult[] { - return toolCallsOf(events).flatMap((call) => { - if (call.name !== BASH_TOOL || call.status !== "completed") return []; + const view = emptySessionView(); + foldEvents(view, events); - return parseCurlBarrierResult(call.output); + return Object.values(view.calls).flatMap((call) => { + if (call.capability.name !== BASH_TOOL || call.outcome !== "completed") return []; + + return parseCurlBarrierResult(callOutputSource(view, call.callId)?.output); }); } diff --git a/e2e/fixtures/agent-tools/agent/channels/action-narration.ts b/e2e/fixtures/agent-tools/agent/channels/action-narration.ts index 7dd2738b02..f75a9c1e2b 100644 --- a/e2e/fixtures/agent-tools/agent/channels/action-narration.ts +++ b/e2e/fixtures/agent-tools/agent/channels/action-narration.ts @@ -71,7 +71,7 @@ export default defineChannel({ auth: null, state: initialState(), }); - return Response.json({ sessionId: session.id }); + return Response.json({ sessionId: session.sessionId }); }), ], diff --git a/e2e/fixtures/agent-tools/agent/channels/anchored.ts b/e2e/fixtures/agent-tools/agent/channels/anchored.ts index b5fdc4e51c..be01c61f38 100644 --- a/e2e/fixtures/agent-tools/agent/channels/anchored.ts +++ b/e2e/fixtures/agent-tools/agent/channels/anchored.ts @@ -63,7 +63,7 @@ export default defineChannel({ ok: true, anchorToken, initialContinuationToken: `pending:${body.threadId}`, - sessionId: session.id, + sessionId: session.sessionId, }); }), @@ -75,7 +75,7 @@ export default defineChannel({ state: initialState(anchorToken), }); - return Response.json({ ok: true, anchorToken, sessionId: session.id }); + return Response.json({ ok: true, anchorToken, sessionId: session.sessionId }); }), ], events: { diff --git a/e2e/fixtures/agent-tools/agent/channels/metadata-provider.ts b/e2e/fixtures/agent-tools/agent/channels/metadata-provider.ts index df4faed775..bb1cb1199a 100644 --- a/e2e/fixtures/agent-tools/agent/channels/metadata-provider.ts +++ b/e2e/fixtures/agent-tools/agent/channels/metadata-provider.ts @@ -34,7 +34,7 @@ export default defineChannel({ }, ); - return Response.json({ ok: true, sessionId: session.id }); + return Response.json({ ok: true, sessionId: session.sessionId }); }), ], }); diff --git a/e2e/fixtures/agent-tools/evals/static-tools/render-stripes-shared.ts b/e2e/fixtures/agent-tools/evals/static-tools/render-stripes-shared.ts index c376026b03..386207263c 100644 --- a/e2e/fixtures/agent-tools/evals/static-tools/render-stripes-shared.ts +++ b/e2e/fixtures/agent-tools/evals/static-tools/render-stripes-shared.ts @@ -1,5 +1,5 @@ +import { emptySessionView, foldEvents, callOutputSource } from "eve/events"; import type { SessionStreamEvent } from "eve/client"; -import { toolCallsOf } from "eve/evals"; export const TOOL_NAME = "render-stripes"; @@ -16,9 +16,12 @@ export function isRenderStripesOutput(value: unknown): boolean { } export function renderedColors(events: readonly SessionStreamEvent[]): readonly string[] { - for (const call of toolCallsOf(events)) { - if (call.name !== TOOL_NAME || call.status !== "completed") continue; - const output = call.output as { readonly colors?: unknown }; + const view = emptySessionView(); + foldEvents(view, events); + + for (const call of Object.values(view.calls)) { + if (call.capability.name !== TOOL_NAME || call.outcome !== "completed") continue; + const output = callOutputSource(view, call.callId)?.output as { readonly colors?: unknown }; if (Array.isArray(output?.colors) && output.colors.every((c) => typeof c === "string")) { return output.colors as string[]; } diff --git a/e2e/fixtures/agent-tools/evals/static-tools/web-search-exa.eval.ts b/e2e/fixtures/agent-tools/evals/static-tools/web-search-exa.eval.ts index fa09a6eab3..2ee502dce6 100644 --- a/e2e/fixtures/agent-tools/evals/static-tools/web-search-exa.eval.ts +++ b/e2e/fixtures/agent-tools/evals/static-tools/web-search-exa.eval.ts @@ -1,5 +1,6 @@ +import { emptySessionView, foldEvents } from "eve/events"; import type { SessionStreamEvent } from "eve/client"; -import { defineEval, toolCallsOf } from "eve/evals"; +import { defineEval } from "eve/evals"; const TOOL_NAME = "web_search"; const SEARCH_COUNT = 8; @@ -15,9 +16,12 @@ const QUERIES = [ ] as const; function completedDistinctCalls(events: readonly SessionStreamEvent[]): number { + const view = emptySessionView(); + foldEvents(view, events); + return new Set( - toolCallsOf(events) - .filter((call) => call.name === TOOL_NAME && call.status === "completed") + Object.values(view.calls) + .filter((call) => call.capability.name === TOOL_NAME && call.outcome === "completed") .map((call) => call.callId), ).size; } diff --git a/e2e/fixtures/agent-tools/evals/static-tools/web-search-parallel.eval.ts b/e2e/fixtures/agent-tools/evals/static-tools/web-search-parallel.eval.ts index 2654e87b77..ad67b30638 100644 --- a/e2e/fixtures/agent-tools/evals/static-tools/web-search-parallel.eval.ts +++ b/e2e/fixtures/agent-tools/evals/static-tools/web-search-parallel.eval.ts @@ -1,5 +1,6 @@ +import { emptySessionView, foldEvents } from "eve/events"; import type { SessionStreamEvent } from "eve/client"; -import { defineEval, toolCallsOf } from "eve/evals"; +import { defineEval } from "eve/evals"; const TOOL_NAME = "web_search"; const MIN_COMPLETED_SEARCHES = 8; @@ -16,9 +17,12 @@ const EXPECTED_WINNERS = [ ]; function completedToolResultCount(events: readonly SessionStreamEvent[], toolName: string) { + const view = emptySessionView(); + foldEvents(view, events); + return new Set( - toolCallsOf(events) - .filter((call) => call.name === toolName && call.status === "completed") + Object.values(view.calls) + .filter((call) => call.capability.name === toolName && call.outcome === "completed") .map((call) => call.callId), ).size; } diff --git a/e2e/fixtures/agent-workflow-stress/evals/parallel-tool-calls.eval.ts b/e2e/fixtures/agent-workflow-stress/evals/parallel-tool-calls.eval.ts index add04045f9..44a34d9369 100644 --- a/e2e/fixtures/agent-workflow-stress/evals/parallel-tool-calls.eval.ts +++ b/e2e/fixtures/agent-workflow-stress/evals/parallel-tool-calls.eval.ts @@ -1,5 +1,6 @@ +import { emptySessionView, foldEvents, callOutputSource } from "eve/events"; import type { SessionStreamEvent } from "eve/client"; -import { defineEval, toolCallsOf } from "eve/evals"; +import { defineEval } from "eve/evals"; import { FANOUT_LABELS, FANOUT_REPLY, FANOUT_TOOL_NAME } from "../agent/lib/fanout"; @@ -23,10 +24,13 @@ export default defineEval({ }); function everyCallReachedBarrier(events: readonly SessionStreamEvent[]): boolean { - const executions = toolCallsOf(events).flatMap((call) => { - if (call.name !== FANOUT_TOOL_NAME || call.status !== "completed") return []; + const view = emptySessionView(); + foldEvents(view, events); - const output = call.output; + const executions = Object.values(view.calls).flatMap((call) => { + if (call.capability.name !== FANOUT_TOOL_NAME || call.outcome !== "completed") return []; + + const output = callOutputSource(view, call.callId)?.output; if (typeof output !== "object" || output === null) return []; const { concurrentCallsAtRelease, label } = output as Record; return typeof concurrentCallsAtRelease === "number" && typeof label === "string" diff --git a/e2e/fixtures/agent-workflow-stress/evals/tool-stubs.eval.ts b/e2e/fixtures/agent-workflow-stress/evals/tool-stubs.eval.ts index 4cfba7653c..5e9ff5495f 100644 --- a/e2e/fixtures/agent-workflow-stress/evals/tool-stubs.eval.ts +++ b/e2e/fixtures/agent-workflow-stress/evals/tool-stubs.eval.ts @@ -1,5 +1,7 @@ -import type { SessionStreamEvent, ToolStub } from "eve/client"; -import { defineEval, toolCallsOf } from "eve/evals"; +import { emptySessionView, foldEvents, callOutputSource } from "eve/events"; +import type { SessionStreamEvent } from "eve/client"; +import type { ToolStub } from "eve/evals"; +import { defineEval } from "eve/evals"; import { FANOUT_TOOL_NAME } from "../agent/lib/fanout"; @@ -49,7 +51,12 @@ export default defineEval({ }); function outputs(events: readonly SessionStreamEvent[]): unknown[] { - return toolCallsOf(events).flatMap((call) => - call.name === FANOUT_TOOL_NAME && call.status === "completed" ? [call.output] : [], + const view = emptySessionView(); + foldEvents(view, events); + + return Object.values(view.calls).flatMap((call) => + call.capability.name === FANOUT_TOOL_NAME && call.outcome === "completed" + ? [callOutputSource(view, call.callId)?.output] + : [], ); } diff --git a/e2e/fixtures/agent-workflow-tools/agent/channels/skill-context.ts b/e2e/fixtures/agent-workflow-tools/agent/channels/skill-context.ts index 1acd909fd9..b09fba9a65 100644 --- a/e2e/fixtures/agent-workflow-tools/agent/channels/skill-context.ts +++ b/e2e/fixtures/agent-workflow-tools/agent/channels/skill-context.ts @@ -25,7 +25,7 @@ export default defineChannel({ }, }); return Response.json({ - sessionId: session.id, + sessionId: session.sessionId, environment: process.env.EVE_DEV === "1" || process.env.VERCEL_ENV === "development" ? "development" diff --git a/e2e/fixtures/agent-workflow-tools/agent/lib/agent-reply.ts b/e2e/fixtures/agent-workflow-tools/agent/lib/agent-reply.ts index 32bb378cd2..5f8e5e7f82 100644 --- a/e2e/fixtures/agent-workflow-tools/agent/lib/agent-reply.ts +++ b/e2e/fixtures/agent-workflow-tools/agent/lib/agent-reply.ts @@ -9,5 +9,5 @@ export async function replyFrom( const response = await ctx.agent(name).send(message); const result = await response.result(); if (result.status === "failed") throw new Error(`Agent "${name}" failed.`); - return result.message ?? null; + return result.reply ?? null; } diff --git a/e2e/fixtures/agent-workflow-tools/agent/tools/research_brief.ts b/e2e/fixtures/agent-workflow-tools/agent/tools/research_brief.ts index 976209f6ee..9604df7e61 100644 --- a/e2e/fixtures/agent-workflow-tools/agent/tools/research_brief.ts +++ b/e2e/fixtures/agent-workflow-tools/agent/tools/research_brief.ts @@ -19,6 +19,6 @@ export default defineWorkflowTool({ const result = await helper.result(); if (result.status === "failed") throw new Error('Agent "workflow-marker" failed.'); await sleep("3s"); - return result.message ?? null; + return result.reply ?? null; }, }); diff --git a/e2e/fixtures/agent-workflow-tools/evals/hold-deploy.cancel.eval.ts b/e2e/fixtures/agent-workflow-tools/evals/hold-deploy.cancel.eval.ts index 760e4317e8..22b58a2057 100644 --- a/e2e/fixtures/agent-workflow-tools/evals/hold-deploy.cancel.eval.ts +++ b/e2e/fixtures/agent-workflow-tools/evals/hold-deploy.cancel.eval.ts @@ -18,7 +18,7 @@ export default defineEval({ await t.require( cancelled, satisfies( - (value: typeof cancelled) => value.status === "accepted", + (value: typeof cancelled) => typeof value.deliveryId === "string", "cancel request is accepted", ), ); diff --git a/packages/eve-self-modification/README.md b/packages/eve-self-modification/README.md index ed7934593e..d60349986e 100644 --- a/packages/eve-self-modification/README.md +++ b/packages/eve-self-modification/README.md @@ -9,7 +9,7 @@ eve add eve/self-modification The command creates `agent/extensions/self-modification/extension.ts`: ```ts -import selfModification from "eve/self-modification"; +import selfModification from "eve/self-modification/local"; export default selfModification({ // model: "provider/model", @@ -29,6 +29,6 @@ Run the registry command to install the packaged extension: eve add eve/self-modification ``` -When setup detects a retired scaffold, it offers to remove the entire legacy directory and selects **Yes** by default. Decline the prompt if you need to inspect those files before removing them. +Setup rejects the retired layout without deleting it. Preserve any authored changes, remove `agent/subagents/self-modification/`, then run the registry command to install the local extension mount. -The legacy `eve/self-modification/agent`, `eve/self-modification/sandbox`, and `eve/self-modification/config` entrypoints remain available so the standard scaffold can build during migration. Do not use the retired agent or sandbox helpers for new installations. +The bare `eve/self-modification` import and `/config` entry are removed. The agent and sandbox integration helpers live under `eve/internal/self-modification/`; they have no compatibility promise and are not authoring APIs. diff --git a/packages/eve-self-modification/extension/extension.ts b/packages/eve-self-modification/extension/extension.ts index 5f046d60dd..dbbfd09115 100644 --- a/packages/eve-self-modification/extension/extension.ts +++ b/packages/eve-self-modification/extension/extension.ts @@ -1 +1 @@ -export { default } from "eve/self-modification"; +export { default } from "eve/self-modification/local"; diff --git a/packages/eve-self-modification/src/agent.ts b/packages/eve-self-modification/src/agent.ts index 2f661bacf6..ce534b5f8f 100644 --- a/packages/eve-self-modification/src/agent.ts +++ b/packages/eve-self-modification/src/agent.ts @@ -1,2 +1,2 @@ -export * from "eve/self-modification/agent"; -export { default } from "eve/self-modification/agent"; +export * from "eve/internal/self-modification/agent"; +export { default } from "eve/internal/self-modification/agent"; diff --git a/packages/eve-self-modification/src/sandbox.ts b/packages/eve-self-modification/src/sandbox.ts index 9bf669acb7..16cec75641 100644 --- a/packages/eve-self-modification/src/sandbox.ts +++ b/packages/eve-self-modification/src/sandbox.ts @@ -1 +1 @@ -export { default } from "eve/self-modification/sandbox"; +export { default } from "eve/internal/self-modification/sandbox"; diff --git a/packages/eve/extension-contracts/compatibility/channel/v63.ts b/packages/eve/extension-contracts/compatibility/channel/v63.ts deleted file mode 100644 index 1f86719553..0000000000 --- a/packages/eve/extension-contracts/compatibility/channel/v63.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineChannel, type ChannelDelivery } from "#public/channels/index.js"; - -export default defineChannel({ - state: { author: "" }, - deliver(payload: ChannelDelivery<{ author: string }>) { - return { ...payload, context: [payload.state?.author ?? "Anonymous"] }; - }, - routes: [], - events: { - "call.started"(event) { - const clearance = event.data.clearedBy; - if (clearance !== undefined && "policy" in clearance) console.info(clearance.policy); - }, - }, -}); diff --git a/packages/eve/extension-contracts/compatibility/hook/v47.ts b/packages/eve/extension-contracts/compatibility/hook/v47.ts deleted file mode 100644 index 37f1d2eb52..0000000000 --- a/packages/eve/extension-contracts/compatibility/hook/v47.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineHook } from "#public/hooks/index.js"; - -export default defineHook({ - events: { - "call.started"(event, ctx) { - const clearance = event.data.clearedBy; - if (clearance !== undefined && "interactionId" in clearance) { - console.info(ctx.session.id, clearance.interactionId); - } - }, - }, -}); diff --git a/packages/eve/extension-contracts/compatibility/schedule/v34.ts b/packages/eve/extension-contracts/compatibility/schedule/v34.ts deleted file mode 100644 index d1b7a3d996..0000000000 --- a/packages/eve/extension-contracts/compatibility/schedule/v34.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineChannel } from "#public/channels/index.js"; -import { defineSchedule } from "#public/schedules/index.js"; - -const reports = defineChannel({ - routes: [], - receive(input, { from }) { - return from("nightly-report").send(input.message, { auth: input.auth }); - }, -}); - -export default defineSchedule({ - cron: "0 2 * * *", - async run({ to, appAuth }) { - const session = await to(reports, {}).send("Prepare the nightly report.", { auth: appAuth }); - const events = await session.getEventStream(); - const reader = events.getReader(); - const { value } = await reader.read(); - if (value?.type === "call.started") { - const clearance = value.data.clearedBy; - if (clearance !== undefined && "policy" in clearance) console.info(clearance.policy); - } - await reader.cancel(); - }, -}); diff --git a/packages/eve/extension-contracts/compatibility/state/v13.ts b/packages/eve/extension-contracts/compatibility/state/v13.ts new file mode 100644 index 0000000000..904f4c6434 --- /dev/null +++ b/packages/eve/extension-contracts/compatibility/state/v13.ts @@ -0,0 +1,7 @@ +import { defineState } from "#public/context/index.js"; + +export const labels = defineState("compatibility.labels", () => [] as readonly string[]); +export function rememberLabel(label: string): readonly string[] { + labels.update((current) => [...current, label]); + return labels.get(); +} diff --git a/packages/eve/extension-contracts/entrypoints/state.ts b/packages/eve/extension-contracts/entrypoints/state.ts index 6dda57e495..e7f1d313d1 100644 --- a/packages/eve/extension-contracts/entrypoints/state.ts +++ b/packages/eve/extension-contracts/entrypoints/state.ts @@ -1,5 +1,6 @@ export { type SessionInfo, + type HistoryMessage, type SessionAuth, type SessionAuthContext, type SessionContext, diff --git a/packages/eve/extension-contracts/entrypoints/tool.ts b/packages/eve/extension-contracts/entrypoints/tool.ts index 913eb66bba..4b0ed39553 100644 --- a/packages/eve/extension-contracts/entrypoints/tool.ts +++ b/packages/eve/extension-contracts/entrypoints/tool.ts @@ -15,11 +15,7 @@ export { type AgentRouterInput, type AgentRouterTool, } from "../../src/public/tools/agent-router.ts"; -export { - defaultWebSearch, - isWebSearchToolDefinition, - webSearch, -} from "../../src/public/tools/web-search.ts"; +export { webSearch } from "../../src/public/tools/web-search.ts"; export { workflow, type WorkflowTool, diff --git a/packages/eve/extension-contracts/reports/channel/v62.json b/packages/eve/extension-contracts/reports/channel/v62.json index b827ab7bae..ab55ee397e 100644 --- a/packages/eve/extension-contracts/reports/channel/v62.json +++ b/packages/eve/extension-contracts/reports/channel/v62.json @@ -2,7 +2,7 @@ "kind": "eve-extension-capability-contract", "capability": "channel", "epoch": 62, - "sha256": "ad9b7dcee70febdf295325b8cf54e2b9666f5240c16fea83600a6263f505d66b", + "sha256": "7ba8f04b8b2ca2cfe6b86a67bf1d77d68814e0c7bdbba0662787821a423b5267", "exports": [ "DELETE", "GET", @@ -11,6 +11,7 @@ "PATCH", "POST", "PUT", + "SessionNotActiveError", "SessionStrandedError", "WS", "answerFromText", diff --git a/packages/eve/extension-contracts/reports/channel/v63.json b/packages/eve/extension-contracts/reports/channel/v63.json deleted file mode 100644 index ac025ba45d..0000000000 --- a/packages/eve/extension-contracts/reports/channel/v63.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "kind": "eve-extension-capability-contract", - "capability": "channel", - "epoch": 63, - "sha256": "ccf41337f5083365033c3267799f0159eefa6e5ad4ad311200e2d9353dd97469", - "exports": [ - "DELETE", - "GET", - "HEAD", - "OPTIONS", - "PATCH", - "POST", - "PUT", - "SessionStrandedError", - "WS", - "attachmentError", - "createWebSocketUpgradeServer", - "defineChannel", - "disableRoute", - "isChannel", - "isDisabledRouteSentinel" - ] -} diff --git a/packages/eve/extension-contracts/reports/channel/v64.json b/packages/eve/extension-contracts/reports/channel/v64.json deleted file mode 100644 index db9ce37e16..0000000000 --- a/packages/eve/extension-contracts/reports/channel/v64.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "kind": "eve-extension-capability-contract", - "capability": "channel", - "epoch": 64, - "sha256": "5a1e48be6be6284f4bd5d44fa4f7c3e35b886c27badf7ca0f76f80e89b8a5362", - "exports": [ - "DELETE", - "GET", - "HEAD", - "OPTIONS", - "PATCH", - "POST", - "PUT", - "SessionStrandedError", - "WS", - "attachmentError", - "createWebSocketUpgradeServer", - "defineChannel", - "disableRoute", - "isChannel", - "isDisabledRouteSentinel" - ] -} diff --git a/packages/eve/extension-contracts/reports/connection/v51.json b/packages/eve/extension-contracts/reports/connection/v51.json index 1a6c4ea310..ebe33fd725 100644 --- a/packages/eve/extension-contracts/reports/connection/v51.json +++ b/packages/eve/extension-contracts/reports/connection/v51.json @@ -2,7 +2,7 @@ "kind": "eve-extension-capability-contract", "capability": "connection", "epoch": 51, - "sha256": "c7df01cd100451f6563497332e8420f30b93973b235e4b1a9e7591a99f9a7ca6", + "sha256": "0f0fb3abff7ca278c7109f95aba438b50d83d84d1f7daf14c4ea82ff04f4e739", "exports": [ "ConnectionAuthorizationFailedError", "ConnectionAuthorizationRequiredError", diff --git a/packages/eve/extension-contracts/reports/connection/v52.json b/packages/eve/extension-contracts/reports/connection/v52.json deleted file mode 100644 index 864fcd0847..0000000000 --- a/packages/eve/extension-contracts/reports/connection/v52.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "kind": "eve-extension-capability-contract", - "capability": "connection", - "epoch": 52, - "sha256": "8d0fef5821201fc60a3136c0805836ae5a4ae8fb5da39b6b96ab7f5af87bb958", - "exports": [ - "ConnectionAuthorizationFailedError", - "ConnectionAuthorizationRequiredError", - "defineDynamic", - "defineInteractiveAuthorization", - "defineMcpClientConnection", - "defineOpenAPIConnection", - "isConnectionAuthorizationFailedError", - "isConnectionAuthorizationRequiredError" - ] -} diff --git a/packages/eve/extension-contracts/reports/dynamicInstructions/v40.json b/packages/eve/extension-contracts/reports/dynamicInstructions/v40.json index 2d600d5944..9b23e6fa3d 100644 --- a/packages/eve/extension-contracts/reports/dynamicInstructions/v40.json +++ b/packages/eve/extension-contracts/reports/dynamicInstructions/v40.json @@ -2,6 +2,6 @@ "kind": "eve-extension-capability-contract", "capability": "dynamicInstructions", "epoch": 40, - "sha256": "e11dcc6441dc2c69c73de78a38364df9964a14d84bd70c457b6dfa14d550f864", + "sha256": "a202dcce705a6195f926128864ade91478582a0fe1a889bcac5d9a8fc1d9a064", "exports": ["defineDynamic"] } diff --git a/packages/eve/extension-contracts/reports/dynamicSkill/v39.json b/packages/eve/extension-contracts/reports/dynamicSkill/v39.json index c5dec28d44..0e0de5f672 100644 --- a/packages/eve/extension-contracts/reports/dynamicSkill/v39.json +++ b/packages/eve/extension-contracts/reports/dynamicSkill/v39.json @@ -2,6 +2,6 @@ "kind": "eve-extension-capability-contract", "capability": "dynamicSkill", "epoch": 39, - "sha256": "66b81287cd75c99ad7b59b170970378ba8b0e58351933e52adb670b71aae8fdd", + "sha256": "d7004385d717d9b23d6989ee38e9bd13bf76f20c5c00341d0e940a4e6b3282cf", "exports": ["defineDynamic"] } diff --git a/packages/eve/extension-contracts/reports/dynamicTool/v87.json b/packages/eve/extension-contracts/reports/dynamicTool/v87.json index 0431b9baa4..6b836d593f 100644 --- a/packages/eve/extension-contracts/reports/dynamicTool/v87.json +++ b/packages/eve/extension-contracts/reports/dynamicTool/v87.json @@ -2,7 +2,7 @@ "kind": "eve-extension-capability-contract", "capability": "dynamicTool", "epoch": 87, - "sha256": "c2f747118e9b5c6d3d11e14a4b80548ee4e248753e1ba42d4aa15c22e9d19fc8", + "sha256": "b49b94b2f891ea661733eb70a56d89dcbb55f81c625b25b2e23856f93675eca0", "exports": [ "DynamicToolEntry", "DynamicToolEvents", diff --git a/packages/eve/extension-contracts/reports/dynamicTool/v88.json b/packages/eve/extension-contracts/reports/dynamicTool/v88.json deleted file mode 100644 index 9db1da76bd..0000000000 --- a/packages/eve/extension-contracts/reports/dynamicTool/v88.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "kind": "eve-extension-capability-contract", - "capability": "dynamicTool", - "epoch": 88, - "sha256": "e1eafb7f44fcb478b1b862ab3c08c3302bfa091d3d4a192f439e0a07dc8b01d4", - "exports": [ - "DynamicToolEntry", - "DynamicToolEvents", - "DynamicToolResult", - "DynamicToolSet", - "auto", - "defineDurableCallback", - "defineDurableSchema", - "defineDynamic" - ] -} diff --git a/packages/eve/extension-contracts/reports/hook/v47.json b/packages/eve/extension-contracts/reports/hook/v47.json index f89b3d71f9..d6e871bd6f 100644 --- a/packages/eve/extension-contracts/reports/hook/v47.json +++ b/packages/eve/extension-contracts/reports/hook/v47.json @@ -2,6 +2,6 @@ "kind": "eve-extension-capability-contract", "capability": "hook", "epoch": 47, - "sha256": "08da2a93c6082868846adc55f68f95163e19017982c03cd60275f4e0e8b74003", + "sha256": "3ebb842f848382c97d91b5e7a7389bb2eed31efe22e3a37907056c32ec99fce2", "exports": ["defineHook"] } diff --git a/packages/eve/extension-contracts/reports/hook/v48.json b/packages/eve/extension-contracts/reports/hook/v48.json deleted file mode 100644 index f0920222c9..0000000000 --- a/packages/eve/extension-contracts/reports/hook/v48.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "kind": "eve-extension-capability-contract", - "capability": "hook", - "epoch": 48, - "sha256": "b55b60928323d6ea960fab7bac8ccb91a5c911e056d155195b53561153ce87d9", - "exports": ["defineHook"] -} diff --git a/packages/eve/extension-contracts/reports/schedule/v34.json b/packages/eve/extension-contracts/reports/schedule/v34.json index 896c213746..c965593387 100644 --- a/packages/eve/extension-contracts/reports/schedule/v34.json +++ b/packages/eve/extension-contracts/reports/schedule/v34.json @@ -2,7 +2,7 @@ "kind": "eve-extension-capability-contract", "capability": "schedule", "epoch": 34, - "sha256": "37a22eac1e8f349f053f43293c61f4ef78736e4da8e5235fdcd8f37e2c15acac", + "sha256": "8698e89d634797958a75bb432e21fd026fcdde3e192a44a787bc6289b1781f13", "exports": [ "ScheduleDefinition", "ScheduleHandlerArgs", diff --git a/packages/eve/extension-contracts/reports/schedule/v35.json b/packages/eve/extension-contracts/reports/schedule/v35.json deleted file mode 100644 index 7bd872aa79..0000000000 --- a/packages/eve/extension-contracts/reports/schedule/v35.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "kind": "eve-extension-capability-contract", - "capability": "schedule", - "epoch": 35, - "sha256": "94b78543c3ce4cf081a6d1135f90274a10678fdc3671888f8ee51f292ddc23d9", - "exports": [ - "ScheduleDefinition", - "ScheduleHandlerArgs", - "ScheduleRunHandler", - "ScheduleToFn", - "TypedReceiveTarget", - "defineSchedule", - "isScheduleAuth" - ] -} diff --git a/packages/eve/extension-contracts/reports/state/v14.json b/packages/eve/extension-contracts/reports/state/v14.json new file mode 100644 index 0000000000..14bbcc018f --- /dev/null +++ b/packages/eve/extension-contracts/reports/state/v14.json @@ -0,0 +1,16 @@ +{ + "kind": "eve-extension-capability-contract", + "capability": "state", + "epoch": 14, + "sha256": "63d8130fc01847a610166183e88bbf95a9df2fb33cb3ce4c2b3ed8b7d64ac073", + "exports": [ + "HistoryMessage", + "SessionAuth", + "SessionAuthContext", + "SessionContext", + "SessionInfo", + "SessionParent", + "SessionTurn", + "defineState" + ] +} diff --git a/packages/eve/extension-contracts/reports/subagent/v39.json b/packages/eve/extension-contracts/reports/subagent/v39.json index 467ca410f3..0bff1b0354 100644 --- a/packages/eve/extension-contracts/reports/subagent/v39.json +++ b/packages/eve/extension-contracts/reports/subagent/v39.json @@ -2,7 +2,7 @@ "kind": "eve-extension-capability-contract", "capability": "subagent", "epoch": 39, - "sha256": "818d7ca5431ac31cf91e25fa31d9dd183cdc2baaaf624dabcb1cc8cd63ba9a31", + "sha256": "7808eaaa7c0b837f1f22288c4a126e4714d0e57fb6b5ee4d2ec547dce4369c84", "exports": [ "AgentCompactionDefinition", "AgentDefinition", diff --git a/packages/eve/extension-contracts/reports/tool/v93.json b/packages/eve/extension-contracts/reports/tool/v93.json index 7f95976c60..3ec4902232 100644 --- a/packages/eve/extension-contracts/reports/tool/v93.json +++ b/packages/eve/extension-contracts/reports/tool/v93.json @@ -2,7 +2,7 @@ "kind": "eve-extension-capability-contract", "capability": "tool", "epoch": 93, - "sha256": "4a386ec809e2058e4759b9c4464107afc7f43c6a87c5c280c472f211b311fba6", + "sha256": "e39f85a0b12f6177441ee427b55cb145790caf646f9a6daa62ead80873593a79", "exports": [ "AgentRouterInput", "AgentRouterTool", @@ -13,12 +13,10 @@ "WorkflowToolOptions", "agentRouter", "decide", - "defaultWebSearch", "defineTool", "defineWorkflowTool", "disableTool", "isDisabledToolSentinel", - "isWebSearchToolDefinition", "serializeModelInputSchema", "toolOutput", "toolOutputPart", diff --git a/packages/eve/extension-contracts/reports/tool/v94.json b/packages/eve/extension-contracts/reports/tool/v94.json deleted file mode 100644 index 321f6e00a0..0000000000 --- a/packages/eve/extension-contracts/reports/tool/v94.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "kind": "eve-extension-capability-contract", - "capability": "tool", - "epoch": 94, - "sha256": "a7f7cf7002e074d66d2485950ae3e7508681aa7581fc87b146ee1997a1f2d394", - "exports": [ - "AgentRouterInput", - "AgentRouterTool", - "WorkflowStepToolContext", - "WorkflowTool", - "WorkflowToolDefinition", - "WorkflowToolInput", - "WorkflowToolOptions", - "agentRouter", - "decide", - "defaultWebSearch", - "defineTool", - "defineWorkflowTool", - "disableTool", - "isDisabledToolSentinel", - "isWebSearchToolDefinition", - "serializeModelInputSchema", - "toolOutput", - "toolOutputPart", - "toolResultFrom", - "webSearch", - "workflow" - ] -} diff --git a/packages/eve/package.json b/packages/eve/package.json index da075a441f..3d4a00bd65 100644 --- a/packages/eve/package.json +++ b/packages/eve/package.json @@ -333,12 +333,6 @@ "import": "./dist/src/public/local-dev.js", "default": "./dist/src/public/local-dev.js" }, - "./self-modification": { - "eve-source": "./dist/src/self-modification/extension/extension.js", - "types": "./dist/src/self-modification/extension/extension.d.ts", - "import": "./dist/src/self-modification/extension/extension.js", - "default": "./dist/src/self-modification/extension/extension.js" - }, "./self-modification/local": { "eve-source": "./dist/src/self-modification/extension/extension.js", "types": "./dist/src/self-modification/extension/extension.d.ts", @@ -351,18 +345,6 @@ "import": "./dist/src/self-modification/remote/extension.js", "default": "./dist/src/self-modification/remote/extension.js" }, - "./self-modification/agent": { - "eve-source": "./dist/src/self-modification/agent.js", - "types": "./dist/src/self-modification/agent.d.ts", - "import": "./dist/src/self-modification/agent.js", - "default": "./dist/src/self-modification/agent.js" - }, - "./self-modification/sandbox": { - "eve-source": "./dist/src/self-modification/sandbox.js", - "types": "./dist/src/self-modification/sandbox.d.ts", - "import": "./dist/src/self-modification/sandbox.js", - "default": "./dist/src/self-modification/sandbox.js" - }, "./extensions/code": { "eve-source": "./dist/src/extensions/code/extension/extension.js", "types": "./dist/src/extensions/code/extension/extension.d.ts", @@ -537,23 +519,23 @@ "default": "./dist/src/public/channels/linear/index.js" }, "./package.json": "./package.json", - "./setup/scaffold": { - "eve-source": "./dist/src/setup/scaffold/index.js", - "types": "./dist/src/setup/scaffold/index.d.ts", - "import": "./dist/src/setup/scaffold/index.js", - "default": "./dist/src/setup/scaffold/index.js" - }, - "./setup": { - "eve-source": "./dist/src/setup/index.js", - "types": "./dist/src/setup/index.d.ts", - "import": "./dist/src/setup/index.js", - "default": "./dist/src/setup/index.js" - }, - "./self-modification/config": { - "eve-source": "./src/self-modification/config.ts", - "types": "./dist/src/self-modification/config.d.ts", - "import": "./dist/src/self-modification/config.js", - "default": "./dist/src/self-modification/config.js" + "./internal/self-modification/agent": { + "eve-source": "./dist/src/self-modification/agent.js", + "types": "./dist/src/self-modification/agent.d.ts", + "import": "./dist/src/self-modification/agent.js", + "default": "./dist/src/self-modification/agent.js" + }, + "./internal/self-modification/sandbox": { + "eve-source": "./dist/src/self-modification/sandbox.js", + "types": "./dist/src/self-modification/sandbox.d.ts", + "import": "./dist/src/self-modification/sandbox.js", + "default": "./dist/src/self-modification/sandbox.js" + }, + "./internal/scaffold": { + "eve-source": "./src/internal/scaffold.ts", + "types": "./dist/src/internal/scaffold.d.ts", + "import": "./dist/src/internal/scaffold.js", + "default": "./dist/src/internal/scaffold.js" } }, "publishConfig": { @@ -696,9 +678,6 @@ }, "eve": { "builtInExtensions": { - "./self-modification": { - "dist": "./dist/src/self-modification/extension" - }, "./self-modification/local": { "dist": "./dist/src/self-modification/extension" }, diff --git a/packages/eve/public-api/eve/channels.json b/packages/eve/public-api/eve/channels.json index 00c640508a..bf4bc1ec63 100644 --- a/packages/eve/public-api/eve/channels.json +++ b/packages/eve/public-api/eve/channels.json @@ -8,7 +8,6 @@ "AudienceContext", "AudienceInput", "AudiencePrincipal", - "CancelTurnResult", "Channel", "ChannelAudience", "ChannelCommitHandler", @@ -25,9 +24,7 @@ "ChannelRespondOptions", "ChannelSendOptions", "ChannelSource", - "ClearSessionResult", "CommittedLine", - "CompactSessionResult", "ConversationEnvironment", "DELETE", "Delivery", @@ -56,11 +53,9 @@ "PATCH", "POST", "PUT", - "ResetSessionResult", "RouteDefinition", "RouteHandlerArgs", "Session", - "SessionCallback", "SessionHandle", "SessionNotActiveError", "SessionOperations", diff --git a/packages/eve/public-api/eve/channels/auth.json b/packages/eve/public-api/eve/channels/auth.json index 665a7179d3..e8686a3194 100644 --- a/packages/eve/public-api/eve/channels/auth.json +++ b/packages/eve/public-api/eve/channels/auth.json @@ -21,7 +21,6 @@ "VerifyVercelOidcOptions", "createIpAllowList", "createUnauthorizedResponse", - "escapeAuthChallengeParameter", "extractBearerToken", "httpBasic", "isIpAllowed", @@ -32,7 +31,6 @@ "oauthResource", "oidc", "placeholderAuth", - "readOAuthResourceOptions", "routeAuth", "vercelOidc", "vercelSubject", diff --git a/packages/eve/public-api/eve/channels/discord.json b/packages/eve/public-api/eve/channels/discord.json index bcc46c23a9..0ff0678476 100644 --- a/packages/eve/public-api/eve/channels/discord.json +++ b/packages/eve/public-api/eve/channels/discord.json @@ -1,13 +1,4 @@ [ - "DISCORD_COMPONENT_TYPE", - "DISCORD_EPHEMERAL_MESSAGE_FLAG", - "DISCORD_HITL_CUSTOM_ID_PREFIX", - "DISCORD_HITL_FREEFORM_CUSTOM_ID_PREFIX", - "DISCORD_HITL_FREEFORM_TEXT_INPUT_ID", - "DISCORD_INTERACTION_RESPONSE_TYPE", - "DISCORD_INTERACTION_TYPE", - "DISCORD_MESSAGE_CONTENT_MAX_LENGTH", - "DISCORD_NO_MENTIONS", "DiscordAnswerContext", "DiscordAnswerInteraction", "DiscordAnswerResult", @@ -44,27 +35,7 @@ "DiscordUser", "DiscordVerifyOptions", "DiscordWebhookVerifier", - "ModelMessage", - "buildFreeformModalResponse", - "callDiscordApi", - "commandInteractionMessage", - "createDiscordFollowupMessage", "defaultDiscordAuth", - "deriveComponentAnswers", - "deriveModalAnswers", "discordChannel", - "discordContinuationToken", - "editDiscordOriginalResponse", - "formatDiscordContextBlock", - "isDiscordFreeformComponent", - "parseDiscordInteraction", - "renderInteractionComponents", - "resolveDiscordApplicationId", - "resolveDiscordBotToken", - "resolveDiscordPublicKey", - "sendDiscordChannelMessage", - "splitDiscordMessageContent", - "triggerDiscordTypingIndicator", - "verifyDiscordRequest", - "verifyDiscordSignature" + "discordContinuationToken" ] diff --git a/packages/eve/public-api/eve/channels/eve.json b/packages/eve/public-api/eve/channels/eve.json index 91586e4d61..3a19b771c9 100644 --- a/packages/eve/public-api/eve/channels/eve.json +++ b/packages/eve/public-api/eve/channels/eve.json @@ -1,7 +1,5 @@ [ "EveChannel", - "EveChannelCors", - "EveChannelCorsOptions", "EveChannelEvents", "EveChannelInput", "EveEventContext", diff --git a/packages/eve/public-api/eve/channels/github.json b/packages/eve/public-api/eve/channels/github.json index 3a44687032..993e15aba8 100644 --- a/packages/eve/public-api/eve/channels/github.json +++ b/packages/eve/public-api/eve/channels/github.json @@ -1,5 +1,4 @@ [ - "GITHUB_DEFAULT_EXCLUDED_DIFF_FILES", "GitHubApiError", "GitHubApiMethod", "GitHubApiOptions", diff --git a/packages/eve/public-api/eve/channels/linear.json b/packages/eve/public-api/eve/channels/linear.json index d9b6ddcf54..d18f838d71 100644 --- a/packages/eve/public-api/eve/channels/linear.json +++ b/packages/eve/public-api/eve/channels/linear.json @@ -1,5 +1,4 @@ [ - "LINEAR_CHANNEL_DEFAULT_ROUTE", "LinearAccessToken", "LinearAgentActivityContent", "LinearAgentActivityCreateInput", @@ -38,18 +37,8 @@ "LinearWebhookVerifier", "callLinearGraphQL", "createLinearAgentActivity", - "createLinearAgentSessionOnComment", - "createLinearAgentSessionOnIssue", "defaultLinearAuth", "defaultOnAgentSession", - "formatLinearContextBlock", "linearChannel", - "linearContinuationToken", - "linearInteractionSignal", - "listLinearAgentSessionActivities", - "messageFromLinearAgentSessionEvent", - "parseLinearWebhookEvent", - "renderLinearInteractions", - "signLinearWebhookBody", - "updateLinearAgentSession" + "linearContinuationToken" ] diff --git a/packages/eve/public-api/eve/channels/slack.json b/packages/eve/public-api/eve/channels/slack.json index 77c650086b..79f8f4d79d 100644 --- a/packages/eve/public-api/eve/channels/slack.json +++ b/packages/eve/public-api/eve/channels/slack.json @@ -50,12 +50,10 @@ "ModalElement", "ModalOptions", "ModalProps", - "ModelMessage", "PostableCard", "RadioSelect", "RadioSelectElement", "RadioSelectOptions", - "SLACK_MARKDOWN_TEXT_MAX_LENGTH", "Section", "SectionElement", "Select", @@ -141,7 +139,6 @@ "TextStyle", "ThreadContextSince", "answerFromSlackAction", - "buildSlackAuthContext", "callSlackApi", "cardChildToFallbackText", "cardToBlocks", diff --git a/packages/eve/public-api/eve/channels/teams.json b/packages/eve/public-api/eve/channels/teams.json index 7090b0e8e0..748e7560f7 100644 --- a/packages/eve/public-api/eve/channels/teams.json +++ b/packages/eve/public-api/eve/channels/teams.json @@ -1,12 +1,4 @@ [ - "ModelMessage", - "TEAMS_ADAPTIVE_CARD_CONTENT_TYPE", - "TEAMS_HITL_CHOICE_INPUT_ID", - "TEAMS_HITL_DATA_KEY", - "TEAMS_HITL_FREEFORM_INPUT_ID", - "TEAMS_HITL_PROMPT_KEY", - "TEAMS_HITL_REQUEST_KIND_KEY", - "TEAMS_MESSAGE_TEXT_MAX_LENGTH", "TeamsAccessTokenResult", "TeamsActivity", "TeamsActivityBase", @@ -53,39 +45,7 @@ "TeamsTokenProvider", "TeamsVerifyOptions", "TeamsWebhookVerifier", - "buildAuthCompletedText", - "buildTeamsTurnMessage", - "callTeamsConnectorApi", - "collectTeamsFileParts", - "createTeamsFetchFile", "defaultTeamsAuth", - "deriveTeamsAnswers", - "formatConnectionDisplayName", - "formatTeamsContextBlock", - "isTeamsAnswerActivity", - "isTeamsPersonalMessage", - "isTeamsToolApprovalResponseActivity", - "normalizeTeamsFilesPolicy", - "normalizeTeamsPostInput", - "parseTeamsActivity", - "readTeamsToolApprovalPrompt", - "renderAnsweredInteractionMessage", - "renderInteractionAttachment", - "renderInteractionMessage", - "replyToTeamsActivity", - "resolveTeamsAccessToken", - "resolveTeamsAppId", - "resolveTeamsAppPassword", - "resolveTeamsTenantId", - "sendTeamsActivity", - "splitTeamsMessageText", "teamsChannel", - "teamsContinuationToken", - "teamsInvokeResponse", - "teamsMentionUser", - "teamsThreadRootActivityId", - "triggerTeamsTypingIndicator", - "updateTeamsActivity", - "verifyTeamsJwt", - "verifyTeamsRequest" + "teamsContinuationToken" ] diff --git a/packages/eve/public-api/eve/channels/telegram.json b/packages/eve/public-api/eve/channels/telegram.json index 28d66fb8ed..b23be2a38f 100644 --- a/packages/eve/public-api/eve/channels/telegram.json +++ b/packages/eve/public-api/eve/channels/telegram.json @@ -1,10 +1,4 @@ [ - "ModelMessage", - "TELEGRAM_CALLBACK_RESPONSE_PREFIX", - "TELEGRAM_FILE_URL_PROTOCOL", - "TELEGRAM_HITL_CALLBACK_PREFIX", - "TELEGRAM_MESSAGE_TEXT_MAX_LENGTH", - "TELEGRAM_REPLY_RESPONSE_PREFIX", "TelegramApiOptions", "TelegramApiResponse", "TelegramAttachment", @@ -39,31 +33,7 @@ "TelegramVerifyOptions", "TelegramWebhookSecretToken", "TelegramWebhookVerifier", - "answerTelegramCallbackQuery", - "buildTelegramTurnMessage", - "callTelegramApi", - "collectTelegramFileParts", - "createTelegramFetchFile", - "createTelegramFileUrl", "defaultTelegramAuth", - "downloadTelegramFile", - "editTelegramMessageReplyMarkup", - "editTelegramMessageText", - "formatTelegramContextBlock", - "getTelegramFile", - "isTelegramSyntheticResponse", - "parseTelegramUpdate", - "registerTelegramFreeformPrompt", - "renderTelegramInteraction", - "resolveTelegramAnswers", - "resolveTelegramBotToken", - "resolveTelegramWebhookSecretToken", - "sendTelegramChatAction", - "sendTelegramMessage", - "splitTelegramMessageText", - "telegramCallbackAnswer", "telegramChannel", - "telegramContinuationToken", - "telegramReplyAnswer", - "verifyTelegramRequest" + "telegramContinuationToken" ] diff --git a/packages/eve/public-api/eve/channels/twilio.json b/packages/eve/public-api/eve/channels/twilio.json index 26c7ed4f4b..e84ad41e43 100644 --- a/packages/eve/public-api/eve/channels/twilio.json +++ b/packages/eve/public-api/eve/channels/twilio.json @@ -33,19 +33,6 @@ "TwilioVoiceResultOrPromise", "TwilioVoiceTranscription", "TwilioWebhookUrl", - "buildTwilioSignatureBase", - "callTwilioApi", - "emptyTwilioResponse", - "escapeXml", - "gatherSpeechTwilioResponse", - "resolveTwilioAccountSid", - "resolveTwilioAuthToken", - "sayTwilioResponse", - "sendTwilioMessage", - "signTwilioRequest", "twilioChannel", - "twilioContinuationToken", - "twimlResponse", - "updateTwilioCall", - "verifyTwilioRequest" + "twilioContinuationToken" ] diff --git a/packages/eve/public-api/eve/client.json b/packages/eve/public-api/eve/client.json index 2fef34a36d..47f66d9aeb 100644 --- a/packages/eve/public-api/eve/client.json +++ b/packages/eve/public-api/eve/client.json @@ -18,33 +18,7 @@ "AgentInfoToolEntry", "AgentInfoTools", "AgentObservation", - "Answer", "AskedCall", - "CALL_OUTCOMES", - "CONTEXT_OUTCOMES", - "CallFact", - "CallInput", - "CallInputData", - "CallOutcome", - "CallOwner", - "CallProgress", - "CallProgressData", - "CallProgressRecord", - "CallRequested", - "CallRequestedData", - "CallSettled", - "CallSettledData", - "CallStarted", - "CallStartedData", - "CancelSessionResult", - "Capability", - "CapabilityKind", - "Cause", - "ChildFact", - "ChildOpened", - "ChildOpenedData", - "ClearResult", - "ClearedBy", "Client", "ClientAgentSession", "ClientAuth", @@ -55,42 +29,13 @@ "ClientSessionState", "ClientSessionStrandedError", "ClientSessions", - "CommitLine", - "CompactResult", - "ContentCompleted", - "ContentCompletedData", - "ContentDelta", - "ContentDeltaData", - "ContentFact", - "ContentKind", - "ContentPhase", - "ContentProgress", - "ContextFact", - "ContextKind", - "ContextOutcome", - "ContextSettled", - "ContextSettledData", - "ContextStarted", - "ContextStartedData", "ConversationAgentSession", "ConversationState", "CreateSessionOptions", "CreatedClientSession", "CreatedIdleClientSession", - "DELIVERY_OUTCOMES", "Delivery", - "DeliveryAdmitted", - "DeliveryAdmittedData", - "DeliveryConsumed", - "DeliveryConsumedData", - "DeliveryFact", - "DeliveryOutcome", "DeliveryResult", - "DeliverySettled", - "DeliverySettledData", - "DeliverySource", - "Envelope", - "ErrorInfo", "EveAgentStore", "EveAgentStoreCallbacks", "EveAgentStoreInit", @@ -101,168 +46,34 @@ "EveMessageMetadata", "EveMessagePart", "EveToolPart", - "FACT_CATALOG", - "Fact", - "FactDescriptor", - "FactOf", - "FactPosition", - "FactRole", - "FactType", - "Family", - "HEARTBEAT_RECORD", "HeadersValue", "HealthResponseError", "HealthResult", - "INTERACTION_OUTCOMES", - "Interaction", - "InteractionDisplay", - "InteractionFact", - "InteractionKind", - "InteractionOpened", - "InteractionOpenedData", - "InteractionOption", - "InteractionOptionStyle", - "InteractionOrigin", - "InteractionOutcome", - "InteractionRequest", - "InteractionSettled", - "InteractionSettledData", - "InteractionSubject", - "JsonValue", - "LEASE_ENDED_RECORD", - "MAX_SESSION_LINE_BYTES", - "MODEL_OUTCOMES", "MessageContent", "MessageFilePart", "MessagePart", "MessageTextPart", - "ModelFact", - "ModelOutcome", - "ModelOwner", - "ModelRequested", - "ModelRequestedData", - "ModelRetried", - "ModelRetriedData", - "ModelSettled", - "ModelSettledData", - "ModelStarted", - "ModelStartedData", - "PROGRESS_CATALOG", - "PositionMarker", "PrepareSend", - "Principal", - "Progress", - "ProgressDescriptor", - "ProgressLine", - "ProgressOf", - "ProgressType", - "RESPONSE_OUTCOMES", - "ReadRecord", - "ResetResult", - "ResolvedStreamReconnectPolicy", "RespondTurnOptions", - "ResponseAdmitted", - "ResponseAdmittedData", - "ResponseFact", - "ResponseOutcome", - "ResponseSettled", - "ResponseSettledData", - "ResponseSubmitted", - "ResponseSubmittedData", - "ResponseValue", - "RuntimeIdentity", - "SESSION_OUTCOMES", - "STREAM_ENDED_RECORD", - "Scope", "SendTurnInput", "SendTurnOptions", - "SessionEnded", - "SessionEndedData", "SessionEvent", "SessionEventMeta", - "SessionFact", - "SessionLineTooLargeError", "SessionNotActiveError", - "SessionOutcome", - "SessionPredecessor", - "SessionPreviews", "SessionSnapshot", - "SessionStarted", - "SessionStartedData", "SessionStreamEvent", - "SessionView", - "SignInChallenge", - "StoredLine", "StreamOptions", "StreamReconnectPolicy", "StreamReconnectRetryPolicy", - "TASK_OUTCOMES", - "TERMINAL_OUTCOMES", - "TURN_OUTCOMES", - "TaskEnded", - "TaskEndedData", - "TaskFact", - "TaskOutcome", - "TaskStarted", - "TaskStartedData", "TokenValue", - "ToolStub", - "ToolStubOutcome", - "TraceContext", "TranscriptData", "TranscriptMessage", "TranscriptOptions", - "TransportRecord", - "TurnFact", - "TurnOutcome", - "TurnPaused", - "TurnPausedData", - "TurnSettled", - "TurnSettledData", - "TurnStarted", - "TurnStartedData", - "Usage", - "UsageFact", - "UsageOwner", - "UsageRecorded", - "UsageRecordedData", - "UserPart", - "ValueReference", - "callProgressSchemas", - "callSchemas", - "childSchemas", - "classifyRecord", - "clearedBySchema", - "contentPhase", - "contentProgressSchemas", - "contentSchemas", - "contextSchemas", "conversationOf", - "createDataUrlFilePart", - "createTextWithFileContent", - "deliverySchemas", - "encodeLine", - "encodeLineBytes", "endsTurn", "failureOf", - "interactionRequestSchema", - "interactionSchemas", "isAnswer", - "isCompleted", - "isFactType", - "isKnownOutcome", - "isProgressType", - "isStoredLine", - "isTerminal", - "isTerminalType", - "modelSchemas", "parseAnswer", "parseAnswers", - "responseSchemas", - "sessionSchemas", - "signInEnding", - "taskSchemas", - "transcriptOf", - "turnSchemas", - "usageSchemas" + "transcriptOf" ] diff --git a/packages/eve/public-api/eve/connections.json b/packages/eve/public-api/eve/connections.json index 964bb2e872..e7facb77a5 100644 --- a/packages/eve/public-api/eve/connections.json +++ b/packages/eve/public-api/eve/connections.json @@ -2,7 +2,6 @@ "AuthorizationCallback", "AuthorizationDefinition", "ConnectionAuthDefinition", - "ConnectionAuthProvider", "ConnectionAuthResolver", "ConnectionAuthorizationChallenge", "ConnectionAuthorizationFailedError", @@ -11,7 +10,6 @@ "ConnectionAuthorizationRequiredErrorOptions", "ConnectionPrincipal", "ConnectionToolCallDefinition", - "CredentialOwner", "DynamicConnectionDefinition", "DynamicConnectionEvents", "DynamicConnectionResolveContext", @@ -25,7 +23,6 @@ "Experimental_ConnectionEvents", "HeadersDefinition", "InteractiveAuthorizationDefinition", - "JsonValue", "McpClientConnectionDefinition", "NonInteractiveAuthorizationDefinition", "OpenAPIConnectionDefinition", diff --git a/packages/eve/public-api/eve/context.json b/packages/eve/public-api/eve/context.json index 8a2a79ec7f..45df7fef14 100644 --- a/packages/eve/public-api/eve/context.json +++ b/packages/eve/public-api/eve/context.json @@ -1,4 +1,5 @@ [ + "HistoryMessage", "SessionAuth", "SessionAuthContext", "SessionContext", diff --git a/packages/eve/public-api/eve/evals.json b/packages/eve/public-api/eve/evals.json index c1d30cb76e..850373b357 100644 --- a/packages/eve/public-api/eve/evals.json +++ b/packages/eve/public-api/eve/evals.json @@ -4,7 +4,6 @@ "AssertionHandle", "AssertionResult", "AssertionSeverity", - "CancelSessionResult", "EveEval", "EveEvalActionStatus", "EveEvalAgentSession", @@ -44,7 +43,6 @@ "EveEvalVerdict", "EveEvalWaitForEventOptions", "EveEvalWatchOptions", - "Interaction", "JudgeBatch", "JudgeContext", "JudgeInput", @@ -59,13 +57,10 @@ "MockModelToolCall", "MockModelToolResult", "MockModelUsage", - "RuntimeIdentity", "ScoreInput", "ToolStub", "ToolStubOutcome", - "TraceContext", "defineEval", "defineEvalConfig", - "mockModel", - "toolCallsOf" + "mockModel" ] diff --git a/packages/eve/public-api/eve/evals/expect.json b/packages/eve/public-api/eve/evals/expect.json index a20b903812..129da7c552 100644 --- a/packages/eve/public-api/eve/evals/expect.json +++ b/packages/eve/public-api/eve/evals/expect.json @@ -1,11 +1 @@ -[ - "Assertion", - "AssertionEvaluation", - "AssertionHandle", - "AssertionSeverity", - "equals", - "includes", - "matches", - "satisfies", - "similarity" -] +["equals", "includes", "matches", "satisfies", "similarity"] diff --git a/packages/eve/public-api/eve/instrumentation.json b/packages/eve/public-api/eve/instrumentation.json index 7ece5c73f9..68cfbebfaa 100644 --- a/packages/eve/public-api/eve/instrumentation.json +++ b/packages/eve/public-api/eve/instrumentation.json @@ -1,5 +1,4 @@ [ - "DISABLED", "EvaluationRef", "Handler", "InstrumentationDisabled", @@ -16,7 +15,6 @@ "InstrumentationTurn", "InstrumentationUsage", "JsonValue", - "PROVIDER", "ProviderContext", "ProviderDefinition", "ProviderEvents", diff --git a/packages/eve/public-api/eve/next.json b/packages/eve/public-api/eve/next.json index 156296cdd8..f285764a63 100644 --- a/packages/eve/public-api/eve/next.json +++ b/packages/eve/public-api/eve/next.json @@ -1,5 +1,4 @@ [ - "EVE_NEXT_SERVICE_PREFIX", "EveNextConfig", "EveNextConfigFunction", "EveNextConfigInput", diff --git a/packages/eve/public-api/eve/sandbox.json b/packages/eve/public-api/eve/sandbox.json index ce8033fadb..33cfd44dc4 100644 --- a/packages/eve/public-api/eve/sandbox.json +++ b/packages/eve/public-api/eve/sandbox.json @@ -2,7 +2,6 @@ "DefaultSandbox", "DefaultSandboxEnvironmentOptions", "RuntimeSandboxSession", - "RuntimeSandboxSessionFor", "SandboxCommandResult", "SandboxEnvironment", "SandboxNetworkOptions", diff --git a/packages/eve/public-api/eve/self-modification.json b/packages/eve/public-api/eve/self-modification.json deleted file mode 100644 index 8b30f8adcd..0000000000 --- a/packages/eve/public-api/eve/self-modification.json +++ /dev/null @@ -1 +0,0 @@ -["default"] diff --git a/packages/eve/public-api/eve/self-modification/agent.json b/packages/eve/public-api/eve/self-modification/agent.json deleted file mode 100644 index 612a544262..0000000000 --- a/packages/eve/public-api/eve/self-modification/agent.json +++ /dev/null @@ -1 +0,0 @@ -["SelfModificationAgentOptions", "default", "defineSelfModificationAgent"] diff --git a/packages/eve/public-api/eve/self-modification/config.json b/packages/eve/public-api/eve/self-modification/config.json deleted file mode 100644 index 55c3114651..0000000000 --- a/packages/eve/public-api/eve/self-modification/config.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - "DEPLOYED_OPTION_MOVED_MESSAGE", - "ResolvedSelfModificationConfig", - "SelfModificationConfig", - "SelfModificationExtensionConfig", - "defineSelfModificationConfig", - "resolveSelfModificationConfig" -] diff --git a/packages/eve/public-api/eve/self-modification/local.json b/packages/eve/public-api/eve/self-modification/local.json index 8b30f8adcd..c883ca40bc 100644 --- a/packages/eve/public-api/eve/self-modification/local.json +++ b/packages/eve/public-api/eve/self-modification/local.json @@ -1 +1 @@ -["default"] +["SelfModificationExtensionConfig", "default"] diff --git a/packages/eve/public-api/eve/self-modification/sandbox.json b/packages/eve/public-api/eve/self-modification/sandbox.json deleted file mode 100644 index c64038b8ae..0000000000 --- a/packages/eve/public-api/eve/self-modification/sandbox.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - "SelfModificationSandbox", - "SelfModificationSandboxOptions", - "default", - "defineSelfModificationSandbox" -] diff --git a/packages/eve/public-api/eve/setup.json b/packages/eve/public-api/eve/setup.json deleted file mode 100644 index 4fd1d5cb73..0000000000 --- a/packages/eve/public-api/eve/setup.json +++ /dev/null @@ -1,79 +0,0 @@ -[ - "AiGatewayEnvFile", - "AnySetupBox", - "ArgsHeadlessAiGateway", - "ArgsHeadlessProject", - "ChannelKind", - "ChatPreference", - "DeploymentInfo", - "DeploymentState", - "HeadlessEvent", - "HeadlessLogSink", - "HeadlessNextStep", - "HeadlessPromptError", - "InspectVerifiedRemoteAgentDeps", - "InteractionRequired", - "MultiSelectOptions", - "NoteTone", - "OutputSink", - "PackageManagerStrategy", - "ProjectResolution", - "PromptCommandLog", - "Prompter", - "PrompterValue", - "ProvisionSlackbotResult", - "ResolvedAiGateway", - "ResolvedAiGatewayCredentials", - "ResolvedProjectPath", - "ResolvedVercelProject", - "ResolvedVercelProjectSpec", - "RunPackageManagerOptions", - "RunPnpmOptions", - "RunResult", - "RunVercelOptions", - "RunnerOptions", - "SelectCommonOptions", - "SelectOption", - "SetupBox", - "SetupConnectionConnectorOptions", - "SetupConnectionConnectorResult", - "SetupMode", - "SetupState", - "SingleSelectOptions", - "SlackConnectorCandidate", - "SlackConnectorSelection", - "SlackbotConnectorInspection", - "VercelProjectIdentity", - "VerifiedRemoteAgentInspection", - "WiringMode", - "WizardCancelledError", - "createDefaultSetupState", - "createHeadlessPrompter", - "createPromptCommandOutput", - "createPrompter", - "detectDeployment", - "detectProjectResolution", - "formatHeadlessEvent", - "getPackageManagerStrategy", - "inspectSlackbotConnectors", - "inspectVerifiedRemoteAgent", - "linkProject", - "projectProductionUrlFromResolution", - "provisionSlackbot", - "reconcileSlackUid", - "requireAuth", - "requireProjectPath", - "resolveProjectByNameOrId", - "resolveTeam", - "runHeadless", - "runInteractive", - "runPackageManagerInstall", - "runPnpmInstall", - "runRemoteAuthFlow", - "runVercel", - "runVercelEnvPull", - "setupConnectionConnector", - "snapshotSetupState", - "spawnPackageManager", - "spawnPnpm" -] diff --git a/packages/eve/public-api/eve/setup/scaffold.json b/packages/eve/public-api/eve/setup/scaffold.json deleted file mode 100644 index c74421ea29..0000000000 --- a/packages/eve/public-api/eve/setup/scaffold.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - "CONNECTION_CATALOG", - "CURRENT_DIRECTORY_PROJECT_NAME", - "CUSTOM_CONNECTION_SLUG", - "ChannelKind", - "ChannelMutationResult", - "ConnectionAuthSpec", - "ConnectionCatalogEntry", - "ConnectionInput", - "ConnectionMutationAction", - "ConnectionMutationResult", - "ConnectionProtocol", - "CustomConnectionInput", - "DEFAULT_SLACK_CONNECTOR_SLUG", - "EnsureChannelOptions", - "EnsureConnectionDependenciesOptions", - "EnsureConnectionOptions", - "EnsurePackageDependenciesOptions", - "EnvHeader", - "EvePackageContract", - "HumanAction", - "HumanActionRequiredError", - "McpEndpoint", - "OpenApiEndpoint", - "PackageDependencyMutation", - "SCAFFOLDABLE_CHANNELS", - "SLACK_CHANNEL_DEFAULT_ROUTE", - "SUPPORTED_PROTOCOLS", - "ScaffoldBaseProjectOptions", - "ScaffoldExtensionProjectOptions", - "ScaffoldableChannel", - "SlackConnectorSlug", - "WebAuthentication", - "WebPackageVersions", - "WriteFileExistsError", - "byokProviderEnvVar", - "catalogSlugs", - "deriveSlackConnectorSlug", - "effectiveProtocols", - "endpointForProtocol", - "ensureChannel", - "ensureConnection", - "ensureConnectionDependencies", - "ensurePackageDependencies", - "getCatalogEntry", - "hasVercelHostFramework", - "isEveProject", - "isNextJsProject", - "isValidConnectionSlug", - "listAuthoredChannels", - "listAuthoredConnections", - "normalizeSlackConnectorSlug", - "resolveVercelHostFrameworkPreset", - "scaffoldBaseProject", - "scaffoldExtensionProject" -] diff --git a/packages/eve/public-api/eve/tools.json b/packages/eve/public-api/eve/tools.json index e80ed7f497..e890d6d2e8 100644 --- a/packages/eve/public-api/eve/tools.json +++ b/packages/eve/public-api/eve/tools.json @@ -15,7 +15,6 @@ "DynamicToolSet", "MatchedConnectionResult", "MatchedToolResult", - "SessionContext", "SettledCallRow", "ToolAnswer", "ToolAskOptions", diff --git a/packages/eve/public-api/eve/tools/bash.json b/packages/eve/public-api/eve/tools/bash.json index c8b29e5bac..f36f712f4d 100644 --- a/packages/eve/public-api/eve/tools/bash.json +++ b/packages/eve/public-api/eve/tools/bash.json @@ -1 +1 @@ -["BASH_INPUT_SCHEMA", "BASH_OUTPUT_SCHEMA", "BashToolInput", "BashToolOutput", "bash", "default"] +["BashToolInput", "BashToolOutput", "bash", "default"] diff --git a/packages/eve/public-api/eve/tools/glob.json b/packages/eve/public-api/eve/tools/glob.json index c3b8cc0529..e3dd4d7325 100644 --- a/packages/eve/public-api/eve/tools/glob.json +++ b/packages/eve/public-api/eve/tools/glob.json @@ -1 +1 @@ -["GLOB_INPUT_SCHEMA", "GLOB_OUTPUT_SCHEMA", "GlobToolInput", "GlobToolOutput", "default", "glob"] +["GlobToolInput", "GlobToolOutput", "default", "glob"] diff --git a/packages/eve/public-api/eve/tools/grep.json b/packages/eve/public-api/eve/tools/grep.json index 8c2ff72054..adf95deeba 100644 --- a/packages/eve/public-api/eve/tools/grep.json +++ b/packages/eve/public-api/eve/tools/grep.json @@ -1 +1 @@ -["GREP_INPUT_SCHEMA", "GREP_OUTPUT_SCHEMA", "GrepToolInput", "GrepToolOutput", "default", "grep"] +["GrepToolInput", "GrepToolOutput", "default", "grep"] diff --git a/packages/eve/public-api/eve/tools/read_file.json b/packages/eve/public-api/eve/tools/read_file.json index 134769b940..b4acf14d7a 100644 --- a/packages/eve/public-api/eve/tools/read_file.json +++ b/packages/eve/public-api/eve/tools/read_file.json @@ -1,8 +1 @@ -[ - "READ_FILE_INPUT_SCHEMA", - "READ_FILE_OUTPUT_SCHEMA", - "ReadFileToolInput", - "ReadFileToolOutput", - "default", - "readFile" -] +["ReadFileToolInput", "ReadFileToolOutput", "default", "readFile"] diff --git a/packages/eve/public-api/eve/tools/web_fetch.json b/packages/eve/public-api/eve/tools/web_fetch.json index 45c00980dc..7547cc6599 100644 --- a/packages/eve/public-api/eve/tools/web_fetch.json +++ b/packages/eve/public-api/eve/tools/web_fetch.json @@ -1,8 +1 @@ -[ - "WEB_FETCH_INPUT_SCHEMA", - "WEB_FETCH_OUTPUT_SCHEMA", - "WebFetchToolInput", - "WebFetchToolOutput", - "default", - "webFetch" -] +["WebFetchToolInput", "WebFetchToolOutput", "default", "webFetch"] diff --git a/packages/eve/public-api/eve/tools/web_search.json b/packages/eve/public-api/eve/tools/web_search.json index 78bad46957..b44fcb7c15 100644 --- a/packages/eve/public-api/eve/tools/web_search.json +++ b/packages/eve/public-api/eve/tools/web_search.json @@ -4,7 +4,5 @@ "WebSearchToolDefinition", "WebSearchToolInput", "default", - "defaultWebSearch", - "isWebSearchToolDefinition", "webSearch" ] diff --git a/packages/eve/public-api/eve/tools/write_file.json b/packages/eve/public-api/eve/tools/write_file.json index 12f5f6d0fc..6089684ac3 100644 --- a/packages/eve/public-api/eve/tools/write_file.json +++ b/packages/eve/public-api/eve/tools/write_file.json @@ -1,8 +1 @@ -[ - "WRITE_FILE_INPUT_SCHEMA", - "WRITE_FILE_OUTPUT_SCHEMA", - "WriteFileToolInput", - "WriteFileToolOutput", - "default", - "writeFile" -] +["WriteFileToolInput", "WriteFileToolOutput", "default", "writeFile"] diff --git a/packages/eve/src/channel/auth/definitions.ts b/packages/eve/src/channel/auth/definitions.ts new file mode 100644 index 0000000000..5d6f5ae920 --- /dev/null +++ b/packages/eve/src/channel/auth/definitions.ts @@ -0,0 +1,1286 @@ +/** + * Route-level HTTP authentication primitives. + * + * Strategy helpers return an {@link AuthFn} for route factories. Verifier + * helpers are lower-level pure functions for custom `fetch` handlers. + */ + +import { areTokenClaimMatchersSatisfied } from "#channel/auth/token-claims.js"; +import { decodeJwt } from "#compiled/jose/index.js"; + +import type { SessionAuthContext } from "#channel/types.js"; +import { isEveDevEnvironment } from "#internal/application/dev-environment.js"; +import { createLogger } from "#internal/logging.js"; +import { authenticateHttpBasicStrategy } from "#channel/auth/http-basic.js"; +import { authenticateJwtEcdsaStrategy } from "#channel/auth/jwt-ecdsa.js"; +import { authenticateJwtHmacStrategy } from "#channel/auth/jwt-hmac.js"; +import { authenticateOidcStrategy } from "#channel/auth/oidc.js"; +import { resolveVercelOidcCurrentProject } from "#channel/auth/vercel-oidc-project.js"; +import { + createRuntimeSessionAuthContext, + type ResolvedJwtEcdsaAuthStrategy, + type ResolvedJwtHmacAuthStrategy, + type ResolvedOidcAuthStrategy, + type RouteStrategyAuthenticationResult, +} from "#channel/auth/types.js"; +import { isVercelOidcIssuer } from "#shared/vercel-project.js"; + +const vercelOidcLog = createLogger("auth.vercel-oidc"); +import { + createRuntimeIpAllowList, + isRuntimeIpAllowed, + type RuntimeIpAllowList, +} from "#channel/ip-allow-list.js"; +import { isLoopbackHostname } from "#shared/network-address.js"; + +// --------------------------------------------------------------------------- +// Result types +// --------------------------------------------------------------------------- + +/** + * Result returned by the verifier helpers below. On success, `sessionAuth` + * is a fully-constructed {@link SessionAuthContext}; on failure no detail is + * returned so routes do not leak which credential check failed. + */ +export type VerifyResult = + | { readonly ok: true; readonly sessionAuth: SessionAuthContext } + | { readonly ok: false }; + +// --------------------------------------------------------------------------- +// HTTP Basic +// --------------------------------------------------------------------------- + +/** + * Credentials accepted by {@link verifyHttpBasic}. + */ +export interface HttpBasicCredentials { + readonly username: string; + readonly password: string; +} + +/** + * Verifies an HTTP Basic credential against the supplied username and + * password. Returns `{ ok: true, sessionAuth }` on success or `{ ok: false }` + * on a missing or mismatched credential. The password is compared with + * constant-time hash equality so a timing side channel cannot leak it. Both + * values are normalized to Unicode NFC before comparison, matching the + * `charset="UTF-8"` challenge advertised by {@link httpBasic}. + */ +export function verifyHttpBasic( + authorizationHeader: string | null, + credentials: HttpBasicCredentials, +): VerifyResult { + if (authorizationHeader === null) { + return { ok: false }; + } + + const result = authenticateHttpBasicStrategy({ + authorization: authorizationHeader, + strategy: { + kind: "http-basic", + password: credentials.password, + username: credentials.username, + }, + }); + + if (result.kind !== "authenticated") { + return { ok: false }; + } + + return { + ok: true, + sessionAuth: createRuntimeSessionAuthContext(result.principal), + }; +} + +// --------------------------------------------------------------------------- +// JWT HMAC +// --------------------------------------------------------------------------- + +/** + * Configuration for {@link verifyJwtHmac}. Optional fields have defaults so + * adapters can omit unused matchers. `secret` is read by the adapter from its + * secret store (e.g. `process.env`) and passed in directly. + */ +export interface VerifyJwtHmacConfig { + readonly algorithm: "HS256" | "HS384" | "HS512"; + readonly audiences: readonly string[]; + readonly issuer: string; + readonly secret: string; + /** + * Tolerance in seconds for the `exp` and `nbf` claims. Defaults to 30. + */ + readonly clockSkewSeconds?: number; + /** + * AWS IAM-style `*`-wildcard patterns matched against the token `sub`. + * When supplied, the token is rejected unless one matches. + */ + readonly subjects?: readonly string[]; + /** + * Per-claim membership matcher: each named claim must contain at least one + * of the listed values, or the token is rejected. + */ + readonly claims?: Readonly>; +} + +/** + * Verifies a bearer JWT signed with an HMAC secret. Pass the token without + * the `Bearer ` prefix (see {@link extractBearerToken}). Returns + * `{ ok: true, sessionAuth }` on success or `{ ok: false }` when verification + * fails or the token's claims don't match the supplied matchers. + */ +export async function verifyJwtHmac( + token: string | null, + config: VerifyJwtHmacConfig, +): Promise { + if (token === null || token.length === 0) { + return { ok: false }; + } + + const strategy: ResolvedJwtHmacAuthStrategy = { + algorithm: config.algorithm, + audiences: [...config.audiences], + clockSkewSeconds: config.clockSkewSeconds ?? 30, + issuer: config.issuer, + kind: "jwt-hmac", + secret: config.secret, + claims: config.claims, + subjects: config.subjects, + }; + + const result = await authenticateJwtHmacStrategy({ strategy, token }); + if (result.kind !== "authenticated") { + return { ok: false }; + } + + return { + ok: true, + sessionAuth: createRuntimeSessionAuthContext(result.principal), + }; +} + +// --------------------------------------------------------------------------- +// JWT ECDSA +// --------------------------------------------------------------------------- + +/** + * Configuration for {@link verifyJwtEcdsa}. + */ +export interface VerifyJwtEcdsaConfig { + readonly algorithm: "ES256" | "ES384" | "ES512"; + readonly audiences: readonly string[]; + readonly issuer: string; + /** + * PEM-encoded ECDSA public key (`-----BEGIN PUBLIC KEY-----` ...). + */ + readonly publicKey: string; + /** + * Tolerance in seconds for the `exp` and `nbf` claims. Defaults to 30. + */ + readonly clockSkewSeconds?: number; + /** + * AWS IAM-style `*`-wildcard patterns matched against the token `sub`. + * When supplied, the token is rejected unless one matches. + */ + readonly subjects?: readonly string[]; + /** + * Per-claim membership matcher: each named claim must contain at least one + * of the listed values, or the token is rejected. + */ + readonly claims?: Readonly>; +} + +/** + * Verifies a bearer JWT signed with an ECDSA private key against the supplied + * PEM public key. Pass the token without the `Bearer ` prefix (see + * {@link extractBearerToken}). Returns `{ ok: true, sessionAuth }` on success + * or `{ ok: false }` when verification fails or the token's claims do not + * match the configured `subjects`/`claims`. + */ +export async function verifyJwtEcdsa( + token: string | null, + config: VerifyJwtEcdsaConfig, +): Promise { + if (token === null || token.length === 0) { + return { ok: false }; + } + + const strategy: ResolvedJwtEcdsaAuthStrategy = { + algorithm: config.algorithm, + audiences: [...config.audiences], + clockSkewSeconds: config.clockSkewSeconds ?? 30, + issuer: config.issuer, + kind: "jwt-ecdsa", + publicKey: config.publicKey, + claims: config.claims, + subjects: config.subjects, + }; + + const result = await authenticateJwtEcdsaStrategy({ strategy, token }); + if (result.kind !== "authenticated") { + return { ok: false }; + } + + return { + ok: true, + sessionAuth: createRuntimeSessionAuthContext(result.principal), + }; +} + +// --------------------------------------------------------------------------- +// OIDC +// --------------------------------------------------------------------------- + +/** + * Configuration for {@link verifyOidc}. + */ +export interface VerifyOidcConfig { + readonly audiences: readonly string[]; + readonly issuer: string; + /** + * OIDC discovery URL. Defaults to + * `${issuer}/.well-known/openid-configuration` (any trailing slash on + * `issuer` is stripped first). + */ + readonly discoveryUrl?: string; + /** + * Tolerance in seconds for the `exp` and `nbf` claims. Defaults to 30. + */ + readonly clockSkewSeconds?: number; + /** + * AWS IAM-style `*`-wildcard patterns matched against the token `sub`. + * When supplied, the token is rejected unless one matches. + */ + readonly subjects?: readonly string[]; + /** + * Per-claim membership matcher: each named claim must contain at least one + * of the listed values, or the token is rejected. + */ + readonly claims?: Readonly>; +} + +/** + * Verifies a bearer OIDC token against an issuer's discovery URL. Pass the + * token without the `Bearer ` prefix (see {@link extractBearerToken}). The + * token must satisfy the configured `subjects`/`claims` matchers; on success + * the principal is tagged `principalType: "service"`. Unlike + * {@link verifyVercelOidc}, this never grants the Vercel current-project + * bypass. Returns `{ ok: false }` on failed verification or matchers. + */ +export async function verifyOidc( + token: string | null, + config: VerifyOidcConfig, +): Promise { + const result = await runOidcVerification(token, { + ...config, + acceptCurrentVercelProject: false, + currentVercelProject: undefined, + }); + return result.kind === "authenticated" + ? { ok: true, sessionAuth: createRuntimeSessionAuthContext(result.principal) } + : { ok: false }; +} + +/** + * Runs OIDC token verification against a resolved strategy and returns + * the strategy authenticator's tagged result. Internal helper shared + * between {@link verifyOidc} (public, never grants the Vercel + * current-project upgrade) and {@link verifyVercelOidc} (always opts + * into it). Returning the raw outcome rather than a boolean lets call + * sites log a structured rejection reason. + */ +async function runOidcVerification( + token: string | null, + config: VerifyOidcConfig & { + readonly acceptCurrentVercelProject: boolean; + readonly currentVercelProject: ResolvedOidcAuthStrategy["currentVercelProject"] | undefined; + }, +): Promise { + if (token === null || token.length === 0) { + return { kind: "not-authenticated" }; + } + + const baseStrategy = { + acceptCurrentVercelProject: config.acceptCurrentVercelProject, + audiences: [...config.audiences], + clockSkewSeconds: config.clockSkewSeconds ?? 30, + discoveryUrl: + config.discoveryUrl ?? `${config.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`, + issuer: config.issuer, + kind: "oidc", + claims: config.claims, + subjects: config.subjects, + } satisfies ResolvedOidcAuthStrategy; + const strategy = + config.currentVercelProject === undefined + ? baseStrategy + : { ...baseStrategy, currentVercelProject: config.currentVercelProject }; + + return await authenticateOidcStrategy({ strategy, token }); +} + +// --------------------------------------------------------------------------- +// Bearer token extraction +// --------------------------------------------------------------------------- + +/** + * Extracts the bearer token from an `Authorization: Bearer ` header. + * Returns `null` if the header is missing, the scheme isn't `Bearer`, or the + * value after `Bearer ` is empty. + */ +export function extractBearerToken(authorizationHeader: string | null): string | null { + if (authorizationHeader === null) { + return null; + } + const match = /^Bearer\s+(.+)$/i.exec(authorizationHeader); + const value = match?.[1]?.trim(); + return value === undefined || value.length === 0 ? null : value; +} + +// --------------------------------------------------------------------------- +// IP allowlist +// --------------------------------------------------------------------------- + +/** + * Parsed IP allowlist for repeated checks. Construct once at module load, + * then call {@link isIpAllowed} per request. + */ +export type IpAllowList = RuntimeIpAllowList; + +/** + * Parses exact IP addresses or CIDR blocks into a reusable + * {@link IpAllowList}. Entries are trimmed; throws on an empty entry, a `*` + * wildcard (use exact IP/CIDR syntax), or an invalid address or CIDR prefix. + */ +export function createIpAllowList(entries: readonly string[]): IpAllowList { + return createRuntimeIpAllowList(entries); +} + +/** + * Returns whether `ip` is permitted by `allowList`. `null` always returns + * `false`. Adapters that need to allow unknown IPs should not call this. + */ +export function isIpAllowed(ip: string | null, allowList: IpAllowList): boolean { + if (ip === null) { + return false; + } + return isRuntimeIpAllowed(ip, allowList); +} + +// --------------------------------------------------------------------------- +// Unauthorized response builder +// --------------------------------------------------------------------------- + +/** + * One challenge entry attached to the `www-authenticate` header on a failure + * response. For a Basic+Bearer prompt, pass both: + * `[{ scheme: "Basic", parameters: { realm: "agent" } }, { scheme: "Bearer" }]` + */ +export interface UnauthorizedChallenge { + readonly scheme: "Basic" | "Bearer"; + readonly parameters?: Readonly>; +} + +/** + * Options accepted by {@link createUnauthorizedResponse}. + */ +export interface UnauthorizedResponseOptions { + /** + * HTTP status code. Defaults to `401`. Use `403` for "authenticated but + * not allowed" outcomes. + */ + readonly status?: 401 | 403; + /** + * Machine-readable error code returned in the JSON body. Defaults to + * `"unauthorized"` (401) or `"forbidden"` (403). + */ + readonly code?: string; + /** + * Human-readable error message returned in the JSON body. Defaults to a + * generic prompt. + */ + readonly message?: string; + /** + * Optional `www-authenticate` challenge entries. + */ + readonly challenges?: readonly UnauthorizedChallenge[]; +} + +/** + * Builds a JSON failure response shaped like the framework's other auth + * failures: `cache-control: no-store`, one `www-authenticate` header per + * challenge, and a `{ ok: false, code, error }` body. + */ +export function createUnauthorizedResponse(opts: UnauthorizedResponseOptions = {}): Response { + const status = opts.status ?? 401; + const code = opts.code ?? (status === 403 ? "forbidden" : "unauthorized"); + const message = + opts.message ?? (status === 403 ? "Forbidden." : "Authorization is required for this route."); + const challenges = opts.challenges ?? []; + + const headers = new Headers({ "cache-control": "no-store" }); + for (const challenge of challenges) { + headers.append("www-authenticate", formatChallenge(challenge)); + } + + return Response.json( + { + code, + error: message, + ok: false, + }, + { + headers, + status, + }, + ); +} + +function formatChallenge(challenge: UnauthorizedChallenge): string { + if (challenge.parameters === undefined || Object.keys(challenge.parameters).length === 0) { + return challenge.scheme; + } + const renderedParameters = Object.entries(challenge.parameters) + .map(([key, value]) => `${key}="${escapeAuthChallengeParameter(value)}"`) + .join(", "); + return `${challenge.scheme} ${renderedParameters}`; +} + +/** @internal Escapes a value for an HTTP auth challenge quoted string. */ +export function escapeAuthChallengeParameter(value: string): string { + return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"'); +} + +function isValidOAuthIdentifierUrl(value: string): boolean { + try { + const url = new URL(value); + const secureTransport = + url.protocol === "https:" || (url.protocol === "http:" && isLoopbackHostname(url.hostname)); + return ( + secureTransport && + url.username.length === 0 && + url.password.length === 0 && + url.search.length === 0 && + url.hash.length === 0 + ); + } catch { + return false; + } +} + +function isValidMetadataPath(value: string): boolean { + if (!value.startsWith("/") || value.startsWith("//")) return false; + const url = new URL(value, "https://eve.invalid"); + return url.origin === "https://eve.invalid" && url.search.length === 0 && url.hash.length === 0; +} + +/** + * Options accepted by auth error classes. The class chooses the HTTP status. + */ +export type AuthErrorOptions = Omit; + +/** + * Error thrown by auth callbacks to reject a route with a structured 401 + * response. `routeAuth` catches it and returns its response; other errors + * propagate through the normal channel failure path. + */ +export class UnauthenticatedError extends Error { + readonly response: Response; + + constructor(opts: AuthErrorOptions = {}) { + super(opts.message ?? "Authorization is required for this route."); + this.name = "UnauthenticatedError"; + this.response = createUnauthorizedResponse({ ...opts, status: 401 }); + } +} + +/** + * Error thrown by auth callbacks to reject a route with a structured 403 + * response. `routeAuth` catches it and returns its response; other errors + * propagate through the normal channel failure path. + */ +export class ForbiddenError extends Error { + readonly response: Response; + + constructor(opts: AuthErrorOptions = {}) { + super(opts.message ?? "Forbidden."); + this.name = "ForbiddenError"; + this.response = createUnauthorizedResponse({ ...opts, status: 403 }); + } +} + +export interface AuthResult extends SessionAuthContext { + /** + * Set to true to let this authenticated caller create sessions with tool stubs. + * Callers without this permission cannot supply stubs. + */ + readonly allowToolStubs?: boolean; +} + +/** + * Route auth callback. Returned value semantics inside {@link routeAuth}: + * + * - A {@link SessionAuthContext} accepts the request and halts the walk. + * - `null` or `undefined` skips to the next entry. + * + * If every entry skips (including the empty `[]` case), the walker returns a + * 401. To reject with a specific response, throw an + * {@link UnauthenticatedError} or {@link ForbiddenError}. To accept anonymous + * traffic, include {@link none} as the final entry. + */ +export type AuthFn = ( + event: TEvent, +) => AuthResult | null | undefined | Promise; + +/** + * OAuth protected-resource metadata attached to an inbound auth policy. + * `issuer` is shorthand for a single authorization server. + */ +export type OAuthResourceOptions = { + readonly metadataPath?: string; + readonly resource?: string; + readonly scopes?: readonly string[]; +} & ( + | { + readonly authorizationServers?: never; + readonly issuer: string; + } + | { + readonly authorizationServers: readonly string[]; + readonly issuer?: never; + } +); + +const OAUTH_RESOURCE_SYMBOL = Symbol.for("eve.channels.auth.oauthResource"); + +/** + * An ordinary route authenticator decorated with OAuth protected-resource + * discovery metadata. + */ +export type OAuthResourceAuth = AuthFn & { + readonly [OAUTH_RESOURCE_SYMBOL]: OAuthResourceOptions; +}; + +/** + * Adds OAuth protected-resource metadata to an existing inbound auth policy. + * + * The returned value remains an {@link AuthFn}: it preserves the ordered auth + * walk and can be used by any channel. A protocol-aware channel such as + * `mcpChannel` may additionally publish the metadata and discovery challenge. + */ +export function oauthResource( + auth: AuthFn | readonly AuthFn[], + options: OAuthResourceOptions, +): OAuthResourceAuth { + const authorizationServers = + options.issuer !== undefined ? [options.issuer] : options.authorizationServers; + if ( + authorizationServers.length === 0 || + authorizationServers.some((value) => !isValidOAuthIdentifierUrl(value)) + ) { + throw new Error( + "oauthResource requires at least one HTTPS authorization server URL (HTTP is allowed only on loopback).", + ); + } + if (options.resource !== undefined && !isValidOAuthIdentifierUrl(options.resource)) { + throw new Error( + "oauthResource resource must be an HTTPS URL without credentials, query, or fragment (HTTP is allowed only on loopback).", + ); + } + if (options.metadataPath !== undefined && !isValidMetadataPath(options.metadataPath)) { + throw new Error( + "oauthResource metadataPath must be an absolute path without a host, query, or fragment.", + ); + } + + const list: readonly AuthFn[] = Array.isArray(auth) + ? (auth as readonly AuthFn[]) + : [auth as AuthFn]; + const composed: AuthFn = async (request) => { + for (const fn of list) { + const result = await fn(request); + if (result) return result; + } + return null; + }; + const declaredChallenges = collectDeclaredChallenges(list); + const wrapped = withAuthChallenges( + composed, + declaredChallenges.length > 0 ? declaredChallenges : [{ scheme: "Bearer" }], + ) as OAuthResourceAuth; + Object.defineProperty(wrapped, OAUTH_RESOURCE_SYMBOL, { + enumerable: false, + value: options, + }); + return wrapped; +} + +/** @internal Reads OAuth resource metadata without widening `AuthFn`. */ +export function readOAuthResourceOptions( + auth: AuthFn | readonly AuthFn[], +): OAuthResourceOptions | undefined { + if (Array.isArray(auth)) return undefined; + return (auth as Partial)[OAUTH_RESOURCE_SYMBOL]; +} + +/** + * Symbol-keyed property carrying the `www-authenticate` challenges an + * {@link AuthFn} would satisfy, attached via {@link withAuthChallenges}. + * Reading it back off the function (rather than widening `AuthFn`'s call + * signature) lets {@link routeAuth} discover the right challenge scheme(s) + * for a 401 without every strategy needing to change shape. + */ +const AUTH_CHALLENGES_SYMBOL = Symbol.for("eve.channels.auth.challenges"); + +/** + * Attaches declared `www-authenticate` challenges to an {@link AuthFn} + * without changing its call signature or behavior. {@link routeAuth} reads + * the declared challenges back when every entry in the walk skips, so the + * resulting 401 advertises the scheme(s) the configured strategies actually + * accept (e.g. `Basic` for {@link httpBasic}) instead of a fixed default. + * + * Built-in strategies (`httpBasic`, `jwtHmac`, `jwtEcdsa`, `oidc`, + * `vercelOidc`) already declare their challenge. Use this directly when + * authoring a custom `AuthFn`: + * + * ```ts + * const apiKey = withAuthChallenges( + * (request) => (isValidApiKey(request) ? sessionAuth : null), + * [{ scheme: "Bearer" }], + * ); + * ``` + */ +export function withAuthChallenges( + fn: AuthFn, + challenges: readonly UnauthorizedChallenge[], +): AuthFn { + const wrapped: AuthFn = (event) => fn(event); + Object.defineProperty(wrapped, AUTH_CHALLENGES_SYMBOL, { + value: challenges, + }); + return wrapped; +} + +/** + * Reads the challenges {@link withAuthChallenges} attached to `fn`, or + * `undefined` when `fn` declared none (e.g. {@link none}, {@link localDev}, + * {@link placeholderAuth}, or a custom `AuthFn` that never opted in). + */ +function getDeclaredChallenges( + fn: AuthFn, +): readonly UnauthorizedChallenge[] | undefined { + return (fn as Partial>)[ + AUTH_CHALLENGES_SYMBOL + ]; +} + +/** + * Collects the deduped, ordered set of challenges declared across `list` via + * {@link withAuthChallenges}. Dedupes by the rendered `www-authenticate` + * string so e.g. two `jwtHmac()` entries only contribute one `Bearer`. + */ +function collectDeclaredChallenges( + list: readonly AuthFn[], +): readonly UnauthorizedChallenge[] { + const seenRendered = new Set(); + const challenges: UnauthorizedChallenge[] = []; + for (const fn of list) { + for (const challenge of getDeclaredChallenges(fn) ?? []) { + const rendered = formatChallenge(challenge); + if (seenRendered.has(rendered)) continue; + seenRendered.add(rendered); + challenges.push(challenge); + } + } + return challenges; +} + +/** + * Walks an `AuthFn` (or array) in order against `request`. The first entry + * returning a {@link SessionAuthContext} wins; entries returning `null` or + * `undefined` are skipped. If the walk exhausts without a winner (including + * the empty-array case), returns a 401 {@link createUnauthorizedResponse} + * whose `www-authenticate` challenges are collected from the entries' + * declared schemes (see {@link withAuthChallenges}), falling back to + * `Bearer` when none declared any. + * + * Channel factories that share this resolution policy (e.g. `eveChannel`, or + * a custom `defineChannel` route handler) should call `routeAuth` rather than + * re-implement the walk. + */ +export async function routeAuth( + request: Request, + auth: AuthFn | readonly AuthFn[], +): Promise { + const list: readonly AuthFn[] = Array.isArray(auth) + ? (auth as readonly AuthFn[]) + : [auth as AuthFn]; + + try { + for (const fn of list) { + const result = await fn(request); + if (result) return result; + } + } catch (error) { + if ( + typeof error === "object" && + error !== null && + "response" in error && + error.response instanceof Response + ) { + return error.response; + } + throw error; + } + + const declaredChallenges = collectDeclaredChallenges(list); + const challenges = + declaredChallenges.length > 0 + ? addInvalidBearerTokenError(request, declaredChallenges) + : [{ scheme: "Bearer" } satisfies UnauthorizedChallenge]; + return createUnauthorizedResponse({ + challenges, + }); +} + +/** + * A supplied Bearer credential that every declared Bearer strategy declined + * is invalid, rather than absent. Add the RFC 6750 error only after the whole + * auth walk has exhausted so another Bearer strategy or a fallback such as + * localDev() still has a chance to accept the request. + */ +function addInvalidBearerTokenError( + request: Request, + challenges: readonly UnauthorizedChallenge[], +): readonly UnauthorizedChallenge[] { + const authorization = request.headers.get("authorization"); + if (authorization === null || !/^Bearer(?:\s|$)/i.test(authorization.trim())) { + return challenges; + } + if (!challenges.some((challenge) => challenge.scheme === "Bearer")) { + return challenges; + } + return challenges.map((challenge) => + challenge.scheme !== "Bearer" || challenge.parameters?.error !== undefined + ? challenge + : { + ...challenge, + parameters: { + ...challenge.parameters, + error: "invalid_token", + }, + }, + ); +} + +/** + * Returns an {@link AuthFn} for scaffolded apps that makes unfinished + * production auth fail as an intentional 401 rather than an internal route + * error. Replace it before serving real users: + * + * ```ts + * eveChannel({ auth: [vercelOidc(), localDev(), placeholderAuth()] }); + * ``` + * + * Outside production it returns `null`, so the auth walk keeps the same local + * development behavior as any other skipped entry. + */ +export function placeholderAuth(): AuthFn { + return () => { + if (process.env.VERCEL_ENV !== "production") { + return null; + } + + throw new UnauthenticatedError({ + code: "eve_production_auth_not_configured", + message: + "Production auth is not configured. Replace placeholderAuth() in agent/channels/eve.ts with your app's auth provider.", + }); + }; +} + +/** + * Returns an {@link AuthFn} that accepts any request anonymously, producing + * a synthetic principal with `principalType: "anonymous"`. Use it as the + * final entry in an `auth` array to opt routes into unauthenticated access: + * + * ```ts + * eveChannel({ auth: [none()] }); // every request accepted anonymously + * ``` + * + * The returned `SessionAuthContext` halts the {@link routeAuth} walk, so + * `none()` terminates whatever array it appears in. It ignores its event + * argument, so `TEvent` is inferred from the surrounding array (defaulting + * to `unknown`) and composes with `AuthFn` entries without a type + * argument. + */ +export function none(): AuthFn { + return () => ANONYMOUS_SESSION_AUTH_CONTEXT; +} + +/** + * Returns an {@link AuthFn} that authenticates a synthetic `local-dev` + * principal while the process is a local development server, and returns + * `null` everywhere else so the {@link routeAuth} walk falls through to the + * next entry. + * + * A process counts as a local development server when it is `eve dev` + * (`EVE_DEV=1`) or `vercel dev` (`VERCEL=1` with `VERCEL_ENV=development`). + * This is a property of the deployment, never of the inbound request, so no + * request header (for example `Host`) can flip the decision. A production + * deployment (`eve start`, a Vercel deployment, or any container host) sets + * neither flag, so `localDev()` authenticates nothing there. Layer a real + * authenticator on those hosts. + */ +export function localDev(): AuthFn { + return () => (isLocalDevelopmentServer() ? LOCAL_DEV_SESSION_AUTH_CONTEXT : null); +} + +function isLocalDevelopmentServer(): boolean { + if (process.env.VERCEL && process.env.VERCEL_ENV === "development") { + return true; + } + return isEveDevEnvironment(); +} + +const ANONYMOUS_SESSION_AUTH_CONTEXT: SessionAuthContext = { + attributes: {}, + authenticator: "none", + principalId: "anonymous", + principalType: "anonymous", +}; + +const LOCAL_DEV_SESSION_AUTH_CONTEXT: SessionAuthContext = { + attributes: {}, + authenticator: "local-dev", + principalId: "local-dev", + principalType: "local-dev", +}; + +/** + * Expected prefix for the `aud` claim of a Vercel-minted OIDC token + * (`https://vercel.com/`). Requiring it gives a real audience + * binding: `verifyVercelOidc` otherwise verifies the token only against its + * own `aud` (a tautology), so a token the project minted for a foreign + * federation audience (e.g. AWS STS) — same `project_id`, different `aud` — + * could be replayed against the agent. Foreign audiences lack this prefix and + * are rejected; every owner's Vercel audience carries it, so the cross-project + * `subjects` path is unaffected. + */ +const VERCEL_OIDC_AUDIENCE_PREFIX = "https://vercel.com/"; + +/** + * Options for verifying a Vercel OIDC token with {@link verifyVercelOidc}. + */ +export interface VerifyVercelOidcOptions { + /** + * Explicit current-project binding for the verified token. When omitted, + * the verifier reads `VERCEL_PROJECT_ID` and `VERCEL_TARGET_ENV` / + * `VERCEL_ENV` from the runtime environment. + */ + readonly currentVercelProject?: { + readonly environment?: string; + readonly projectId: string; + }; + /** + * Optional `sub` patterns granting callers access on top of the always-on + * current-project bypass. Patterns use AWS IAM-style `*` wildcards and may + * target tokens minted by other Vercel projects (e.g. + * `"owner:acme:project:partner-agent:environment:*"`). The current project + * is always accepted regardless of this list. + */ + readonly subjects?: readonly string[]; +} + +/** + * Verifies a bearer JWT minted by Vercel OIDC. + * + * Acceptance rule: + * + * - Current-project tokens that reach the service/runtime path are accepted + * regardless of `subjects`, so the deployment's own runtime callers + * (subagent, internal fetches) authenticate without being enumerated. + * - Tokens with an `external_sub` claim authenticate as + * `principalType: "user"` when they match the configured current project + * and environment. `external_sub` + * becomes the eve subject, `external_iss` or `connector_id` the eve issuer + * when present, and string-valued OIDC profile claims (`name`, `picture`, + * `email`) are exposed as auth attributes. + * - Development tokens with a `user_id` claim authenticate as + * `principalType: "user"` only when both the token and configured current + * project environment are `development`. Other same-project target + * environments may use one as a `"service"` principal. + * - Tokens from other Vercel projects are accepted **only** when their `sub` + * matches one of {@link VerifyVercelOidcOptions.subjects}. + * + * Tokens without a `user_id` claim are accepted for the current project in + * production, preview, and development. They are tagged `"runtime"` when the + * token's `environment` matches the current deployment, otherwise `"service"`. + */ +export async function verifyVercelOidc( + token: string | null, + opts: VerifyVercelOidcOptions = {}, +): Promise { + if (token === null || token.length === 0) { + return { ok: false }; + } + + const claims = decodeUnverifiedJwtClaims(token); + if (claims === null) { + vercelOidcLog.debug("Rejected token that failed to decode as a JWT."); + return { ok: false }; + } + + if (!isVercelOidcIssuer(claims.issuer)) { + vercelOidcLog.debug("Rejected token whose issuer is not a Vercel OIDC issuer.", { + issuer: claims.issuer, + }); + return { ok: false }; + } + + if (claims.audiences.length === 0) { + vercelOidcLog.debug("Rejected token with no audience claim.", { issuer: claims.issuer }); + return { ok: false }; + } + + // Bind the audience: the token must carry a Vercel audience. The verifier + // below otherwise checks the token's `aud` against itself (a tautology), so + // without this a token minted for a foreign federation audience would still + // authenticate on a matching `project_id`. + if (!claims.audiences.some((audience) => audience.startsWith(VERCEL_OIDC_AUDIENCE_PREFIX))) { + vercelOidcLog.debug("Rejected token whose audience is not a Vercel audience.", { + audiences: claims.audiences, + issuer: claims.issuer, + }); + return { ok: false }; + } + + // `acceptCurrentVercelProject: true` activates the same-project bypass + // inside the OIDC verifier so any token minted for the configured project + // is accepted regardless of `subjects`. The supplied `subjects` + // matcher (defaulting to an empty list that matches nothing) + // determines which **other** callers are allowed in. + const currentVercelProject = resolveCurrentVercelProject(opts); + const result = await runOidcVerification(token, { + acceptCurrentVercelProject: true, + audiences: claims.audiences, + currentVercelProject, + issuer: claims.issuer, + subjects: opts.subjects ?? [], + }); + + if (result.kind === "authenticated") { + vercelOidcLog.debug("Accepted Vercel OIDC token.", { + issuer: claims.issuer, + principalType: result.principal.principalType, + subject: result.principal.subject, + }); + return { ok: true, sessionAuth: createRuntimeSessionAuthContext(result.principal) }; + } + + vercelOidcLog.debug("Rejected Vercel OIDC token after verification.", { + audiences: claims.audiences, + issuer: claims.issuer, + reason: result.kind, + subjectsConfigured: (opts.subjects ?? []).length > 0, + detail: result.kind === "misconfigured" ? result.message : undefined, + }); + return { ok: false }; +} + +function resolveCurrentVercelProject( + opts: VerifyVercelOidcOptions, +): NonNullable | undefined { + if (opts.currentVercelProject !== undefined) return opts.currentVercelProject; + + const projectId = process.env.VERCEL_PROJECT_ID?.trim(); + if (projectId === undefined || projectId.length === 0) return undefined; + + const environment = process.env.VERCEL_TARGET_ENV?.trim() || process.env.VERCEL_ENV?.trim(); + return environment === undefined || environment.length === 0 + ? { projectId } + : { environment, projectId }; +} + +/** + * Allowed values for {@link VercelSubjectInput.environment}. Use `"*"` + * to match any of `production`, `preview`, and `development` for the + * named project. + */ +export type VercelSubjectEnvironment = "production" | "preview" | "development" | "*"; + +/** + * Strict input shape accepted by {@link vercelSubject}. + * + * The `sub` claim carries human-readable slugs/names, not IDs. Stable + * project and team IDs (`prj_...`, `team_...`) live on the separate + * `project_id` / `owner_id` claims, which `verifyVercelOidc` matches + * against `VERCEL_PROJECT_ID` for the current-project bind. This helper + * composes a `sub` matcher, so its inputs are slugs. + */ +export interface VercelSubjectInput { + /** + * Vercel team slug (a.k.a. "owner"; e.g. `"acme"`) as embedded in the + * `sub` claim, **not** the stable team ID `team_...` (`VERCEL_TEAM_ID`). + * + * Must not contain `*` or `:` so authors cannot widen the matcher to + * "any team with a project of this name". Hand-write the subject string + * when cross-team federation is intentional. + */ + readonly teamSlug: string; + /** + * Vercel project name (e.g. `"acme_website"`) as embedded in the `sub` + * claim, **not** the stable project ID `prj_...` (`VERCEL_PROJECT_ID`). + * Same constraints as {@link teamSlug}. + */ + readonly projectName: string; + /** + * Vercel deployment environment, or `"*"` to match any environment for + * the named project. Defaults to `"production"` so an unspecified + * environment cannot silently accept preview/development tokens. + */ + readonly environment?: VercelSubjectEnvironment; +} + +/** + * Builds a Vercel OIDC `sub` matcher pattern from a typed input. + * + * Vercel-issued tokens carry a `sub` of the form + * `owner:[TEAM_SLUG]:project:[PROJECT_NAME]:environment:[ENVIRONMENT]`. + * Hand-writing it invites two foot-guns: a misspelled slug (silently + * rejecting all callers) and over-broad wildcards (silently accepting + * unrelated callers). This helper rejects malformed inputs at construction + * time and forces an explicit `environment`. + * + * Use it inside {@link VerifyVercelOidcOptions.subjects} to layer + * additional Vercel-project callers on top of the current-project bypass: + * + * ```ts + * vercelOidc({ + * subjects: [vercelSubject({ teamSlug: "partner", projectName: "data" })], + * }); + * ``` + * + * The `sub` claim flips when a team or project name changes, so any policy + * built on this helper must update with it; the stable team/project IDs are + * not exposed in `sub`. See Vercel's OIDC reference for the token anatomy. + */ +export function vercelSubject(input: VercelSubjectInput): string { + assertVercelSubjectSegment("teamSlug", input.teamSlug); + assertVercelSubjectSegment("projectName", input.projectName); + const environment = input.environment ?? "production"; + if ( + environment !== "production" && + environment !== "preview" && + environment !== "development" && + environment !== "*" + ) { + throw new Error( + `vercelSubject: invalid environment ${JSON.stringify(environment)}; expected "production", "preview", "development", or "*".`, + ); + } + return `owner:${input.teamSlug}:project:${input.projectName}:environment:${environment}`; +} + +function assertVercelSubjectSegment(field: "teamSlug" | "projectName", value: string): void { + if (value.length === 0) { + throw new Error(`vercelSubject: ${field} must be a non-empty string.`); + } + if (value.includes("*") || value.includes(":")) { + throw new Error( + `vercelSubject: ${field} ${JSON.stringify(value)} may not contain ${value.includes(":") ? "':'" : "'*'"}. Hand-write the subject string when wildcards are intentional.`, + ); + } +} + +export interface VercelOidcOptions extends Omit { + /** + * Additional token subjects allowed to authenticate. + * To allow stubs, use { subject, allowToolStubs: true } for that subject. + */ + readonly subjects?: readonly ( + | string + | { + readonly subject: string; + readonly allowToolStubs?: boolean; + } + )[]; +} + +/** + * Returns an HTTP route auth callback backed by Vercel OIDC. See + * {@link verifyVercelOidc} for the always-on current-project bypass and how + * `subjects` extends acceptance to other Vercel projects. Declares a + * `Bearer` {@link withAuthChallenges} challenge for {@link routeAuth}'s 401. + */ +export function vercelOidc(opts: VercelOidcOptions = {}): AuthFn { + const { subjects, ...verification } = opts; + const verifyOptions = { + ...verification, + subjects: subjects?.map((entry) => (typeof entry === "string" ? entry : entry.subject)), + }; + return withAuthChallenges( + async (request) => { + const token = extractBearerToken(request.headers.get("authorization")); + const currentVercelProject = + opts.currentVercelProject ?? + (token !== null && isLocalDevelopmentVercelOidc() + ? await resolveVercelOidcCurrentProject(request) + : undefined); + const result = await verifyVercelOidc( + token, + currentVercelProject === undefined + ? verifyOptions + : { ...verifyOptions, currentVercelProject }, + ); + if (!result.ok) return null; + const auth = result.sessionAuth; + const allowed = + (auth.principalType === "service" || auth.principalType === "runtime") && + subjects?.some( + (entry) => + typeof entry !== "string" && + entry.allowToolStubs === true && + areTokenClaimMatchersSatisfied({ sub: auth.subject }, { subjects: [entry.subject] }), + ); + return allowed ? { ...auth, allowToolStubs: true } : auth; + }, + [{ scheme: "Bearer" }], + ); +} + +function isLocalDevelopmentVercelOidc(): boolean { + if (process.env.VERCEL_ENV === "development") return true; + if (process.env.VERCEL_ENV === "preview" || process.env.VERCEL_ENV === "production") { + return false; + } + return isEveDevEnvironment(); +} + +/** + * Options accepted by {@link httpBasic}. + */ +export interface HttpBasicAuthOptions { + /** + * Optional `realm` parameter advertised on the `WWW-Authenticate: Basic` + * challenge (e.g. browsers show it in the native login prompt). Defaults + * to `"eve"`. + */ + readonly realm?: string; +} + +/** + * Returns an {@link AuthFn} that verifies HTTP Basic credentials via + * {@link verifyHttpBasic}. Declares a `Basic` {@link withAuthChallenges} + * challenge so a {@link routeAuth} 401 advertises `WWW-Authenticate: Basic` + * (with `realm` first, when given, then `charset="UTF-8"`) instead of the + * `Bearer` fallback. + */ +export function httpBasic( + credentials: HttpBasicCredentials, + options?: HttpBasicAuthOptions, +): AuthFn { + const parameters: Record = { + realm: options?.realm ?? "eve", + charset: "UTF-8", + }; + return withAuthChallenges( + (request) => { + const result = verifyHttpBasic(request.headers.get("authorization"), credentials); + return result.ok ? result.sessionAuth : null; + }, + [ + { + parameters, + scheme: "Basic", + }, + ], + ); +} + +/** + * Returns an {@link AuthFn} that verifies an HMAC-signed bearer JWT via + * {@link verifyJwtHmac}. Declares a `Bearer` {@link withAuthChallenges} + * challenge for {@link routeAuth}'s 401. + */ +export function jwtHmac(config: VerifyJwtHmacConfig): AuthFn { + return withAuthChallenges( + async (request) => { + const token = extractBearerToken(request.headers.get("authorization")); + const result = await verifyJwtHmac(token, config); + return result.ok ? result.sessionAuth : null; + }, + [{ scheme: "Bearer" }], + ); +} + +/** + * Returns an {@link AuthFn} that verifies an ECDSA-signed bearer JWT via + * {@link verifyJwtEcdsa}. Declares a `Bearer` {@link withAuthChallenges} + * challenge for {@link routeAuth}'s 401. + */ +export function jwtEcdsa(config: VerifyJwtEcdsaConfig): AuthFn { + return withAuthChallenges( + async (request) => { + const token = extractBearerToken(request.headers.get("authorization")); + const result = await verifyJwtEcdsa(token, config); + return result.ok ? result.sessionAuth : null; + }, + [{ scheme: "Bearer" }], + ); +} + +/** + * Returns an {@link AuthFn} that verifies an OIDC bearer token on the inbound + * request via {@link verifyOidc}. Use {@link vercelOidc} instead for + * Vercel-issued tokens: it preconfigures the issuer, audience, and runtime + * principal flag. Declares a `Bearer` {@link withAuthChallenges} challenge + * for {@link routeAuth}'s 401. + */ +export function oidc(config: VerifyOidcConfig): AuthFn { + return withAuthChallenges( + async (request) => { + const token = extractBearerToken(request.headers.get("authorization")); + const result = await verifyOidc(token, config); + return result.ok ? result.sessionAuth : null; + }, + [{ scheme: "Bearer" }], + ); +} + +// --------------------------------------------------------------------------- +// Internals +// --------------------------------------------------------------------------- + +/** + * Decodes a bearer JWT into its issuer and audience(s) without verifying + * the signature. Returns `null` for malformed tokens or tokens missing + * the `iss` claim. + * + * Used by {@link verifyVercelOidc} to discover the team-scoped issuer and + * audience baked into Vercel-issued tokens before running full + * verification. + */ +function decodeUnverifiedJwtClaims( + token: string, +): { readonly issuer: string; readonly audiences: readonly string[] } | null { + let payload: ReturnType; + try { + payload = decodeJwt(token); + } catch { + return null; + } + + if (typeof payload.iss !== "string" || payload.iss.length === 0) { + return null; + } + + const audiences = + typeof payload.aud === "string" + ? [payload.aud] + : Array.isArray(payload.aud) + ? payload.aud.filter((value): value is string => typeof value === "string") + : []; + + return { audiences, issuer: payload.iss }; +} diff --git a/packages/eve/src/channel/channel-operations.ts b/packages/eve/src/channel/channel-operations.ts index 6b2445e7aa..b28a57453d 100644 --- a/packages/eve/src/channel/channel-operations.ts +++ b/packages/eve/src/channel/channel-operations.ts @@ -8,7 +8,6 @@ import type { Session } from "#channel/session.js"; import type { Runtime, SessionAuthContext, - SessionCallback, TurnPolicy, SessionControlOptions, } from "#channel/types.js"; @@ -18,9 +17,7 @@ import { attachInputText, readInputText } from "#internal/input-text.js"; interface BaseChannelSendOptions { readonly auth: SessionAuthContext | null; - readonly callback?: SessionCallback; readonly context?: readonly string[]; - readonly initiatorAuth?: SessionAuthContext | null; /** JSON Schema for a structured final answer, as on `Session.send()`. */ readonly outputSchema?: JsonObject; readonly title?: string; diff --git a/packages/eve/src/channel/cors.test.ts b/packages/eve/src/channel/cors.test.ts new file mode 100644 index 0000000000..7f07295a2c --- /dev/null +++ b/packages/eve/src/channel/cors.test.ts @@ -0,0 +1,13 @@ +import { expect, it } from "vitest"; +import { normalizeChannelCors } from "#channel/cors.js"; + +it.each(["https://client.example", ["https://client.example"]])( + "normalizes a declared origin to the compiled origin list: %j", + (origin) => { + expect(normalizeChannelCors({ origin })).toEqual({ origin: ["https://client.example"] }); + }, +); + +it("rejects an empty single origin rather than compiling an unusable policy", () => { + expect(() => normalizeChannelCors({ origin: "" })).toThrow(/non-empty origin/); +}); diff --git a/packages/eve/src/channel/cors.ts b/packages/eve/src/channel/cors.ts index 2d9665a77d..55d9ec9d3c 100644 --- a/packages/eve/src/channel/cors.ts +++ b/packages/eve/src/channel/cors.ts @@ -7,9 +7,9 @@ export interface ChannelCorsOptions { /** * Allowed request origins. Omit or pass `"*"` for all origins, pass - * `"null"` for the literal `null` origin, or pass an array of exact origins. + * `"null"` for the literal `null` origin, or pass one exact origin or an array of exact origins. */ - readonly origin?: "*" | "null" | readonly string[]; + readonly origin?: string | readonly string[]; /** Methods emitted on preflight responses. Omit or pass `"*"` for all methods. */ readonly methods?: "*" | readonly string[]; /** @@ -97,8 +97,11 @@ function normalizeOrigin(origin: ChannelCorsOptions["origin"]): "*" | "null" | r return origin; } + if (typeof origin === "string" && origin.length > 0) return [origin]; if (!Array.isArray(origin)) { - throw new Error('Expected channel cors.origin to be "*", "null", or an array of origins.'); + throw new Error( + "Expected channel cors.origin to be a non-empty origin string or an array of origins.", + ); } return origin.map((entry) => { diff --git a/packages/eve/src/channel/pending-authorization.ts b/packages/eve/src/channel/pending-authorization.ts index 571640faaf..458412f7ab 100644 --- a/packages/eve/src/channel/pending-authorization.ts +++ b/packages/eve/src/channel/pending-authorization.ts @@ -9,11 +9,11 @@ const PENDING_AUTHORIZATION_WINDOW = 500; /** The latest matching sign-in still open in the recent durable stream. */ export async function findPendingAuthorization( - session: Pick, + session: Pick, matches: (signIn: SignIn) => boolean, ): Promise { const view = emptySessionView(); - for await (const event of await session.getEventStream({ + for await (const event of session.stream({ follow: false, startIndex: -PENDING_AUTHORIZATION_WINDOW, })) { diff --git a/packages/eve/src/channel/session.test.ts b/packages/eve/src/channel/session.test.ts index 8792b5f89d..88321afb36 100644 --- a/packages/eve/src/channel/session.test.ts +++ b/packages/eve/src/channel/session.test.ts @@ -58,7 +58,8 @@ describe("createSession#cancel", () => { const runtime = createRuntime(); const session = createSession("sess_1", runtime); - await expect(session.cancel()).resolves.toEqual({ sessionId: "sess_1", status: "accepted" }); + const receipt = await session.cancel(); + expect(receipt).toMatchObject({ sessionId: "sess_1", deliveryId: expect.any(String) }); expect(runtime.dispatchSession).toHaveBeenCalledWith({ command: { deliveryId: expect.any(String), kind: "cancel" }, sessionId: "sess_1", @@ -81,7 +82,8 @@ describe("createSession#cancel", () => { const runtime = createRuntime(); const session = createAttachSessionFn(runtime)("sess_2"); - await expect(session.cancel()).resolves.toEqual({ sessionId: "sess_2", status: "accepted" }); + const receipt = await session.cancel(); + expect(receipt).toMatchObject({ sessionId: "sess_2", deliveryId: expect.any(String) }); expect(runtime.dispatchSession).toHaveBeenCalledWith({ command: { deliveryId: expect.any(String), kind: "cancel" }, sessionId: "sess_2", @@ -102,6 +104,7 @@ describe("fixed session operations", () => { expect(runtime.dispatchSession).toHaveBeenCalledWith({ command: { auth: null, + delivery: expect.objectContaining({ deliveryId: expect.any(String) }), kind: "send", payload: expect.objectContaining({ context: ["durable"], @@ -109,6 +112,7 @@ describe("fixed session operations", () => { }), requestId: undefined, turnPolicy: "steer", + title: undefined, }, sessionId: "sess_1", }); @@ -153,16 +157,19 @@ describe("fixed session operations", () => { expect(runtime.dispatchSession).toHaveBeenNthCalledWith(1, { command: { auth: null, + delivery: expect.objectContaining({ deliveryId: expect.any(String) }), kind: "send", payload: { message: "hello" }, requestId: "req_1", turnPolicy: "steer", + title: undefined, }, sessionId: "sess_1", }); expect(runtime.dispatchSession).toHaveBeenNthCalledWith(2, { command: { auth: null, + delivery: expect.objectContaining({ deliveryId: expect.any(String) }), kind: "send", payload: { answers: [{ optionId: "approve", interactionId: "request_1" }] }, requestId: "req_1", @@ -183,7 +190,7 @@ describe("fixed session operations", () => { }); }); - it("maps the public callback abstraction onto internal turn routing", async () => { + it("maps internal callback metadata onto turn routing", async () => { const runtime = createRuntime(); const session = createSession("sess_1", runtime); const callback = { @@ -193,7 +200,8 @@ describe("fixed session operations", () => { url: "https://caller.example.com/eve/v1/callback/callback-token", }; - await session.send("continue", { auth: null, callback }); + const options = { auth: null, callback }; + await session.send("continue", options); const invalidSend = async () => { // @ts-expect-error runtime TurnCaller routing is not public session input. await session.send("continue", { auth: null, caller: {} }); @@ -203,6 +211,7 @@ describe("fixed session operations", () => { expect(runtime.dispatchSession).toHaveBeenCalledWith({ command: { auth: null, + delivery: expect.objectContaining({ deliveryId: expect.any(String) }), caller: { callId: "call_1", replyTo: { kind: "callback", token: callback.token, url: callback.url }, @@ -213,6 +222,7 @@ describe("fixed session operations", () => { payload: { message: "continue" }, requestId: undefined, turnPolicy: "steer", + title: undefined, }, sessionId: "sess_1", }); diff --git a/packages/eve/src/channel/session.ts b/packages/eve/src/channel/session.ts index 76d9208787..3c77037710 100644 --- a/packages/eve/src/channel/session.ts +++ b/packages/eve/src/channel/session.ts @@ -1,7 +1,7 @@ +import type { RemoteChildBinding } from "#execution/child-binding.js"; import type { SessionOperations } from "#shared/session.js"; export type { SessionOperations } from "#shared/session.js"; import { commandDelivery } from "#channel/delivery.js"; -import type { RemoteChildBinding } from "#execution/child-binding.js"; import type { SessionStreamEvent } from "#protocol/session-event.js"; import type { ContextAccessor } from "#context/key.js"; import { @@ -40,18 +40,21 @@ export interface Session extends SessionOperations { * the current tail without inspecting or changing the session. */ stream(options?: SessionStreamOptions): AsyncIterable; - getEventStream(options?: SessionStreamOptions): Promise>; - /** The session's stored lines from `startIndex`: one parsed record per line. Follows like `getEventStream`. */ - getLineStream(options?: SessionStreamOptions): Promise>; - /** Where a remote child of this session runs; read only by the parent's stream proxy. */ +} + +export interface InternalSession extends Session { getChildBinding?(childSessionId: string): Promise; + getLineStream(options?: SessionStreamOptions): Promise>; getStreamTailIndex(): Promise; } +/** HTTP framing and conformance readers operate on framework-created handles. */ +export function internalSession(session: Session): InternalSession { + return session as InternalSession; +} + interface SessionDeliveryOptions { readonly auth: SessionAuthContext | null; - /** Public callback destination for a delegated continuation turn. */ - readonly callback?: SessionCallback; readonly context?: readonly string[]; readonly outputSchema?: JsonObject; } @@ -66,6 +69,10 @@ export type SessionSendOptions = SessionDeliveryOptions & { /** Options for answering pending input requests through a fixed session handle. */ export type SessionRespondOptions = SessionDeliveryOptions; +interface InternalSessionDeliveryOptions extends SessionDeliveryOptions { + readonly callback?: SessionCallback; +} + /** * Live handle to the current session, exposed on `ctx.session` to * `deliver` and event handlers. The framework hydrates the read-only @@ -87,12 +94,14 @@ export function createSession( id: string, runtime: Runtime, metadata: Partial & { readonly turnPolicy?: TurnPolicy } = {}, -): Session { +): InternalSession { return { id, async send(message, options) { const delivery = createDelivery(metadata); - const caller = sessionCallbackToTurnCaller(options.callback); + const caller = sessionCallbackToTurnCaller( + (options as InternalSessionDeliveryOptions).callback, + ); const payload = attachClientContext<{ context?: readonly string[]; message: MessageContent | undefined; @@ -120,7 +129,9 @@ export function createSession( throw new Error("respond() requires at least one answer."); } const validatedAnswers = parseAnswers(answers); - const caller = sessionCallbackToTurnCaller(options.callback); + const caller = sessionCallbackToTurnCaller( + (options as InternalSessionDeliveryOptions).callback, + ); const delivery = createDelivery(metadata); const payload = attachClientContext<{ context?: readonly string[]; @@ -165,9 +176,6 @@ export function createSession( async *stream(options?: SessionStreamOptions) { yield* await runtime.getEventStream(id, options); }, - async getEventStream(options?: SessionStreamOptions) { - return runtime.getEventStream(id, options); - }, async getLineStream(options?: SessionStreamOptions) { return runtime.getLineStream(id, options); }, diff --git a/packages/eve/src/cli/dev/tui/runner.ts b/packages/eve/src/cli/dev/tui/runner.ts index 28359e6782..94f72de10e 100644 --- a/packages/eve/src/cli/dev/tui/runner.ts +++ b/packages/eve/src/cli/dev/tui/runner.ts @@ -14,13 +14,9 @@ import { isTerminalToolCallPart } from "./terminal-tool-part.js"; import { isTaskAdmissionRefused } from "#protocol/task-tools.js"; import type { SendTurnPayload } from "#client/types.js"; import type { ModelAccessChange } from "#shared/model-connection.js"; -import type { - AgentInfoResult, - Client, - InteractionOption, - Answer, - SessionStreamEvent, -} from "#client/index.js"; +import type { AgentInfoResult, Client, SessionStreamEvent } from "#client/index.js"; +import type { InteractionOption } from "#protocol/session-events/families/interaction.js"; +import type { Answer } from "#shared/input.js"; import { renderApplicationInfo } from "#cli/commands/info.js"; import type { EveCliSetupStepEvent, EveCliSetupTerminalEvent } from "#cli/telemetry/index.js"; import type { OnboardingScreenEvent } from "./setup-commands.js"; @@ -493,7 +489,7 @@ export class EveTUIRunner { this.#renderer = createRenderer(options); this.#store = new EveAgentStore({ client: options.client, - followSubagents: true, + followAgents: true, }); this.#store.setCallbacks({ onEvent: (event) => this.#recordDiagnostics(event), diff --git a/packages/eve/src/client/eve-agent-store-state.ts b/packages/eve/src/client/eve-agent-store-state.ts index 8ee37f509d..18932f3c39 100644 --- a/packages/eve/src/client/eve-agent-store-state.ts +++ b/packages/eve/src/client/eve-agent-store-state.ts @@ -61,7 +61,7 @@ export interface EveAgentStoreInit { * Follow each agent tool's session into `agents` while the store streams its session or a * framework binding has it mounted. */ - readonly followSubagents?: boolean; + readonly followAgents?: boolean; /** * Keep the session stream open while the session is idle, so the store sees events that arrive * between turns from schedules, channels, or other clients. When false, the store closes the diff --git a/packages/eve/src/client/eve-agent-store.ts b/packages/eve/src/client/eve-agent-store.ts index 01ba8d8a7c..f8d41466ca 100644 --- a/packages/eve/src/client/eve-agent-store.ts +++ b/packages/eve/src/client/eve-agent-store.ts @@ -66,7 +66,7 @@ export class EveAgentStore { constructor(init: EveAgentStoreInit) { this.#autoPrewarm = init.prewarm ?? false; - this.#followChildStreams = init.followSubagents ?? false; + this.#followChildStreams = init.followAgents ?? false; this.#followIdle = init.followIdle ?? true; this.#externalSession = init.session !== undefined; this.#client = this.#externalSession @@ -324,7 +324,7 @@ export class EveAgentStore { [attachStore](): void { this.#attached = true; - this.#followSubagents(); + this.#followAgents(); if (this.#autoPrewarm && this.#session === undefined) void this.prewarm().catch(() => {}); } @@ -341,7 +341,7 @@ export class EveAgentStore { this.#conversationClient.reset(); this.#messageSubmissions.reset(); this.#error = undefined; - if (this.#attached) this.#followSubagents(); + if (this.#attached) this.#followAgents(); this.#callbacks.onSessionChange?.(this.#session?.state); this.#publish(); if (this.#autoPrewarm && this.#attached) void this.prewarm().catch(() => {}); @@ -380,7 +380,7 @@ export class EveAgentStore { this.#session = session; this.#error = undefined; this.#callbacks.onSessionChange?.(session.state); - this.#followSubagents(); + this.#followAgents(); this.#publish(); return response; })(); @@ -420,7 +420,7 @@ export class EveAgentStore { ): SessionEventStream { if (this.#session === undefined) throw new Error("A session is required before opening its stream."); - this.#followSubagents(); + this.#followAgents(); const lifetime = this.#controller; return this.#conversationClient.stream(this.#session, { ...options, @@ -433,7 +433,7 @@ export class EveAgentStore { }); } - #followSubagents(): void { + #followAgents(): void { if (this.#followChildStreams && this.#session && !this.#conversationClient.following) this.#conversationClient.follow(this.#session, this.#events); } diff --git a/packages/eve/src/client/index.ts b/packages/eve/src/client/index.ts index 8ffe333b36..d14072d56a 100644 --- a/packages/eve/src/client/index.ts +++ b/packages/eve/src/client/index.ts @@ -20,7 +20,6 @@ export type { ConversationAgentSession, ConversationState, } from "#client/conversation-state.js"; -export { createDataUrlFilePart, createTextWithFileContent } from "#client/file-parts.js"; export type { MessageContent, MessageFilePart, @@ -67,9 +66,6 @@ export type { AgentInfoSubagentEntry, AgentInfoToolEntry, AgentInfoTools, - CancelSessionResult, - ClearResult, - CompactResult, ClientAuth, ClientOptions, ClientRedirectPolicy, @@ -78,8 +74,6 @@ export type { HealthResult, DeliveryResult, RespondTurnOptions, - ResetResult, - ResolvedStreamReconnectPolicy, SendTurnInput, SendTurnOptions, SessionSnapshot, @@ -98,27 +92,12 @@ export type { EveToolPart, } from "#client/message-types.js"; -// --------------------------------------------------------------------------- -// Stream event types (re-exported so consumers can type-narrow without -// importing from the main package). -// --------------------------------------------------------------------------- - -export type { - TraceContext, - SessionPredecessor, -} from "#protocol/session-events/families/session.js"; export type { SessionEvent, SessionEventMeta, SessionStreamEvent, } from "#protocol/session-event.js"; -export type * from "#protocol/session-events/index.js"; export { endsTurn, failureOf } from "#client/session-utils.js"; -export type { SessionView, SessionPreviews } from "#protocol/session-projection/tables.js"; -export type { Answer } from "#shared/input.js"; -export type { Interaction, AskedCall } from "#protocol/session-reader.js"; -export { signInEnding } from "#protocol/session-reader.js"; +export type { AskedCall } from "#protocol/session-reader.js"; export { isAnswer, parseAnswer, parseAnswers } from "#shared/input.js"; - -export type { ToolStub, ToolStubOutcome } from "#tool-stubs/types.js"; diff --git a/packages/eve/src/compiler/extension-compatibility.ts b/packages/eve/src/compiler/extension-compatibility.ts index f2f35f1338..a08b432b87 100644 --- a/packages/eve/src/compiler/extension-compatibility.ts +++ b/packages/eve/src/compiler/extension-compatibility.ts @@ -22,302 +22,299 @@ interface ExtensionCapabilityContract { const EXTENSION_CAPABILITY_CONTRACTS = { extension: { current: 1, supported: [1], dropped: {} }, tool: { - current: 94, - supported: [94], + current: 93, + supported: [93], dropped: { - 1: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 2: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 3: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 4: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 5: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 6: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 7: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 8: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 9: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 10: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 11: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 12: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 13: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 14: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 15: "TaskExec replaces stageEffect with send", - 16: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 17: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 18: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 19: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 20: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 21: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 22: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 23: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 24: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 25: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 26: "Background tools now use task yield descriptors", - 27: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 28: "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", - 29: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 30: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 31: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 32: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 33: "ctx.agent now accepts the subagent name as its first argument, derives invocation identity internally, and infers structured output types", - 34: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 35: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 36: "experimental_workflow and eve/tools/workflow were removed; migrate to the workflow factory from eve/tools/workflow", - 37: "experimental_workflow and eve/tools/workflow were removed; migrate to the workflow factory from eve/tools/workflow", - 38: "experimental_workflow and eve/tools/workflow were removed; migrate to the workflow factory from eve/tools/workflow", - 39: "experimental_workflow and eve/tools/workflow were removed; migrate to the workflow factory from eve/tools/workflow", - 40: "runWorkflowProgram was made internal; use the workflow factory from eve/tools/workflow", - 41: "workflow no longer accepts agents and its options argument is optional; use workflow() or workflow({ maxSubagents })", - 42: "Legacy session history migration was removed; user-role messages require current provenance kinds.", - 43: "Legacy session history migration was removed; user-role messages require current provenance kinds.", - 44: "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", - 45: "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", - 46: "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", - 47: "eve/experimental/evaluate was removed; import decide from eve/ai", - 48: "eve/experimental/evaluate was removed; import decide from eve/ai", - 49: "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", - 50: "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", - 51: "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", - 52: "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", - 53: "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", - 54: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 55: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 56: "SandboxSession no longer exposes setNetworkPolicy; pass the configured environment to ctx.getSandbox(environment) to access network policy capabilities", - 57: "agentRouter() no longer accepts a model-facing outputSchema; structured subagent output is requested only through ctx.agent().", - 58: "Workflow tools no longer accept execution or return background task receipts; every call blocks until it settles.", - 59: "Workflow tools no longer accept execution or return background task receipts, and every call blocks until it settles. ctx.ask() no longer accepts dismissible and reports a withdrawn request as cancelled, workflow tool contexts carry interruptSignal, and ctx.agent(name) returns a session handle whose send() delivers a message and whose result() resolves the turn; ctx.agent(name, { message, agentId }) and AgentInput were removed. A ctx.ask() question parks the open turn with turn.waiting instead of ending it.", - 60: "Workflow tools define exactly one of execute(input, ctx), task(input, ctx), or serve(receive, ctx): a task() call returns a receipt and runs as a task, a serve() call reaches a resumable task through receive() and its model input gains taskId, and stream events gained task.started and task.settled. Agent tools are serve() tools with taskId in place of agentId, and the subagent.* stream events were removed. ctx.interruptSignal and WorkflowTaskContext were removed: a steering message aborts the abortSignal of an execute call the turn waits on.", - 61: "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", - 62: "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", - 63: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 64: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 65: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 66: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 67: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 68: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 69: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 70: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 71: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 72: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 73: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 74: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 75: "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", - 76: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 77: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 78: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 79: "Sandbox provider handle hooks were renamed to onSandboxStop and onSandboxDelete.", - 80: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 81: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 82: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 83: 'webSearch provider "openai" was replaced by "native" before it shipped in a release', - 84: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 85: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 86: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 87: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 88: "toolResultFrom takes a settled call's row, such as ctx.view.calls[event.data.callId], instead of a v26 action result; rebuild the extension", - 89: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 90: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 91: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 92: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 93: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", + "1": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "2": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "3": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "4": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "5": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "6": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "7": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "8": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "9": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "10": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "11": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "12": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "13": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "14": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "15": "TaskExec replaces stageEffect with send", + "16": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "17": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "18": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "19": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "20": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "21": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "22": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "23": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "24": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "25": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "26": "Background tools now use task yield descriptors", + "27": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "28": "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", + "29": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "30": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "31": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "32": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "33": "ctx.agent now accepts the subagent name as its first argument, derives invocation identity internally, and infers structured output types", + "34": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "35": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "36": "experimental_workflow and eve/tools/workflow were removed; migrate to the workflow factory from eve/tools/workflow", + "37": "experimental_workflow and eve/tools/workflow were removed; migrate to the workflow factory from eve/tools/workflow", + "38": "experimental_workflow and eve/tools/workflow were removed; migrate to the workflow factory from eve/tools/workflow", + "39": "experimental_workflow and eve/tools/workflow were removed; migrate to the workflow factory from eve/tools/workflow", + "40": "runWorkflowProgram was made internal; use the workflow factory from eve/tools/workflow", + "41": "workflow no longer accepts agents and its options argument is optional; use workflow() or workflow({ maxSubagents })", + "42": "Legacy session history migration was removed; user-role messages require current provenance kinds.", + "43": "Legacy session history migration was removed; user-role messages require current provenance kinds.", + "44": "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", + "45": "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", + "46": "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", + "47": "eve/experimental/evaluate was removed; import decide from eve/ai", + "48": "eve/experimental/evaluate was removed; import decide from eve/ai", + "49": "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", + "50": "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", + "51": "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", + "52": "Background defineTool and TaskExec were removed; use defineWorkflowTool for durable background work.", + "53": "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", + "54": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "55": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "56": "SandboxSession no longer exposes setNetworkPolicy; pass the configured environment to ctx.getSandbox(environment) to access network policy capabilities", + "57": "agentRouter() no longer accepts a model-facing outputSchema; structured subagent output is requested only through ctx.agent().", + "58": "Workflow tools no longer accept execution or return background task receipts; every call blocks until it settles.", + "59": "Workflow tools no longer accept execution or return background task receipts, and every call blocks until it settles. ctx.ask() no longer accepts dismissible and reports a withdrawn request as cancelled, workflow tool contexts carry interruptSignal, and ctx.agent(name) returns a session handle whose send() delivers a message and whose result() resolves the turn; ctx.agent(name, { message, agentId }) and AgentInput were removed. A ctx.ask() question parks the open turn with turn.waiting instead of ending it.", + "60": "Workflow tools define exactly one of execute(input, ctx), task(input, ctx), or serve(receive, ctx): a task() call returns a receipt and runs as a task, a serve() call reaches a resumable task through receive() and its model input gains taskId, and stream events gained task.started and task.settled. Agent tools are serve() tools with taskId in place of agentId, and the subagent.* stream events were removed. ctx.interruptSignal and WorkflowTaskContext were removed: a steering message aborts the abortSignal of an execute call the turn waits on.", + "61": "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", + "62": "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", + "63": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "64": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "65": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "66": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "67": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "68": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "69": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "70": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "71": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "72": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "73": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "74": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "75": "eve/ai evaluate was renamed to decide and AI SDK evaluation models became decision models; import decide from eve/ai", + "76": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "77": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "78": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "79": "Sandbox provider handle hooks were renamed to onSandboxStop and onSandboxDelete.", + "80": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "81": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "82": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "83": 'webSearch provider "openai" was replaced by "native" before it shipped in a release', + "84": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "85": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "86": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "87": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "88": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "89": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "90": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "91": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", + "92": "v27 tool contexts and keyed approval grants replace the previous approval and workflow result APIs; rebuild the extension.", }, }, dynamicTool: { - current: 88, - supported: [88], + current: 87, + supported: [87], dropped: { - 1: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 2: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 3: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 4: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 5: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 6: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 7: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 8: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 9: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 10: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 11: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 12: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 13: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 14: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 15: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 16: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 17: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 18: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 19: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 20: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 21: "Message and reasoning append events now expose deltas instead of cumulative snapshots.", - 22: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 23: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 24: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 25: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 26: "Background tools now use task yield descriptors", - 27: "TaskExec.delegated was removed; migrate to workflow-backed background tools", - 28: "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", - 29: "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", - 30: "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", - 31: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 32: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 33: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 34: "Legacy session history migration was removed; user-role messages require current provenance kinds.", - 35: "workflowMaxSubagents was removed with experimental_workflow; configure generated-program limits with the workflow factory", - 36: "workflowMaxSubagents was removed with experimental_workflow; configure generated-program limits with the workflow factory", - 37: "workflowMaxSubagents was removed with experimental_workflow; configure generated-program limits with the workflow factory", - 38: "workflowMaxSubagents was removed with experimental_workflow; configure generated-program limits with the workflow factory", - 39: "Legacy session history migration was removed; user-role messages require current provenance kinds.", - 40: "Legacy session history migration was removed; user-role messages require current provenance kinds.", - 41: "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", - 42: "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", - 43: "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", - 44: "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", - 45: "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", - 46: "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", - 47: "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", - 48: "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", - 49: "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", - 50: "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", - 51: "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", - 52: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 53: "SandboxSession no longer exposes setNetworkPolicy; pass the configured environment to ctx.getSandbox(environment) to access network policy capabilities", - 54: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 55: "Run mode was removed; sessions always park and ConversationContext no longer carries mode.", - 56: 'Background task execution was removed: subagent results no longer carry backgroundTask and message.completed always carries text. input.resolved reports outcome "cancelled" when a workflow tool withdraws a ctx.ask() request, and stream events gained agent.started, published when a workflow run opens a ctx.agent session, and turn.waiting, which parks the open turn instead of emitting turn.completed and session.waiting.', - 57: "Stream events gained task.started and task.settled, and input.requested, authorization events, and agent.started carry the taskId of the task they come from. The subagent.called, subagent.completed, subagent.started, and subagent.event stream events were removed; agent calls report task.started, task.settled, and agent.started.", - 58: "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", - 59: "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", - 60: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 61: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 62: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 63: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 64: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 65: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 66: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 67: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 68: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 69: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 70: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 71: "AI SDK evaluation models became decision models; auto() accepts decision models only", - 72: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 73: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 74: "Sandbox provider handle hooks were renamed to onSandboxStop and onSandboxDelete.", - 75: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 76: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 77: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 78: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 79: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 80: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 81: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 82: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 83: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 84: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 85: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 86: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 87: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", + "1": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "2": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "3": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "4": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "5": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "6": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "7": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "8": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "9": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "10": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "11": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "12": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "13": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "14": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "15": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "16": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "17": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "18": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "19": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "20": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "21": "Message and reasoning append events now expose deltas instead of cumulative snapshots.", + "22": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "23": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "24": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "25": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "26": "Background tools now use task yield descriptors", + "27": "TaskExec.delegated was removed; migrate to workflow-backed background tools", + "28": "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", + "29": "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", + "30": "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", + "31": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "32": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "33": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "34": "Legacy session history migration was removed; user-role messages require current provenance kinds.", + "35": "workflowMaxSubagents was removed with experimental_workflow; configure generated-program limits with the workflow factory", + "36": "workflowMaxSubagents was removed with experimental_workflow; configure generated-program limits with the workflow factory", + "37": "workflowMaxSubagents was removed with experimental_workflow; configure generated-program limits with the workflow factory", + "38": "workflowMaxSubagents was removed with experimental_workflow; configure generated-program limits with the workflow factory", + "39": "Legacy session history migration was removed; user-role messages require current provenance kinds.", + "40": "Legacy session history migration was removed; user-role messages require current provenance kinds.", + "41": "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", + "42": "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", + "43": "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", + "44": "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", + "45": "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", + "46": "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", + "47": "autoModel and eve/experimental/evaluate were removed; import auto from eve/models", + "48": "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", + "49": "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", + "50": "Background dynamic tools were removed; use a static defineWorkflowTool for durable background work.", + "51": "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", + "52": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "53": "SandboxSession no longer exposes setNetworkPolicy; pass the configured environment to ctx.getSandbox(environment) to access network policy capabilities", + "54": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "55": "Run mode was removed; sessions always park and ConversationContext no longer carries mode.", + "56": 'Background task execution was removed: subagent results no longer carry backgroundTask and message.completed always carries text. input.resolved reports outcome "cancelled" when a workflow tool withdraws a ctx.ask() request, and stream events gained agent.started, published when a workflow run opens a ctx.agent session, and turn.waiting, which parks the open turn instead of emitting turn.completed and session.waiting.', + "57": "Stream events gained task.started and task.settled, and input.requested, authorization events, and agent.started carry the taskId of the task they come from. The subagent.called, subagent.completed, subagent.started, and subagent.event stream events were removed; agent calls report task.started, task.settled, and agent.started.", + "58": "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", + "59": "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", + "60": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "61": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "62": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "63": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "64": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "65": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "66": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "67": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "68": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "69": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "70": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "71": "AI SDK evaluation models became decision models; auto() accepts decision models only", + "72": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "73": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "74": "Sandbox provider handle hooks were renamed to onSandboxStop and onSandboxDelete.", + "75": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "76": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "77": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "78": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "79": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "80": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "81": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "82": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "83": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "84": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "85": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", + "86": "v27 dynamic tool contexts and keyed approval grants replace the previous approval APIs; rebuild the extension.", }, }, channel: { - current: 64, - supported: [63, 64], + current: 62, + supported: [62], dropped: { - 1: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 2: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 3: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 4: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 5: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 6: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 7: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 8: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 9: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 10: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 11: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 12: "Message and reasoning append events now expose deltas instead of cumulative snapshots.", - 13: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 14: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 15: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 16: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 17: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 18: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 19: "Continuation rekey was removed; channel extensions must use additive continuation.alias instead.", - 20: "Continuation rekey was removed; channel extensions must use additive continuation.alias instead.", - 21: "Continuation rekey was removed; channel extensions must use additive continuation.alias instead.", - 22: "Continuation rekey was removed; channel extensions must use additive continuation.alias instead.", - 23: "Task views no longer expose executor bindings; background work is owned by workflow runs.", - 24: "Task views no longer expose executor bindings; background work is owned by workflow runs.", - 25: "Task views no longer expose executor bindings; background work is owned by workflow runs.", - 26: "Task views no longer expose executor bindings; background work is owned by workflow runs.", - 27: "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", - 28: "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", - 29: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 30: "SandboxSession no longer exposes setNetworkPolicy; pass the configured environment to ctx.getSandbox(environment) to access network policy capabilities", - 31: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 32: "Run mode and caller-supplied output schemas were removed from channel operations; sessions always park, and send options, audience inputs, and ConversationContext no longer carry mode, while from().send() and from().respond() no longer accept outputSchema.", - 33: "Background task delivery was removed: sends no longer accept taskDeliveryPolicy and message.completed always carries text.", - 34: 'Background task delivery was removed: sends no longer accept taskDeliveryPolicy and message.completed always carries text. input.resolved reports outcome "cancelled" when a workflow tool withdraws a ctx.ask() request, and stream events gained agent.started, published when a workflow run opens a ctx.agent session, and turn.waiting, which parks the open turn instead of emitting turn.completed and session.waiting.', - 35: "Stream events gained task.started and task.settled, and input.requested, authorization events, and agent.started carry the taskId of the task they come from. The subagent.called, subagent.completed, subagent.started, and subagent.event stream events were removed; agent calls report task.started, task.settled, and agent.started.", - 36: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 37: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 38: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 39: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 40: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 41: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 42: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 43: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 44: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 45: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 46: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 47: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 48: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 49: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 50: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 51: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 52: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 53: "Sandbox provider handle hooks were renamed to onSandboxStop and onSandboxDelete.", - 54: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 55: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 56: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 57: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 58: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 59: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 60: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 61: "v27 replaces v26 session event payloads; rebuild the extension against eve/events facts", - 62: "Channel deliver hooks return typed delivery payloads; void preserves input and kindHint is removed.", + "1": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "2": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "3": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "4": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "5": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "6": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "7": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "8": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "9": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "10": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "11": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "12": "Message and reasoning append events now expose deltas instead of cumulative snapshots.", + "13": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "14": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "15": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "16": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "17": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "18": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "19": "Continuation rekey was removed; channel extensions must use additive continuation.alias instead.", + "20": "Continuation rekey was removed; channel extensions must use additive continuation.alias instead.", + "21": "Continuation rekey was removed; channel extensions must use additive continuation.alias instead.", + "22": "Continuation rekey was removed; channel extensions must use additive continuation.alias instead.", + "23": "Task views no longer expose executor bindings; background work is owned by workflow runs.", + "24": "Task views no longer expose executor bindings; background work is owned by workflow runs.", + "25": "Task views no longer expose executor bindings; background work is owned by workflow runs.", + "26": "Task views no longer expose executor bindings; background work is owned by workflow runs.", + "27": "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", + "28": "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", + "29": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "30": "SandboxSession no longer exposes setNetworkPolicy; pass the configured environment to ctx.getSandbox(environment) to access network policy capabilities", + "31": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "32": "Run mode and caller-supplied output schemas were removed from channel operations; sessions always park, and send options, audience inputs, and ConversationContext no longer carry mode, while from().send() and from().respond() no longer accept outputSchema.", + "33": "Background task delivery was removed: sends no longer accept taskDeliveryPolicy and message.completed always carries text.", + "34": 'Background task delivery was removed: sends no longer accept taskDeliveryPolicy and message.completed always carries text. input.resolved reports outcome "cancelled" when a workflow tool withdraws a ctx.ask() request, and stream events gained agent.started, published when a workflow run opens a ctx.agent session, and turn.waiting, which parks the open turn instead of emitting turn.completed and session.waiting.', + "35": "Stream events gained task.started and task.settled, and input.requested, authorization events, and agent.started carry the taskId of the task they come from. The subagent.called, subagent.completed, subagent.started, and subagent.event stream events were removed; agent calls report task.started, task.settled, and agent.started.", + "36": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "37": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "38": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "39": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "40": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "41": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "42": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "43": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "44": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "45": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "46": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "47": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "48": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "49": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "50": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "51": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "52": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "53": "Sandbox provider handle hooks were renamed to onSandboxStop and onSandboxDelete.", + "54": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "55": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "56": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "57": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "58": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "59": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "60": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", + "61": "Channel commands now return Delivery receipts and session handles read with stream(); rebuild the extension.", }, }, schedule: { - current: 35, - supported: [34, 35], + current: 34, + supported: [34], dropped: { - 1: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 2: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 3: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 4: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 5: "Message and reasoning append events now expose deltas instead of cumulative snapshots.", - 6: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 7: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 8: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 9: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 10: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 11: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 12: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 13: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 14: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 15: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 16: 'Background task delivery was removed: sends no longer accept taskDeliveryPolicy and message.completed always carries text. input.resolved reports outcome "cancelled" when a workflow tool withdraws a ctx.ask() request, and stream events gained agent.started, published when a workflow run opens a ctx.agent session, and turn.waiting, which parks the open turn instead of emitting turn.completed and session.waiting.', - 17: "Stream events gained task.started and task.settled, and input.requested, authorization events, and agent.started carry the taskId of the task they come from. The subagent.called, subagent.completed, subagent.started, and subagent.event stream events were removed; agent calls report task.started, task.settled, and agent.started.", - 18: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 19: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 20: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 21: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 22: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 23: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 24: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 25: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 26: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 27: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 28: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 29: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 30: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 31: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 32: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 33: "v27 replaces v26 session event payloads; rebuild the extension against eve/events facts", + "1": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "2": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "3": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "4": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "5": "Message and reasoning append events now expose deltas instead of cumulative snapshots.", + "6": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "7": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "8": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "9": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "10": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "11": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "12": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "13": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "14": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "15": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "16": 'Background task delivery was removed: sends no longer accept taskDeliveryPolicy and message.completed always carries text. input.resolved reports outcome "cancelled" when a workflow tool withdraws a ctx.ask() request, and stream events gained agent.started, published when a workflow run opens a ctx.agent session, and turn.waiting, which parks the open turn instead of emitting turn.completed and session.waiting.', + "17": "Stream events gained task.started and task.settled, and input.requested, authorization events, and agent.started carry the taskId of the task they come from. The subagent.called, subagent.completed, subagent.started, and subagent.event stream events were removed; agent calls report task.started, task.settled, and agent.started.", + "18": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "19": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "20": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "21": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "22": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "23": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "24": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "25": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "26": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "27": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "28": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "29": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "30": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "31": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "32": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", + "33": "Schedule dispatch now returns Delivery receipts rather than session handles; rebuild the extension.", }, }, subagent: { @@ -338,112 +335,111 @@ const EXTENSION_CAPABILITY_CONTRACTS = { }, }, connection: { - current: 52, - supported: [52], + current: 51, + supported: [51], dropped: { - 1: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 2: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 3: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 4: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 5: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 6: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 7: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 8: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 9: "Dynamic connection resolvers no longer receive conversation or channel continuation data", - 10: "Message and reasoning append events now expose deltas instead of cumulative snapshots.", - 11: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 12: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 13: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 14: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 15: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 16: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 17: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 18: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 19: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 20: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 21: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 22: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 23: "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", - 24: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 25: "SandboxSession no longer exposes setNetworkPolicy; pass the configured environment to ctx.getSandbox(environment) to access network policy capabilities", - 26: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 27: "Run mode was removed; sessions always park and ConversationContext no longer carries mode.", - 28: 'Background task execution was removed: subagent results no longer carry backgroundTask and message.completed always carries text. input.resolved reports outcome "cancelled" when a workflow tool withdraws a ctx.ask() request, and stream events gained agent.started, published when a workflow run opens a ctx.agent session, and turn.waiting, which parks the open turn instead of emitting turn.completed and session.waiting.', - 29: "Stream events gained task.started and task.settled, and input.requested, authorization events, and agent.started carry the taskId of the task they come from. The subagent.called, subagent.completed, subagent.started, and subagent.event stream events were removed; agent calls report task.started, task.settled, and agent.started.", - 30: "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", - 31: "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", - 32: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 33: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 34: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 35: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 36: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 37: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 38: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 39: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 40: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 41: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 42: "Sandbox provider handle hooks were renamed to onSandboxStop and onSandboxDelete.", - 43: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 44: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 45: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 46: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 47: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 48: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 49: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 50: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", - 51: "approvalKey and ApprovalContext.approvedTools were removed; rebuild extensions using approval policies against the current API.", + "1": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "2": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "3": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "4": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "5": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "6": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "7": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "8": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "9": "Dynamic connection resolvers no longer receive conversation or channel continuation data", + "10": "Message and reasoning append events now expose deltas instead of cumulative snapshots.", + "11": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "12": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "13": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "14": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "15": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "16": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "17": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "18": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "19": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "20": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "21": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "22": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "23": "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", + "24": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "25": "SandboxSession no longer exposes setNetworkPolicy; pass the configured environment to ctx.getSandbox(environment) to access network policy capabilities", + "26": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "27": "Run mode was removed; sessions always park and ConversationContext no longer carries mode.", + "28": 'Background task execution was removed: subagent results no longer carry backgroundTask and message.completed always carries text. input.resolved reports outcome "cancelled" when a workflow tool withdraws a ctx.ask() request, and stream events gained agent.started, published when a workflow run opens a ctx.agent session, and turn.waiting, which parks the open turn instead of emitting turn.completed and session.waiting.', + "29": "Stream events gained task.started and task.settled, and input.requested, authorization events, and agent.started carry the taskId of the task they come from. The subagent.called, subagent.completed, subagent.started, and subagent.event stream events were removed; agent calls report task.started, task.settled, and agent.started.", + "30": "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", + "31": "Approval response policies now run for Cancel as well as Approve; a policy written for Approve only could reject every Cancel.", + "32": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "33": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "34": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "35": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "36": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "37": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "38": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "39": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "40": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "41": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "42": "Sandbox provider handle hooks were renamed to onSandboxStop and onSandboxDelete.", + "43": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "44": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "45": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "46": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "47": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "48": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "49": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", + "50": "Connection authorization uses principalType and AuthorizationDefinition, and approval context grants changed; rebuild the extension.", }, }, hook: { - current: 48, - supported: [47, 48], + current: 47, + supported: [47], dropped: { - 1: "Model identity moved from session.started runtime metadata to step.started call attribution.", - 2: "Model identity moved from session.started runtime metadata to step.started call attribution.", - 3: "Model identity moved from session.started runtime metadata to step.started call attribution.", - 4: "Model identity moved from session.started runtime metadata to step.started call attribution.", - 5: "Model identity moved from session.started runtime metadata to step.started call attribution.", - 6: "Model identity moved from session.started runtime metadata to step.started call attribution.", - 7: "Model identity moved from session.started runtime metadata to step.started call attribution.", - 8: "Model identity moved from session.started runtime metadata to step.started call attribution.", - 9: "Model identity moved from session.started runtime metadata to step.started call attribution.", - 10: "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", - 11: "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", - 12: "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", - 13: "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", - 14: "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", - 15: "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", - 16: "Message and reasoning append events now expose deltas instead of cumulative snapshots.", - 17: "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", - 18: "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", - 19: "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", - 20: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 21: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 22: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 23: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 24: "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", - 25: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 26: "SandboxSession no longer exposes setNetworkPolicy; pass the configured environment to ctx.getSandbox(environment) to access network policy capabilities", - 27: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 28: "Background task execution was removed: subagent results no longer carry backgroundTask and message.completed always carries text.", - 29: 'Background task execution was removed: subagent results no longer carry backgroundTask and message.completed always carries text. input.resolved reports outcome "cancelled" when a workflow tool withdraws a ctx.ask() request, and stream events gained agent.started, published when a workflow run opens a ctx.agent session, and turn.waiting, which parks the open turn instead of emitting turn.completed and session.waiting.', - 30: "Stream events gained task.started and task.settled, and input.requested, authorization events, and agent.started carry the taskId of the task they come from. The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", - 31: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 32: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 33: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 34: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 35: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 36: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 37: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 38: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 39: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 40: "Sandbox provider handle hooks were renamed to onSandboxStop and onSandboxDelete.", - 41: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 42: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 43: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 44: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 45: "v27 removes v26 conversation event keys, metadata and handler payloads; migrate handlers to eve/events facts and explicit lifecycle scopes, then rebuild the extension", - 46: "v27 replaces v26 session event payloads; rebuild the extension against eve/events facts", + "1": "Model identity moved from session.started runtime metadata to step.started call attribution.", + "2": "Model identity moved from session.started runtime metadata to step.started call attribution.", + "3": "Model identity moved from session.started runtime metadata to step.started call attribution.", + "4": "Model identity moved from session.started runtime metadata to step.started call attribution.", + "5": "Model identity moved from session.started runtime metadata to step.started call attribution.", + "6": "Model identity moved from session.started runtime metadata to step.started call attribution.", + "7": "Model identity moved from session.started runtime metadata to step.started call attribution.", + "8": "Model identity moved from session.started runtime metadata to step.started call attribution.", + "9": "Model identity moved from session.started runtime metadata to step.started call attribution.", + "10": "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", + "11": "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", + "12": "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", + "13": "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", + "14": "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", + "15": "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", + "16": "Message and reasoning append events now expose deltas instead of cumulative snapshots.", + "17": "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", + "18": "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", + "19": "The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", + "20": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "21": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "22": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "23": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "24": "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", + "25": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "26": "SandboxSession no longer exposes setNetworkPolicy; pass the configured environment to ctx.getSandbox(environment) to access network policy capabilities", + "27": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "28": "Background task execution was removed: subagent results no longer carry backgroundTask and message.completed always carries text.", + "29": 'Background task execution was removed: subagent results no longer carry backgroundTask and message.completed always carries text. input.resolved reports outcome "cancelled" when a workflow tool withdraws a ctx.ask() request, and stream events gained agent.started, published when a workflow run opens a ctx.agent session, and turn.waiting, which parks the open turn instead of emitting turn.completed and session.waiting.', + "30": "Stream events gained task.started and task.settled, and input.requested, authorization events, and agent.started carry the taskId of the task they come from. The subagent.called, subagent.completed, subagent.started, and subagent.event hook events were removed; subscribe to task.started, task.settled, and agent.started.", + "31": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "32": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "33": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "34": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "35": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "36": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "37": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "38": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "39": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "40": "Sandbox provider handle hooks were renamed to onSandboxStop and onSandboxDelete.", + "41": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "42": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "43": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "44": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "45": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", + "46": "Hooks now receive the v27 fact and session context contracts; rebuild the extension.", }, }, skill: { current: 3, supported: [1, 2, 3], dropped: {} }, @@ -476,8 +472,8 @@ const EXTENSION_CAPABILITY_CONTRACTS = { }, config: { current: 1, supported: [1], dropped: {} }, state: { - current: 13, - supported: [1, 2, 3, 4, 5, 7, 9, 10, 12, 13], + current: 14, + supported: [1, 2, 3, 4, 5, 7, 9, 10, 12, 13, 14], dropped: { 6: "Sandbox sessions no longer expose core identity and mutable networking is provider-specific.", 8: "SandboxSession no longer exposes setNetworkPolicy; pass the configured environment to ctx.getSandbox(environment) to access network policy capabilities", diff --git a/packages/eve/src/context/dynamic-resolve-context.ts b/packages/eve/src/context/dynamic-resolve-context.ts index f9bf8f9385..c1b7c28222 100644 --- a/packages/eve/src/context/dynamic-resolve-context.ts +++ b/packages/eve/src/context/dynamic-resolve-context.ts @@ -1,4 +1,4 @@ -import type { ModelMessage } from "ai"; +import type { HistoryMessage as ModelMessage } from "#shared/history-message.js"; import type { DynamicResolveContext } from "#dynamic/definition.js"; import type { AlsContext } from "#context/container.js"; diff --git a/packages/eve/src/context/keys.ts b/packages/eve/src/context/keys.ts index 28e53ad2e8..c8af2102ec 100644 --- a/packages/eve/src/context/keys.ts +++ b/packages/eve/src/context/keys.ts @@ -4,7 +4,8 @@ * `#runtime/sessions/runtime-context-keys.ts`. */ -import type { LanguageModel, ModelMessage, SystemModelMessage } from "ai"; +import type { LanguageModel, SystemModelMessage } from "ai"; +import type { HistoryMessage as ModelMessage } from "#shared/history-message.js"; import type { ChannelDeliveryMetadata, diff --git a/packages/eve/src/context/session-context.ts b/packages/eve/src/context/session-context.ts index 321412b44f..b5091d0298 100644 --- a/packages/eve/src/context/session-context.ts +++ b/packages/eve/src/context/session-context.ts @@ -1,9 +1,5 @@ import type { SandboxEnvironment } from "#shared/sandbox-environment.js"; -import type { - RuntimeSandboxSession, - RuntimeSandboxSessionFor, - SandboxSession, -} from "#shared/sandbox-session.js"; +import type { RuntimeSandboxSession, SandboxSession } from "#shared/sandbox-session.js"; import type { SessionAuth, SessionParent, SessionPredecessor, SessionTurn } from "#context/keys.js"; import type { SessionSchedule } from "#context/session-schedule.js"; @@ -47,7 +43,7 @@ export interface SessionContext { */ getSandbox( environment: SandboxEnvironment, - ): Promise>; + ): Promise>; } /** Active session metadata observed by authored callbacks as `ctx.session`. */ diff --git a/packages/eve/src/discover/extensions.test.ts b/packages/eve/src/discover/extensions.test.ts index 6540441110..492783c44c 100644 --- a/packages/eve/src/discover/extensions.test.ts +++ b/packages/eve/src/discover/extensions.test.ts @@ -23,7 +23,7 @@ describe("locateExtensionMountPackage", () => { const source = createMemoryProjectSource({ files: { [`${agentRoot}/extensions/selfmod.ts`]: - 'export { default } from "eve/self-modification";\n', + 'export { default } from "eve/self-modification/local";\n', [`${packageRoot}/package.json`]: JSON.stringify({ name: "eve", eve: { diff --git a/packages/eve/src/dynamic/definition.ts b/packages/eve/src/dynamic/definition.ts index dd120fc5c8..5438ccbc51 100644 --- a/packages/eve/src/dynamic/definition.ts +++ b/packages/eve/src/dynamic/definition.ts @@ -1,4 +1,4 @@ -import type { ModelMessage } from "ai"; +import type { HistoryMessage as ModelMessage } from "#shared/history-message.js"; import type { SessionAuth, SessionPredecessor } from "#context/keys.js"; import type { diff --git a/packages/eve/src/evals/expect/index.ts b/packages/eve/src/evals/expect/index.ts index 713ee36145..d709531a70 100644 --- a/packages/eve/src/evals/expect/index.ts +++ b/packages/eve/src/evals/expect/index.ts @@ -6,13 +6,6 @@ import { deepEquals } from "#evals/match.js"; import { testRegExp } from "#evals/match.js"; import type { Assertion, AssertionEvaluation, AssertionSeverity } from "#evals/types.js"; -export type { - Assertion, - AssertionEvaluation, - AssertionHandle, - AssertionSeverity, -} from "#evals/types.js"; - interface AssertionSpec { readonly name: string; readonly severity: AssertionSeverity; diff --git a/packages/eve/src/evals/index.ts b/packages/eve/src/evals/index.ts index d30ac91059..48491354db 100644 --- a/packages/eve/src/evals/index.ts +++ b/packages/eve/src/evals/index.ts @@ -6,16 +6,11 @@ export { defineEval } from "#evals/define-eval.js"; export { defineEvalConfig } from "#evals/define-eval-config.js"; export { EveEvalTurnFailedError } from "#evals/session.js"; export { mockModel } from "#evals/mock-model.js"; -export { toolCallsOf } from "#evals/tool-calls.js"; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- -export type { RuntimeIdentity, TraceContext } from "#protocol/session-events/families/session.js"; -export type { Interaction } from "#protocol/session-reader.js"; -export type { CancelSessionResult } from "#client/types.js"; - export type { EveEvalCountMatcher, EveEvalEventMatch, diff --git a/packages/eve/src/evals/tool-calls.test.ts b/packages/eve/src/evals/tool-calls.test.ts deleted file mode 100644 index df2b7a49e7..0000000000 --- a/packages/eve/src/evals/tool-calls.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import type { SessionEvent, SessionStreamEvent } from "#protocol/session-event.js"; -import { toolCallsOf } from "#evals/tool-calls.js"; - -function stream(events: readonly SessionEvent[]): SessionStreamEvent[] { - return events.map((event, line) => ({ - ...event, - meta: { at: "2026-10-09T00:00:00.000Z", position: { index: 0, line } }, - })); -} - -describe("toolCallsOf", () => { - it("names each call's settlement by its request", () => { - const calls = toolCallsOf( - stream([ - { - data: { cause: { deliveryId: "d_0" }, follows: null, turnId: "turn_0" }, - type: "turn.started", - }, - { - data: { - callId: "call_a", - capability: { kind: "tool", name: "lookup" }, - input: { q: "a" }, - owner: { runId: "run_0" }, - }, - scope: { runId: "run_0", turnId: "turn_0" }, - type: "call.requested", - }, - { - data: { - callId: "call_b", - capability: { kind: "tool", name: "lookup" }, - input: { q: "b" }, - owner: { runId: "run_0" }, - }, - scope: { runId: "run_0", turnId: "turn_0" }, - type: "call.requested", - }, - { - data: { callId: "call_b", outcome: "completed", output: { found: "b" } }, - scope: { turnId: "turn_0" }, - type: "call.settled", - }, - ]), - ); - - expect( - calls.map(({ callId, name, output, status }) => ({ callId, name, output, status })), - ).toEqual([ - { callId: "call_a", name: "lookup", output: undefined, status: "pending" }, - { callId: "call_b", name: "lookup", output: { found: "b" }, status: "completed" }, - ]); - }); - - it("counts every call the model makes by name except a skill load", () => { - const requested = (callId: string, kind: string, name: string): SessionEvent => - ({ - data: { callId, capability: { kind, name }, input: {}, owner: { runId: "run_0" } }, - scope: { runId: "run_0", turnId: "turn_0" }, - type: "call.requested", - }) as SessionEvent; - const calls = toolCallsOf( - stream([ - { - data: { cause: { deliveryId: "d_0" }, follows: null, turnId: "turn_0" }, - type: "turn.started", - }, - requested("call_tool", "tool", "lookup"), - requested("call_agent", "agent", "researcher"), - requested("call_wait", "control", "eve__task_wait"), - requested("call_skill", "skill", "triage"), - ]), - ); - - expect(calls.map((call) => call.name)).toEqual(["lookup", "researcher", "eve__task_wait"]); - }); -}); diff --git a/packages/eve/src/evals/tool-calls.ts b/packages/eve/src/evals/tool-calls.ts deleted file mode 100644 index 3ac1e9183e..0000000000 --- a/packages/eve/src/evals/tool-calls.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { SessionStreamEvent } from "#protocol/session-event.js"; -import { deriveRunFacts } from "#evals/runner/derive-run-facts.js"; -import type { EveEvalToolCall } from "#evals/types.js"; - -/** - * The tool calls in `events`, each pairing its `call.requested` with its `call.settled` by call - * id: the same derivation as `turn.toolCalls`, for checks over a session's whole stream. `call.*` - * facts carry ids rather than names, so this is how a check finds a named tool's output. - */ -export function toolCallsOf(events: readonly SessionStreamEvent[]): readonly EveEvalToolCall[] { - return deriveRunFacts(events).toolCalls; -} diff --git a/packages/eve/src/eve-channel/index.ts b/packages/eve/src/eve-channel/index.ts index 73d4a81f16..d26b314edd 100644 --- a/packages/eve/src/eve-channel/index.ts +++ b/packages/eve/src/eve-channel/index.ts @@ -5,7 +5,7 @@ import { proxyStreamHeaders } from "#eve-channel/stream-headers.js"; import { handleExpiredLegacyAuthorization } from "#execution/legacy-session/authorization.js"; import { EVE_ROUTE_PREFIX } from "#protocol/routes.js"; import type { SessionAuthContext, SessionParent, SessionTraceSeed } from "#channel/types.js"; -import type { Session } from "#channel/session.js"; +import { internalSession, type Session } from "#channel/session.js"; import { resolveForwardedPrincipal } from "#channel/forwarded-principal.js"; import { handleConnectionCallbackRequest } from "#execution/connections/callback-route.js"; import { handleSessionCallbackRequest } from "#subagents/callback-route.js"; @@ -79,7 +79,6 @@ import { findRemoteAgentBinding, healthResponse, type RemoteAgentBinding, - normalizeEveCors, resolveOnMessage, sessionCommandErrorResponse, deliveryResponse, @@ -117,7 +116,7 @@ export function eveChannel(input: EveChannelInput): EveChannel { const uploadPolicy = mergeUploadPolicy(input.uploadPolicy); return defineChannel({ - cors: normalizeEveCors(input.cors), + cors: input.cors, turnPolicy: input.turnPolicy, audience: (classifierInput) => { const audience = input.audience ?? defaultEveAudience; @@ -642,7 +641,7 @@ export function eveChannel(input: EveChannelInput): EveChannel { callId, childSessionId, childStreamPath, - parent, + parent: internalSession(parent), }); if (found === undefined) { throw new Error("Remote subagent binding not found."); diff --git a/packages/eve/src/eve-channel/request.stream.test.ts b/packages/eve/src/eve-channel/request.stream.test.ts index 3bb83ec15c..36751427d4 100644 --- a/packages/eve/src/eve-channel/request.stream.test.ts +++ b/packages/eve/src/eve-channel/request.stream.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import type { Session } from "#channel/session.js"; +import type { InternalSession as Session } from "#channel/session.js"; import { SessionStrandedError } from "#channel/session-stranded-error.js"; import { createSessionStreamResponse } from "#eve-channel/request.js"; import { EVE_STREAM_TAIL_INDEX_HEADER } from "#protocol/session-http.js"; diff --git a/packages/eve/src/eve-channel/request.ts b/packages/eve/src/eve-channel/request.ts index 04dc59c314..4d2922ef60 100644 --- a/packages/eve/src/eve-channel/request.ts +++ b/packages/eve/src/eve-channel/request.ts @@ -1,3 +1,4 @@ +import { internalSession } from "#channel/session.js"; import { parseToolStubs } from "#tool-stubs/rules.js"; import type { MessageContent, MessageFilePart, MessageTextPart } from "#shared/message-content.js"; @@ -309,20 +310,21 @@ export async function createSessionStreamResponse( // alongside it to save a round trip; a tail-relative cursor needs the tail to become one. const early = startIndex === undefined || startIndex >= 0 - ? session.getLineStream({ follow, startIndex: startIndex ?? 0 }) + ? internalSession(session).getLineStream({ follow, startIndex: startIndex ?? 0 }) : undefined; // Handled below; this keeps an early rejection from being reported as unhandled. early?.catch(() => {}); let tailIndex: number; try { - tailIndex = await session.getStreamTailIndex(); + tailIndex = await internalSession(session).getStreamTailIndex(); } catch (error) { void early?.then((lines) => lines.cancel()).catch(() => {}); throw error; } const from = early === undefined ? Math.max(0, tailIndex + 1 + (startIndex ?? 0)) : (startIndex ?? 0); - const lines = await (early ?? session.getLineStream({ follow, startIndex: from })); + const lines = await (early ?? + internalSession(session).getLineStream({ follow, startIndex: from })); const headers = new Headers({ "cache-control": "no-store, no-transform", "content-type": EVE_MESSAGE_STREAM_CONTENT_TYPE, diff --git a/packages/eve/src/eve-channel/support.ts b/packages/eve/src/eve-channel/support.ts index cefaac2a3d..ccdf5d264a 100644 --- a/packages/eve/src/eve-channel/support.ts +++ b/packages/eve/src/eve-channel/support.ts @@ -7,11 +7,8 @@ import { EVE_SESSION_ID_HEADER } from "#protocol/session-http.js"; import type { SessionAuthContext } from "#channel/types.js"; import { workflowEntryReference } from "#execution/workflow-runtime.js"; import { createLogger, logError } from "#internal/logging.js"; -import type { ChannelCors } from "#public/definitions/channel.js"; import { defaultEveAuth, - type EveChannelCors, - type EveChannelCorsOptions, type EveChannelInput, type EveHandle, type EveMessageContext, @@ -113,63 +110,6 @@ export async function findRemoteAgentBinding( return binding; } -export function normalizeEveCors(cors: EveChannelCors | undefined): ChannelCors { - if (cors === undefined || cors === false) { - return false; - } - if (cors === true) { - return true; - } - - const result: { - origin?: "*" | "null" | readonly string[]; - methods?: "*" | readonly string[]; - allowHeaders?: "*" | readonly string[]; - exposeHeaders?: "*" | readonly string[]; - credentials?: boolean; - maxAge?: number | false; - preflight?: { - statusCode?: number; - }; - } = {}; - - if (cors.origin !== undefined) { - result.origin = normalizeEveCorsOrigin(cors.origin); - } - if (cors.methods !== undefined) { - result.methods = cors.methods; - } - if (cors.allowedHeaders !== undefined) { - result.allowHeaders = cors.allowedHeaders; - } - if (cors.exposedHeaders !== undefined) { - result.exposeHeaders = cors.exposedHeaders; - } - if (cors.credentials !== undefined) { - result.credentials = cors.credentials; - } - if (cors.maxAge !== undefined) { - result.maxAge = cors.maxAge; - } - if (cors.preflightStatus !== undefined) { - result.preflight = { statusCode: cors.preflightStatus }; - } - - return result; -} - -function normalizeEveCorsOrigin( - origin: NonNullable, -): "*" | "null" | readonly string[] { - if (origin === "*" || origin === "null") { - return origin; - } - if (typeof origin === "string") { - return [origin]; - } - return origin; -} - interface OnMessageOutcome { readonly auth: SessionAuthContext | null; readonly context?: readonly string[]; diff --git a/packages/eve/src/eve-channel/types.ts b/packages/eve/src/eve-channel/types.ts index 6a54c68d45..3d2f669c84 100644 --- a/packages/eve/src/eve-channel/types.ts +++ b/packages/eve/src/eve-channel/types.ts @@ -1,3 +1,4 @@ +import type { ChannelCors } from "#channel/cors.js"; import type { MessageContent } from "#shared/message-content.js"; import type { SessionAuthContext, TurnPolicy } from "#channel/types.js"; @@ -10,7 +11,6 @@ import type { Channel, ChannelContinuationOps, ChannelEvents, - ChannelMethod, } from "#public/definitions/channel.js"; import type { ChannelAudience } from "#shared/channel-audience.js"; @@ -26,32 +26,6 @@ export type EveEventContext = ChannelContinuationOps; /** Runtime stream-event handlers supported by `eveChannel({ events })`. */ export type EveChannelEvents = ChannelEvents; -export interface EveChannelCorsOptions { - /** - * Allowed request origin. Pass a single origin string, an exact-origin list, - * `"null"`, or `"*"`. Omit for `"*"`. - */ - readonly origin?: "*" | "null" | string | readonly string[]; - /** Methods emitted on preflight responses. Omit for `"*"`. */ - readonly methods?: "*" | readonly ChannelMethod[]; - /** Request headers emitted on preflight responses. Omit for `"*"`. */ - readonly allowedHeaders?: "*" | readonly string[]; - /** Response headers exposed to browser callers. Omit for `"*"`. */ - readonly exposedHeaders?: "*" | readonly string[]; - /** Whether to emit `access-control-allow-credentials: true`. */ - readonly credentials?: boolean; - /** Max age, in seconds, emitted on preflight responses. */ - readonly maxAge?: number | false; - /** Preflight response status code. Defaults to 204. */ - readonly preflightStatus?: number; -} - -/** - * Higher-level CORS policy for the default eve HTTP channel. Pass `true` for - * fully permissive browser access, or pass an options object to narrow it. - */ -export type EveChannelCors = boolean | EveChannelCorsOptions; - /** Low-level eve HTTP handle exposed to `eveChannel({ onMessage })`. */ export interface EveHandle { /** Route-auth result for the request; `onMessage` chooses session auth by returning `{ auth }`. */ @@ -165,7 +139,7 @@ export interface EveChannelInput { * CORS untouched, pass `true` for fully permissive CORS, or pass an options * object to narrow the policy. */ - readonly cors?: EveChannelCors; + readonly cors?: ChannelCors; /** Policy for follow-up messages that arrive while a turn is active. */ readonly turnPolicy?: TurnPolicy; /** diff --git a/packages/eve/src/execution/tool-auth.integration.test.ts b/packages/eve/src/execution/tool-auth.integration.test.ts index 94acea9bae..34e70bec4e 100644 --- a/packages/eve/src/execution/tool-auth.integration.test.ts +++ b/packages/eve/src/execution/tool-auth.integration.test.ts @@ -247,7 +247,7 @@ describe("tool-hosted authorization", () => { it("resolves and caches an inline provider on a plain tool", async () => { let calls = 0; const inlineAuth = { - credentialOwner: "app" as const, + principalType: "app" as const, async getToken(): Promise { calls += 1; return { token: `inline-${calls}` }; diff --git a/packages/eve/src/internal/authored-definition/connection.test.ts b/packages/eve/src/internal/authored-definition/connection.test.ts index 0ca2bc6085..8eb5455615 100644 --- a/packages/eve/src/internal/authored-definition/connection.test.ts +++ b/packages/eve/src/internal/authored-definition/connection.test.ts @@ -1,7 +1,10 @@ import { describe, expect, it } from "vitest"; import { normalizeMcpClientConnectionDefinition } from "#internal/authored-definition/connection.js"; -import type { ConnectionAuthDefinition, ConnectionAuthProvider } from "#shared/connection-types.js"; +import type { + ConnectionAuthDefinition, + AuthorizationDefinition, +} from "#shared/connection-types.js"; const MSG = "Expected the connection export to match the public eve shape."; @@ -17,7 +20,7 @@ function validInput(overrides: Record = {}) { }; } -function authProvider(auth: ConnectionAuthDefinition | undefined): ConnectionAuthProvider { +function authProvider(auth: ConnectionAuthDefinition | undefined): AuthorizationDefinition { if (auth === undefined || typeof auth === "function") { throw new Error("Expected a static auth provider."); } @@ -77,15 +80,14 @@ describe("normalizeMcpClientConnectionDefinition", () => { expect(result.auth).toMatchObject({ getToken, principalType: "app" }); }); - it("normalizes credentialOwner into the runtime principalType", () => { + it("rejects retired credentialOwner with guidance to use principalType", () => { const getToken = async () => ({ token: "x" }); - const result = normalizeMcpClientConnectionDefinition( - validInput({ auth: { credentialOwner: "user", getToken } }), - MSG, - ); - - expect(result.auth).toMatchObject({ getToken, principalType: "user" }); - expect(result.auth).not.toHaveProperty("credentialOwner"); + expect(() => + normalizeMcpClientConnectionDefinition( + validInput({ auth: { credentialOwner: "user", getToken } }), + MSG, + ), + ).toThrow(/credentialOwner.*no longer supported.*principalType/); }); it("preserves a context-aware auth resolver without invoking it at build time", () => { @@ -158,7 +160,7 @@ describe("normalizeMcpClientConnectionDefinition", () => { MSG, ); - expect((result.auth as Record).vercelConnect).toEqual({ + expect(authProvider(result.auth).vercelConnect).toEqual({ connector: "oauth/mcp-linear-app", requirement: { reference: "connector:oauth/mcp-linear-app", @@ -182,7 +184,7 @@ describe("normalizeMcpClientConnectionDefinition", () => { MSG, ); - expect((result.auth as Record).evict).toBe(evict); + expect(authProvider(result.auth).evict).toBe(evict); }); it("drops a malformed vercelConnect marker without throwing", () => { @@ -200,7 +202,7 @@ describe("normalizeMcpClientConnectionDefinition", () => { MSG, ); - expect((result.auth as Record).vercelConnect).toBeUndefined(); + expect(authProvider(result.auth).vercelConnect).toBeUndefined(); }); }); @@ -316,7 +318,7 @@ describe("normalizeMcpClientConnectionDefinition", () => { }), MSG, ), - ).toThrow(/must not provide both "credentialOwner" and "principalType"/); + ).toThrow(/credentialOwner.*no longer supported.*principalType/); }); it('rejects interactive auth with principalType "app"', () => { diff --git a/packages/eve/src/internal/mcp/protected-resource.ts b/packages/eve/src/internal/mcp/protected-resource.ts index bb1a7c9166..46bad05fef 100644 --- a/packages/eve/src/internal/mcp/protected-resource.ts +++ b/packages/eve/src/internal/mcp/protected-resource.ts @@ -1,4 +1,4 @@ -import { escapeAuthChallengeParameter } from "#public/channels/auth.js"; +import { escapeAuthChallengeParameter } from "#channel/auth/definitions.js"; interface McpProtectedResourceMetadataOptions { readonly authorizationServers: readonly string[]; diff --git a/packages/eve/src/internal/nitro/dev-generation-artifacts.scenario.test.ts b/packages/eve/src/internal/nitro/dev-generation-artifacts.scenario.test.ts index 35162cf366..42e74a1f8e 100644 --- a/packages/eve/src/internal/nitro/dev-generation-artifacts.scenario.test.ts +++ b/packages/eve/src/internal/nitro/dev-generation-artifacts.scenario.test.ts @@ -265,7 +265,7 @@ describe("development generation artifacts", () => { "agent/agent.mjs": 'export default { model: "openai/gpt-5.4" };\n', "agent/instructions.md": "Use the available tools.", "agent/extensions/self-modification.mjs": [ - 'import selfModification from "eve/self-modification";', + 'import selfModification from "eve/self-modification/local";', "export default selfModification({ local: { enabled: false } });", "", ].join("\n"), diff --git a/packages/eve/src/internal/scaffold.ts b/packages/eve/src/internal/scaffold.ts new file mode 100644 index 0000000000..fb190f3ff0 --- /dev/null +++ b/packages/eve/src/internal/scaffold.ts @@ -0,0 +1,21 @@ +/** Internal Agent Builder integration; not a stable authoring API. */ +export { + scaffoldBaseProject, + type EvePackageContract, + type ScaffoldBaseProjectOptions, +} from "#setup/scaffold/create/project.js"; +export { + ensureChannel, + SLACK_CHANNEL_DEFAULT_ROUTE, + type ChannelMutationResult, + type EnsureChannelOptions, +} from "#setup/scaffold/update/channels.js"; +export { + ensureConnection, + type ConnectionMutationResult, + type EnsureConnectionOptions, +} from "#setup/scaffold/update/connections.js"; +export { + CONNECTION_CATALOG, + type ConnectionCatalogEntry, +} from "#setup/scaffold/connections/catalog.js"; diff --git a/packages/eve/src/internal/testing/channel-conformance/harness.ts b/packages/eve/src/internal/testing/channel-conformance/harness.ts index 3cd0ad38bc..1d122d5397 100644 --- a/packages/eve/src/internal/testing/channel-conformance/harness.ts +++ b/packages/eve/src/internal/testing/channel-conformance/harness.ts @@ -1,3 +1,4 @@ +import { internalSession } from "#channel/session.js"; import type { SessionStreamEvent } from "#protocol/session-event.js"; import type { Delivery } from "#shared/delivery.js"; import { createChannelDeliveryMetadata } from "#channel/delivery-metadata.js"; @@ -1088,11 +1089,11 @@ async function cancelUntilResting(session: Session): Promise { * not events, so a tail line that materializes no event (one a reader skips) still ends the read. */ async function readThroughTail(session: Session, startIndex = 0): Promise { - const tail = await session.getStreamTailIndex(); + const tail = await internalSession(session).getStreamTailIndex(); if (tail < startIndex) return []; const events: SessionStreamEvent[] = []; const eventsOf = createEventReader(); - const reader = (await session.getLineStream({ startIndex })).getReader(); + const reader = (await internalSession(session).getLineStream({ startIndex })).getReader(); try { for (let position = startIndex; position <= tail; position += 1) { const { done, value } = await reader.read(); @@ -1154,7 +1155,7 @@ async function waitForRest(sessions: readonly Session[], wait: Wait): Promise { const states = await Promise.all( sessions.map(async (session) => { - const tail = await session.getStreamTailIndex(); + const tail = await internalSession(session).getStreamTailIndex(); const waiting = (await readThroughTail(session, Math.max(tail, 0))).some(endsTurn); const steps = await world.steps.list({ resolveData: "none", runId: session.id }); const idle = steps.data.every((step) => TERMINAL_STEP_STATUSES.has(step.status)); diff --git a/packages/eve/src/internal/testing/scenario-apps/custom-channel-portability.ts b/packages/eve/src/internal/testing/scenario-apps/custom-channel-portability.ts index 9a0d6d0927..3fd8fdb203 100644 --- a/packages/eve/src/internal/testing/scenario-apps/custom-channel-portability.ts +++ b/packages/eve/src/internal/testing/scenario-apps/custom-channel-portability.ts @@ -18,20 +18,21 @@ export default defineChannel({ auth: null, state: { lastSender: body.userId ?? "anonymous" }, }); - return Response.json({ sessionId: session.id }); + return Response.json({ sessionId: session.sessionId }); }), GET("/api/stream/:sessionId", async (req, { attachSession, params }) => { const session = attachSession(params.sessionId); - const events = await session.getEventStream(); - return new Response(events, { headers: { "content-type": "text/event-stream" } }); + const events = ReadableStream.from(session.stream()).pipeThrough(new TransformStream({ + transform(event, controller) { controller.enqueue(JSON.stringify(event) + "\\n"); }, + })).pipeThrough(new TextEncoderStream()); + return new Response(events, { headers: { "content-type": "application/x-ndjson" } }); }), ], events: { - "message.completed"(event, ctx) { - if (event.finishReason === "tool-calls") return; - console.log("Reply from", ctx.sender, ":", event.message); + "content.completed"(_event, ctx) { + void ctx.channel.sender; }, }, }); diff --git a/packages/eve/src/public/channels/auth.ts b/packages/eve/src/public/channels/auth.ts index 77017050fd..849cfeacc6 100644 --- a/packages/eve/src/public/channels/auth.ts +++ b/packages/eve/src/public/channels/auth.ts @@ -1,1286 +1,44 @@ -/** - * Route-level HTTP authentication primitives. - * - * Strategy helpers return an {@link AuthFn} for route factories. Verifier - * helpers are lower-level pure functions for custom `fetch` handlers. - */ - -import { areTokenClaimMatchersSatisfied } from "#channel/auth/token-claims.js"; -import { decodeJwt } from "#compiled/jose/index.js"; - -import type { SessionAuthContext } from "#channel/types.js"; -import { isEveDevEnvironment } from "#internal/application/dev-environment.js"; -import { createLogger } from "#internal/logging.js"; -import { authenticateHttpBasicStrategy } from "#channel/auth/http-basic.js"; -import { authenticateJwtEcdsaStrategy } from "#channel/auth/jwt-ecdsa.js"; -import { authenticateJwtHmacStrategy } from "#channel/auth/jwt-hmac.js"; -import { authenticateOidcStrategy } from "#channel/auth/oidc.js"; -import { resolveVercelOidcCurrentProject } from "#channel/auth/vercel-oidc-project.js"; -import { - createRuntimeSessionAuthContext, - type ResolvedJwtEcdsaAuthStrategy, - type ResolvedJwtHmacAuthStrategy, - type ResolvedOidcAuthStrategy, - type RouteStrategyAuthenticationResult, -} from "#channel/auth/types.js"; -import { isVercelOidcIssuer } from "#shared/vercel-project.js"; - -const vercelOidcLog = createLogger("auth.vercel-oidc"); -import { - createRuntimeIpAllowList, - isRuntimeIpAllowed, - type RuntimeIpAllowList, -} from "#channel/ip-allow-list.js"; -import { isLoopbackHostname } from "#shared/network-address.js"; - -// --------------------------------------------------------------------------- -// Result types -// --------------------------------------------------------------------------- - -/** - * Result returned by the verifier helpers below. On success, `sessionAuth` - * is a fully-constructed {@link SessionAuthContext}; on failure no detail is - * returned so routes do not leak which credential check failed. - */ -export type VerifyResult = - | { readonly ok: true; readonly sessionAuth: SessionAuthContext } - | { readonly ok: false }; - -// --------------------------------------------------------------------------- -// HTTP Basic -// --------------------------------------------------------------------------- - -/** - * Credentials accepted by {@link verifyHttpBasic}. - */ -export interface HttpBasicCredentials { - readonly username: string; - readonly password: string; -} - -/** - * Verifies an HTTP Basic credential against the supplied username and - * password. Returns `{ ok: true, sessionAuth }` on success or `{ ok: false }` - * on a missing or mismatched credential. The password is compared with - * constant-time hash equality so a timing side channel cannot leak it. Both - * values are normalized to Unicode NFC before comparison, matching the - * `charset="UTF-8"` challenge advertised by {@link httpBasic}. - */ -export function verifyHttpBasic( - authorizationHeader: string | null, - credentials: HttpBasicCredentials, -): VerifyResult { - if (authorizationHeader === null) { - return { ok: false }; - } - - const result = authenticateHttpBasicStrategy({ - authorization: authorizationHeader, - strategy: { - kind: "http-basic", - password: credentials.password, - username: credentials.username, - }, - }); - - if (result.kind !== "authenticated") { - return { ok: false }; - } - - return { - ok: true, - sessionAuth: createRuntimeSessionAuthContext(result.principal), - }; -} - -// --------------------------------------------------------------------------- -// JWT HMAC -// --------------------------------------------------------------------------- - -/** - * Configuration for {@link verifyJwtHmac}. Optional fields have defaults so - * adapters can omit unused matchers. `secret` is read by the adapter from its - * secret store (e.g. `process.env`) and passed in directly. - */ -export interface VerifyJwtHmacConfig { - readonly algorithm: "HS256" | "HS384" | "HS512"; - readonly audiences: readonly string[]; - readonly issuer: string; - readonly secret: string; - /** - * Tolerance in seconds for the `exp` and `nbf` claims. Defaults to 30. - */ - readonly clockSkewSeconds?: number; - /** - * AWS IAM-style `*`-wildcard patterns matched against the token `sub`. - * When supplied, the token is rejected unless one matches. - */ - readonly subjects?: readonly string[]; - /** - * Per-claim membership matcher: each named claim must contain at least one - * of the listed values, or the token is rejected. - */ - readonly claims?: Readonly>; -} - -/** - * Verifies a bearer JWT signed with an HMAC secret. Pass the token without - * the `Bearer ` prefix (see {@link extractBearerToken}). Returns - * `{ ok: true, sessionAuth }` on success or `{ ok: false }` when verification - * fails or the token's claims don't match the supplied matchers. - */ -export async function verifyJwtHmac( - token: string | null, - config: VerifyJwtHmacConfig, -): Promise { - if (token === null || token.length === 0) { - return { ok: false }; - } - - const strategy: ResolvedJwtHmacAuthStrategy = { - algorithm: config.algorithm, - audiences: [...config.audiences], - clockSkewSeconds: config.clockSkewSeconds ?? 30, - issuer: config.issuer, - kind: "jwt-hmac", - secret: config.secret, - ...(config.claims === undefined ? {} : { claims: config.claims }), - ...(config.subjects === undefined ? {} : { subjects: config.subjects }), - }; - - const result = await authenticateJwtHmacStrategy({ strategy, token }); - if (result.kind !== "authenticated") { - return { ok: false }; - } - - return { - ok: true, - sessionAuth: createRuntimeSessionAuthContext(result.principal), - }; -} - -// --------------------------------------------------------------------------- -// JWT ECDSA -// --------------------------------------------------------------------------- - -/** - * Configuration for {@link verifyJwtEcdsa}. - */ -export interface VerifyJwtEcdsaConfig { - readonly algorithm: "ES256" | "ES384" | "ES512"; - readonly audiences: readonly string[]; - readonly issuer: string; - /** - * PEM-encoded ECDSA public key (`-----BEGIN PUBLIC KEY-----` ...). - */ - readonly publicKey: string; - /** - * Tolerance in seconds for the `exp` and `nbf` claims. Defaults to 30. - */ - readonly clockSkewSeconds?: number; - /** - * AWS IAM-style `*`-wildcard patterns matched against the token `sub`. - * When supplied, the token is rejected unless one matches. - */ - readonly subjects?: readonly string[]; - /** - * Per-claim membership matcher: each named claim must contain at least one - * of the listed values, or the token is rejected. - */ - readonly claims?: Readonly>; -} - -/** - * Verifies a bearer JWT signed with an ECDSA private key against the supplied - * PEM public key. Pass the token without the `Bearer ` prefix (see - * {@link extractBearerToken}). Returns `{ ok: true, sessionAuth }` on success - * or `{ ok: false }` when verification fails or the token's claims do not - * match the configured `subjects`/`claims`. - */ -export async function verifyJwtEcdsa( - token: string | null, - config: VerifyJwtEcdsaConfig, -): Promise { - if (token === null || token.length === 0) { - return { ok: false }; - } - - const strategy: ResolvedJwtEcdsaAuthStrategy = { - algorithm: config.algorithm, - audiences: [...config.audiences], - clockSkewSeconds: config.clockSkewSeconds ?? 30, - issuer: config.issuer, - kind: "jwt-ecdsa", - publicKey: config.publicKey, - ...(config.claims === undefined ? {} : { claims: config.claims }), - ...(config.subjects === undefined ? {} : { subjects: config.subjects }), - }; - - const result = await authenticateJwtEcdsaStrategy({ strategy, token }); - if (result.kind !== "authenticated") { - return { ok: false }; - } - - return { - ok: true, - sessionAuth: createRuntimeSessionAuthContext(result.principal), - }; -} - -// --------------------------------------------------------------------------- -// OIDC -// --------------------------------------------------------------------------- - -/** - * Configuration for {@link verifyOidc}. - */ -export interface VerifyOidcConfig { - readonly audiences: readonly string[]; - readonly issuer: string; - /** - * OIDC discovery URL. Defaults to - * `${issuer}/.well-known/openid-configuration` (any trailing slash on - * `issuer` is stripped first). - */ - readonly discoveryUrl?: string; - /** - * Tolerance in seconds for the `exp` and `nbf` claims. Defaults to 30. - */ - readonly clockSkewSeconds?: number; - /** - * AWS IAM-style `*`-wildcard patterns matched against the token `sub`. - * When supplied, the token is rejected unless one matches. - */ - readonly subjects?: readonly string[]; - /** - * Per-claim membership matcher: each named claim must contain at least one - * of the listed values, or the token is rejected. - */ - readonly claims?: Readonly>; -} - -/** - * Verifies a bearer OIDC token against an issuer's discovery URL. Pass the - * token without the `Bearer ` prefix (see {@link extractBearerToken}). The - * token must satisfy the configured `subjects`/`claims` matchers; on success - * the principal is tagged `principalType: "service"`. Unlike - * {@link verifyVercelOidc}, this never grants the Vercel current-project - * bypass. Returns `{ ok: false }` on failed verification or matchers. - */ -export async function verifyOidc( - token: string | null, - config: VerifyOidcConfig, -): Promise { - const result = await runOidcVerification(token, { - ...config, - acceptCurrentVercelProject: false, - currentVercelProject: undefined, - }); - return result.kind === "authenticated" - ? { ok: true, sessionAuth: createRuntimeSessionAuthContext(result.principal) } - : { ok: false }; -} - -/** - * Runs OIDC token verification against a resolved strategy and returns - * the strategy authenticator's tagged result. Internal helper shared - * between {@link verifyOidc} (public, never grants the Vercel - * current-project upgrade) and {@link verifyVercelOidc} (always opts - * into it). Returning the raw outcome rather than a boolean lets call - * sites log a structured rejection reason. - */ -async function runOidcVerification( - token: string | null, - config: VerifyOidcConfig & { - readonly acceptCurrentVercelProject: boolean; - readonly currentVercelProject: ResolvedOidcAuthStrategy["currentVercelProject"] | undefined; - }, -): Promise { - if (token === null || token.length === 0) { - return { kind: "not-authenticated" }; - } - - const baseStrategy = { - acceptCurrentVercelProject: config.acceptCurrentVercelProject, - audiences: [...config.audiences], - clockSkewSeconds: config.clockSkewSeconds ?? 30, - discoveryUrl: - config.discoveryUrl ?? `${config.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`, - issuer: config.issuer, - kind: "oidc", - ...(config.claims === undefined ? {} : { claims: config.claims }), - ...(config.subjects === undefined ? {} : { subjects: config.subjects }), - } satisfies ResolvedOidcAuthStrategy; - const strategy = - config.currentVercelProject === undefined - ? baseStrategy - : { ...baseStrategy, currentVercelProject: config.currentVercelProject }; - - return await authenticateOidcStrategy({ strategy, token }); -} - -// --------------------------------------------------------------------------- -// Bearer token extraction -// --------------------------------------------------------------------------- - -/** - * Extracts the bearer token from an `Authorization: Bearer ` header. - * Returns `null` if the header is missing, the scheme isn't `Bearer`, or the - * value after `Bearer ` is empty. - */ -export function extractBearerToken(authorizationHeader: string | null): string | null { - if (authorizationHeader === null) { - return null; - } - const match = /^Bearer\s+(.+)$/i.exec(authorizationHeader); - const value = match?.[1]?.trim(); - return value === undefined || value.length === 0 ? null : value; -} - -// --------------------------------------------------------------------------- -// IP allowlist -// --------------------------------------------------------------------------- - -/** - * Parsed IP allowlist for repeated checks. Construct once at module load, - * then call {@link isIpAllowed} per request. - */ -export type IpAllowList = RuntimeIpAllowList; - -/** - * Parses exact IP addresses or CIDR blocks into a reusable - * {@link IpAllowList}. Entries are trimmed; throws on an empty entry, a `*` - * wildcard (use exact IP/CIDR syntax), or an invalid address or CIDR prefix. - */ -export function createIpAllowList(entries: readonly string[]): IpAllowList { - return createRuntimeIpAllowList(entries); -} - -/** - * Returns whether `ip` is permitted by `allowList`. `null` always returns - * `false`. Adapters that need to allow unknown IPs should not call this. - */ -export function isIpAllowed(ip: string | null, allowList: IpAllowList): boolean { - if (ip === null) { - return false; - } - return isRuntimeIpAllowed(ip, allowList); -} - -// --------------------------------------------------------------------------- -// Unauthorized response builder -// --------------------------------------------------------------------------- - -/** - * One challenge entry attached to the `www-authenticate` header on a failure - * response. For a Basic+Bearer prompt, pass both: - * `[{ scheme: "Basic", parameters: { realm: "agent" } }, { scheme: "Bearer" }]` - */ -export interface UnauthorizedChallenge { - readonly scheme: "Basic" | "Bearer"; - readonly parameters?: Readonly>; -} - -/** - * Options accepted by {@link createUnauthorizedResponse}. - */ -export interface UnauthorizedResponseOptions { - /** - * HTTP status code. Defaults to `401`. Use `403` for "authenticated but - * not allowed" outcomes. - */ - readonly status?: 401 | 403; - /** - * Machine-readable error code returned in the JSON body. Defaults to - * `"unauthorized"` (401) or `"forbidden"` (403). - */ - readonly code?: string; - /** - * Human-readable error message returned in the JSON body. Defaults to a - * generic prompt. - */ - readonly message?: string; - /** - * Optional `www-authenticate` challenge entries. - */ - readonly challenges?: readonly UnauthorizedChallenge[]; -} - -/** - * Builds a JSON failure response shaped like the framework's other auth - * failures: `cache-control: no-store`, one `www-authenticate` header per - * challenge, and a `{ ok: false, code, error }` body. - */ -export function createUnauthorizedResponse(opts: UnauthorizedResponseOptions = {}): Response { - const status = opts.status ?? 401; - const code = opts.code ?? (status === 403 ? "forbidden" : "unauthorized"); - const message = - opts.message ?? (status === 403 ? "Forbidden." : "Authorization is required for this route."); - const challenges = opts.challenges ?? []; - - const headers = new Headers({ "cache-control": "no-store" }); - for (const challenge of challenges) { - headers.append("www-authenticate", formatChallenge(challenge)); - } - - return Response.json( - { - code, - error: message, - ok: false, - }, - { - headers, - status, - }, - ); -} - -function formatChallenge(challenge: UnauthorizedChallenge): string { - if (challenge.parameters === undefined || Object.keys(challenge.parameters).length === 0) { - return challenge.scheme; - } - const renderedParameters = Object.entries(challenge.parameters) - .map(([key, value]) => `${key}="${escapeAuthChallengeParameter(value)}"`) - .join(", "); - return `${challenge.scheme} ${renderedParameters}`; -} - -/** @internal Escapes a value for an HTTP auth challenge quoted string. */ -export function escapeAuthChallengeParameter(value: string): string { - return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"'); -} - -function isValidOAuthIdentifierUrl(value: string): boolean { - try { - const url = new URL(value); - const secureTransport = - url.protocol === "https:" || (url.protocol === "http:" && isLoopbackHostname(url.hostname)); - return ( - secureTransport && - url.username.length === 0 && - url.password.length === 0 && - url.search.length === 0 && - url.hash.length === 0 - ); - } catch { - return false; - } -} - -function isValidMetadataPath(value: string): boolean { - if (!value.startsWith("/") || value.startsWith("//")) return false; - const url = new URL(value, "https://eve.invalid"); - return url.origin === "https://eve.invalid" && url.search.length === 0 && url.hash.length === 0; -} - -/** - * Options accepted by auth error classes. The class chooses the HTTP status. - */ -export type AuthErrorOptions = Omit; - -/** - * Error thrown by auth callbacks to reject a route with a structured 401 - * response. `routeAuth` catches it and returns its response; other errors - * propagate through the normal channel failure path. - */ -export class UnauthenticatedError extends Error { - readonly response: Response; - - constructor(opts: AuthErrorOptions = {}) { - super(opts.message ?? "Authorization is required for this route."); - this.name = "UnauthenticatedError"; - this.response = createUnauthorizedResponse({ ...opts, status: 401 }); - } -} - -/** - * Error thrown by auth callbacks to reject a route with a structured 403 - * response. `routeAuth` catches it and returns its response; other errors - * propagate through the normal channel failure path. - */ -export class ForbiddenError extends Error { - readonly response: Response; - - constructor(opts: AuthErrorOptions = {}) { - super(opts.message ?? "Forbidden."); - this.name = "ForbiddenError"; - this.response = createUnauthorizedResponse({ ...opts, status: 403 }); - } -} - -export interface AuthResult extends SessionAuthContext { - /** - * Set to true to let this authenticated caller create sessions with tool stubs. - * Callers without this permission cannot supply stubs. - */ - readonly allowToolStubs?: boolean; -} - -/** - * Route auth callback. Returned value semantics inside {@link routeAuth}: - * - * - A {@link SessionAuthContext} accepts the request and halts the walk. - * - `null` or `undefined` skips to the next entry. - * - * If every entry skips (including the empty `[]` case), the walker returns a - * 401. To reject with a specific response, throw an - * {@link UnauthenticatedError} or {@link ForbiddenError}. To accept anonymous - * traffic, include {@link none} as the final entry. - */ -export type AuthFn = ( - event: TEvent, -) => AuthResult | null | undefined | Promise; - -/** - * OAuth protected-resource metadata attached to an inbound auth policy. - * `issuer` is shorthand for a single authorization server. - */ -export type OAuthResourceOptions = { - readonly metadataPath?: string; - readonly resource?: string; - readonly scopes?: readonly string[]; -} & ( - | { - readonly authorizationServers?: never; - readonly issuer: string; - } - | { - readonly authorizationServers: readonly string[]; - readonly issuer?: never; - } -); - -const OAUTH_RESOURCE_SYMBOL = Symbol.for("eve.channels.auth.oauthResource"); - -/** - * An ordinary route authenticator decorated with OAuth protected-resource - * discovery metadata. - */ -export type OAuthResourceAuth = AuthFn & { - readonly [OAUTH_RESOURCE_SYMBOL]: OAuthResourceOptions; -}; - -/** - * Adds OAuth protected-resource metadata to an existing inbound auth policy. - * - * The returned value remains an {@link AuthFn}: it preserves the ordered auth - * walk and can be used by any channel. A protocol-aware channel such as - * `mcpChannel` may additionally publish the metadata and discovery challenge. - */ -export function oauthResource( - auth: AuthFn | readonly AuthFn[], - options: OAuthResourceOptions, -): OAuthResourceAuth { - const authorizationServers = - options.issuer !== undefined ? [options.issuer] : options.authorizationServers; - if ( - authorizationServers.length === 0 || - authorizationServers.some((value) => !isValidOAuthIdentifierUrl(value)) - ) { - throw new Error( - "oauthResource requires at least one HTTPS authorization server URL (HTTP is allowed only on loopback).", - ); - } - if (options.resource !== undefined && !isValidOAuthIdentifierUrl(options.resource)) { - throw new Error( - "oauthResource resource must be an HTTPS URL without credentials, query, or fragment (HTTP is allowed only on loopback).", - ); - } - if (options.metadataPath !== undefined && !isValidMetadataPath(options.metadataPath)) { - throw new Error( - "oauthResource metadataPath must be an absolute path without a host, query, or fragment.", - ); - } - - const list: readonly AuthFn[] = Array.isArray(auth) - ? (auth as readonly AuthFn[]) - : [auth as AuthFn]; - const composed: AuthFn = async (request) => { - for (const fn of list) { - const result = await fn(request); - if (result) return result; - } - return null; - }; - const declaredChallenges = collectDeclaredChallenges(list); - const wrapped = withAuthChallenges( - composed, - declaredChallenges.length > 0 ? declaredChallenges : [{ scheme: "Bearer" }], - ) as OAuthResourceAuth; - Object.defineProperty(wrapped, OAUTH_RESOURCE_SYMBOL, { - enumerable: false, - value: options, - }); - return wrapped; -} - -/** @internal Reads OAuth resource metadata without widening `AuthFn`. */ -export function readOAuthResourceOptions( - auth: AuthFn | readonly AuthFn[], -): OAuthResourceOptions | undefined { - if (Array.isArray(auth)) return undefined; - return (auth as Partial)[OAUTH_RESOURCE_SYMBOL]; -} - -/** - * Symbol-keyed property carrying the `www-authenticate` challenges an - * {@link AuthFn} would satisfy, attached via {@link withAuthChallenges}. - * Reading it back off the function (rather than widening `AuthFn`'s call - * signature) lets {@link routeAuth} discover the right challenge scheme(s) - * for a 401 without every strategy needing to change shape. - */ -const AUTH_CHALLENGES_SYMBOL = Symbol.for("eve.channels.auth.challenges"); - -/** - * Attaches declared `www-authenticate` challenges to an {@link AuthFn} - * without changing its call signature or behavior. {@link routeAuth} reads - * the declared challenges back when every entry in the walk skips, so the - * resulting 401 advertises the scheme(s) the configured strategies actually - * accept (e.g. `Basic` for {@link httpBasic}) instead of a fixed default. - * - * Built-in strategies (`httpBasic`, `jwtHmac`, `jwtEcdsa`, `oidc`, - * `vercelOidc`) already declare their challenge. Use this directly when - * authoring a custom `AuthFn`: - * - * ```ts - * const apiKey = withAuthChallenges( - * (request) => (isValidApiKey(request) ? sessionAuth : null), - * [{ scheme: "Bearer" }], - * ); - * ``` - */ -export function withAuthChallenges( - fn: AuthFn, - challenges: readonly UnauthorizedChallenge[], -): AuthFn { - const wrapped: AuthFn = (event) => fn(event); - Object.defineProperty(wrapped, AUTH_CHALLENGES_SYMBOL, { - value: challenges, - }); - return wrapped; -} - -/** - * Reads the challenges {@link withAuthChallenges} attached to `fn`, or - * `undefined` when `fn` declared none (e.g. {@link none}, {@link localDev}, - * {@link placeholderAuth}, or a custom `AuthFn` that never opted in). - */ -function getDeclaredChallenges( - fn: AuthFn, -): readonly UnauthorizedChallenge[] | undefined { - return (fn as Partial>)[ - AUTH_CHALLENGES_SYMBOL - ]; -} - -/** - * Collects the deduped, ordered set of challenges declared across `list` via - * {@link withAuthChallenges}. Dedupes by the rendered `www-authenticate` - * string so e.g. two `jwtHmac()` entries only contribute one `Bearer`. - */ -function collectDeclaredChallenges( - list: readonly AuthFn[], -): readonly UnauthorizedChallenge[] { - const seenRendered = new Set(); - const challenges: UnauthorizedChallenge[] = []; - for (const fn of list) { - for (const challenge of getDeclaredChallenges(fn) ?? []) { - const rendered = formatChallenge(challenge); - if (seenRendered.has(rendered)) continue; - seenRendered.add(rendered); - challenges.push(challenge); - } - } - return challenges; -} - -/** - * Walks an `AuthFn` (or array) in order against `request`. The first entry - * returning a {@link SessionAuthContext} wins; entries returning `null` or - * `undefined` are skipped. If the walk exhausts without a winner (including - * the empty-array case), returns a 401 {@link createUnauthorizedResponse} - * whose `www-authenticate` challenges are collected from the entries' - * declared schemes (see {@link withAuthChallenges}), falling back to - * `Bearer` when none declared any. - * - * Channel factories that share this resolution policy (e.g. `eveChannel`, or - * a custom `defineChannel` route handler) should call `routeAuth` rather than - * re-implement the walk. - */ -export async function routeAuth( - request: Request, - auth: AuthFn | readonly AuthFn[], -): Promise { - const list: readonly AuthFn[] = Array.isArray(auth) - ? (auth as readonly AuthFn[]) - : [auth as AuthFn]; - - try { - for (const fn of list) { - const result = await fn(request); - if (result) return result; - } - } catch (error) { - if ( - typeof error === "object" && - error !== null && - "response" in error && - error.response instanceof Response - ) { - return error.response; - } - throw error; - } - - const declaredChallenges = collectDeclaredChallenges(list); - const challenges = - declaredChallenges.length > 0 - ? addInvalidBearerTokenError(request, declaredChallenges) - : [{ scheme: "Bearer" } satisfies UnauthorizedChallenge]; - return createUnauthorizedResponse({ - challenges, - }); -} - -/** - * A supplied Bearer credential that every declared Bearer strategy declined - * is invalid, rather than absent. Add the RFC 6750 error only after the whole - * auth walk has exhausted so another Bearer strategy or a fallback such as - * localDev() still has a chance to accept the request. - */ -function addInvalidBearerTokenError( - request: Request, - challenges: readonly UnauthorizedChallenge[], -): readonly UnauthorizedChallenge[] { - const authorization = request.headers.get("authorization"); - if (authorization === null || !/^Bearer(?:\s|$)/i.test(authorization.trim())) { - return challenges; - } - if (!challenges.some((challenge) => challenge.scheme === "Bearer")) { - return challenges; - } - return challenges.map((challenge) => - challenge.scheme !== "Bearer" || challenge.parameters?.error !== undefined - ? challenge - : { - ...challenge, - parameters: { - ...challenge.parameters, - error: "invalid_token", - }, - }, - ); -} - -/** - * Returns an {@link AuthFn} for scaffolded apps that makes unfinished - * production auth fail as an intentional 401 rather than an internal route - * error. Replace it before serving real users: - * - * ```ts - * eveChannel({ auth: [vercelOidc(), localDev(), placeholderAuth()] }); - * ``` - * - * Outside production it returns `null`, so the auth walk keeps the same local - * development behavior as any other skipped entry. - */ -export function placeholderAuth(): AuthFn { - return () => { - if (process.env.VERCEL_ENV !== "production") { - return null; - } - - throw new UnauthenticatedError({ - code: "eve_production_auth_not_configured", - message: - "Production auth is not configured. Replace placeholderAuth() in agent/channels/eve.ts with your app's auth provider.", - }); - }; -} - -/** - * Returns an {@link AuthFn} that accepts any request anonymously, producing - * a synthetic principal with `principalType: "anonymous"`. Use it as the - * final entry in an `auth` array to opt routes into unauthenticated access: - * - * ```ts - * eveChannel({ auth: [none()] }); // every request accepted anonymously - * ``` - * - * The returned `SessionAuthContext` halts the {@link routeAuth} walk, so - * `none()` terminates whatever array it appears in. It ignores its event - * argument, so `TEvent` is inferred from the surrounding array (defaulting - * to `unknown`) and composes with `AuthFn` entries without a type - * argument. - */ -export function none(): AuthFn { - return () => ANONYMOUS_SESSION_AUTH_CONTEXT; -} - -/** - * Returns an {@link AuthFn} that authenticates a synthetic `local-dev` - * principal while the process is a local development server, and returns - * `null` everywhere else so the {@link routeAuth} walk falls through to the - * next entry. - * - * A process counts as a local development server when it is `eve dev` - * (`EVE_DEV=1`) or `vercel dev` (`VERCEL=1` with `VERCEL_ENV=development`). - * This is a property of the deployment, never of the inbound request, so no - * request header (for example `Host`) can flip the decision. A production - * deployment (`eve start`, a Vercel deployment, or any container host) sets - * neither flag, so `localDev()` authenticates nothing there. Layer a real - * authenticator on those hosts. - */ -export function localDev(): AuthFn { - return () => (isLocalDevelopmentServer() ? LOCAL_DEV_SESSION_AUTH_CONTEXT : null); -} - -function isLocalDevelopmentServer(): boolean { - if (process.env.VERCEL && process.env.VERCEL_ENV === "development") { - return true; - } - return isEveDevEnvironment(); -} - -const ANONYMOUS_SESSION_AUTH_CONTEXT: SessionAuthContext = { - attributes: {}, - authenticator: "none", - principalId: "anonymous", - principalType: "anonymous", -}; - -const LOCAL_DEV_SESSION_AUTH_CONTEXT: SessionAuthContext = { - attributes: {}, - authenticator: "local-dev", - principalId: "local-dev", - principalType: "local-dev", -}; - -/** - * Expected prefix for the `aud` claim of a Vercel-minted OIDC token - * (`https://vercel.com/`). Requiring it gives a real audience - * binding: `verifyVercelOidc` otherwise verifies the token only against its - * own `aud` (a tautology), so a token the project minted for a foreign - * federation audience (e.g. AWS STS) — same `project_id`, different `aud` — - * could be replayed against the agent. Foreign audiences lack this prefix and - * are rejected; every owner's Vercel audience carries it, so the cross-project - * `subjects` path is unaffected. - */ -const VERCEL_OIDC_AUDIENCE_PREFIX = "https://vercel.com/"; - -/** - * Options for verifying a Vercel OIDC token with {@link verifyVercelOidc}. - */ -export interface VerifyVercelOidcOptions { - /** - * Explicit current-project binding for the verified token. When omitted, - * the verifier reads `VERCEL_PROJECT_ID` and `VERCEL_TARGET_ENV` / - * `VERCEL_ENV` from the runtime environment. - */ - readonly currentVercelProject?: { - readonly environment?: string; - readonly projectId: string; - }; - /** - * Optional `sub` patterns granting callers access on top of the always-on - * current-project bypass. Patterns use AWS IAM-style `*` wildcards and may - * target tokens minted by other Vercel projects (e.g. - * `"owner:acme:project:partner-agent:environment:*"`). The current project - * is always accepted regardless of this list. - */ - readonly subjects?: readonly string[]; -} - -/** - * Verifies a bearer JWT minted by Vercel OIDC. - * - * Acceptance rule: - * - * - Current-project tokens that reach the service/runtime path are accepted - * regardless of `subjects`, so the deployment's own runtime callers - * (subagent, internal fetches) authenticate without being enumerated. - * - Tokens with an `external_sub` claim authenticate as - * `principalType: "user"` when they match the configured current project - * and environment. `external_sub` - * becomes the eve subject, `external_iss` or `connector_id` the eve issuer - * when present, and string-valued OIDC profile claims (`name`, `picture`, - * `email`) are exposed as auth attributes. - * - Development tokens with a `user_id` claim authenticate as - * `principalType: "user"` only when both the token and configured current - * project environment are `development`. Other same-project target - * environments may use one as a `"service"` principal. - * - Tokens from other Vercel projects are accepted **only** when their `sub` - * matches one of {@link VerifyVercelOidcOptions.subjects}. - * - * Tokens without a `user_id` claim are accepted for the current project in - * production, preview, and development. They are tagged `"runtime"` when the - * token's `environment` matches the current deployment, otherwise `"service"`. - */ -export async function verifyVercelOidc( - token: string | null, - opts: VerifyVercelOidcOptions = {}, -): Promise { - if (token === null || token.length === 0) { - return { ok: false }; - } - - const claims = decodeUnverifiedJwtClaims(token); - if (claims === null) { - vercelOidcLog.debug("Rejected token that failed to decode as a JWT."); - return { ok: false }; - } - - if (!isVercelOidcIssuer(claims.issuer)) { - vercelOidcLog.debug("Rejected token whose issuer is not a Vercel OIDC issuer.", { - issuer: claims.issuer, - }); - return { ok: false }; - } - - if (claims.audiences.length === 0) { - vercelOidcLog.debug("Rejected token with no audience claim.", { issuer: claims.issuer }); - return { ok: false }; - } - - // Bind the audience: the token must carry a Vercel audience. The verifier - // below otherwise checks the token's `aud` against itself (a tautology), so - // without this a token minted for a foreign federation audience would still - // authenticate on a matching `project_id`. - if (!claims.audiences.some((audience) => audience.startsWith(VERCEL_OIDC_AUDIENCE_PREFIX))) { - vercelOidcLog.debug("Rejected token whose audience is not a Vercel audience.", { - audiences: claims.audiences, - issuer: claims.issuer, - }); - return { ok: false }; - } - - // `acceptCurrentVercelProject: true` activates the same-project bypass - // inside the OIDC verifier so any token minted for the configured project - // is accepted regardless of `subjects`. The supplied `subjects` - // matcher (defaulting to an empty list that matches nothing) - // determines which **other** callers are allowed in. - const currentVercelProject = resolveCurrentVercelProject(opts); - const result = await runOidcVerification(token, { - acceptCurrentVercelProject: true, - audiences: claims.audiences, - currentVercelProject, - issuer: claims.issuer, - subjects: opts.subjects ?? [], - }); - - if (result.kind === "authenticated") { - vercelOidcLog.debug("Accepted Vercel OIDC token.", { - issuer: claims.issuer, - principalType: result.principal.principalType, - subject: result.principal.subject, - }); - return { ok: true, sessionAuth: createRuntimeSessionAuthContext(result.principal) }; - } - - vercelOidcLog.debug("Rejected Vercel OIDC token after verification.", { - audiences: claims.audiences, - issuer: claims.issuer, - reason: result.kind, - subjectsConfigured: (opts.subjects ?? []).length > 0, - ...(result.kind === "misconfigured" ? { detail: result.message } : {}), - }); - return { ok: false }; -} - -function resolveCurrentVercelProject( - opts: VerifyVercelOidcOptions, -): NonNullable | undefined { - if (opts.currentVercelProject !== undefined) return opts.currentVercelProject; - - const projectId = process.env.VERCEL_PROJECT_ID?.trim(); - if (projectId === undefined || projectId.length === 0) return undefined; - - const environment = process.env.VERCEL_TARGET_ENV?.trim() || process.env.VERCEL_ENV?.trim(); - return environment === undefined || environment.length === 0 - ? { projectId } - : { environment, projectId }; -} - -/** - * Allowed values for {@link VercelSubjectInput.environment}. Use `"*"` - * to match any of `production`, `preview`, and `development` for the - * named project. - */ -export type VercelSubjectEnvironment = "production" | "preview" | "development" | "*"; - -/** - * Strict input shape accepted by {@link vercelSubject}. - * - * The `sub` claim carries human-readable slugs/names, not IDs. Stable - * project and team IDs (`prj_...`, `team_...`) live on the separate - * `project_id` / `owner_id` claims, which `verifyVercelOidc` matches - * against `VERCEL_PROJECT_ID` for the current-project bind. This helper - * composes a `sub` matcher, so its inputs are slugs. - */ -export interface VercelSubjectInput { - /** - * Vercel team slug (a.k.a. "owner"; e.g. `"acme"`) as embedded in the - * `sub` claim, **not** the stable team ID `team_...` (`VERCEL_TEAM_ID`). - * - * Must not contain `*` or `:` so authors cannot widen the matcher to - * "any team with a project of this name". Hand-write the subject string - * when cross-team federation is intentional. - */ - readonly teamSlug: string; - /** - * Vercel project name (e.g. `"acme_website"`) as embedded in the `sub` - * claim, **not** the stable project ID `prj_...` (`VERCEL_PROJECT_ID`). - * Same constraints as {@link teamSlug}. - */ - readonly projectName: string; - /** - * Vercel deployment environment, or `"*"` to match any environment for - * the named project. Defaults to `"production"` so an unspecified - * environment cannot silently accept preview/development tokens. - */ - readonly environment?: VercelSubjectEnvironment; -} - -/** - * Builds a Vercel OIDC `sub` matcher pattern from a typed input. - * - * Vercel-issued tokens carry a `sub` of the form - * `owner:[TEAM_SLUG]:project:[PROJECT_NAME]:environment:[ENVIRONMENT]`. - * Hand-writing it invites two foot-guns: a misspelled slug (silently - * rejecting all callers) and over-broad wildcards (silently accepting - * unrelated callers). This helper rejects malformed inputs at construction - * time and forces an explicit `environment`. - * - * Use it inside {@link VerifyVercelOidcOptions.subjects} to layer - * additional Vercel-project callers on top of the current-project bypass: - * - * ```ts - * vercelOidc({ - * subjects: [vercelSubject({ teamSlug: "partner", projectName: "data" })], - * }); - * ``` - * - * The `sub` claim flips when a team or project name changes, so any policy - * built on this helper must update with it; the stable team/project IDs are - * not exposed in `sub`. See Vercel's OIDC reference for the token anatomy. - */ -export function vercelSubject(input: VercelSubjectInput): string { - assertVercelSubjectSegment("teamSlug", input.teamSlug); - assertVercelSubjectSegment("projectName", input.projectName); - const environment = input.environment ?? "production"; - if ( - environment !== "production" && - environment !== "preview" && - environment !== "development" && - environment !== "*" - ) { - throw new Error( - `vercelSubject: invalid environment ${JSON.stringify(environment)}; expected "production", "preview", "development", or "*".`, - ); - } - return `owner:${input.teamSlug}:project:${input.projectName}:environment:${environment}`; -} - -function assertVercelSubjectSegment(field: "teamSlug" | "projectName", value: string): void { - if (value.length === 0) { - throw new Error(`vercelSubject: ${field} must be a non-empty string.`); - } - if (value.includes("*") || value.includes(":")) { - throw new Error( - `vercelSubject: ${field} ${JSON.stringify(value)} may not contain ${value.includes(":") ? "':'" : "'*'"}. Hand-write the subject string when wildcards are intentional.`, - ); - } -} - -export interface VercelOidcOptions extends Omit { - /** - * Additional token subjects allowed to authenticate. - * To allow stubs, use { subject, allowToolStubs: true } for that subject. - */ - readonly subjects?: readonly ( - | string - | { - readonly subject: string; - readonly allowToolStubs?: boolean; - } - )[]; -} - -/** - * Returns an HTTP route auth callback backed by Vercel OIDC. See - * {@link verifyVercelOidc} for the always-on current-project bypass and how - * `subjects` extends acceptance to other Vercel projects. Declares a - * `Bearer` {@link withAuthChallenges} challenge for {@link routeAuth}'s 401. - */ -export function vercelOidc(opts: VercelOidcOptions = {}): AuthFn { - const { subjects, ...verification } = opts; - const verifyOptions = { - ...verification, - subjects: subjects?.map((entry) => (typeof entry === "string" ? entry : entry.subject)), - }; - return withAuthChallenges( - async (request) => { - const token = extractBearerToken(request.headers.get("authorization")); - const currentVercelProject = - opts.currentVercelProject ?? - (token !== null && isLocalDevelopmentVercelOidc() - ? await resolveVercelOidcCurrentProject(request) - : undefined); - const result = await verifyVercelOidc( - token, - currentVercelProject === undefined - ? verifyOptions - : { ...verifyOptions, currentVercelProject }, - ); - if (!result.ok) return null; - const auth = result.sessionAuth; - const allowed = - (auth.principalType === "service" || auth.principalType === "runtime") && - subjects?.some( - (entry) => - typeof entry !== "string" && - entry.allowToolStubs === true && - areTokenClaimMatchersSatisfied({ sub: auth.subject }, { subjects: [entry.subject] }), - ); - return allowed ? { ...auth, allowToolStubs: true } : auth; - }, - [{ scheme: "Bearer" }], - ); -} - -function isLocalDevelopmentVercelOidc(): boolean { - if (process.env.VERCEL_ENV === "development") return true; - if (process.env.VERCEL_ENV === "preview" || process.env.VERCEL_ENV === "production") { - return false; - } - return isEveDevEnvironment(); -} - -/** - * Options accepted by {@link httpBasic}. - */ -export interface HttpBasicAuthOptions { - /** - * Optional `realm` parameter advertised on the `WWW-Authenticate: Basic` - * challenge (e.g. browsers show it in the native login prompt). Defaults - * to `"eve"`. - */ - readonly realm?: string; -} - -/** - * Returns an {@link AuthFn} that verifies HTTP Basic credentials via - * {@link verifyHttpBasic}. Declares a `Basic` {@link withAuthChallenges} - * challenge so a {@link routeAuth} 401 advertises `WWW-Authenticate: Basic` - * (with `realm` first, when given, then `charset="UTF-8"`) instead of the - * `Bearer` fallback. - */ -export function httpBasic( - credentials: HttpBasicCredentials, - options?: HttpBasicAuthOptions, -): AuthFn { - const parameters: Record = { - realm: options?.realm ?? "eve", - charset: "UTF-8", - }; - return withAuthChallenges( - (request) => { - const result = verifyHttpBasic(request.headers.get("authorization"), credentials); - return result.ok ? result.sessionAuth : null; - }, - [ - { - parameters, - scheme: "Basic", - }, - ], - ); -} - -/** - * Returns an {@link AuthFn} that verifies an HMAC-signed bearer JWT via - * {@link verifyJwtHmac}. Declares a `Bearer` {@link withAuthChallenges} - * challenge for {@link routeAuth}'s 401. - */ -export function jwtHmac(config: VerifyJwtHmacConfig): AuthFn { - return withAuthChallenges( - async (request) => { - const token = extractBearerToken(request.headers.get("authorization")); - const result = await verifyJwtHmac(token, config); - return result.ok ? result.sessionAuth : null; - }, - [{ scheme: "Bearer" }], - ); -} - -/** - * Returns an {@link AuthFn} that verifies an ECDSA-signed bearer JWT via - * {@link verifyJwtEcdsa}. Declares a `Bearer` {@link withAuthChallenges} - * challenge for {@link routeAuth}'s 401. - */ -export function jwtEcdsa(config: VerifyJwtEcdsaConfig): AuthFn { - return withAuthChallenges( - async (request) => { - const token = extractBearerToken(request.headers.get("authorization")); - const result = await verifyJwtEcdsa(token, config); - return result.ok ? result.sessionAuth : null; - }, - [{ scheme: "Bearer" }], - ); -} - -/** - * Returns an {@link AuthFn} that verifies an OIDC bearer token on the inbound - * request via {@link verifyOidc}. Use {@link vercelOidc} instead for - * Vercel-issued tokens: it preconfigures the issuer, audience, and runtime - * principal flag. Declares a `Bearer` {@link withAuthChallenges} challenge - * for {@link routeAuth}'s 401. - */ -export function oidc(config: VerifyOidcConfig): AuthFn { - return withAuthChallenges( - async (request) => { - const token = extractBearerToken(request.headers.get("authorization")); - const result = await verifyOidc(token, config); - return result.ok ? result.sessionAuth : null; - }, - [{ scheme: "Bearer" }], - ); -} - -// --------------------------------------------------------------------------- -// Internals -// --------------------------------------------------------------------------- - -/** - * Decodes a bearer JWT into its issuer and audience(s) without verifying - * the signature. Returns `null` for malformed tokens or tokens missing - * the `iss` claim. - * - * Used by {@link verifyVercelOidc} to discover the team-scoped issuer and - * audience baked into Vercel-issued tokens before running full - * verification. - */ -function decodeUnverifiedJwtClaims( - token: string, -): { readonly issuer: string; readonly audiences: readonly string[] } | null { - let payload: ReturnType; - try { - payload = decodeJwt(token); - } catch { - return null; - } - - if (typeof payload.iss !== "string" || payload.iss.length === 0) { - return null; - } - - const audiences = - typeof payload.aud === "string" - ? [payload.aud] - : Array.isArray(payload.aud) - ? payload.aud.filter((value): value is string => typeof value === "string") - : []; - - return { audiences, issuer: payload.iss }; -} +/** HTTP authentication strategies and verifiers. */ +export { + verifyHttpBasic, + verifyJwtHmac, + verifyJwtEcdsa, + verifyOidc, + extractBearerToken, + createIpAllowList, + isIpAllowed, + createUnauthorizedResponse, + oauthResource, + withAuthChallenges, + routeAuth, + placeholderAuth, + none, + localDev, + verifyVercelOidc, + vercelSubject, + vercelOidc, + httpBasic, + jwtHmac, + jwtEcdsa, + oidc, + type VerifyResult, + type HttpBasicCredentials, + type VerifyJwtHmacConfig, + type VerifyJwtEcdsaConfig, + type VerifyOidcConfig, + type IpAllowList, + type UnauthorizedChallenge, + type UnauthorizedResponseOptions, + type AuthErrorOptions, + UnauthenticatedError, + ForbiddenError, + type AuthResult, + type AuthFn, + type OAuthResourceOptions, + type OAuthResourceAuth, + type VerifyVercelOidcOptions, + type VercelSubjectEnvironment, + type VercelSubjectInput, + type VercelOidcOptions, + type HttpBasicAuthOptions, +} from "#channel/auth/definitions.js"; diff --git a/packages/eve/src/public/channels/discord/index.ts b/packages/eve/src/public/channels/discord/index.ts index 93b3a90251..6ac820d56f 100644 --- a/packages/eve/src/public/channels/discord/index.ts +++ b/packages/eve/src/public/channels/discord/index.ts @@ -1,11 +1,3 @@ -/** - * AI SDK conversation-message shape that the harness uses as the unit of - * session history and turn input. Re-exported from the `ai` package so - * authors wiring up the Discord channel can type their own message arrays - * without taking a direct `ai` dependency. - */ -export type { ModelMessage } from "ai"; - /** * Instrumentation metadata the channel's `metadata` callback reports for * Discord sessions, used for tracing and observability. @@ -37,18 +29,7 @@ export { } from "#public/channels/discord/discordChannel.js"; export { - callDiscordApi, - createDiscordFollowupMessage, discordContinuationToken, - DISCORD_MESSAGE_CONTENT_MAX_LENGTH, - DISCORD_NO_MENTIONS, - editDiscordOriginalResponse, - resolveDiscordApplicationId, - resolveDiscordBotToken, - resolveDiscordPublicKey, - sendDiscordChannelMessage, - splitDiscordMessageContent, - triggerDiscordTypingIndicator, type DiscordApiOptions, type DiscordApiResponse, type DiscordApplicationId, @@ -60,12 +41,6 @@ export { } from "#public/channels/discord/api.js"; export { - DISCORD_EPHEMERAL_MESSAGE_FLAG, - DISCORD_INTERACTION_RESPONSE_TYPE, - DISCORD_INTERACTION_TYPE, - commandInteractionMessage, - formatDiscordContextBlock, - parseDiscordInteraction, type DiscordCommandInteraction, type DiscordCommandOption, type DiscordComponentInteraction, @@ -77,23 +52,9 @@ export { type DiscordUser, } from "#public/channels/discord/inbound.js"; -export { - DISCORD_COMPONENT_TYPE, - DISCORD_HITL_CUSTOM_ID_PREFIX, - DISCORD_HITL_FREEFORM_CUSTOM_ID_PREFIX, - DISCORD_HITL_FREEFORM_TEXT_INPUT_ID, - buildFreeformModalResponse, - deriveComponentAnswers, - deriveModalAnswers, - isDiscordFreeformComponent, - renderInteractionComponents, -} from "#public/channels/discord/hitl.js"; - export { defaultDiscordAuth } from "#public/channels/discord/defaults.js"; export { - verifyDiscordRequest, - verifyDiscordSignature, type DiscordPublicKey, type DiscordVerifyOptions, type DiscordWebhookVerifier, diff --git a/packages/eve/src/public/channels/eve-session-routes.test.ts b/packages/eve/src/public/channels/eve-session-routes.test.ts index 5aa674020a..3874dbec9b 100644 --- a/packages/eve/src/public/channels/eve-session-routes.test.ts +++ b/packages/eve/src/public/channels/eve-session-routes.test.ts @@ -4,7 +4,7 @@ import { SessionNotActiveError } from "#channel/session-not-active-error.js"; import { REMOTE_AGENT_PROTOCOL_VERSION } from "#protocol/remote-agent-protocol.js"; import type { RouteHandlerArgs } from "#channel/routes.js"; -import type { Session } from "#channel/session.js"; +import type { InternalSession as Session } from "#channel/session.js"; import { attachRouteSessionCreator } from "#internal/nitro/routes/channel-route-context.js"; import { mockChannelContext } from "#internal/testing/mocks/mock-channel-operations.js"; import { writeForwardedParentSessionBaggage } from "#protocol/baggage.js"; @@ -41,7 +41,6 @@ function createFixedSession(overrides: Partial = {}): Session { compact: vi.fn().mockResolvedValue(receipt), clear: vi.fn().mockResolvedValue(receipt), reset: vi.fn().mockResolvedValue(receipt), - getEventStream: vi.fn().mockResolvedValue(new ReadableStream()), getLineStream: vi.fn().mockResolvedValue(new ReadableStream()), getStreamTailIndex: vi.fn().mockResolvedValue(-1), ...overrides, diff --git a/packages/eve/src/public/channels/github/index.ts b/packages/eve/src/public/channels/github/index.ts index 9f4643d9b8..06309dda0d 100644 --- a/packages/eve/src/public/channels/github/index.ts +++ b/packages/eve/src/public/channels/github/index.ts @@ -47,9 +47,6 @@ export { type GitHubProgressConfig, type GitHubReceiveTarget, } from "#public/channels/github/githubChannel.js"; -export { - GITHUB_DEFAULT_EXCLUDED_DIFF_FILES, - type GitHubPullRequestContextConfig, -} from "#public/channels/github/pr-context.js"; +export { type GitHubPullRequestContextConfig } from "#public/channels/github/pr-context.js"; export { type GitHubChannelState } from "#public/channels/github/state.js"; export { type GitHubWebhookVerifier } from "#public/channels/github/verify.js"; diff --git a/packages/eve/src/public/channels/index.ts b/packages/eve/src/public/channels/index.ts index 9617aeb10d..14006980f3 100644 --- a/packages/eve/src/public/channels/index.ts +++ b/packages/eve/src/public/channels/index.ts @@ -10,10 +10,6 @@ export { DELETE, WS, type AttachSessionFn, - type CancelTurnResult, - type ClearSessionResult, - type CompactSessionResult, - type ResetSessionResult, SessionStrandedError, SessionNotActiveError, type Delivery, @@ -41,7 +37,6 @@ export { type InferChannelMetadata, type Session, type SessionOperations, - type SessionCallback, type SessionHandle, type SessionRespondOptions, type SessionSendOptions, diff --git a/packages/eve/src/public/channels/linear/index.ts b/packages/eve/src/public/channels/linear/index.ts index 3be6de6cbc..b267e08e46 100644 --- a/packages/eve/src/public/channels/linear/index.ts +++ b/packages/eve/src/public/channels/linear/index.ts @@ -2,10 +2,6 @@ export { LinearApiError, callLinearGraphQL, createLinearAgentActivity, - createLinearAgentSessionOnComment, - createLinearAgentSessionOnIssue, - listLinearAgentSessionActivities, - updateLinearAgentSession, type LinearAgentActivityContent, type LinearAgentActivityCreateInput, type LinearAgentActivityRecord, @@ -21,14 +17,11 @@ export { type LinearChannelCredentials, type LinearWebhookSecret, } from "#public/channels/linear/auth.js"; -export { LINEAR_CHANNEL_DEFAULT_ROUTE } from "#public/channels/linear/constants.js"; + export { defaultLinearAuth, defaultOnAgentSession } from "#public/channels/linear/defaults.js"; -export { linearInteractionSignal, renderLinearInteractions } from "#public/channels/linear/hitl.js"; + export { - formatLinearContextBlock, linearContinuationToken, - messageFromLinearAgentSessionEvent, - parseLinearWebhookEvent, type LinearAgentActivityRef, type LinearAgentSessionAction, type LinearAgentSessionEvent, @@ -55,7 +48,6 @@ export { type LinearSessionContext, } from "#public/channels/linear/linearChannel.js"; export { - signLinearWebhookBody, type LinearVerifyOptions, type LinearWebhookVerifier, } from "#public/channels/linear/verify.js"; diff --git a/packages/eve/src/public/channels/mcp.ts b/packages/eve/src/public/channels/mcp.ts index 0c42cfb293..a8e047a0d2 100644 --- a/packages/eve/src/public/channels/mcp.ts +++ b/packages/eve/src/public/channels/mcp.ts @@ -1,3 +1,7 @@ +import { + escapeAuthChallengeParameter, + readOAuthResourceOptions, +} from "#channel/auth/definitions.js"; import { interactionJsonSchema } from "#protocol/interaction-json.js"; import { sessionAuthFromResult } from "#channel/auth/result.js"; import { parseJsonObject } from "#shared/json.js"; @@ -43,13 +47,7 @@ import { createMcpResourceChallenge, } from "#internal/mcp/protected-resource.js"; import { answerSchema } from "#shared/input.js"; -import { - escapeAuthChallengeParameter, - readOAuthResourceOptions, - routeAuth, - type AuthFn, - type OAuthResourceOptions, -} from "#public/channels/auth.js"; +import { routeAuth, type AuthFn, type OAuthResourceOptions } from "#public/channels/auth.js"; import { readAgentInfoRouteResponse, readRouteChannelName, diff --git a/packages/eve/src/public/channels/oauth-resource.test.ts b/packages/eve/src/public/channels/oauth-resource.test.ts index 83d8253738..07533072b9 100644 --- a/packages/eve/src/public/channels/oauth-resource.test.ts +++ b/packages/eve/src/public/channels/oauth-resource.test.ts @@ -1,12 +1,8 @@ +import { readOAuthResourceOptions } from "#channel/auth/definitions.js"; import { describe, expect, it, vi } from "vitest"; import type { SessionAuthContext } from "#channel/types.js"; -import { - oauthResource, - readOAuthResourceOptions, - routeAuth, - type AuthFn, -} from "#public/channels/auth.js"; +import { oauthResource, routeAuth, type AuthFn } from "#public/channels/auth.js"; const principal: SessionAuthContext = { attributes: {}, diff --git a/packages/eve/src/public/channels/slack/index.ts b/packages/eve/src/public/channels/slack/index.ts index 55cf3558df..e5b8d2c002 100644 --- a/packages/eve/src/public/channels/slack/index.ts +++ b/packages/eve/src/public/channels/slack/index.ts @@ -1,11 +1,3 @@ -/** - * AI SDK conversation-message shape that the harness uses as the unit of - * session history and turn input. Re-exported from the `ai` package so - * authors wiring up the Slack channel can type their own message arrays - * without taking a direct `ai` dependency. - */ -export type { ModelMessage } from "ai"; - export { slackChannel, type SlackApiResponse, @@ -74,15 +66,12 @@ export { export { type SlackFetch, type SlackTransportOptions } from "#public/channels/slack/transport.js"; export { defaultSlackAuth } from "#public/channels/slack/defaults.js"; -export { buildSlackAuthContext } from "#public/channels/slack/auth.js"; export { postCompletedSlackReply, type SlackCompletedReply, } from "#public/channels/slack/reply-delivery.js"; -export { SLACK_MARKDOWN_TEXT_MAX_LENGTH } from "#public/channels/slack/limits.js"; - export { defineSlackRenderer, type SlackRenderer, diff --git a/packages/eve/src/public/channels/teams/index.ts b/packages/eve/src/public/channels/teams/index.ts index 5d96ddd5ac..ab8db4ab99 100644 --- a/packages/eve/src/public/channels/teams/index.ts +++ b/packages/eve/src/public/channels/teams/index.ts @@ -1,5 +1,3 @@ -export type { ModelMessage } from "ai"; - /** * Instrumentation metadata for Teams sessions, read from the channel state: the * Teams team/channel id (`channelId`), the conversation type (`personal`, @@ -34,19 +32,7 @@ export { } from "#public/channels/teams/teamsChannel.js"; export { - callTeamsConnectorApi, - normalizeTeamsPostInput, - replyToTeamsActivity, - resolveTeamsAccessToken, - resolveTeamsAppId, - resolveTeamsAppPassword, - resolveTeamsTenantId, - sendTeamsActivity, - splitTeamsMessageText, teamsContinuationToken, - triggerTeamsTypingIndicator, - updateTeamsActivity, - TEAMS_MESSAGE_TEXT_MAX_LENGTH, type TeamsAccessTokenResult, type TeamsApiOptions, type TeamsApiResponse, @@ -65,10 +51,6 @@ export { } from "#public/channels/teams/api.js"; export { - formatTeamsContextBlock, - isTeamsPersonalMessage, - parseTeamsActivity, - teamsThreadRootActivityId, type TeamsActivity, type TeamsActivityBase, type TeamsConversationAccount, @@ -80,41 +62,13 @@ export { } from "#public/channels/teams/inbound.js"; export { - deriveTeamsAnswers, - isTeamsAnswerActivity, - isTeamsToolApprovalResponseActivity, - readTeamsToolApprovalPrompt, - renderAnsweredInteractionMessage, - renderInteractionAttachment, - renderInteractionMessage, - teamsInvokeResponse, - TEAMS_ADAPTIVE_CARD_CONTENT_TYPE, - TEAMS_HITL_CHOICE_INPUT_ID, - TEAMS_HITL_DATA_KEY, - TEAMS_HITL_FREEFORM_INPUT_ID, - TEAMS_HITL_REQUEST_KIND_KEY, - TEAMS_HITL_PROMPT_KEY, -} from "#public/channels/teams/hitl.js"; - -export { - collectTeamsFileParts, - buildTeamsTurnMessage, - createTeamsFetchFile, - normalizeTeamsFilesPolicy, type TeamsFilesConfig, type TeamsFilesPolicy, } from "#public/channels/teams/attachments.js"; -export { - buildAuthCompletedText, - defaultTeamsAuth, - formatConnectionDisplayName, - teamsMentionUser, -} from "#public/channels/teams/defaults.js"; +export { defaultTeamsAuth } from "#public/channels/teams/defaults.js"; export { - verifyTeamsJwt, - verifyTeamsRequest, type TeamsJwtVerifyOptions, type TeamsVerifyOptions, type TeamsWebhookVerifier, diff --git a/packages/eve/src/public/channels/telegram/index.ts b/packages/eve/src/public/channels/telegram/index.ts index fd9dc90d0e..d4dfb015f0 100644 --- a/packages/eve/src/public/channels/telegram/index.ts +++ b/packages/eve/src/public/channels/telegram/index.ts @@ -1,5 +1,3 @@ -export type { ModelMessage } from "ai"; - /** * Per-session instrumentation metadata for the Telegram channel, exposed to * observability tooling. The channel derives `chatId`, `chatType`, and @@ -28,18 +26,7 @@ export { } from "#public/channels/telegram/telegramChannel.js"; export { - callTelegramApi, - answerTelegramCallbackQuery, - downloadTelegramFile, - editTelegramMessageReplyMarkup, - editTelegramMessageText, - getTelegramFile, - resolveTelegramBotToken, - sendTelegramChatAction, - sendTelegramMessage, - splitTelegramMessageText, telegramContinuationToken, - TELEGRAM_MESSAGE_TEXT_MAX_LENGTH, type TelegramApiOptions, type TelegramApiResponse, type TelegramBotToken, @@ -51,8 +38,6 @@ export { } from "#public/channels/telegram/api.js"; export { - formatTelegramContextBlock, - parseTelegramUpdate, type TelegramAttachment, type TelegramCallbackQuery, type TelegramChat, @@ -65,32 +50,13 @@ export { } from "#public/channels/telegram/inbound.js"; export { - TELEGRAM_CALLBACK_RESPONSE_PREFIX, - TELEGRAM_HITL_CALLBACK_PREFIX, - TELEGRAM_REPLY_RESPONSE_PREFIX, - isTelegramSyntheticResponse, - registerTelegramFreeformPrompt, - renderTelegramInteraction, - resolveTelegramAnswers, - telegramCallbackAnswer, - telegramReplyAnswer, type TelegramHitlState, type TelegramInteractionMessage, } from "#public/channels/telegram/hitl.js"; -export { - TELEGRAM_FILE_URL_PROTOCOL, - buildTelegramTurnMessage, - collectTelegramFileParts, - createTelegramFetchFile, - createTelegramFileUrl, -} from "#public/channels/telegram/attachments.js"; - export { defaultTelegramAuth } from "#public/channels/telegram/defaults.js"; export { - resolveTelegramWebhookSecretToken, - verifyTelegramRequest, type TelegramVerifyOptions, type TelegramWebhookSecretToken, type TelegramWebhookVerifier, diff --git a/packages/eve/src/public/channels/twilio/index.ts b/packages/eve/src/public/channels/twilio/index.ts index c1ec698317..3ab7a996e3 100644 --- a/packages/eve/src/public/channels/twilio/index.ts +++ b/packages/eve/src/public/channels/twilio/index.ts @@ -21,11 +21,7 @@ export { } from "#public/channels/twilio/twilioChannel.js"; export { - callTwilioApi, - resolveTwilioAccountSid, - sendTwilioMessage, twilioContinuationToken, - updateTwilioCall, type TwilioAccountSid, type TwilioApiOptions, type TwilioApiResponse, @@ -42,20 +38,9 @@ export type { TwilioVoiceTranscription, } from "#public/channels/twilio/inbound.js"; -export { - emptyTwilioResponse, - escapeXml, - gatherSpeechTwilioResponse, - sayTwilioResponse, - twimlResponse, - type TwilioGatherTwimlOptions, -} from "#public/channels/twilio/twiml.js"; +export { type TwilioGatherTwimlOptions } from "#public/channels/twilio/twiml.js"; export { - buildTwilioSignatureBase, - resolveTwilioAuthToken, - signTwilioRequest, - verifyTwilioRequest, type TwilioAuthToken, type TwilioVerifiedRequest, type TwilioVerifyOptions, diff --git a/packages/eve/src/public/connections/index.ts b/packages/eve/src/public/connections/index.ts index 1694c378a4..bb865f7308 100644 --- a/packages/eve/src/public/connections/index.ts +++ b/packages/eve/src/public/connections/index.ts @@ -5,9 +5,7 @@ export type { AuthorizationCallback, AuthorizationDefinition, - CredentialOwner, ConnectionAuthDefinition, - ConnectionAuthProvider, ConnectionAuthResolver, ConnectionPrincipal, HeadersDefinition, @@ -17,7 +15,6 @@ export type { ToolFilterDefinition, } from "#shared/connection-types.js"; export { defineInteractiveAuthorization } from "#shared/connection-types.js"; -export type { JsonValue } from "#public/types/json.js"; export { defineDynamic, type DynamicConnectionDefinition, diff --git a/packages/eve/src/public/context/index.ts b/packages/eve/src/public/context/index.ts index bc1765414d..67ac4ef2b9 100644 --- a/packages/eve/src/public/context/index.ts +++ b/packages/eve/src/public/context/index.ts @@ -19,3 +19,4 @@ export type { } from "#context/accessors.js"; export { defineState, type StateHandle } from "#public/definitions/state.js"; export type { SessionContext, SessionInfo, SessionSchedule } from "#context/session-context.js"; +export type { HistoryMessage } from "#shared/history-message.js"; diff --git a/packages/eve/src/public/definitions/connections/mcp.test.ts b/packages/eve/src/public/definitions/connections/mcp.test.ts index 41665972df..749f4d76c6 100644 --- a/packages/eve/src/public/definitions/connections/mcp.test.ts +++ b/packages/eve/src/public/definitions/connections/mcp.test.ts @@ -14,10 +14,10 @@ describe("defineMcpClientConnection", () => { expect(definition.auth).toMatchObject({ getToken, principalType: "app" }); }); - it("normalizes credentialOwner into the runtime principalType", () => { + it("normalizes principalType into the runtime principalType", () => { const getToken = async () => ({ token: "test-token" }); const definition = defineMcpClientConnection({ - auth: { credentialOwner: "user", getToken }, + auth: { principalType: "user", getToken }, description: "test connection", url: "https://mcp.example.com", }); diff --git a/packages/eve/src/public/definitions/connections/openapi.test.ts b/packages/eve/src/public/definitions/connections/openapi.test.ts index e01e328054..35aaa52a75 100644 --- a/packages/eve/src/public/definitions/connections/openapi.test.ts +++ b/packages/eve/src/public/definitions/connections/openapi.test.ts @@ -26,10 +26,10 @@ describe("defineOpenAPIConnection", () => { expect(definition.auth).toMatchObject({ getToken, principalType: "app" }); }); - it("normalizes credentialOwner into the runtime principalType", () => { + it("normalizes principalType into the runtime principalType", () => { const getToken = async () => ({ token: "test-token" }); const definition = defineOpenAPIConnection({ - auth: { credentialOwner: "user", getToken }, + auth: { principalType: "user", getToken }, baseUrl: "https://api.example.com", description: "test connection", spec: "https://api.example.com/openapi.json", diff --git a/packages/eve/src/public/definitions/sandbox.ts b/packages/eve/src/public/definitions/sandbox.ts index ccaaf08284..7968758fb4 100644 --- a/packages/eve/src/public/definitions/sandbox.ts +++ b/packages/eve/src/public/definitions/sandbox.ts @@ -10,7 +10,6 @@ export type { SandboxRunOptions, SandboxSession, RuntimeSandboxSession, - RuntimeSandboxSessionFor, SandboxSpawnOptions, SandboxWriteBinaryFileOptions, SandboxWriteFileOptions, diff --git a/packages/eve/src/public/instrumentation/provider.ts b/packages/eve/src/public/instrumentation/provider.ts index 553c754967..7103c7d60d 100644 --- a/packages/eve/src/public/instrumentation/provider.ts +++ b/packages/eve/src/public/instrumentation/provider.ts @@ -1 +1,25 @@ -export * from "#instrumentation/provider.js"; +export { + defineInstrumentation, + disableInstrumentation, + isInstrumentationProvider, + isInstrumentationDisabled, + type InstrumentationProvider, + type InstrumentationDisabled, + type InstrumentationEnvironment, + type EvaluationRef, + type ProviderSetupContext, + type ProviderState, + type ProviderContext, + type Handler, + type ProviderEvents, + type ProviderDefinition, + type InstrumentationScope, + type InstrumentationEvent, + type InstrumentationModelInput, + type InstrumentationParentLineage, + type InstrumentationUsage, + type JsonValue, + type TraceCaptureContext, + type TraceCapturePolicy, + type TracePolicyDecision, +} from "#instrumentation/provider.js"; diff --git a/packages/eve/src/public/memory/index.ts b/packages/eve/src/public/memory/index.ts index a93e48bea7..b8437974a3 100644 --- a/packages/eve/src/public/memory/index.ts +++ b/packages/eve/src/public/memory/index.ts @@ -1,6 +1,6 @@ import { createHash } from "node:crypto"; -import type { ModelMessage } from "ai"; +import type { HistoryMessage as ModelMessage } from "#shared/history-message.js"; import type { SessionAuth } from "#context/keys.js"; import type { Approval } from "#public/definitions/approval.js"; diff --git a/packages/eve/src/public/next/index.test.ts b/packages/eve/src/public/next/index.test.ts index 4c1e0fa598..aa6ee31852 100644 --- a/packages/eve/src/public/next/index.test.ts +++ b/packages/eve/src/public/next/index.test.ts @@ -45,12 +45,9 @@ vi.mock("#internal/nitro/host/workspace-extensions.js", () => ({ const { buildWorkspaceExtensions } = await import("#internal/nitro/host/workspace-extensions.js"); -import { - EVE_NEXT_SERVICE_PREFIX, - withEve, - type EveNextConfig, - type EveNextRewriteSections, -} from "./index.js"; +import { withEve, type EveNextConfig, type EveNextRewriteSections } from "./index.js"; + +const EVE_NEXT_SERVICE_PREFIX = "/_eve_internal/eve"; interface TestConfig extends EveNextConfig { readonly basePath?: string; diff --git a/packages/eve/src/public/next/index.ts b/packages/eve/src/public/next/index.ts index b87f3e6bbb..1ec462de72 100644 --- a/packages/eve/src/public/next/index.ts +++ b/packages/eve/src/public/next/index.ts @@ -22,7 +22,7 @@ import { ensureEveVercelOutputConfig } from "./vercel-output-config.js"; * Default private route namespace for legacy manually configured Vercel * services. {@link WithEveOptions.servicePrefix} defaults to this value. */ -export const EVE_NEXT_SERVICE_PREFIX = "/_eve_internal/eve"; +const EVE_NEXT_SERVICE_PREFIX = "/_eve_internal/eve"; const EVE_NEXT_PRODUCTION_ORIGIN_ENV = "EVE_NEXT_PRODUCTION_ORIGIN"; const EVE_NEXT_PRODUCTION_PORT_ENV = "EVE_NEXT_PRODUCTION_PORT"; @@ -143,8 +143,8 @@ export interface WithEveOptions { readonly eveBuildCommand?: string; /** * Private route namespace for legacy manually configured Vercel services and - * non-Vercel production proxying. Defaults to {@link EVE_NEXT_SERVICE_PREFIX} - * (`/_eve_internal/eve`). `withEve` normalizes the prefix (adds a leading + * non-Vercel production proxying. Defaults to `/_eve_internal/eve`. + * `withEve` normalizes the prefix (adds a leading * slash, strips trailing slashes) and rejects a prefix that resolves to the * root route. */ diff --git a/packages/eve/src/public/sandbox/index.ts b/packages/eve/src/public/sandbox/index.ts index 19a8ce5460..6bcfb517b5 100644 --- a/packages/eve/src/public/sandbox/index.ts +++ b/packages/eve/src/public/sandbox/index.ts @@ -13,7 +13,6 @@ export { type SandboxRunOptions, type SandboxSession, type RuntimeSandboxSession, - type RuntimeSandboxSessionFor, type SandboxSpawnOptions, type SandboxWriteBinaryFileOptions, type SandboxWriteFileOptions, diff --git a/packages/eve/src/public/sandbox/vercel.test.ts b/packages/eve/src/public/sandbox/vercel.test.ts index b5c8440054..c73432936e 100644 --- a/packages/eve/src/public/sandbox/vercel.test.ts +++ b/packages/eve/src/public/sandbox/vercel.test.ts @@ -1,6 +1,6 @@ import { describe, expect, expectTypeOf, it } from "vitest"; import type { SessionContext } from "#context/session-context.js"; -import type { RuntimeSandboxSessionFor } from "#public/definitions/sandbox.js"; +import type { RuntimeSandboxSession } from "#public/definitions/sandbox.js"; import { Drive, VercelSandbox, type VercelSandboxSession } from "#public/sandbox/vercel.js"; describe("VercelSandbox", () => { it("creates environments", () => { @@ -14,7 +14,7 @@ describe("VercelSandbox", () => { const environment = VercelSandbox.environment({}); const assertTypes = (ctx: SessionContext) => { expectTypeOf(ctx.getSandbox(environment)).toEqualTypeOf< - Promise> + Promise> >(); }; diff --git a/packages/eve/src/public/tools/bash.ts b/packages/eve/src/public/tools/bash.ts index 58fb3939e7..50e0f455e8 100644 --- a/packages/eve/src/public/tools/bash.ts +++ b/packages/eve/src/public/tools/bash.ts @@ -1,11 +1,5 @@ import bashDefinition from "#tools/provided/bash.js"; -export { - BASH_INPUT_SCHEMA, - BASH_OUTPUT_SCHEMA, - type BashToolInput, - type BashToolOutput, - bash, -} from "#tools/provided/bash.js"; +export { type BashToolInput, type BashToolOutput, bash } from "#tools/provided/bash.js"; export default bashDefinition; diff --git a/packages/eve/src/public/tools/glob.ts b/packages/eve/src/public/tools/glob.ts index e77cc46914..be5836abc1 100644 --- a/packages/eve/src/public/tools/glob.ts +++ b/packages/eve/src/public/tools/glob.ts @@ -1,11 +1,5 @@ import globDefinition from "#tools/provided/glob.js"; -export { - GLOB_INPUT_SCHEMA, - GLOB_OUTPUT_SCHEMA, - type GlobToolInput, - type GlobToolOutput, - glob, -} from "#tools/provided/glob.js"; +export { type GlobToolInput, type GlobToolOutput, glob } from "#tools/provided/glob.js"; export default globDefinition; diff --git a/packages/eve/src/public/tools/grep.ts b/packages/eve/src/public/tools/grep.ts index 99253f68c2..2cbc189ce9 100644 --- a/packages/eve/src/public/tools/grep.ts +++ b/packages/eve/src/public/tools/grep.ts @@ -1,11 +1,5 @@ import grepDefinition from "#tools/provided/grep.js"; -export { - GREP_INPUT_SCHEMA, - GREP_OUTPUT_SCHEMA, - type GrepToolInput, - type GrepToolOutput, - grep, -} from "#tools/provided/grep.js"; +export { type GrepToolInput, type GrepToolOutput, grep } from "#tools/provided/grep.js"; export default grepDefinition; diff --git a/packages/eve/src/public/tools/index.ts b/packages/eve/src/public/tools/index.ts index ecd9225034..eb6cd5bdb4 100644 --- a/packages/eve/src/public/tools/index.ts +++ b/packages/eve/src/public/tools/index.ts @@ -29,7 +29,6 @@ export type { DynamicToolSet, DynamicToolResult, } from "#tools/dynamic.js"; -export { type SessionContext } from "#public/definitions/callback-context.js"; export { toolResultFrom, type MatchedConnectionResult, diff --git a/packages/eve/src/public/tools/read-file.ts b/packages/eve/src/public/tools/read-file.ts index deeda10388..cfff944683 100644 --- a/packages/eve/src/public/tools/read-file.ts +++ b/packages/eve/src/public/tools/read-file.ts @@ -1,8 +1,6 @@ import readFileDefinition from "#tools/provided/read-file.js"; export { - READ_FILE_INPUT_SCHEMA, - READ_FILE_OUTPUT_SCHEMA, type ReadFileToolInput, type ReadFileToolOutput, readFile, diff --git a/packages/eve/src/public/tools/web-fetch.ts b/packages/eve/src/public/tools/web-fetch.ts index 816e7870a5..016d7cd639 100644 --- a/packages/eve/src/public/tools/web-fetch.ts +++ b/packages/eve/src/public/tools/web-fetch.ts @@ -1,8 +1,6 @@ import webFetchDefinition from "#tools/provided/web-fetch.js"; export { - WEB_FETCH_INPUT_SCHEMA, - WEB_FETCH_OUTPUT_SCHEMA, type WebFetchToolInput, type WebFetchToolOutput, webFetch, diff --git a/packages/eve/src/public/tools/web-search.ts b/packages/eve/src/public/tools/web-search.ts index 117cd14dea..6bf0473e9b 100644 --- a/packages/eve/src/public/tools/web-search.ts +++ b/packages/eve/src/public/tools/web-search.ts @@ -5,8 +5,6 @@ export { type WebSearchProvider, type WebSearchToolDefinition, type WebSearchToolInput, - defaultWebSearch, - isWebSearchToolDefinition, webSearch, } from "#tools/provided/web-search.js"; diff --git a/packages/eve/src/public/tools/write-file.ts b/packages/eve/src/public/tools/write-file.ts index 1be9fcb513..2e251161f4 100644 --- a/packages/eve/src/public/tools/write-file.ts +++ b/packages/eve/src/public/tools/write-file.ts @@ -1,8 +1,6 @@ import writeFileDefinition from "#tools/provided/write-file.js"; export { - WRITE_FILE_INPUT_SCHEMA, - WRITE_FILE_OUTPUT_SCHEMA, type WriteFileToolInput, type WriteFileToolOutput, writeFile, diff --git a/packages/eve/src/react/use-eve-agent.ts b/packages/eve/src/react/use-eve-agent.ts index 21bd869322..f3b5e625b2 100644 --- a/packages/eve/src/react/use-eve-agent.ts +++ b/packages/eve/src/react/use-eve-agent.ts @@ -112,7 +112,7 @@ export interface UseEveAgentOptions extends EveAgentStoreCallbacks { */ readonly optimistic?: boolean; /** Follow each subagent call's session into `data.agents` while mounted. @default false */ - readonly followSubagents?: boolean; + readonly followAgents?: boolean; /** * Keep the session stream open between turns to show events that arrive from schedules, * channels, or other clients. Set to false to close it once the session is idle and reopen it @@ -177,7 +177,7 @@ export function useEveAgent(options: UseEveAgentOptions = {}): UseEveAgentHelper initialEvents: options.initialEvents, initialSession: options.initialSession, optimistic: options.optimistic, - followSubagents: options.followSubagents, + followAgents: options.followAgents, followIdle: options.followIdle, session: options.session, }); diff --git a/packages/eve/src/runtime/connections/validate-authorization.test.ts b/packages/eve/src/runtime/connections/validate-authorization.test.ts index 7ead833e27..203db0f53d 100644 --- a/packages/eve/src/runtime/connections/validate-authorization.test.ts +++ b/packages/eve/src/runtime/connections/validate-authorization.test.ts @@ -22,10 +22,6 @@ describe("validateAuthorizationSpec", () => { expect(validateAuthorizationSpec({ getToken, principalType: "user" })).toBeUndefined(); }); - it("returns undefined for a getToken-only definition with credentialOwner", () => { - expect(validateAuthorizationSpec({ credentialOwner: "user", getToken })).toBeUndefined(); - }); - it("returns undefined for a getToken-only definition without principalType", () => { expect(validateAuthorizationSpec({ getToken })).toBeUndefined(); }); @@ -62,30 +58,6 @@ describe("validateAuthorizationSpec", () => { }); }); - describe("credential owner rejection", () => { - it("rejects an unknown credentialOwner value using the authored field name", () => { - expect(validateAuthorizationSpec({ credentialOwner: "service", getToken })).toMatch( - /"auth\.credentialOwner" field must be "app" or "user"/, - ); - }); - - it("rejects both ownership fields even when they agree", () => { - expect( - validateAuthorizationSpec({ credentialOwner: "user", getToken, principalType: "user" }), - ).toMatch(/must not provide both "credentialOwner" and "principalType"/); - }); - - it("treats an undefined credentialOwner as omitted", () => { - expect( - validateAuthorizationSpec({ - credentialOwner: undefined, - getToken, - principalType: "user", - }), - ).toBeUndefined(); - }); - }); - describe("getToken rejection", () => { it("rejects a missing getToken", () => { expect(validateAuthorizationSpec({ principalType: "app" })).toMatch( @@ -149,17 +121,6 @@ describe("validateAuthorizationSpec", () => { }); }); - it("rejects credentialOwner on interactive authorization", () => { - expect( - validateAuthorizationSpec({ - completeAuthorization, - credentialOwner: "user", - getToken, - startAuthorization, - }), - ).toMatch(/credentialOwner.*only supported by getToken-only authorization/); - }); - describe("displayName validation", () => { it("accepts a non-empty string displayName", () => { expect(validateAuthorizationSpec({ displayName: "Salesforce", getToken })).toBeUndefined(); @@ -196,19 +157,6 @@ describe("normalizeAuthorizationSpec", () => { }); }); - it("normalizes credentialOwner and legacy principalType identically", () => { - const credentialOwner = normalizeAuthorizationSpec( - { credentialOwner: "user", getToken }, - "testPrefix:", - ); - const principalType = normalizeAuthorizationSpec( - { getToken, principalType: "user" }, - "testPrefix:", - ); - - expect(credentialOwner).toEqual(principalType); - }); - it("carries displayName through the non-interactive branch", () => { expect( normalizeAuthorizationSpec({ displayName: "Salesforce", getToken }, "testPrefix:"), diff --git a/packages/eve/src/sandbox/providers.test.ts b/packages/eve/src/sandbox/providers.test.ts index a208495b31..b54cb6834e 100644 --- a/packages/eve/src/sandbox/providers.test.ts +++ b/packages/eve/src/sandbox/providers.test.ts @@ -3,7 +3,7 @@ import { describe, expect, expectTypeOf, it } from "vitest"; import { defineDefaultSandboxProvider } from "#sandbox/providers/default.js"; import { DockerSandbox, MicrosandboxSandbox, VercelSandbox } from "#sandbox/providers.js"; -import type { RuntimeSandboxSessionFor } from "#shared/sandbox-session.js"; +import type { RuntimeSandboxSession } from "#shared/sandbox-session.js"; describe("sandbox providers", () => { it("implements default selection through the provider contract", () => { @@ -28,7 +28,7 @@ describe("sandbox providers", () => { it("returns runtime sandboxes from open", () => { const environment = DockerSandbox.environment({}); expectTypeOf(environment.open).returns.toEqualTypeOf< - Promise> + Promise> >(); }); }); diff --git a/packages/eve/src/self-modification/authored-runtime-boundary.scenario.test.ts b/packages/eve/src/self-modification/authored-runtime-boundary.scenario.test.ts index 9b779784b7..abd5213cb5 100644 --- a/packages/eve/src/self-modification/authored-runtime-boundary.scenario.test.ts +++ b/packages/eve/src/self-modification/authored-runtime-boundary.scenario.test.ts @@ -17,7 +17,7 @@ describe("self-modification authored runtime boundary", () => { expect(code).not.toContain("createJustBashSandboxBackend"); expect(code).not.toContain("loadJustBashModule"); if (entry === "sandbox") { - expect(code).toContain('from "eve/self-modification/sandbox"'); + expect(code).toContain('from "eve/internal/self-modification/sandbox"'); } else { expect(code).toContain('from "eve"'); } diff --git a/packages/eve/src/self-modification/extension/extension.ts b/packages/eve/src/self-modification/extension/extension.ts index 11099fcd28..0684af90b6 100644 --- a/packages/eve/src/self-modification/extension/extension.ts +++ b/packages/eve/src/self-modification/extension/extension.ts @@ -10,3 +10,5 @@ const config: StandardSchemaV1 = selfModificati /** Extension mount configured with the same policy as the agent and sandbox. */ export default defineExtension({ config }); + +export type { SelfModificationExtensionConfig } from "../config.js"; diff --git a/packages/eve/src/self-modification/extension/subagents/agent/sandbox.ts b/packages/eve/src/self-modification/extension/subagents/agent/sandbox.ts index 9d60f8ebc2..3114af2ae7 100644 --- a/packages/eve/src/self-modification/extension/subagents/agent/sandbox.ts +++ b/packages/eve/src/self-modification/extension/subagents/agent/sandbox.ts @@ -1,6 +1,6 @@ -import { defineSelfModificationSandbox } from "eve/self-modification/sandbox"; +import { defineSelfModificationSandbox } from "eve/internal/self-modification/sandbox"; import selfModification from "../../extension.js"; -export { defineSelfModificationSandbox } from "eve/self-modification/sandbox"; +export { defineSelfModificationSandbox } from "eve/internal/self-modification/sandbox"; export default defineSelfModificationSandbox({ config: selfModification.config }); diff --git a/packages/eve/src/self-modification/migration.integration.test.ts b/packages/eve/src/self-modification/migration.integration.test.ts index 8ea3249ecf..543b19bf26 100644 --- a/packages/eve/src/self-modification/migration.integration.test.ts +++ b/packages/eve/src/self-modification/migration.integration.test.ts @@ -1,55 +1,38 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; -import { join, relative } from "node:path"; +import { join } from "node:path"; import { tmpdir } from "node:os"; -import { afterEach, describe, expect, it, vi } from "vitest"; - +import { afterEach, expect, it, vi } from "vitest"; import { createFakePrompter } from "#internal/testing/fake-prompter.js"; import type { SelfModificationSetupOperations } from "#self-modification/setup.js"; -import { headlessAsker, interactiveAsker, withAnswers } from "#setup/ask.js"; +import { headlessAsker, withAnswers } from "#setup/ask.js"; import { integrationSetupEnvironment } from "#setup/integrations/shared/environment.js"; import { createSetupContexts } from "#setup/integrations/shared/ui.js"; -import { - applySelfModificationSetup, - prepareLocalSelfModificationSetup, -} from "#setup/integrations/self-modification/setup.js"; - -import { - detectLegacySelfModificationScaffold, - removeLegacySelfModificationScaffold, -} from "./migration.js"; +import { prepareLocalSelfModificationSetup } from "#setup/integrations/self-modification/setup.js"; const roots: string[] = []; -const legacy = { - "agent.ts": - 'import { defineSelfModificationAgent } from "eve/self-modification/agent";\n\nimport config from "./config";\n\nexport default defineSelfModificationAgent({\n config,\n\n // To use a specific model instead of eve\'s default, add:\n // model: "provider/model",\n});\n', - "config.ts": - 'import { defineSelfModificationConfig } from "eve/self-modification/config";\n\nexport default defineSelfModificationConfig({});\n', - "sandbox.ts": - 'import { defineSelfModificationSandbox } from "eve/self-modification/sandbox";\n\nimport config from "./config";\n\nexport default defineSelfModificationSandbox({ config });\n', - "extensions/selfmod.ts": - 'import selfModification from "eve/self-modification";\nimport config from "../config";\n\nexport default selfModification(config);\n', -}; - -async function scaffold(custom = false): Promise { - const root = await mkdtemp(join(tmpdir(), "eve-self-modification-migration-")); - roots.push(root); - for (const [path, source] of Object.entries(legacy)) { - await mkdir(join(root, "agent/subagents/self-modification", path, ".."), { recursive: true }); - await writeFile( - join(root, "agent/subagents/self-modification", path), - custom && path === "config.ts" ? `${source}// custom\n` : source, - "utf8", - ); - } - return root; -} - afterEach(async () => { await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); -function setupOperations(): SelfModificationSetupOperations { - return { +it("rejects the retired layout with mount guidance without deleting authored files", async () => { + const root = await mkdtemp(join(tmpdir(), "eve-retired-self-modification-")); + roots.push(root); + const directory = join(root, "agent/subagents/self-modification"); + await mkdir(directory, { recursive: true }); + await writeFile( + join(directory, "agent.ts"), + 'import { defineSelfModificationAgent } from "eve/self-modification/agent";\n', + ); + const notes = "Alice's authored deployment notes"; + await writeFile(join(directory, "notes.md"), notes); + const contexts = createSetupContexts({ + appRoot: root, + asker: withAnswers({ "self-modification-cleanup": true })(headlessAsker()), + environment: integrationSetupEnvironment("unavailable", { kind: "unresolved" }), + prompter: createFakePrompter().prompter, + resolveVercelProject: async () => ({ orgId: "team", projectId: "project" }), + }); + const operations: SelfModificationSetupOperations = { attachConnector: vi.fn(), detectChannelNames: vi.fn(async () => []), detectGitRepository: vi.fn(async () => ({ remoteKind: "missing" as const })), @@ -58,90 +41,9 @@ function setupOperations(): SelfModificationSetupOperations { readLocalConfig: vi.fn(async () => undefined), writeConfig: vi.fn(), }; -} - -describe("self-modification scaffold migration", () => { - it("recognizes the default scaffold without evaluating consumer code", async () => { - const root = await scaffold(); - await expect(detectLegacySelfModificationScaffold(root)).resolves.toMatchObject({ - customized: false, - }); - }); - - it("marks changed configuration and extra files as customized", async () => { - const root = await scaffold(true); - await writeFile(join(root, "agent/subagents/self-modification/notes.md"), "custom", "utf8"); - await expect(detectLegacySelfModificationScaffold(root)).resolves.toMatchObject({ - customized: true, - }); - }); - - it("rechecks and removes the scaffold without writing the extension", async () => { - const root = await scaffold(); - const detected = await detectLegacySelfModificationScaffold(root); - if (detected === undefined) throw new Error("Expected legacy scaffold"); - await removeLegacySelfModificationScaffold(root, detected); - await expect( - readFile(join(root, "agent/extensions/self-modification/extension.ts"), "utf8"), - ).rejects.toMatchObject({ code: "ENOENT" }); - await expect(detectLegacySelfModificationScaffold(root)).resolves.toBeUndefined(); - }); - - it("defaults to removing a legacy scaffold without reporting customizations", async () => { - const root = await scaffold(true); - const fake = createFakePrompter({ - single: (options) => { - expect(options).toMatchObject({ - message: `A legacy self-modification scaffold was found at ${relative(root, join(root, "agent/subagents/self-modification"))}. This scaffold format is no longer supported. Do you want to remove it?`, - initialValue: "yes", - options: [ - { value: "yes", label: "Yes" }, - { value: "no", label: "No" }, - ], - }); - return "yes"; - }, - }); - const contexts = createSetupContexts({ - appRoot: root, - asker: interactiveAsker(fake.prompter), - environment: integrationSetupEnvironment("unavailable", { kind: "unresolved" }), - prompter: fake.prompter, - resolveVercelProject: async () => ({ orgId: "team", projectId: "project" }), - }); - - const plan = await prepareLocalSelfModificationSetup(contexts.prepare, setupOperations()); - await applySelfModificationSetup(plan, contexts.apply, setupOperations()); - - await expect(detectLegacySelfModificationScaffold(root)).resolves.toBeUndefined(); - expect(fake.prompter.log.success).toHaveBeenCalledWith( - "Removed the retired self-modification scaffold.", - ); - }); - - it("keeps a declined legacy scaffold while continuing setup", async () => { - const root = await scaffold(); - const contexts = createSetupContexts({ - appRoot: root, - asker: withAnswers({ "self-modification-cleanup": false })(headlessAsker()), - environment: integrationSetupEnvironment("unavailable", { kind: "unresolved" }), - prompter: createFakePrompter().prompter, - resolveVercelProject: async () => ({ orgId: "team", projectId: "project" }), - }); - - const plan = await prepareLocalSelfModificationSetup(contexts.prepare, setupOperations()); - await applySelfModificationSetup(plan, contexts.apply, setupOperations()); - - await expect(detectLegacySelfModificationScaffold(root)).resolves.toBeDefined(); - }); - - it("rejects concurrent changes", async () => { - const root = await scaffold(); - const detected = await detectLegacySelfModificationScaffold(root); - if (detected === undefined) throw new Error("Expected legacy scaffold"); - await writeFile(join(root, "agent/subagents/self-modification/config.ts"), "changed", "utf8"); - await expect(removeLegacySelfModificationScaffold(root, detected)).rejects.toThrow( - /changed while setup/, - ); - }); + await expect(prepareLocalSelfModificationSetup(contexts.prepare, operations)).rejects.toThrow( + /agent\/subagents\/self-modification.*eve\/self-modification\/local/, + ); + expect(await readFile(join(directory, "notes.md"), "utf8")).toBe(notes); + expect(operations.writeConfig).not.toHaveBeenCalled(); }); diff --git a/packages/eve/src/self-modification/migration.ts b/packages/eve/src/self-modification/migration.ts deleted file mode 100644 index 8e8eac1b4c..0000000000 --- a/packages/eve/src/self-modification/migration.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { readdir, readFile, rm } from "node:fs/promises"; -import { join, relative } from "node:path"; - -const LEGACY_SELF_MODIFICATION_ROOT = "agent/subagents/self-modification"; -const LEGACY_FILES = ["agent.ts", "config.ts", "sandbox.ts", "extensions/selfmod.ts"] as const; -const LEGACY_DEFAULTS: Readonly> = { - "agent.ts": [ - 'import { defineSelfModificationAgent } from "eve/self-modification/agent";\n\nimport config from "./config";\n\nexport default defineSelfModificationAgent({\n config,\n\n // To use a specific model instead of eve\'s default, add:\n // model: "provider/model",\n});\n', - ], - "sandbox.ts": [ - 'import { defineSelfModificationSandbox } from "eve/self-modification/sandbox";\n\nimport config from "./config";\n\nexport default defineSelfModificationSandbox({ config });\n', - ], - "config.ts": [ - 'import { defineSelfModificationConfig } from "eve/self-modification/config";\n\nexport default defineSelfModificationConfig({});\n', - 'import { defineSelfModificationConfig } from "eve/self-modification/config";\n\nexport default defineSelfModificationConfig({\n local: { enabled: true },\n});\n', - ], - "extensions/selfmod.ts": [ - 'import selfModification from "eve/self-modification";\nimport config from "../config";\n\nexport default selfModification(config);\n', - ], -}; - -export interface LegacySelfModificationScaffold { - readonly root: string; - readonly paths: readonly string[]; - readonly customized: boolean; - readonly signature: string; -} - -/** Reads only the bounded legacy scaffold; consumer modules are never imported or executed. */ -export async function detectLegacySelfModificationScaffold( - appRoot: string, -): Promise { - const root = join(appRoot, LEGACY_SELF_MODIFICATION_ROOT); - const paths = await listFiles(root); - if (!paths.includes("agent.ts")) return undefined; - const agent = await readText(join(root, "agent.ts")); - if (agent === undefined || !agent.includes("defineSelfModificationAgent")) return undefined; - - const expected = new Set(LEGACY_FILES); - const signature = JSON.stringify( - await Promise.all(paths.map(async (path) => [path, await readText(join(root, path))] as const)), - ); - const customized = - paths.some((path) => !expected.has(path as (typeof LEGACY_FILES)[number])) || - ( - await Promise.all( - Object.entries(LEGACY_DEFAULTS).map(async ([path, contents]) => { - const actual = await readText(join(root, path)); - return actual === undefined || !contents.includes(actual); - }), - ) - ).some(Boolean) || - !agent.includes('from "eve/self-modification/agent"') || - (await readText(join(root, "sandbox.ts")))?.includes('from "eve/self-modification/sandbox"') !== - true; - - return { - root, - paths: paths.map((path) => join(LEGACY_SELF_MODIFICATION_ROOT, path)), - customized, - signature, - }; -} - -export async function removeLegacySelfModificationScaffold( - appRoot: string, - scaffold: LegacySelfModificationScaffold, -): Promise { - const current = await detectLegacySelfModificationScaffold(appRoot); - if ( - current === undefined || - current.root !== scaffold.root || - current.customized !== scaffold.customized || - current.signature !== scaffold.signature - ) { - throw new Error( - "The self-modification scaffold changed while setup was waiting for cleanup approval.", - ); - } - await rm(scaffold.root, { recursive: true, force: true }); -} - -async function listFiles(root: string): Promise { - try { - const entries = await readdir(root, { recursive: true, withFileTypes: true }); - return entries - .filter((entry) => entry.isFile()) - .map((entry) => relative(root, join(entry.parentPath, entry.name)).replaceAll("\\", "/")) - .sort(); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === "ENOENT") return []; - throw error; - } -} - -async function readText(path: string): Promise { - try { - return await readFile(path, "utf8"); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined; - throw error; - } -} diff --git a/packages/eve/src/self-modification/setup.test.ts b/packages/eve/src/self-modification/setup.test.ts index 1335feebf4..2a4cbaceaf 100644 --- a/packages/eve/src/self-modification/setup.test.ts +++ b/packages/eve/src/self-modification/setup.test.ts @@ -32,7 +32,7 @@ function evaluateGeneratedConfig(source: string, getToken = vi.fn()) { } describe("self-modification setup", () => { - it("recognizes default local configurations", () => { + it("recognizes the current local mount without migrating retired configuration", () => { expect(renderSelfModificationConfig()).toContain( 'import selfModification from "eve/self-modification/local";', ); @@ -45,12 +45,12 @@ describe("self-modification setup", () => { '"eve/self-modification"', ), ), - ).toBe("local"); + ).toBe("authored"); expect( classifySelfModificationConfig( 'import { defineSelfModificationConfig } from "eve/self-modification/config";\n\nexport default defineSelfModificationConfig({});\n', ), - ).toBe("local"); + ).toBe("authored"); }); it("renders a generated Connect-backed deployed configuration", () => { diff --git a/packages/eve/src/self-modification/setup.ts b/packages/eve/src/self-modification/setup.ts index 8986dca868..a2ef3f387e 100644 --- a/packages/eve/src/self-modification/setup.ts +++ b/packages/eve/src/self-modification/setup.ts @@ -21,13 +21,7 @@ export const DEPLOYED_SELF_MODIFICATION_CONFIG_PATH = const runFile = promisify(execFile); const GENERATED_MARKER = "// eve-self-modification: generated-v1"; -const LEGACY_LOCAL_CONFIG = - 'import { defineSelfModificationConfig } from "eve/self-modification/config";\n\nexport default defineSelfModificationConfig({});\n'; const DEFAULT_EXTENSION = renderLocalSelfModificationExtension(); -const LEGACY_DEFAULT_EXTENSION = DEFAULT_EXTENSION.replace( - '"eve/self-modification/local"', - '"eve/self-modification"', -); export interface SelfModificationSetupValues { readonly branch: string; @@ -147,13 +141,7 @@ export function classifySelfModificationConfig( source: string | undefined, ): "missing" | "local" | "generated" | "authored" { if (source === undefined) return "missing"; - if ( - source === DEFAULT_EXTENSION || - source === LEGACY_DEFAULT_EXTENSION || - source === LEGACY_LOCAL_CONFIG - ) { - return "local"; - } + if (source === DEFAULT_EXTENSION) return "local"; const [marker, ...body] = source.split("\n"); const match = /^\/\/ eve-self-modification: generated-v1 digest:([a-f0-9]{64})$/u.exec( marker ?? "", diff --git a/packages/eve/src/setup/integrations/self-modification/setup.ts b/packages/eve/src/setup/integrations/self-modification/setup.ts index c696831b11..0d62670ff2 100644 --- a/packages/eve/src/setup/integrations/self-modification/setup.ts +++ b/packages/eve/src/setup/integrations/self-modification/setup.ts @@ -1,4 +1,5 @@ -import { relative } from "node:path"; +import { access } from "node:fs/promises"; +import { join } from "node:path"; import { confirm, select, text } from "#setup/ask.js"; import { @@ -14,11 +15,6 @@ import { type SelfModificationSetupOperations, type SelfModificationSetupValues, } from "#self-modification/setup.js"; -import { - detectLegacySelfModificationScaffold, - removeLegacySelfModificationScaffold, - type LegacySelfModificationScaffold, -} from "#self-modification/migration.js"; import type { VercelProjectReference } from "#setup/project-resolution.js"; import { ensurePackageDependencies } from "#setup/scaffold/index.js"; import { @@ -49,7 +45,7 @@ const SELF_MODIFICATION_PRODUCTION_DEPENDENCIES = { microsandbox: DEFAULT_MICROSANDBOX_PACKAGE_VERSION, }; -type SelfModificationSetupPlan = ( +type SelfModificationSetupPlan = | { readonly kind: "authored" } | { readonly kind: "local" } | { @@ -57,35 +53,22 @@ type SelfModificationSetupPlan = ( readonly connectorName: string; readonly project: VercelProjectReference; readonly values: SelfModificationSetupValues; - } -) & { readonly legacyScaffold?: LegacySelfModificationScaffold }; - -function validationResult(error: string | undefined): string | null { - return error ?? null; -} - -async function prepareLegacyScaffoldCleanup( - context: SetupPrepareContext, -): Promise { - const scaffold = await detectLegacySelfModificationScaffold(context.appRoot); - if (scaffold === undefined) return undefined; + }; - const approved = await context.asker.ask( - confirm({ - key: "self-modification-cleanup", - message: `A legacy self-modification scaffold was found at ${relative(context.appRoot, scaffold.root)}. This scaffold format is no longer supported. Do you want to remove it?`, - recommended: true, - required: true, - }), +async function assertCurrentLayout(appRoot: string): Promise { + try { + await access(join(appRoot, "agent/subagents/self-modification")); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return; + throw error; + } + throw new Error( + "The legacy agent/subagents/self-modification layout is no longer supported. Preserve any authored changes, remove that directory, and mount eve/self-modification/local at agent/extensions/self-modification/extension.ts instead.", ); - return approved ? scaffold : undefined; } -function withLegacyScaffold( - plan: SelfModificationSetupPlan, - legacyScaffold: LegacySelfModificationScaffold | undefined, -): SelfModificationSetupPlan { - return legacyScaffold === undefined ? plan : { ...plan, legacyScaffold }; +function validationResult(error: string | undefined): string | null { + return error ?? null; } export async function prepareSelfModificationSetup( @@ -96,7 +79,7 @@ export async function prepareSelfModificationSetup( context.projectRoot, ), ): Promise { - const legacyScaffold = await prepareLegacyScaffoldCleanup(context); + await assertCurrentLayout(context.appRoot); const existing = await operations.readConfig(); if (classifySelfModificationConfig(existing) === "authored") { context.presenter.note( @@ -104,7 +87,7 @@ export async function prepareSelfModificationSetup( "Manual update required", { tone: "warning" }, ); - return withLegacyScaffold({ kind: "authored" }, legacyScaffold); + return { kind: "authored" }; } const mode = await context.asker.ask( @@ -129,7 +112,7 @@ export async function prepareSelfModificationSetup( required: true, }), ); - if (mode === "local") return withLegacyScaffold({ kind: "local" }, legacyScaffold); + if (mode === "local") return { kind: "local" }; const [project, detected, channelNames] = await Promise.all([ context.resolveVercelProject("self-modification"), @@ -197,10 +180,7 @@ export async function prepareSelfModificationSetup( required: true, }), ); - return withLegacyScaffold( - confirmed ? { kind: "deployed", connectorName: name, project, values } : { kind: "local" }, - legacyScaffold, - ); + return confirmed ? { kind: "deployed", connectorName: name, project, values } : { kind: "local" }; } export async function applySelfModificationSetup( @@ -213,10 +193,6 @@ export async function applySelfModificationSetup( ), deps: SelfModificationApplyDependencies = defaultApplyDependencies, ) { - if (plan.legacyScaffold !== undefined) { - await removeLegacySelfModificationScaffold(context.appRoot, plan.legacyScaffold); - context.presenter.log.success("Removed the retired self-modification scaffold."); - } if (plan.kind === "authored") { return { facts: [{ label: "Self-modification", value: "manual configuration update required" }], @@ -265,7 +241,7 @@ export async function prepareLocalSelfModificationSetup( context.projectRoot, ), ): Promise { - const legacyScaffold = await prepareLegacyScaffoldCleanup(context); + await assertCurrentLayout(context.appRoot); const existing = await operations.readLocalConfig(); if (classifySelfModificationConfig(existing) === "authored") { context.presenter.note( @@ -273,9 +249,9 @@ export async function prepareLocalSelfModificationSetup( "Manual update required", { tone: "warning" }, ); - return withLegacyScaffold({ kind: "authored" }, legacyScaffold); + return { kind: "authored" }; } - return withLegacyScaffold({ kind: "local" }, legacyScaffold); + return { kind: "local" }; } function describeEnvironment(environment: SetupPrepareContext["environment"]): string { diff --git a/packages/eve/src/shared/connection-types.ts b/packages/eve/src/shared/connection-types.ts index c50d1cfae0..c1c3cbce1d 100644 --- a/packages/eve/src/shared/connection-types.ts +++ b/packages/eve/src/shared/connection-types.ts @@ -165,38 +165,6 @@ export type AuthorizationDefinition = | NonInteractiveAuthorizationDefinition | InteractiveAuthorizationDefinition; -/** Declares whether a connection credential belongs to the agent or end user. */ -export type CredentialOwner = "app" | "user"; - -type NonInteractiveCredentialOwner = - | { - readonly credentialOwner?: CredentialOwner; - readonly principalType?: never; - } - | { - readonly credentialOwner?: never; - /** - * @deprecated Use `credentialOwner` for connection credential ownership. - * Session auth continues to use `principalType`. - */ - readonly principalType?: CredentialOwner; - }; - -/** - * Auth provider returned directly from or resolved by a connection's `auth` - * field. - * - * Identical to {@link AuthorizationDefinition} except the - * non-interactive form may omit `credentialOwner`; normalization - * defaults it to `"app"`. Its legacy `principalType` remains supported - * and deprecated for compatibility. Interactive definitions continue to - * use `principalType: "user"`. The resolved token is sent as - * `Authorization: Bearer `. - */ -export type ConnectionAuthProvider = - | (Omit & NonInteractiveCredentialOwner) - | AuthorizationDefinition; - /** * Resolves a connection auth provider from the active turn. * @@ -206,14 +174,14 @@ export type ConnectionAuthProvider = */ export type ConnectionAuthResolver = ( ctx: SessionContext, -) => ConnectionAuthProvider | Promise; +) => AuthorizationDefinition | Promise; /** * Protocol-agnostic `auth` shape accepted by every connection `define*` * factory. Pass a provider directly for static configuration, or a resolver * to select the provider from the current caller's session context. */ -export type ConnectionAuthDefinition = ConnectionAuthProvider | ConnectionAuthResolver; +export type ConnectionAuthDefinition = AuthorizationDefinition | ConnectionAuthResolver; /** * Fields shared by every {@link AuthorizationDefinition} shape. @@ -235,7 +203,7 @@ interface AuthorizationDefinitionBase { * no authenticated user. The token cache keys on a JSON tuple of * the issuer and id so concurrent users never share tokens. */ - readonly principalType: "app" | "user"; + readonly principalType?: "app" | "user"; /** * Optional metadata marker attached by `connect()` from diff --git a/packages/eve/src/shared/history-message.ts b/packages/eve/src/shared/history-message.ts new file mode 100644 index 0000000000..b48e490070 --- /dev/null +++ b/packages/eve/src/shared/history-message.ts @@ -0,0 +1,235 @@ +// Shapes adapted from @ai-sdk/provider-utils 5.0.58 (Apache-2.0). +// Kept locally so authoring types do not depend on the app's AI SDK version. + +type JSONValue = + | null + | boolean + | number + | string + | readonly JSONValue[] + | { readonly [key: string]: JSONValue | undefined }; +type ProviderOptions = Record; +type DataContent = string | Uint8Array | ArrayBuffer; +type ProviderReference = Record & { type?: never }; +type FileDataData = { type: "data"; data: DataContent }; +type FileDataUrl = { type: "url"; url: URL; originalUrl?: string }; +type FileData = + | FileDataData + | FileDataUrl + | { type: "reference"; reference: ProviderReference } + | { type: "text"; text: string }; + +interface TextPart { + type: "text"; + text: string; + providerOptions?: ProviderOptions; +} + +interface ImagePart { + type: "image"; + image: DataContent | URL | ProviderReference; + mediaType?: string; + providerOptions?: ProviderOptions; +} + +interface FilePart { + type: "file"; + data: FileData | DataContent | URL | ProviderReference; + filename?: string; + mediaType: string; + providerOptions?: ProviderOptions; +} + +interface ReasoningPart { + type: "reasoning"; + text: string; + providerOptions?: ProviderOptions; +} + +interface CustomPart { + type: "custom"; + kind: `${string}.${string}`; + providerOptions?: ProviderOptions; +} + +interface ReasoningFilePart { + type: "reasoning-file"; + data: FileDataData | FileDataUrl | DataContent | URL; + mediaType: string; + providerOptions?: ProviderOptions; +} + +interface ToolCallPart { + type: "tool-call"; + toolCallId: string; + toolName: string; + input: unknown; + providerOptions?: ProviderOptions; + providerExecuted?: boolean; +} + +interface ToolResultPart { + type: "tool-result"; + toolCallId: string; + toolName: string; + output: ToolResultOutput; + providerOptions?: ProviderOptions; +} + +type ToolResultOutput = + | { + type: "text"; + value: string; + providerOptions?: ProviderOptions; + } + | { + type: "json"; + value: JSONValue; + providerOptions?: ProviderOptions; + } + | { + type: "execution-denied"; + reason?: string; + providerOptions?: ProviderOptions; + } + | { + type: "error-text"; + value: string; + providerOptions?: ProviderOptions; + } + | { + type: "error-json"; + value: JSONValue; + providerOptions?: ProviderOptions; + } + | { + type: "content"; + value: Array< + | { + type: "text"; + text: string; + providerOptions?: ProviderOptions; + } + | { + type: "file"; + data: FileData; + mediaType: string; + filename?: string; + providerOptions?: ProviderOptions; + } + | { + type: "file-data"; + data: string; + mediaType: string; + filename?: string; + providerOptions?: ProviderOptions; + } + | { + type: "file-url"; + url: string; + mediaType?: string; + providerOptions?: ProviderOptions; + } + | { + type: "file-id"; + fileId: string | Record; + providerOptions?: ProviderOptions; + } + | { + type: "file-reference"; + providerReference: ProviderReference; + providerOptions?: ProviderOptions; + } + | { + type: "image-data"; + data: string; + mediaType: string; + providerOptions?: ProviderOptions; + } + | { + type: "image-url"; + url: string; + providerOptions?: ProviderOptions; + } + | { + type: "image-file-id"; + fileId: string | Record; + providerOptions?: ProviderOptions; + } + | { + type: "image-file-reference"; + providerReference: ProviderReference; + providerOptions?: ProviderOptions; + } + | { + type: "custom"; + providerOptions?: ProviderOptions; + } + >; + }; + +type ToolApprovalRequest = { + type: "tool-approval-request"; + approvalId: string; + toolCallId: string; + reason?: string; + isAutomatic?: boolean; + signature?: string; + inputSchemaInput?: unknown; +}; + +type ToolApprovalResponse = { + type: "tool-approval-response"; + approvalId: string; + approved: boolean; + reason?: string; + providerExecuted?: boolean; +}; + +type SystemModelMessage = { + role: "system"; + content: string; + providerOptions?: ProviderOptions; +}; + +type UserModelMessage = { + role: "user"; + content: UserContent; + providerOptions?: ProviderOptions; +}; + +type UserContent = string | Array; + +type AssistantModelMessage = { + role: "assistant"; + content: AssistantContent; + providerOptions?: ProviderOptions; +}; + +type AssistantContent = + | string + | Array< + | TextPart + | CustomPart + | FilePart + | ReasoningPart + | ReasoningFilePart + | ToolCallPart + | ToolResultPart + | ToolApprovalRequest + >; + +type ToolModelMessage = { + role: "tool"; + content: ToolContent; + providerOptions?: ProviderOptions; +}; + +type ToolContent = Array; + +/** One system, user, assistant, or tool message in an agent's history. */ +export type HistoryMessage = + | SystemModelMessage + | UserModelMessage + | AssistantModelMessage + | ToolModelMessage; diff --git a/packages/eve/src/shared/sandbox-environment.ts b/packages/eve/src/shared/sandbox-environment.ts index cc04dedd46..ce1072275b 100644 --- a/packages/eve/src/shared/sandbox-environment.ts +++ b/packages/eve/src/shared/sandbox-environment.ts @@ -3,11 +3,7 @@ import { createHash } from "node:crypto"; import { ContextContainer, contextStorage, type AlsContext } from "#context/container.js"; import type { SessionAuth, SessionParent, SessionTurn } from "#context/keys.js"; import type { SandboxProviderRuntime } from "#shared/sandbox-provider.js"; -import type { - RuntimeSandboxSession, - RuntimeSandboxSessionFor, - SandboxSession, -} from "#shared/sandbox-session.js"; +import type { RuntimeSandboxSession, SandboxSession } from "#shared/sandbox-session.js"; const ENVIRONMENT = Symbol.for("eve.sandbox-environment"); const PROVIDER_RUNTIME = Symbol.for("eve.sandbox-provider-runtime"); @@ -44,7 +40,7 @@ export interface SandboxEnvironment< Options extends object | undefined = object, Session extends SandboxSession = SandboxSession, > extends SandboxEnvironmentIdentity { - open(...args: SandboxOpenArguments): Promise>; + open(...args: SandboxOpenArguments): Promise>; } interface ConstructorRuntime { @@ -93,7 +89,7 @@ export function createSandboxEnvironment< environmentConfigurationHash: environment[CONFIGURATION_HASH], options, provider: input.runtime, - })) as RuntimeSandboxSessionFor; + })) as RuntimeSandboxSession; }, }; return environment; diff --git a/packages/eve/src/shared/sandbox-session.ts b/packages/eve/src/shared/sandbox-session.ts index 3184e24f49..0425183122 100644 --- a/packages/eve/src/shared/sandbox-session.ts +++ b/packages/eve/src/shared/sandbox-session.ts @@ -1,74 +1,67 @@ -import type { Experimental_SandboxSession as AiSdkSandbox } from "ai"; - import type { SandboxDeleteOptions } from "#shared/sandbox-provider.js"; -/** - * Options for running one command in a sandbox. Shape mirrors the AI - * SDK {@link AiSdkSandbox} `run` argument so authored code that targets - * either surface uses the same call shape. - */ -export type SandboxRunOptions = Parameters[0]; +/** Options for running a command in the sandbox's working directory. */ +export interface SandboxRunOptions { + command: string; + workingDirectory?: string; + /** Per-command environment overrides; avoids embedding credentials in command strings. */ + env?: Record; + abortSignal?: AbortSignal; +} -/** - * Serializable result returned after running one sandbox command. - */ -export type SandboxCommandResult = Awaited>; +/** Result of a completed sandbox command. */ +export interface SandboxCommandResult { + exitCode: number; + stdout: string; + stderr: string; +} -/** - * Options for spawning one long-running process in a sandbox. Shape - * mirrors the AI SDK {@link AiSdkSandbox} `spawn` argument. - */ -export type SandboxSpawnOptions = Parameters[0]; +/** Options for starting a process without waiting for it to exit. */ +export interface SandboxSpawnOptions extends SandboxRunOptions {} -/** - * Handle to a long-running process spawned via {@link SandboxSession.spawn}. - * Mirrors the AI SDK `Experimental_SandboxProcess` type. - */ -export type SandboxProcess = Awaited>; +/** A running process with byte streams and explicit lifecycle operations. */ +export interface SandboxProcess { + readonly pid?: number; + readonly stdout: ReadableStream; + readonly stderr: ReadableStream; + wait(): PromiseLike<{ exitCode: number }>; + kill(): PromiseLike; +} -/** - * Options for reading one file as a stream of bytes. - */ -export type SandboxReadFileOptions = Parameters[0]; +/** Options for reading a file; relative paths resolve from `/workspace`. */ +export interface SandboxReadFileOptions { + path: string; + abortSignal?: AbortSignal; +} + +/** Options for reading all bytes of a file. */ +export interface SandboxReadBinaryFileOptions extends SandboxReadFileOptions {} -/** - * Options for reading one file as raw bytes. - */ -export type SandboxReadBinaryFileOptions = Parameters[0]; +/** Text decoding and 1-based inclusive line selection. Missing files return `null`. */ +export interface SandboxReadTextFileOptions extends SandboxReadFileOptions { + encoding?: string; + startLine?: number; + endLine?: number; +} -/** - * Options for reading one text file from a sandbox. - * - * `encoding`, `startLine`, and `endLine` are passed through to the - * public-surface decoder. `"utf-8"` decodes with `TextDecoder` in fatal - * mode; other encodings fall back to Node's `Buffer.toString(encoding)`. - * Line ranges are 1-based and inclusive; `endLine` past the file's line - * count returns through EOF without error. - */ -export type SandboxReadTextFileOptions = Parameters[0]; +interface WriteOptions { + path: string; + content: T; + abortSignal?: AbortSignal; +} -/** - * Options for writing one file from a stream of bytes. - */ -export type SandboxWriteFileOptions = Parameters[0]; +/** Writes a byte stream, creating parent directories and overwriting existing content. */ +export interface SandboxWriteFileOptions extends WriteOptions> {} -/** - * Options for writing one file from raw bytes. - */ -export type SandboxWriteBinaryFileOptions = Parameters[0]; +/** Writes a complete byte buffer. */ +export interface SandboxWriteBinaryFileOptions extends WriteOptions {} -/** - * Options for writing one text file to a sandbox. - */ -export type SandboxWriteTextFileOptions = Parameters[0]; +/** Writes text with the requested encoding (UTF-8 by default). */ +export interface SandboxWriteTextFileOptions extends WriteOptions { + encoding?: string; +} -/** - * Options for removing a path from a sandbox. - * - * Relative paths resolve from `/workspace`; absolute paths pass through. - * `force` ignores missing paths. `recursive` permits removing non-empty - * directories. - */ +/** Removes a path; relative paths resolve from `/workspace`. */ export interface SandboxRemovePathOptions { readonly abortSignal?: AbortSignal; readonly force?: boolean; @@ -76,80 +69,34 @@ export interface SandboxRemovePathOptions { readonly recursive?: boolean; } -/** - * Public eve-owned sandbox session exposed to authored lifecycle hooks. - * - * The eight I/O methods (`run`, `spawn`, `readFile`, `readBinaryFile`, - * `readTextFile`, `writeFile`, `writeBinaryFile`, `writeTextFile`) are - * pulled directly from the AI SDK {@link AiSdkSandbox} type, so authored - * code that targets either surface uses identical signatures. `resolvePath` - * is an eve-specific extension for `/workspace` path anchoring. - * - * Relative paths resolve from `/workspace`, the live working directory - * for every provider. Absolute paths pass through unchanged. - * - */ -export interface SandboxSession extends Pick< - AiSdkSandbox, - | "run" - | "spawn" - | "readFile" - | "readBinaryFile" - | "readTextFile" - | "writeFile" - | "writeBinaryFile" - | "writeTextFile" -> { - /** - * Anchors a sandbox-relative path to `/workspace` and returns the - * resulting absolute path. - * - * Relative paths resolve from `/workspace`; absolute paths pass through. - * The read and write methods already apply this internally. - */ +/** eve-owned sandbox I/O and workspace-relative path handling. */ +export interface SandboxSession { + readonly run: (options: SandboxRunOptions) => PromiseLike; + readonly spawn: (options: SandboxSpawnOptions) => PromiseLike; + readonly readFile: ( + options: SandboxReadFileOptions, + ) => PromiseLike | null>; + readonly readBinaryFile: ( + options: SandboxReadBinaryFileOptions, + ) => PromiseLike; + readonly readTextFile: (options: SandboxReadTextFileOptions) => PromiseLike; + readonly writeFile: (options: SandboxWriteFileOptions) => PromiseLike; + readonly writeBinaryFile: (options: SandboxWriteBinaryFileOptions) => PromiseLike; + readonly writeTextFile: (options: SandboxWriteTextFileOptions) => PromiseLike; resolvePath(path: string): string; - /** - * Removes one file or directory from the sandbox filesystem. - * - * Relative paths resolve from `/workspace`; absolute paths pass through. - */ removePath(options: SandboxRemovePathOptions): Promise; } -export interface RuntimeSandboxSession extends SandboxSession { - /** Permanently deletes this sandbox and its disposable provider state. */ +/** A provider session plus the runtime's disposable-resource lifecycle. */ +export type RuntimeSandboxSession = Session & { + /** Permanently deletes the sandbox and its disposable provider state. */ delete(options?: SandboxDeleteOptions): Promise; - /** - * Stops the backing sandbox compute while preserving the durable session. - * A later runtime callback reopens the session through its configured - * provider. Providers may also support resuming the same handle. - */ + /** Stops compute while retaining the durable session for a later reopen. */ stop(): Promise; -} - -export type RuntimeSandboxSessionFor = Session & - Pick; +}; -/** - * Internal sandbox session, used to construct the public {@link SandboxSession}. - * - * Provider implementations only need to provide byte-oriented file I/O and - * a `spawn` primitive; the public surface (binary and text variants, - * line-range slicing, encoding handling, the `run` wrapper) is built on - * top of these primitives by `buildSandboxSession`. - * - * Each method's signature mirrors its public counterpart (and the AI - * SDK {@link AiSdkSandbox} surface) so providers look symmetric with - * what authored code sees. The `path` field on `readFile`/`writeFile` - * here is the **already-resolved** path: the public-surface builder - * calls `resolvePath` before delegating. - */ +/** Provider primitives; the shared builder implements binary, text, and run conveniences. */ export interface InternalSandboxSession extends Pick< - AiSdkSandbox, - "spawn" | "readFile" | "writeFile" -> { - /** Removes an already-resolved path from the provider filesystem. */ - removePath(options: SandboxRemovePathOptions): Promise; - /** Translates a user-facing path to the provider's native path. */ - resolvePath(path: string): string; -} + SandboxSession, + "spawn" | "readFile" | "writeFile" | "removePath" | "resolvePath" +> {} diff --git a/packages/eve/src/shared/validate-authorization.ts b/packages/eve/src/shared/validate-authorization.ts index dea75c62dc..1410698d57 100644 --- a/packages/eve/src/shared/validate-authorization.ts +++ b/packages/eve/src/shared/validate-authorization.ts @@ -35,17 +35,15 @@ export function validateAuthorizationSpec( const hasStart = auth.startAuthorization !== undefined; const hasComplete = auth.completeAuthorization !== undefined; - const hasCredentialOwner = auth.credentialOwner !== undefined; - const hasPrincipalType = auth.principalType !== undefined; - - if (hasCredentialOwner && hasPrincipalType) { - return `The "${fieldName}" field must not provide both "credentialOwner" and "principalType".`; + if ("credentialOwner" in auth) { + return `The "${fieldName}.credentialOwner" field is no longer supported. Use "principalType": "app" or "user".`; } - - const credentialOwnerField = hasCredentialOwner ? "credentialOwner" : "principalType"; - const credentialOwner = hasCredentialOwner ? auth.credentialOwner : auth.principalType; - if (credentialOwner !== undefined && credentialOwner !== "app" && credentialOwner !== "user") { - return `The "${fieldName}.${credentialOwnerField}" field must be "app" or "user".`; + if ( + auth.principalType !== undefined && + auth.principalType !== "app" && + auth.principalType !== "user" + ) { + return `The "${fieldName}.principalType" field must be "app" or "user".`; } if (hasStart !== hasComplete) { @@ -60,10 +58,6 @@ export function validateAuthorizationSpec( return `The "${fieldName}.completeAuthorization" field must be a function when provided.`; } - if (hasStart && hasCredentialOwner) { - return `The "${fieldName}.credentialOwner" field is only supported by getToken-only authorization. Interactive authorization is restricted to "principalType": "user" in v1.`; - } - if (hasStart && auth.principalType !== "user") { return `Interactive authorization (startAuthorization + completeAuthorization) is restricted to "principalType": "user" in v1. App-level credentials must use a getToken-only definition.`; } @@ -124,8 +118,7 @@ export function normalizeAuthorizationSpec( let nonInteractive: NonInteractiveAuthorizationDefinition = { getToken: auth.getToken as NonInteractiveAuthorizationDefinition["getToken"], - principalType: (auth.credentialOwner ?? - auth.principalType ?? + principalType: (auth.principalType ?? "app") as NonInteractiveAuthorizationDefinition["principalType"], }; if (runtimeConnect !== undefined) diff --git a/packages/eve/src/svelte/use-eve-agent.ts b/packages/eve/src/svelte/use-eve-agent.ts index 50b52ff231..fe8e1ffff4 100644 --- a/packages/eve/src/svelte/use-eve-agent.ts +++ b/packages/eve/src/svelte/use-eve-agent.ts @@ -134,7 +134,7 @@ export interface UseEveAgentOptions extends EveAgentStoreCallbacks { */ readonly optimistic?: boolean; /** Follow each subagent call's session into `data.agents` while mounted. @default false */ - readonly followSubagents?: boolean; + readonly followAgents?: boolean; /** * Keep the session stream open between turns to show events that arrive from schedules, * channels, or other clients. Set to false to close it once the session is idle and reopen it @@ -264,7 +264,7 @@ export function useEveAgent(options: UseEveAgentOptions = {}): UseEveAgentReturn initialEvents: options.initialEvents, initialSession: options.initialSession, optimistic: options.optimistic, - followSubagents: options.followSubagents, + followAgents: options.followAgents, followIdle: options.followIdle, prewarm: options.prewarm, session: options.session, diff --git a/packages/eve/src/tools/auth.ts b/packages/eve/src/tools/auth.ts index 06993225e9..e807e6a8aa 100644 --- a/packages/eve/src/tools/auth.ts +++ b/packages/eve/src/tools/auth.ts @@ -1,18 +1,18 @@ import type { ConnectionAuthorizationContext, - ConnectionAuthProvider, + AuthorizationDefinition, } from "#shared/connection-types.js"; /** * Authorization provider passed to the tool context's `getToken` or * `requireAuth` method. Accepts the same shapes as a connection's `auth`: * - a `getToken`-only object (static API keys, pre-provisioned JWTs); - * `credentialOwner` may be omitted and defaults to `"app"`. + * `principalType` may be omitted and defaults to `"app"`. * - a full interactive OAuth definition (e.g. `connect("okta/myagent")` from * `@vercel/connect/eve`, or `defineInteractiveAuthorization`), which keeps * `principalType: "user"`. */ -export type ToolAuthDefinition = ConnectionAuthProvider; +export type ToolAuthDefinition = AuthorizationDefinition; export type ToolAuthProvider = ToolAuthDefinition; diff --git a/packages/eve/src/tools/definition.ts b/packages/eve/src/tools/definition.ts index e08a7c9880..7d6514c8a0 100644 --- a/packages/eve/src/tools/definition.ts +++ b/packages/eve/src/tools/definition.ts @@ -1,5 +1,6 @@ import type { Principal } from "#protocol/session-events/envelope.js"; -import type { ModelMessage, ToolExecutionOptions } from "ai"; +import type { ToolExecutionOptions } from "ai"; +import type { HistoryMessage as ModelMessage } from "#shared/history-message.js"; import type { StandardJSONSchemaV1, StandardSchemaV1, diff --git a/packages/eve/src/tracing/eve/otel-declaration.ts b/packages/eve/src/tracing/eve/otel-declaration.ts index f9f6a805a5..7cdc442989 100644 --- a/packages/eve/src/tracing/eve/otel-declaration.ts +++ b/packages/eve/src/tracing/eve/otel-declaration.ts @@ -7,7 +7,7 @@ import type { SpanProcessorOrName, } from "#compiled/@vercel/otel/index.js"; -import { PROVIDER, type InstrumentationProvider } from "#public/instrumentation/provider.js"; +import { PROVIDER, type InstrumentationProvider } from "#instrumentation/provider.js"; import type { InstrumentationModelStartedInput } from "#public/instrumentation/index.js"; import type { JsonObject } from "#shared/json.js"; import { batchSpanProcessor } from "#tracing/eve/batch-span-processor.js"; diff --git a/packages/eve/src/vue/use-eve-agent.ts b/packages/eve/src/vue/use-eve-agent.ts index 16e2c495d2..022c5e3b5c 100644 --- a/packages/eve/src/vue/use-eve-agent.ts +++ b/packages/eve/src/vue/use-eve-agent.ts @@ -133,7 +133,7 @@ export interface UseEveAgentOptions extends EveAgentStoreCallbacks { */ readonly optimistic?: boolean; /** Follow each subagent call's session into `data.agents` while mounted. @default false */ - readonly followSubagents?: boolean; + readonly followAgents?: boolean; /** * Keep the session stream open between turns to show events that arrive from schedules, * channels, or other clients. Set to false to close it once the session is idle and reopen it @@ -173,7 +173,7 @@ export function useEveAgent(options: UseEveAgentOptions = {}): UseEveAgentReturn initialEvents: options.initialEvents, initialSession: options.initialSession, optimistic: options.optimistic, - followSubagents: options.followSubagents, + followAgents: options.followAgents, followIdle: options.followIdle, prewarm: options.prewarm, session: options.session, diff --git a/packages/eve/test/tui-client/tui-connection-auth-user.ts b/packages/eve/test/tui-client/tui-connection-auth-user.ts index 95ac496247..44645f978d 100644 --- a/packages/eve/test/tui-client/tui-connection-auth-user.ts +++ b/packages/eve/test/tui-client/tui-connection-auth-user.ts @@ -3,12 +3,8 @@ import { createServer } from "node:net"; import type { AddressInfo } from "node:net"; import { createEmulator, type Emulator } from "emulate"; -import { - Client, - type InteractionOpened, - type InteractionSettled, - type SessionStreamEvent, -} from "eve/client"; +import { Client, type SessionStreamEvent } from "eve/client"; +import { type InteractionOpened, type InteractionSettled } from "eve/events"; import { startMcpStubServer } from "./lib/mcp-stub-server.ts"; import { runEnvironment } from "./lib/run.ts"; diff --git a/scripts/guard-invariants-baseline.json b/scripts/guard-invariants-baseline.json index bd55a80cd9..74d09d6b5c 100644 --- a/scripts/guard-invariants-baseline.json +++ b/scripts/guard-invariants-baseline.json @@ -1,8 +1,6 @@ { "_description": "Baselines for framework mechanical lints. Each rule guard refuses to let new violations slip in beyond what is recorded here. Lower a count by removing offenders, never raise it. Empty allowlists/maps mean the rule is fully enforced.", - "rule13_spreadTernaryByFile": { - "packages/eve/src/public/channels/auth.ts": 7 - }, + "rule13_spreadTernaryByFile": {}, "rule19_asyncLocalStorageAllowlist": [ "packages/eve/src/context/container.ts", "packages/eve/src/public/channels/slack/slackChannel.ts", From d80e9018998c44d3c6e1d30de1e5c029f2b5dc6e Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:26 +0000 Subject: [PATCH 03/17] fix(api): align create receipts and command migration documentation Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- docs/channels/eve.mdx | 8 ++++---- docs/channels/slack.mdx | 6 +++--- docs/concepts/sessions-runs-and-streaming.md | 11 +++++------ docs/evals/cases.mdx | 2 +- docs/guides/frontend/overview.mdx | 9 ++++----- packages/eve/src/eve-channel/index.ts | 3 +-- .../src/public/channels/eve-session-routes.test.ts | 3 --- 7 files changed, 18 insertions(+), 24 deletions(-) diff --git a/docs/channels/eve.mdx b/docs/channels/eve.mdx index 511f0ae56c..73cba660c1 100644 --- a/docs/channels/eve.mdx +++ b/docs/channels/eve.mdx @@ -108,7 +108,7 @@ Create a conversation session before its first message with a bodyless request: ```bash curl -X POST https:///eve/v1/session -# {"ok":true,"sessionId":"wrun_A","status":"accepted"} +# {"ok":true,"sessionId":"wrun_A"} ``` See [Start a session](../concepts/sessions-runs-and-streaming#start-a-session) for the @@ -124,7 +124,7 @@ Include an initial message to create the session and start its first turn in one curl -X POST https:///eve/v1/session \ -H "Content-Type: application/json" \ -d '{"message":"What is the weather in Paris?"}' -# {"ok":true,"sessionId":"wrun_A","status":"accepted"} +# {"ok":true,"sessionId":"wrun_A","deliveryId":"delivery_A"} ``` Authenticated callers that may retry a create request can pass their own `operationId` for @@ -141,7 +141,7 @@ curl -X POST https:///eve/v1/session \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"message":"What is the weather in Paris?","operationId":"order-4213-research"}' -# {"ok":true,"sessionId":"wrun_A","status":"accepted"} +# {"ok":true,"sessionId":"wrun_A","deliveryId":"delivery_A"} ``` When the create request includes a message, a follow-up request accepts exactly one of @@ -209,7 +209,7 @@ After a session ends, the World keeps its stream only for its retention period. Post to `/eve/v1/session/:sessionId/cancel` to request cancellation of the active turn. You can include the observed `turnId` to keep a late request from cancelling a newer turn. Cancellation is asynchronous; confirm it on the stream as `turn.settled` with `outcome: "cancelled"`. -See [Cancel the in-flight turn](../concepts/sessions-runs-and-streaming#cancel-the-in-flight-turn) for response statuses, HTTP status codes, subagent cancellation, and race behavior. +See [Cancel the in-flight turn](../concepts/sessions-runs-and-streaming#cancel-the-in-flight-turn) for delivery receipts, HTTP status codes, subagent cancellation, and race behavior. ### Clear context diff --git a/docs/channels/slack.mdx b/docs/channels/slack.mdx index 71a772c1ea..54f18c7ad7 100644 --- a/docs/channels/slack.mdx +++ b/docs/channels/slack.mdx @@ -262,7 +262,7 @@ export default slackChannel({ With `"queue"`, adjacent messages with matching full auth contexts can combine into one turn, keeping their attachments and context in order. Messages from different users, or from the same user with different authorization attributes, stay separate. Anonymous deliveries also stay separate. Batching follows session-inbox order, and turns still share the thread's session history. -Message hooks (`onMessage`, `onAppMention`, and `onDirectMessage`) also receive thread-bound session operations directly on `ctx`. `ctx.cancel({ turnId? })` remains useful for a Stop button: it cancels without sending replacement input. `"accepted"` means the live thread session durably queued that request; a parked session consumes it as a no-op, and `"no_active_turn"` means the thread has no active session owner. +Message hooks (`onMessage`, `onAppMention`, and `onDirectMessage`) also receive thread-bound session operations directly on `ctx`. `ctx.cancel({ turnId? })` remains useful for a Stop button: it cancels without sending replacement input. The returned `Delivery` identifies the queued command. An idle session accepts a no-op cancellation; an unowned thread throws `SessionNotActiveError`. Message and interaction hooks expose every current-owner operation directly on `ctx`: @@ -298,11 +298,11 @@ export default slackChannel({ }); ``` -Reset returns `{ status: "reset", previousSessionId }` when the thread had a session, or `{ status: "no_active_session" }` when it was already free. Both outcomes are successful. Returning `null` after reset consumes the triggering message as a command; return `{ auth }` instead to deliver that message as the first turn of the fresh session. Custom `onInteraction` handlers receive the same flat operations, which is useful for a New conversation button. +Reset returns a `Delivery` for the retiring session; `await delivery.result()` waits for retirement and address release. An already unowned thread throws `SessionNotActiveError`. Returning `null` after reset consumes the triggering message as a command; return `{ auth }` instead to deliver that message as the first turn of the fresh session. Custom `onInteraction` handlers receive the same flat operations, which is useful for a New conversation button. ### Other Events API callbacks -Use `onEvent` for subscribed events such as `reaction_added`, `team_join`, or `channel_created`. It receives the raw, open-ended Slack event and owns control flow. Call `ctx.send(message, { target, auth, title })` zero, one, or many times to start turns. `title` sets the run title without changing the message sent to the model. Each call returns the resulting session. +Use `onEvent` for subscribed events such as `reaction_added`, `team_join`, or `channel_created`. It receives the raw, open-ended Slack event and owns control flow. Call `ctx.send(message, { target, auth, title })` zero, one, or many times to start turns. `title` sets the run title without changing the message sent to the model. Each call returns a delivery receipt; use its `sessionId` to attach a session handle. ```ts title="agent/channels/slack.ts" import { connectSlackCredentials } from "@vercel/connect/eve"; diff --git a/docs/concepts/sessions-runs-and-streaming.md b/docs/concepts/sessions-runs-and-streaming.md index 4ab7349bcf..0963c84a87 100644 --- a/docs/concepts/sessions-runs-and-streaming.md +++ b/docs/concepts/sessions-runs-and-streaming.md @@ -267,13 +267,12 @@ POST to the session's cancel endpoint to stop the turn that is currently running ```bash curl -X POST http://127.0.0.1:2000/eve/v1/session//cancel -# {"ok":true,"sessionId":"","status":"accepted"} +# {"ok":true,"sessionId":"","deliveryId":"delivery_cancel"} ``` -`"accepted"` means the live session durably queued the request; cancellation completes asynchronously. The cancel arrives as a control delivery; confirm it on the stream as `turn.settled` with `outcome: "cancelled"` and `cause` naming that delivery. The session then accepts the next message normally. Each cancelled child reports its own boundary on its child-session stream. Cancelling also stops every working task, each reported as `task.ended` with `outcome: "cancelled"`, including while the session waits between turns. A live but already-parked session returns `"accepted"`; with no working tasks, cancellation is a no-op there. `"no_active_turn"` means the session or channel address is unknown or terminal. Both statuses are success, so clients can fire and forget. See the [eve channel](../channels/eve) for the full route contract. +The HTTP route returns `202` with the command's `sessionId` and `deliveryId`. Cancellation completes asynchronously. The cancel arrives as a control delivery; confirm it on the stream as `turn.settled` with `outcome: "cancelled"` and `cause` naming that delivery. Each cancelled child reports its own boundary on its child-session stream. An unguarded cancellation also stops working tasks, including between turns, with `task.ended` facts. -The HTTP route returns `202` for `"accepted"` and `200` for -`"no_active_turn"`. Only the accepted result includes `sessionId`. +A live idle session accepts a no-op cancellation. A guard for a different turn, including `turnId: null` for no observed turn, cannot cancel unrelated work. The cancellation delivery settles when applied or ignored. An unknown or terminal session returns `409 session_not_active`, not a separate `no_active_turn` result. Custom channel routes request the same cancellation through `from(address).cancel()` or `attachSession(sessionId).cancel()`. See @@ -295,9 +294,9 @@ Compaction summarizes context without adding a user message. User-role instructi Clear removes model-message history in place, including static and dynamic user-role instructions and recalled memory records, while preserving the session identity, system-role instructions, tools, skills, application-defined durable state, limits, and sandbox. It clears framework memory locks and replay bookkeeping but does not delete data from a provider's external store. It does not rerun instruction definitions or resolvers. Approvals, the session-limit prompt, and sign-ins the cleared history asked for are withdrawn: each settles its interaction. Requests relayed from tasks that keep running stay answerable. The clear is a context change with `kind: "clear"`. -Reset terminally retires the exact session ID. A reset ID never becomes a new session; create another session explicitly for a fresh conversation. Compact, clear, and reset return `"no_active_session"` when the target is already inactive. +Reset terminally retires the exact session ID. A reset ID never becomes a new session; create another session explicitly for a fresh conversation. All controls return delivery receipts when accepted. An inactive target throws `SessionNotActiveError` from a handle or returns `409 session_not_active` over HTTP. -A [stranded session](./execution-model-and-durability#stranded-sessions) cannot run commands. Reset still works: eve cancels the stranded workflow run and returns `"reset"`. Clear is refused with `409 session_stranded` (`SessionStrandedError` from a `Session` handle), because it would keep a session that cannot run. Compact returns `"no_active_session"` and cancel returns `"no_active_turn"`. +A [stranded session](./execution-model-and-durability#stranded-sessions) cannot run commands. Reset still works: eve retires the stranded workflow run and returns its delivery. Its settlement follows retirement and address release. Clear and compact cannot repair a stranded owner; use reset instead. ## Reconnect and rewind diff --git a/docs/evals/cases.mdx b/docs/evals/cases.mdx index 7ea888e6ab..dfcf589e98 100644 --- a/docs/evals/cases.mdx +++ b/docs/evals/cases.mdx @@ -109,7 +109,7 @@ Every `t.session()` or `t.send()` creates a new independent conversation. Contin - `t.send(message, options?)` creates a new session with its first message in one request and waits for the turn to settle. The returned turn carries `.session`, `.message`, and `.expectOk()`. - `session.send(message, options?)` sends a follow-up on that session and waits for it to settle. - `session.start(message, options?)` starts a text turn and returns as soon as the server accepts it. The live turn exposes `.session`, `.sessionId`, `.waitForEvent(...)`, `.cancel()`, and `.result()`. -- `session.cancel()` requests cooperative cancellation of that session's active turn. Both `accepted` and `no_active_turn` are successful outcomes. +- `session.cancel()` returns a delivery for cooperative cancellation. A live idle session also accepts a no-op cancellation; an inactive session throws `SessionNotActiveError`. - `session.compact()` compacts that session's history between turns and waits for it to finish. It resolves to the captured events of the compaction, a context change, so assertions such as `.event("context.settled", { data: { kind: "compaction", outcome: "completed" } })` work on the result. It throws when the session is no longer active. - `session.sendFile(text, path, mediaType?)` attaches a local file as a data URL. - `session.agent(opened).stream(options?)` follows a delegated child's stream through the parent with the eval client's credentials. Pass a `child.opened` event from that session. It also reaches children on another deployment, which `t.target.attach(...).watch()` cannot address. diff --git a/docs/guides/frontend/overview.mdx b/docs/guides/frontend/overview.mdx index 67e97d6fb8..34c13cec74 100644 --- a/docs/guides/frontend/overview.mdx +++ b/docs/guides/frontend/overview.mdx @@ -167,7 +167,7 @@ and the rest below. The pieces share `metadata.turnId`. A single event that coal deliveries remains one bubble. A turn settling does not close the connection. The transport reconnects from its cursor after a disconnect or the server's renewable 60-second lease; lease renewal does not start a turn or clear UI state. -Unmounting or resetting closes the local stream without cancelling the old durable session. +Unmounting or calling `newSession()` closes the local stream without cancelling the old durable session. An open stream reconnects for as long as the chat stays mounted, even when nothing happens. If your UI only shows replies to its own messages, such as a help sidebar that stays mounted across @@ -178,8 +178,7 @@ the stream from the session cursor, so events that arrived in between are applie `followIdle` on when other sources, such as schedules, channels, or another tab, can start turns in the session while your UI is idle. -`newSession()` reuses the hook instance and clears the local conversation. `reset()` instead retires the server-side session and returns its delivery. When `prewarm` is true after -the reset, it prepares the next owned session; otherwise, the next send creates it. An explicitly +`newSession()` reuses the hook instance and clears the local conversation. `reset()` instead retires the server-side session and returns its delivery. Call `newSession()` to clear the retired conversation and prepare a fresh one. If `prewarm` is true, that call prepares the next owned session; otherwise, the next send creates it. An explicitly supplied `session` remains externally owned and is never replaced by prewarming or `newSession()`. Use `initialSession` with `resume: true` to restore a saved chat. @@ -249,9 +248,9 @@ Call `cancel()` to stop the durable turn without replacing it. `cancel()` targets the currently observed turn and keeps the event stream attached. If no turn is active, it waits for a submission pending when you called `cancel()` to identify its turn. This includes preparation, session creation, and POST acceptance. Cancellation always names the identified turn, so a delayed request cannot stop an unrelated later turn. -This wait does not withdraw a queued message or prevent its turn from starting. If the pending deliveries settle without a running turn, cancellation returns `"no_active_turn"`. Calling `cancel()` while idle, with no pending submissions, does not arm cancellation for future messages. Resetting or unmounting aborts the wait. +This wait does not withdraw a queued message or prevent its turn from starting. If the pending deliveries settle without a running turn, cancellation returns an accepted no-op delivery. Calling `cancel()` while idle, with no pending submissions, does not arm cancellation for future messages. Calling `newSession()` or unmounting aborts the wait. -The promise resolves when eve accepts cancellation or reports no active turn. It rejects if submission, observation, or the cancellation request fails while waiting. An accepted cancellation settles cooperatively on the same stream with `outcome: "cancelled"`; a turn can finish before cancellation reaches the server. +The promise resolves with the cancellation delivery when eve accepts it. It rejects if submission, observation, or the cancellation request fails while waiting. An accepted cancellation settles cooperatively on the same stream with `outcome: "cancelled"`; a turn can finish before cancellation reaches the server. ```tsx if (agent.status === "busy") { diff --git a/packages/eve/src/eve-channel/index.ts b/packages/eve/src/eve-channel/index.ts index d26b314edd..0e4b9f5ced 100644 --- a/packages/eve/src/eve-channel/index.ts +++ b/packages/eve/src/eve-channel/index.ts @@ -104,9 +104,8 @@ function createdSessionBody(sessionId: string, body: ParsedCreateBody, deliveryI ok: true; protocolVersion?: number; sessionId: string; - status: "accepted"; deliveryId?: string; - } = { ok: true, sessionId, status: "accepted" }; + } = { ok: true, sessionId }; if (deliveryId !== undefined) created.deliveryId = deliveryId; if (body.protocolVersion !== undefined) created.protocolVersion = body.protocolVersion; return created; diff --git a/packages/eve/src/public/channels/eve-session-routes.test.ts b/packages/eve/src/public/channels/eve-session-routes.test.ts index 3874dbec9b..2a271a4c80 100644 --- a/packages/eve/src/public/channels/eve-session-routes.test.ts +++ b/packages/eve/src/public/channels/eve-session-routes.test.ts @@ -87,7 +87,6 @@ describe("eve ID-addressed session routes", () => { await expect(response.json()).resolves.toEqual({ ok: true, sessionId: "wrun_A", - status: "accepted", }); expect(onMessage).not.toHaveBeenCalled(); expect(createSession).toHaveBeenCalledWith( @@ -146,7 +145,6 @@ describe("eve ID-addressed session routes", () => { await expect(response.json()).resolves.toEqual({ ok: true, sessionId: "wrun_A", - status: "accepted", deliveryId: "delivery_first", }); expect(createSession).toHaveBeenCalledWith( @@ -337,7 +335,6 @@ describe("eve ID-addressed session routes", () => { await expect(response.json()).resolves.toMatchObject({ ok: true, sessionId: "wrun_A", - status: "accepted", }); expect(createSession).toHaveBeenCalledWith( expect.objectContaining({ From 8ba935ce20d271fcd5e52973a8aa4da7032f060d Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:26 +0000 Subject: [PATCH 04/17] fix(fixtures): follow command receipts and canonical interaction options Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- .../evals/runtime/workflow-retention.eval.ts | 2 +- .../cancellation/generated-program-child-hitl.eval.ts | 4 +++- .../evals/cancellation/queued-request-identity.eval.ts | 7 ++----- .../evals/cancellation/reset-channel-session.eval.ts | 8 ++++---- .../agent-decide/evals/auto-resume-question.eval.ts | 4 +++- .../evals/deploy-service.approval.eval.ts | 2 +- .../agent-openapi-swagger/evals/petstore-approval.eval.ts | 2 +- .../evals/limits/session-token-limit-continuation.eval.ts | 8 ++++---- .../evals/hitl/relayed-budget-stop-cancels-parent.eval.ts | 4 ++-- .../agent-subagents/evals/delegated-usage-limit.shared.ts | 4 ++-- .../evals/descendant-session-limit.eval.ts | 8 ++++---- .../evals/hitl/approval-requester-cancel.eval.ts | 4 ++-- .../evals/hitl/approval-response-settlement.eval.ts | 8 ++++---- .../evals/hitl/ask-question-answer.eval.ts | 8 ++++++-- .../evals/hitl/authorized-response-oauth-cancel.eval.ts | 2 +- .../evals/hitl/budget/continue-runs-the-same-call.eval.ts | 2 +- .../evals/hitl/budget/message-waits-behind-budget.eval.ts | 2 +- .../evals/hitl/budget/stale-budget-answer-dropped.eval.ts | 4 ++-- .../evals/hitl/budget/stop-cancels-once.eval.ts | 2 +- .../evals/hitl/budget/typed-continue-answers.eval.ts | 2 +- .../agent-tools-hitl/evals/hitl/button-cancel.eval.ts | 2 +- .../evals/hitl/continuation/budget-grant.control.eval.ts | 4 ++-- .../evals/hitl/continuation/budget-grant.eval.ts | 4 ++-- .../evals/hitl/continuation/cancel-sibling.eval.ts | 2 +- .../evals/hitl/continuation/stale-response.eval.ts | 2 +- .../agent-tools-sandbox/evals/sandbox/redeploy.eval.ts | 5 +++-- .../evals/confirm-deploy.hitl.eval.ts | 5 +++-- .../evals/escalate-deploy.deadline.eval.ts | 5 +++-- .../agent-workflow-tools/evals/held-turn.question.eval.ts | 8 +++++--- 29 files changed, 67 insertions(+), 57 deletions(-) diff --git a/e2e/fixtures/agent-basic-runtime/evals/runtime/workflow-retention.eval.ts b/e2e/fixtures/agent-basic-runtime/evals/runtime/workflow-retention.eval.ts index a0d1fd42a9..e10d488ddd 100644 --- a/e2e/fixtures/agent-basic-runtime/evals/runtime/workflow-retention.eval.ts +++ b/e2e/fixtures/agent-basic-runtime/evals/runtime/workflow-retention.eval.ts @@ -29,7 +29,7 @@ export default defineEval({ method: "POST", signal: t.signal, }); - await t.require(reset.status, equals(200)); + await t.require(reset.status, equals(202)); await reset.body?.cancel(); if (!metadataOnly) { const retained = await readHistory(); diff --git a/e2e/fixtures/agent-cancellation/evals/cancellation/generated-program-child-hitl.eval.ts b/e2e/fixtures/agent-cancellation/evals/cancellation/generated-program-child-hitl.eval.ts index e2382a5878..0d3d8f538a 100644 --- a/e2e/fixtures/agent-cancellation/evals/cancellation/generated-program-child-hitl.eval.ts +++ b/e2e/fixtures/agent-cancellation/evals/cancellation/generated-program-child-hitl.eval.ts @@ -14,9 +14,11 @@ export default defineEval({ if (request.request.kind !== "question") throw new Error("Child input request is not a question."); - const resumed = await session.respond([ + const answered = await session.respond([ { interactionId: request.interactionId, text: "GENERATED-HITL-MARKER" }, ]); + answered.expectOk(); + const resumed = await session.nextTurn(); resumed.expectOk(); t.succeeded(); diff --git a/e2e/fixtures/agent-cancellation/evals/cancellation/queued-request-identity.eval.ts b/e2e/fixtures/agent-cancellation/evals/cancellation/queued-request-identity.eval.ts index 42da1d598a..2538de3c4b 100644 --- a/e2e/fixtures/agent-cancellation/evals/cancellation/queued-request-identity.eval.ts +++ b/e2e/fixtures/agent-cancellation/evals/cancellation/queued-request-identity.eval.ts @@ -1,4 +1,3 @@ -import { callOutputSource } from "eve/events"; import { defineEval } from "eve/evals"; import { equals } from "eve/evals/expect"; @@ -56,10 +55,8 @@ export default defineEval({ const turn = await live.result(); turn.expectOk(); turn.calledTool("record-request", { count: markers.length, status: "completed" }); - const outputs = Object.values(turn.view.calls).flatMap((call) => - call.capability.name === "record-request" && call.outcome === "completed" - ? [callOutputSource(turn.view, call.callId)?.output] - : [], + const outputs = turn.toolCalls.flatMap((call) => + call.name === "record-request" && call.status === "completed" ? [call.output] : [], ); t.check( outputs, diff --git a/e2e/fixtures/agent-cancellation/evals/cancellation/reset-channel-session.eval.ts b/e2e/fixtures/agent-cancellation/evals/cancellation/reset-channel-session.eval.ts index 1ff08be4d5..4aad5ed2a3 100644 --- a/e2e/fixtures/agent-cancellation/evals/cancellation/reset-channel-session.eval.ts +++ b/e2e/fixtures/agent-cancellation/evals/cancellation/reset-channel-session.eval.ts @@ -8,8 +8,8 @@ interface Delivery { interface ResetResponse { readonly acknowledgement?: string; - readonly previousSessionId?: string; - readonly status?: "no_active_session" | "reset"; + readonly sessionId?: string; + readonly deliveryId?: string; } interface OwnerResponse { @@ -65,8 +65,8 @@ export default defineEval({ reset, satisfies( (value: ResetResponse) => - value.status === "reset" && - value.previousSessionId === previousSessionId && + typeof value.deliveryId === "string" && + value.sessionId === previousSessionId && value.acknowledgement === "Started a new conversation.", "the route retires the observed session and returns its fixed acknowledgement", ), diff --git a/e2e/fixtures/agent-decide/evals/auto-resume-question.eval.ts b/e2e/fixtures/agent-decide/evals/auto-resume-question.eval.ts index e86420625e..14c6ab3bdf 100644 --- a/e2e/fixtures/agent-decide/evals/auto-resume-question.eval.ts +++ b/e2e/fixtures/agent-decide/evals/auto-resume-question.eval.ts @@ -13,10 +13,12 @@ export default defineEval({ const staging = question.request.options?.find((option) => option.label === "Staging"); if (staging === undefined) throw new Error("ask_question did not offer Staging."); - const resumed = await parked.session.respond([ + const answered = await parked.session.respond([ { optionId: staging.id, interactionId: question.interactionId }, ]); + answered.expectOk(); + const resumed = await parked.session.nextTurn(); resumed.expectOk(); resumed.calledTool("ask_question", { count: 1, diff --git a/e2e/fixtures/agent-deferred-tools/evals/deploy-service.approval.eval.ts b/e2e/fixtures/agent-deferred-tools/evals/deploy-service.approval.eval.ts index 889ed7a87d..0a8c0aa2ec 100644 --- a/e2e/fixtures/agent-deferred-tools/evals/deploy-service.approval.eval.ts +++ b/e2e/fixtures/agent-deferred-tools/evals/deploy-service.approval.eval.ts @@ -12,7 +12,7 @@ export default defineEval({ const parked = await t.send("DEFERRED-DEPLOY Alice asks to deploy the billing service."); const session = parked.session; parked.expectOk(); - session.requireInteraction({ optionIds: ["approve", "cancel"], toolName: "deploy_service" }); + session.requireInteraction({ optionIds: ["approve", "deny"], toolName: "deploy_service" }); parked.calledTool("deploy_service", { count: 1, status: "pending" }); // notCalledTool rejects CALL_TOOL because no call is reported under it, so check the raw calls. parked.notEvent("call.requested", { data: { capability: { name: CALL_TOOL } } }); diff --git a/e2e/fixtures/agent-openapi-swagger/evals/petstore-approval.eval.ts b/e2e/fixtures/agent-openapi-swagger/evals/petstore-approval.eval.ts index e530182ede..af13abd7fe 100644 --- a/e2e/fixtures/agent-openapi-swagger/evals/petstore-approval.eval.ts +++ b/e2e/fixtures/agent-openapi-swagger/evals/petstore-approval.eval.ts @@ -16,7 +16,7 @@ export default defineEval({ session.requireInteraction({ display: "confirmation", - optionIds: ["approve", "cancel"], + optionIds: ["approve", "deny"], toolName: INVENTORY_TOOL, }); parked.calledTool(INVENTORY_TOOL, { count: 1, status: "pending" }); diff --git a/e2e/fixtures/agent-session-limits/evals/limits/session-token-limit-continuation.eval.ts b/e2e/fixtures/agent-session-limits/evals/limits/session-token-limit-continuation.eval.ts index 2266d08b17..418e70c57c 100644 --- a/e2e/fixtures/agent-session-limits/evals/limits/session-token-limit-continuation.eval.ts +++ b/e2e/fixtures/agent-session-limits/evals/limits/session-token-limit-continuation.eval.ts @@ -24,12 +24,12 @@ export default defineEval({ await session.send('Reply with exactly the text "limit pong" and nothing else.'); const request = session.requireInteraction({ display: "confirmation", - optionIds: ["continue", "stop"], + optionIds: ["approve", "deny"], kind: "budget", }); const resumed = await session.respond([ - { optionId: "continue", interactionId: request.interactionId }, + { optionId: "approve", interactionId: request.interactionId }, ]); resumed.expectOk(); t.succeeded(); @@ -44,13 +44,13 @@ export default defineEval({ await stopSession.send('Reply with exactly the text "stop pong" and nothing else.'); const stopRequest = stopSession.requireInteraction({ display: "confirmation", - optionIds: ["continue", "stop"], + optionIds: ["approve", "deny"], kind: "budget", }); const stopped = await stopSession.respond([ { - optionId: "stop", + optionId: "deny", interactionId: stopRequest.interactionId, }, ]); diff --git a/e2e/fixtures/agent-subagents-hitl/evals/hitl/relayed-budget-stop-cancels-parent.eval.ts b/e2e/fixtures/agent-subagents-hitl/evals/hitl/relayed-budget-stop-cancels-parent.eval.ts index 73f1549a69..de14cf845e 100644 --- a/e2e/fixtures/agent-subagents-hitl/evals/hitl/relayed-budget-stop-cancels-parent.eval.ts +++ b/e2e/fixtures/agent-subagents-hitl/evals/hitl/relayed-budget-stop-cancels-parent.eval.ts @@ -19,14 +19,14 @@ export default defineEval({ } const stopped = await session.respond([ - { optionId: "stop", interactionId: request.interactionId }, + { optionId: "deny", interactionId: request.interactionId }, ]); stopped.event("interaction.settled", { count: 1, data: { interactionId: request.interactionId, outcome: "declined", - response: { optionId: "stop" }, + response: { optionId: "deny" }, }, }); stopped.event("turn.settled", { count: 1, data: { outcome: "cancelled" } }); diff --git a/e2e/fixtures/agent-subagents/evals/delegated-usage-limit.shared.ts b/e2e/fixtures/agent-subagents/evals/delegated-usage-limit.shared.ts index 1855776afc..63c17a2acf 100644 --- a/e2e/fixtures/agent-subagents/evals/delegated-usage-limit.shared.ts +++ b/e2e/fixtures/agent-subagents/evals/delegated-usage-limit.shared.ts @@ -17,7 +17,7 @@ export async function expectSurveyCountedAgainstParent(t: EveEvalContext, messag const { session } = await t.send(message); const request = session.requireInteraction({ display: "confirmation", - optionIds: ["continue", "stop"], + optionIds: ["approve", "deny"], kind: "budget", }); const parentLimitId = new RegExp(`^${session.id ?? ""}:\\d+:limit:input:(\\d+)$`, "u"); @@ -31,7 +31,7 @@ export async function expectSurveyCountedAgainstParent(t: EveEvalContext, messag ); const resumed = await session.respond([ - { optionId: "continue", interactionId: request.interactionId }, + { optionId: "approve", interactionId: request.interactionId }, ]); resumed.expectOk(); resumed.messageIncludes("SURVEY-REPLY Alice's tide survey has 12 stations."); diff --git a/e2e/fixtures/agent-subagents/evals/descendant-session-limit.eval.ts b/e2e/fixtures/agent-subagents/evals/descendant-session-limit.eval.ts index 258ec8e80a..c8d4377151 100644 --- a/e2e/fixtures/agent-subagents/evals/descendant-session-limit.eval.ts +++ b/e2e/fixtures/agent-subagents/evals/descendant-session-limit.eval.ts @@ -25,7 +25,7 @@ export default defineEval({ const continueSession = await waitForInput(t, session); const continueRequest = continueSession.requireInteraction({ display: "confirmation", - optionIds: ["continue", "stop"], + optionIds: ["approve", "deny"], kind: "budget", }); const rootSessionId = session.id; @@ -43,7 +43,7 @@ export default defineEval({ const resumed = await continueSession.respond([ { - optionId: "continue", + optionId: "approve", interactionId: continueRequest.interactionId, }, ]); @@ -60,13 +60,13 @@ export default defineEval({ const blockedStopSession = await waitForInput(t, stopSession); const stopRequest = blockedStopSession.requireInteraction({ display: "confirmation", - optionIds: ["continue", "stop"], + optionIds: ["approve", "deny"], kind: "budget", }); const stopped = await blockedStopSession.respond([ { - optionId: "stop", + optionId: "deny", interactionId: stopRequest.interactionId, }, ]); diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-requester-cancel.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-requester-cancel.eval.ts index fc53520972..3dd671f607 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-requester-cancel.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-requester-cancel.eval.ts @@ -18,7 +18,7 @@ export default defineEval({ const parked = await session.send("Prepare an authorized change.", { headers: ALICE }); const { interactionId } = parked.session.requireInteraction({ toolName: TOOL }); - const refused = await session.respond([{ optionId: "cancel", interactionId }], { + const refused = await session.respond([{ optionId: "deny", interactionId }], { headers: BOB, }); refused.event("delivery.admitted", { count: 1, data: { principal: { id: "bob" } } }); @@ -29,7 +29,7 @@ export default defineEval({ }); refused.notEvent("interaction.settled"); - const cancelled = await session.respond([{ optionId: "cancel", interactionId }], { + const cancelled = await session.respond([{ optionId: "deny", interactionId }], { headers: ALICE, }); cancelled.event("delivery.admitted", { count: 1, data: { principal: { id: "alice" } } }); diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts index 1c4a5861ef..7b3d443651 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts @@ -41,7 +41,7 @@ async function expectAnswerable( interactionId: string, ) { // The approval holds its turn open, and the store keeps following it live. - await t.require(store.snapshot.status, equals("idle")); + await t.require(store.snapshot.status, equals("busy")); await t.require(store.snapshot.error, equals(undefined)); const part = toolPart(store, callId); await t.require(part.state, equals("approval")); @@ -96,7 +96,7 @@ async function refuse(t: EveEvalContext, store: Store, callId: string, interacti export default defineEval({ description: - "A refused approval returns the frontend to an answerable idle state, including after reload (#3757).", + "A refused approval returns the frontend to an answerable state, including after reload (#3757).", tags: ["hitl", "regression", "authorization", "client"], timeoutMs: 120_000, async test(t) { @@ -176,8 +176,8 @@ export default defineEval({ signal: t.signal, }), ); - await waitForStore(reload, (snapshot) => snapshot.status === "idle", t.signal); - await t.require(reload.snapshot.status, equals("idle")); + await waitForStore(reload, (snapshot) => snapshot.status === "busy", t.signal); + await t.require(reload.snapshot.status, equals("busy")); await t.require(reload.snapshot.error, equals(undefined)); const finished = ( await t.target.attach(session.id, { startIndex }).watch().result() diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts index 1b5d6ed4c5..6b8198ba40 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts @@ -23,9 +23,11 @@ export default defineEval({ const production = choice.request.options?.find((option) => /production/i.test(option.label)); if (production === undefined) throw new Error("ask_question did not offer Production."); - const chosen = await session.respond([ + const choiceApplied = await session.respond([ { optionId: production.id, interactionId: choice.interactionId }, ]); + choiceApplied.expectOk(); + const chosen = await session.nextTurn(); chosen.expectOk(); chosen.calledTool("ask_question", { count: 1, @@ -40,9 +42,11 @@ export default defineEval({ prompt: (prompt) => /Bob ship first/i.test(prompt), toolName: "ask_question", }); - const answered = await session.respond([ + const textApplied = await session.respond([ { interactionId: typed.interactionId, text: "Canary pool in us-east" }, ]); + textApplied.expectOk(); + const answered = await session.nextTurn(); answered.expectOk(); answered.calledTool("ask_question", { output: { answer: "Canary pool in us-east", status: "answered" }, diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/authorized-response-oauth-cancel.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/authorized-response-oauth-cancel.eval.ts index 4a30198a66..69f3ad0b09 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/authorized-response-oauth-cancel.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/authorized-response-oauth-cancel.eval.ts @@ -25,7 +25,7 @@ export default defineEval({ const held = await approvalTurn.result(); const cancelled = await held.session.respond([ - { optionId: "cancel", interactionId: approval.interactionId }, + { optionId: "deny", interactionId: approval.interactionId }, ]); cancelled.event("interaction.settled", { count: 1, diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/continue-runs-the-same-call.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/continue-runs-the-same-call.eval.ts index d5280fe572..a14e5bde94 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/continue-runs-the-same-call.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/continue-runs-the-same-call.eval.ts @@ -14,7 +14,7 @@ export default defineEval({ const { held, request, session } = await budgetQuestion(t); const turnId = held.events.find((event) => event.type === "turn.started")?.data.turnId; - const resumed = (await session.respond(answers("continue", request), asAlice)).expectOk(); + const resumed = (await session.respond(answers("approve", request), asAlice)).expectOk(); expectResolved(resumed, request, "accepted"); resumed.notEvent("turn.started"); resumed.eventOrder([ diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/message-waits-behind-budget.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/message-waits-behind-budget.eval.ts index 54511a6a5a..d60f5fdcb5 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/message-waits-behind-budget.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/message-waits-behind-budget.eval.ts @@ -25,7 +25,7 @@ export default defineEval({ added.notEvent("model.requested"); added.notEvent("content.completed"); - const resumed = (await session.respond(answers("continue", request), asAlice)).expectOk(); + const resumed = (await session.respond(answers("approve", request), asAlice)).expectOk(); resumed.event("delivery.consumed", { count: 1, data: { parts: [{ kind: "text", text: SAY.deadline }] }, diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/stale-budget-answer-dropped.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/stale-budget-answer-dropped.eval.ts index 86c8658d82..0c3ee23f8d 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/stale-budget-answer-dropped.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/stale-budget-answer-dropped.eval.ts @@ -14,9 +14,9 @@ export default defineEval({ timeoutMs: 60_000, async test(t) { const { request, session } = await budgetQuestion(t); - (await session.respond(answers("continue", request), asAlice)).expectOk(); + (await session.respond(answers("approve", request), asAlice)).expectOk(); - await session.startRespond(answers("continue", request), asAlice); + await session.startRespond(answers("approve", request), asAlice); const next = (await session.send(SAY.bobStatus, asAlice)).expectOk(); next.event("content.completed", { data: { phase: "reply", value: REPLY.bobStatus } }); session.notEvent("delivery.consumed", { diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/stop-cancels-once.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/stop-cancels-once.eval.ts index 1f2b65e757..1a2968cea9 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/stop-cancels-once.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/stop-cancels-once.eval.ts @@ -10,7 +10,7 @@ export default defineEval({ async test(t) { const { request, session } = await budgetQuestion(t); - const stopped = await session.respond(answers("stop", request), asAlice); + const stopped = await session.respond(answers("deny", request), asAlice); // Stop declines to go on. expectResolved(stopped, request, "declined"); stopped.event("interaction.settled", { count: 1 }); diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/typed-continue-answers.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/typed-continue-answers.eval.ts index b049a363ec..164ef041eb 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/typed-continue-answers.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/typed-continue-answers.eval.ts @@ -13,7 +13,7 @@ export default defineEval({ const typed = (await session.send("continue", asAlice)).expectOk(); expectResolved(typed, request, "accepted"); typed.event("interaction.settled", { - data: { interactionId: request.interactionId, response: { optionId: "continue" } }, + data: { interactionId: request.interactionId, response: { optionId: "approve" } }, }); typed.notEvent("turn.started"); typed.event("content.completed", { data: { phase: "reply", value: REPLY.statusNote } }); diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/button-cancel.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/button-cancel.eval.ts index a9861c25c5..59fa55ed0f 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/button-cancel.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/button-cancel.eval.ts @@ -12,7 +12,7 @@ export default defineEval({ toolName: "guarded-echo", }); const cancelled = await session.respond([ - { optionId: "cancel", interactionId: approval.interactionId }, + { optionId: "deny", interactionId: approval.interactionId }, ]); cancelled.expectOk(); t.succeeded(); diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/budget-grant.control.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/budget-grant.control.eval.ts index 453465792b..f9569a7cac 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/budget-grant.control.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/budget-grant.control.eval.ts @@ -15,7 +15,7 @@ export default defineEval({ // When the user grants another budget window. const live = await session.startRespond([ - { interactionId: budget.interactionId, optionId: "continue" }, + { interactionId: budget.interactionId, optionId: "approve" }, ]); // Then the read executes and a new budget request appears before any final reply. @@ -25,6 +25,6 @@ export default defineEval({ await t.check(nextBudget.interactionId === budget.interactionId, equals(false)); next.calledTool("read-draft", { status: "completed", count: 1 }); next.notEvent("content.completed", { data: { kind: "text", phase: "reply" } }); - await session.respond([{ interactionId: nextBudget.interactionId, optionId: "stop" }]); + await session.respond([{ interactionId: nextBudget.interactionId, optionId: "deny" }]); }, }); diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/budget-grant.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/budget-grant.eval.ts index ceaaf8a900..22db7287ed 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/budget-grant.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/budget-grant.eval.ts @@ -21,7 +21,7 @@ export default defineEval({ // When the user grants another budget window. const live = await session.startRespond([ - { interactionId: budget.interactionId, optionId: "continue" }, + { interactionId: budget.interactionId, optionId: "approve" }, ]); // Then the read executes and a new budget request appears; A stays unexecuted. @@ -32,6 +32,6 @@ export default defineEval({ next.calledTool("read-draft", { status: "completed", count: 1 }); next.notEvent("content.completed", { data: { kind: "text", phase: "reply" } }); expectChangeStillUnexecuted(session); - await session.respond([{ interactionId: nextBudget.interactionId, optionId: "stop" }]); + await session.respond([{ interactionId: nextBudget.interactionId, optionId: "deny" }]); }, }); diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/cancel-sibling.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/cancel-sibling.eval.ts index 720cb47877..2e7822b90d 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/cancel-sibling.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/cancel-sibling.eval.ts @@ -23,7 +23,7 @@ export default defineEval({ // When the user cancels B only. const live = await session.startRespond([ - { interactionId: current.interactionId, optionId: "cancel" }, + { interactionId: current.interactionId, optionId: "deny" }, ]); // Then B does not execute and the read gets a completed reply; the message cancelled A, which never runs. diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/stale-response.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/stale-response.eval.ts index 3a88cf30ff..4b48ba4850 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/stale-response.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/continuation/stale-response.eval.ts @@ -21,7 +21,7 @@ export default defineEval({ const session = first.session; const second = await session.send("Prepare change B, then acknowledge my decision."); const approvalB = requestFrom(second, "change-b"); - const cancelB = [{ interactionId: approvalB.interactionId, optionId: "cancel" }]; + const cancelB = [{ interactionId: approvalB.interactionId, optionId: "deny" }]; await expectResponseReply( t, await session.startRespond(cancelB), diff --git a/e2e/fixtures/agent-tools-sandbox/evals/sandbox/redeploy.eval.ts b/e2e/fixtures/agent-tools-sandbox/evals/sandbox/redeploy.eval.ts index 209d79ba27..c21674d439 100644 --- a/e2e/fixtures/agent-tools-sandbox/evals/sandbox/redeploy.eval.ts +++ b/e2e/fixtures/agent-tools-sandbox/evals/sandbox/redeploy.eval.ts @@ -90,7 +90,7 @@ export default defineEval({ const dynamicSession = dynamicParked.session; dynamicSession.requireInteraction({ display: "confirmation", - optionIds: ["approve", "cancel"], + optionIds: ["approve", "deny"], toolName: "dynamic-turn-replay-gate", }); dynamicParked.calledTool("dynamic-turn-replay-gate", { status: "pending", count: 1 }); @@ -112,7 +112,8 @@ export default defineEval({ await deployToAlias(t, alias, "instructions"); await waitForAliasToServe(t, INSTRUCTIONS_MARKER); - const dynamicResumed = await dynamicSession.respondAll("approve"); + await dynamicSession.respondAll("approve"); + const dynamicResumed = await dynamicSession.nextTurn(); dynamicResumed.expectOk(); dynamicResumed.calledTool("dynamic_turn_replay_probe", { count: 1, diff --git a/e2e/fixtures/agent-workflow-tools/evals/confirm-deploy.hitl.eval.ts b/e2e/fixtures/agent-workflow-tools/evals/confirm-deploy.hitl.eval.ts index 3e6b62b455..f79f303a48 100644 --- a/e2e/fixtures/agent-workflow-tools/evals/confirm-deploy.hitl.eval.ts +++ b/e2e/fixtures/agent-workflow-tools/evals/confirm-deploy.hitl.eval.ts @@ -8,7 +8,7 @@ export default defineEval({ const session = parked.session; session.requireInteraction({ display: "confirmation", - optionIds: ["approve", "cancel"], + optionIds: ["approve", "deny"], toolName: "confirm_deploy", }); parked.calledTool("confirm_deploy", { status: "pending", count: 1 }); @@ -18,7 +18,8 @@ export default defineEval({ data: { callId, output: "awaiting approval" }, }); - const approved = await session.respondAll("approve"); + await session.respondAll("approve"); + const approved = await session.nextTurn(); approved.expectOk(); approved.calledTool("confirm_deploy", { count: 1, diff --git a/e2e/fixtures/agent-workflow-tools/evals/escalate-deploy.deadline.eval.ts b/e2e/fixtures/agent-workflow-tools/evals/escalate-deploy.deadline.eval.ts index 9ef74cf06f..15846f6dcb 100644 --- a/e2e/fixtures/agent-workflow-tools/evals/escalate-deploy.deadline.eval.ts +++ b/e2e/fixtures/agent-workflow-tools/evals/escalate-deploy.deadline.eval.ts @@ -13,12 +13,13 @@ export default defineEval({ const session = parked.session; session.requireInteraction({ display: "confirmation", - optionIds: ["approve", "cancel"], + optionIds: ["approve", "deny"], toolName: "escalate_deploy", }); parked.calledTool("escalate_deploy", { status: "pending", count: 1 }); - const answered = await session.respondAll("approve"); + await session.respondAll("approve"); + const answered = await session.nextTurn(); answered.expectOk(); answered.calledTool("escalate_deploy", { output: /"decided":"approved"/u }); answered.messageIncludes("WORKFLOW-ESCALATE-RESULT"); diff --git a/e2e/fixtures/agent-workflow-tools/evals/held-turn.question.eval.ts b/e2e/fixtures/agent-workflow-tools/evals/held-turn.question.eval.ts index 2a5fd6b554..e0fbbf783f 100644 --- a/e2e/fixtures/agent-workflow-tools/evals/held-turn.question.eval.ts +++ b/e2e/fixtures/agent-workflow-tools/evals/held-turn.question.eval.ts @@ -30,14 +30,16 @@ export default defineEval({ parked.notEvent("turn.settled"); const request = parked.session.requireInteraction({ toolName: "approve_rollout" }); - const answered = await parked.session.respond([ + const applied = await parked.session.respond([ { optionId: "approve", interactionId: request.interactionId }, ]); - answered.expectOk(); - answered.event("interaction.settled", { + applied.expectOk(); + applied.event("interaction.settled", { count: 1, data: { interactionId: request.interactionId, outcome: "accepted" }, }); + const answered = await parked.session.nextTurn(); + answered.expectOk(); answered.event("call.settled", { count: 1, data: { callId: "rollout", outcome: "completed", output: { approved: true, service: "api" } }, From f039502ce43d6690b16d9a496f3e51ffa4d1a79e Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:27 +0000 Subject: [PATCH 05/17] fix(runtime): retain answer deliveries through resumed workflow work Incorporates the runtime repair and regression from 79259e537f257ebc0dfd06cd9b2a7c41e28b4fb6. Co-authored-by: OwenKephart Signed-off-by: OwenKephart --- .changeset/command-deliveries.md | 2 +- .../generated-program-child-hitl.eval.ts | 4 +- .../evals/auto-resume-question.eval.ts | 4 +- .../approvals/deny-records-not-run.eval.ts | 2 +- .../partial-answer-keeps-turn-held.eval.ts | 2 +- .../evals/hitl/ask-question-answer.eval.ts | 8 +-- .../evals/hitl/deny-then-regate.eval.ts | 2 +- .../evals/sandbox/redeploy.eval.ts | 5 +- .../evals/confirm-deploy.hitl.eval.ts | 5 +- .../evals/escalate-deploy.deadline.eval.ts | 5 +- .../evals/held-turn.question.eval.ts | 8 +-- .../eve/src/execution/proxied-deliver-step.ts | 32 +++++---- .../hook-cancellation.integration.test.ts | 68 +++++++++++++++++++ packages/eve/src/harness/approval-recheck.ts | 20 ++++-- .../approved-call-recheck.integration.test.ts | 2 +- .../harness/session-machine/transitions.ts | 8 +-- .../testing/workflow-tool-run-harness.ts | 16 ++--- 17 files changed, 126 insertions(+), 67 deletions(-) diff --git a/.changeset/command-deliveries.md b/.changeset/command-deliveries.md index ba70b8ab83..379136f65d 100644 --- a/.changeset/command-deliveries.md +++ b/.changeset/command-deliveries.md @@ -2,4 +2,4 @@ "eve": minor --- -Session commands now return serializable, followable `Delivery` receipts, with request statuses distinct from session termination and explicit retryability on `SessionNotActiveError`. Session handles use `id`/`stream()` with one line-indexed `SessionStreamOptions`, `SessionInfo` matches `ctx.session`, client handles are type-only exports, local resets become `newSession()`, eval/CLI results use `reply`/`data`, and `invokeTool` returns call-shaped outcomes. +Session commands now return serializable, followable `Delivery` receipts, with request statuses distinct from session termination and explicit retryability on `SessionNotActiveError`; answers follow the turn work they resume. Session handles use `id`/`stream()` with one line-indexed `SessionStreamOptions`, `SessionInfo` matches `ctx.session`, client handles are type-only exports, local resets become `newSession()`, eval/CLI results use `reply`/`data`, and `invokeTool` returns call-shaped outcomes. diff --git a/e2e/fixtures/agent-cancellation/evals/cancellation/generated-program-child-hitl.eval.ts b/e2e/fixtures/agent-cancellation/evals/cancellation/generated-program-child-hitl.eval.ts index 0d3d8f538a..e2382a5878 100644 --- a/e2e/fixtures/agent-cancellation/evals/cancellation/generated-program-child-hitl.eval.ts +++ b/e2e/fixtures/agent-cancellation/evals/cancellation/generated-program-child-hitl.eval.ts @@ -14,11 +14,9 @@ export default defineEval({ if (request.request.kind !== "question") throw new Error("Child input request is not a question."); - const answered = await session.respond([ + const resumed = await session.respond([ { interactionId: request.interactionId, text: "GENERATED-HITL-MARKER" }, ]); - answered.expectOk(); - const resumed = await session.nextTurn(); resumed.expectOk(); t.succeeded(); diff --git a/e2e/fixtures/agent-decide/evals/auto-resume-question.eval.ts b/e2e/fixtures/agent-decide/evals/auto-resume-question.eval.ts index 14c6ab3bdf..e86420625e 100644 --- a/e2e/fixtures/agent-decide/evals/auto-resume-question.eval.ts +++ b/e2e/fixtures/agent-decide/evals/auto-resume-question.eval.ts @@ -13,12 +13,10 @@ export default defineEval({ const staging = question.request.options?.find((option) => option.label === "Staging"); if (staging === undefined) throw new Error("ask_question did not offer Staging."); - const answered = await parked.session.respond([ + const resumed = await parked.session.respond([ { optionId: staging.id, interactionId: question.interactionId }, ]); - answered.expectOk(); - const resumed = await parked.session.nextTurn(); resumed.expectOk(); resumed.calledTool("ask_question", { count: 1, diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/approvals/deny-records-not-run.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/approvals/deny-records-not-run.eval.ts index 0325c43eec..657008088e 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/approvals/deny-records-not-run.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/approvals/deny-records-not-run.eval.ts @@ -19,7 +19,7 @@ export default defineEval({ const session = await aliceSession(t); const request = approvalFor(await session.send(SAY.changeA, asAlice), "change-a"); - const denied = (await session.respond(answers("cancel", request), asAlice)).expectOk(); + const denied = (await session.respond(answers("deny", request), asAlice)).expectOk(); expectResolved(denied, request, "declined"); expectNotRun(denied, "change-a"); denied.event("call.settled", { diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/approvals/partial-answer-keeps-turn-held.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/approvals/partial-answer-keeps-turn-held.eval.ts index b98a1ad66f..39447d5afe 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/approvals/partial-answer-keeps-turn-held.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/approvals/partial-answer-keeps-turn-held.eval.ts @@ -32,7 +32,7 @@ export default defineEval({ partial.notEvent("model.started"); partial.notEvent("call.settled"); - const finished = (await session.respond(answers("cancel", b), asAlice)).expectOk(); + const finished = (await session.respond(answers("deny", b), asAlice)).expectOk(); // Both approvals settle in the one commit, each with its own answer. finished.event("interaction.settled", { count: 2 }); finished.event("interaction.settled", { diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts index 6b8198ba40..1b5d6ed4c5 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts @@ -23,11 +23,9 @@ export default defineEval({ const production = choice.request.options?.find((option) => /production/i.test(option.label)); if (production === undefined) throw new Error("ask_question did not offer Production."); - const choiceApplied = await session.respond([ + const chosen = await session.respond([ { optionId: production.id, interactionId: choice.interactionId }, ]); - choiceApplied.expectOk(); - const chosen = await session.nextTurn(); chosen.expectOk(); chosen.calledTool("ask_question", { count: 1, @@ -42,11 +40,9 @@ export default defineEval({ prompt: (prompt) => /Bob ship first/i.test(prompt), toolName: "ask_question", }); - const textApplied = await session.respond([ + const answered = await session.respond([ { interactionId: typed.interactionId, text: "Canary pool in us-east" }, ]); - textApplied.expectOk(); - const answered = await session.nextTurn(); answered.expectOk(); answered.calledTool("ask_question", { output: { answer: "Canary pool in us-east", status: "answered" }, diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/deny-then-regate.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/deny-then-regate.eval.ts index ccae3750ed..c44316b5b2 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/deny-then-regate.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/deny-then-regate.eval.ts @@ -12,7 +12,7 @@ export default defineEval({ const { session } = await t.send('Call the guarded-echo tool with note "denied-call".'); const request = session.requireInteraction({ toolName: "guarded-echo" }); - const denied = await session.respondAll("cancel"); + const denied = await session.respondAll("deny"); denied.expectOk(); denied.event("call.settled", { data: { diff --git a/e2e/fixtures/agent-tools-sandbox/evals/sandbox/redeploy.eval.ts b/e2e/fixtures/agent-tools-sandbox/evals/sandbox/redeploy.eval.ts index c21674d439..209d79ba27 100644 --- a/e2e/fixtures/agent-tools-sandbox/evals/sandbox/redeploy.eval.ts +++ b/e2e/fixtures/agent-tools-sandbox/evals/sandbox/redeploy.eval.ts @@ -90,7 +90,7 @@ export default defineEval({ const dynamicSession = dynamicParked.session; dynamicSession.requireInteraction({ display: "confirmation", - optionIds: ["approve", "deny"], + optionIds: ["approve", "cancel"], toolName: "dynamic-turn-replay-gate", }); dynamicParked.calledTool("dynamic-turn-replay-gate", { status: "pending", count: 1 }); @@ -112,8 +112,7 @@ export default defineEval({ await deployToAlias(t, alias, "instructions"); await waitForAliasToServe(t, INSTRUCTIONS_MARKER); - await dynamicSession.respondAll("approve"); - const dynamicResumed = await dynamicSession.nextTurn(); + const dynamicResumed = await dynamicSession.respondAll("approve"); dynamicResumed.expectOk(); dynamicResumed.calledTool("dynamic_turn_replay_probe", { count: 1, diff --git a/e2e/fixtures/agent-workflow-tools/evals/confirm-deploy.hitl.eval.ts b/e2e/fixtures/agent-workflow-tools/evals/confirm-deploy.hitl.eval.ts index f79f303a48..3e6b62b455 100644 --- a/e2e/fixtures/agent-workflow-tools/evals/confirm-deploy.hitl.eval.ts +++ b/e2e/fixtures/agent-workflow-tools/evals/confirm-deploy.hitl.eval.ts @@ -8,7 +8,7 @@ export default defineEval({ const session = parked.session; session.requireInteraction({ display: "confirmation", - optionIds: ["approve", "deny"], + optionIds: ["approve", "cancel"], toolName: "confirm_deploy", }); parked.calledTool("confirm_deploy", { status: "pending", count: 1 }); @@ -18,8 +18,7 @@ export default defineEval({ data: { callId, output: "awaiting approval" }, }); - await session.respondAll("approve"); - const approved = await session.nextTurn(); + const approved = await session.respondAll("approve"); approved.expectOk(); approved.calledTool("confirm_deploy", { count: 1, diff --git a/e2e/fixtures/agent-workflow-tools/evals/escalate-deploy.deadline.eval.ts b/e2e/fixtures/agent-workflow-tools/evals/escalate-deploy.deadline.eval.ts index 15846f6dcb..9ef74cf06f 100644 --- a/e2e/fixtures/agent-workflow-tools/evals/escalate-deploy.deadline.eval.ts +++ b/e2e/fixtures/agent-workflow-tools/evals/escalate-deploy.deadline.eval.ts @@ -13,13 +13,12 @@ export default defineEval({ const session = parked.session; session.requireInteraction({ display: "confirmation", - optionIds: ["approve", "deny"], + optionIds: ["approve", "cancel"], toolName: "escalate_deploy", }); parked.calledTool("escalate_deploy", { status: "pending", count: 1 }); - await session.respondAll("approve"); - const answered = await session.nextTurn(); + const answered = await session.respondAll("approve"); answered.expectOk(); answered.calledTool("escalate_deploy", { output: /"decided":"approved"/u }); answered.messageIncludes("WORKFLOW-ESCALATE-RESULT"); diff --git a/e2e/fixtures/agent-workflow-tools/evals/held-turn.question.eval.ts b/e2e/fixtures/agent-workflow-tools/evals/held-turn.question.eval.ts index e0fbbf783f..2a5fd6b554 100644 --- a/e2e/fixtures/agent-workflow-tools/evals/held-turn.question.eval.ts +++ b/e2e/fixtures/agent-workflow-tools/evals/held-turn.question.eval.ts @@ -30,16 +30,14 @@ export default defineEval({ parked.notEvent("turn.settled"); const request = parked.session.requireInteraction({ toolName: "approve_rollout" }); - const applied = await parked.session.respond([ + const answered = await parked.session.respond([ { optionId: "approve", interactionId: request.interactionId }, ]); - applied.expectOk(); - applied.event("interaction.settled", { + answered.expectOk(); + answered.event("interaction.settled", { count: 1, data: { interactionId: request.interactionId, outcome: "accepted" }, }); - const answered = await parked.session.nextTurn(); - answered.expectOk(); answered.event("call.settled", { count: 1, data: { callId: "rollout", outcome: "completed", output: { approved: true, service: "api" } }, diff --git a/packages/eve/src/execution/proxied-deliver-step.ts b/packages/eve/src/execution/proxied-deliver-step.ts index 0d082eff79..81fc2a1472 100644 --- a/packages/eve/src/execution/proxied-deliver-step.ts +++ b/packages/eve/src/execution/proxied-deliver-step.ts @@ -92,8 +92,7 @@ async function routeProxiedDeliver( // A message that answers a question is still the person's turn in the // conversation, so the stream records it with its delivery ids. const answerDeliveryIds: string[] = []; - // A delivery that carried only relayed answers is this session's to record: it took effect - // once forwarded, or, as a message, joins the turn that asked. + // Relayed answers belong to the asking turn's resumed work, even without message text. const tables = storedProjection(durableSession.state).view; const routedDeliveries: Parameters[1]["deliveries"][number][] = []; const answers: ResponseSubmittedData[] = []; @@ -113,16 +112,27 @@ async function routeProxiedDeliver( const answeredBy = payloadMetadata[0]?.deliveryId; if (routed.forChildren.length > 0 && answeredBy !== undefined) { const message = routed.forChildren.find((forChild) => forChild.message !== undefined); - const askedIn = message?.payload.answers + const askedIn = routed.forChildren + .flatMap((child) => child.payload.answers) .map((response) => tables.interactions[response.interactionId]) - .find((row) => row !== undefined); + .find((row) => { + if (row === undefined) return false; + const turnId = interactionOwner(tables, row).turnId; + return turnId !== undefined && tables.turns[turnId]?.status === "running"; + }); const turnId = askedIn === undefined ? undefined : interactionOwner(tables, askedIn).turnId; + if (routed.forSelf === undefined && turnId !== undefined) { + answerDeliveryIds.push(...payloadMetadata.map((metadata) => metadata.deliveryId)); + } for (const metadata of payloadMetadata) { // Input left for this session goes on to its turn, which admits nothing twice. routedDeliveries.push({ consumed: - routed.forSelf === undefined && message?.message !== undefined && turnId !== undefined - ? { parts: userPartsOf(message.message), turnId } + routed.forSelf === undefined && turnId !== undefined + ? { + parts: message?.message === undefined ? [] : userPartsOf(message.message), + turnId, + } : undefined, continues: routed.forSelf === undefined ? undefined : true, deliveryId: metadata.deliveryId, @@ -153,13 +163,6 @@ async function routeProxiedDeliver( for (const { interactionId } of forChild.payload.answers) resolvedRequests.add(interactionId); } - if (forChild.message !== undefined) { - answerDeliveryIds.push( - ...(sourceDelivery.deliveryMetadata ?? []) - .filter((metadata) => metadata.payloadIndex === sourcePayloadIndex) - .map((metadata) => metadata.deliveryId), - ); - } const key = JSON.stringify([ forChild.childContinuationToken, forChild.childSessionInbox?.sessionId ?? "", @@ -190,8 +193,7 @@ async function routeProxiedDeliver( } } - // Like a steering message, an answering message joins the open turn, so the turn's later events - // carry its delivery ids too. + // Answers that resume a turn own its later events, whether they came from text or buttons. const published: PublishedSessionEvents = { serializedContext: joinTurnDeliveryIds(serializedContext, answerDeliveryIds), sessionState: input.sessionState, diff --git a/packages/eve/src/execution/tools/workflow/hook-cancellation.integration.test.ts b/packages/eve/src/execution/tools/workflow/hook-cancellation.integration.test.ts index 90e8c5289f..73a04a510f 100644 --- a/packages/eve/src/execution/tools/workflow/hook-cancellation.integration.test.ts +++ b/packages/eve/src/execution/tools/workflow/hook-cancellation.integration.test.ts @@ -15,6 +15,74 @@ import { } from "#internal/testing/workflow-tool-run-harness.js"; describe("workflow hook cancellation", () => { + it.each(["button", "text"] as const)( + "keeps the %s answer delivery open until the workflow's resumed turn settles", + async (mode) => { + const runtime = await createWorkflowToolRuntime({ + agentName: `workflow-answer-delivery-${mode}`, + execute: confirmDeployWorkflow, + toolName: "confirm_deploy", + }); + await runtime.run(async () => { + const run = await startSessionOwner(workflowEntry, [ + { + kind: "initial", + ownerDeploymentId: "dpl_inline", + input: { message: 'Run confirm_deploy with service "api"' }, + serializedContext: buildWorkflowToolSerializedContext({ + acceptedDeploymentId: "dpl_inline", + continuationToken: `http:workflow-answer-delivery-${mode}`, + requestInput: true, + }), + }, + ]); + const stream = captureTurnEvents(run); + try { + const asked = await stream.nextTurn(); + const question = filterEventsByType(asked, "interaction.opened").find( + (event) => event.data.request.kind === "question", + ); + expect(question).toBeDefined(); + await resumeSessionInbox(sessionCommandHookToken(run.runId), { + kind: "send", + delivery: { channelKind: "http", channelName: "test", deliveryId: "answer-delivery" }, + payload: + mode === "button" + ? { + answers: [{ interactionId: question!.data.interactionId, optionId: "approve" }], + } + : { message: "Deploy" }, + }); + const answered = await stream.nextUntil( + (event) => + event.type === "delivery.settled" && event.data.deliveryId === "answer-delivery", + ); + expect(filterEventsByType(answered, "delivery.settled").at(-1)?.data).toMatchObject({ + deliveryId: "answer-delivery", + outcome: "handled", + turnId: "turn_0", + }); + expect(filterEventsByType(answered, "call.settled")).toContainEqual( + expect.objectContaining({ + data: expect.objectContaining({ + outcome: "completed", + output: { approved: true, service: "api" }, + }), + }), + ); + expect(filterEventsByType(answered, "turn.settled").at(-1)?.data.outcome).toBe( + "completed", + ); + expect(filterEventsByType(answered, "turn.started")).toHaveLength(0); + } finally { + stream.dispose(); + await run.cancel().catch(() => {}); + } + }); + }, + 30_000, + ); + it("lets a delivery.consumed hook cancel a typed answer before it resolves ctx.ask", async () => { const hooksAfterCancel: string[] = []; const runtime = await createWorkflowToolRuntime({ diff --git a/packages/eve/src/harness/approval-recheck.ts b/packages/eve/src/harness/approval-recheck.ts index db5206b0a3..71de179229 100644 --- a/packages/eve/src/harness/approval-recheck.ts +++ b/packages/eve/src/harness/approval-recheck.ts @@ -19,7 +19,7 @@ interface Evaluation { } // Enumerable symbols survive policy context spreads without entering JSON or the public type. -const evaluationKey = Symbol("eve.approval-evaluation"); +const evaluationKey = Symbol.for("eve.approval-evaluation"); type EvaluatedContext = ApprovalContext & { readonly [evaluationKey]?: Evaluation }; export function withApprovalGrants( @@ -32,8 +32,11 @@ export function withApprovalGrants( } export function evaluateOnce(context: ApprovalContext, once: OnceApproval): boolean { - const evaluation = approvalEvaluation(context) ?? { grants: [] }; - Object.assign(context, { [evaluationKey]: evaluation }); + let evaluation = approvalEvaluation(context); + if (evaluation === undefined) { + evaluation = { grants: [] }; + Object.assign(context, { [evaluationKey]: evaluation }); + } evaluation.once = once; evaluation.grant = once.grantable ? evaluation.grants.find( @@ -50,7 +53,9 @@ export function approvalEvaluation(context: ApprovalContext): Evaluation | undef export function markApprovalRecheck(context: T): T { const evaluation = approvalEvaluation(context) ?? { grants: [] }; evaluation.recheck = true; - return Object.assign(context, { [evaluationKey]: evaluation }); + if (approvalEvaluation(context) === undefined) + Object.assign(context, { [evaluationKey]: evaluation }); + return context; } export function inheritApprovalContext( @@ -58,9 +63,10 @@ export function inheritApprovalContext( context: T, ): T { const evaluation = approvalEvaluation(source); - return evaluation === undefined - ? context - : Object.assign(context, { [evaluationKey]: evaluation }); + if (evaluation !== undefined && approvalEvaluation(context) !== evaluation) { + Object.assign(context, { [evaluationKey]: evaluation }); + } + return context; } /** Whether `context` is the re-check of an approved call just before it runs. */ diff --git a/packages/eve/src/harness/approved-call-recheck.integration.test.ts b/packages/eve/src/harness/approved-call-recheck.integration.test.ts index caccb4efa1..7b7374690b 100644 --- a/packages/eve/src/harness/approved-call-recheck.integration.test.ts +++ b/packages/eve/src/harness/approved-call-recheck.integration.test.ts @@ -94,7 +94,7 @@ describe("approved call recheck (real AI SDK)", () => { name: "deploy", description: "Deploy Alice's release", inputSchema: jsonSchema({ type: "object" }), - approval: (context) => perEnvironment({ ...context }), + approval: (context) => perEnvironment(Object.freeze({ ...context })), execute, }; const call = (toolName: string, environment: string, toolCallId: string) => ({ diff --git a/packages/eve/src/harness/session-machine/transitions.ts b/packages/eve/src/harness/session-machine/transitions.ts index d3d080cc8f..2886f7fa93 100644 --- a/packages/eve/src/harness/session-machine/transitions.ts +++ b/packages/eve/src/harness/session-machine/transitions.ts @@ -535,9 +535,9 @@ function forwardedAnswer( /** * Answers to requests this session relays went on to their askers. Each delivery that carried - * such answers is admitted here, with the answers it forwarded: a message that answered joins - * the turn that asked, one that also carries input for this session goes on to its turn, and - * any other took effect once forwarded. A question this session decides settles now; an + * such answers is admitted here, with the answers it forwarded. An answer joins the open turn + * that asked; a delivery that also carries input for this session goes on to its turn. Between + * turns, forwarding alone applies the delivery. A question this session decides settles now; an * approval stays open until its asker settles it. */ export function routeAnswer( @@ -547,7 +547,7 @@ export function routeAnswer( readonly deliveryId: string; readonly principal?: Principal; readonly source?: DeliverySource; - /** A message that answered: the turn that asked consumes it. */ + /** The open asking turn consumes the answer, with any message parts it carried. */ readonly consumed?: { readonly turnId: string; readonly parts: readonly UserPart[] }; /** The delivery also carries input for this session, whose turn consumes and settles it. */ readonly continues?: true; diff --git a/packages/eve/src/internal/testing/workflow-tool-run-harness.ts b/packages/eve/src/internal/testing/workflow-tool-run-harness.ts index d5caa64df9..af631e62c6 100644 --- a/packages/eve/src/internal/testing/workflow-tool-run-harness.ts +++ b/packages/eve/src/internal/testing/workflow-tool-run-harness.ts @@ -27,16 +27,12 @@ export function buildWorkflowToolSerializedContext(input: { "eve.stateLayout": 1, "eve.capabilities": { requestInput: input.requestInput ?? false }, "eve.channel": { kind: "http", state: {} }, - ...(input.acceptedDeploymentId === undefined - ? {} - : { - "eve.channelDelivery": { - acceptedDeploymentId: input.acceptedDeploymentId, - channelKind: "http", - channelName: "test", - deliveryId: "delivery-initial", - }, - }), + "eve.channelDelivery": { + acceptedDeploymentId: input.acceptedDeploymentId, + channelKind: "http", + channelName: "test", + deliveryId: "delivery-initial", + }, "eve.continuationToken": input.continuationToken, }; } From 0cbbaa4294617e681bc4019e1192c5ba1f7db69d Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:28 +0000 Subject: [PATCH 06/17] fix(evals): follow exact delivery settlement past unrelated turn boundaries Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- .../hitl/approval-response-settlement.eval.ts | 16 ++++++---- packages/eve/src/evals/live-turn.test.ts | 29 +++++++++++++++++++ packages/eve/src/evals/session.ts | 4 ++- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts index 7b3d443651..52ff155f96 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/approval-response-settlement.eval.ts @@ -112,7 +112,7 @@ export default defineEval({ }; try { - for (const decision of ["approve", "cancel"] as const) { + for (const decision of ["approve", "deny"] as const) { t.log( `Alice prepares a change; after a refused answer and reload, finish with ${decision}.`, ); @@ -142,7 +142,7 @@ export default defineEval({ // Retrying on the same page must reach the policy again, not reuse the refused candidate. const retryCandidate = await refuse(t, page, callId, interactionId); await t.require(retryCandidate !== firstCandidate, equals(true)); - page.reset(); + page.newSession(); const reload = new EveAgentStore({ host, @@ -176,8 +176,14 @@ export default defineEval({ signal: t.signal, }), ); - await waitForStore(reload, (snapshot) => snapshot.status === "busy", t.signal); - await t.require(reload.snapshot.status, equals("busy")); + await waitForStore( + reload, + (snapshot) => + snapshot.status === "idle" && + snapshot.view.interactions[interactionId]?.status === "settled", + t.signal, + ); + await t.require(reload.snapshot.status, equals("idle")); await t.require(reload.snapshot.error, equals(undefined)); const finished = ( await t.target.attach(session.id, { startIndex }).watch().result() @@ -196,7 +202,7 @@ export default defineEval({ finished.calledTool(TOOL, { status: "completed", count: 0 }); } await t.require(toolPart(reload, callId).interaction?.status, equals("settled")); - reload.reset(); + reload.newSession(); } } finally { for (const store of stores) store.newSession(); diff --git a/packages/eve/src/evals/live-turn.test.ts b/packages/eve/src/evals/live-turn.test.ts index d64d23e313..962d01984b 100644 --- a/packages/eve/src/evals/live-turn.test.ts +++ b/packages/eve/src/evals/live-turn.test.ts @@ -57,6 +57,35 @@ function watch(events: readonly SessionEvent[]) { } describe("EveEvalLiveTurn", () => { + it("keeps a queued delivery open past another turn's pause and settlement", async () => { + const old = testTurnFacts(0, "Earlier.", ["old"]); + const own = testTurnFacts(1, "Queued reply.", ["d_1"]); + const events: SessionEvent[] = [ + ...old.slice(0, 3), + own[0]!, + { + type: "turn.paused", + data: { turnId: "turn_0", awaiting: [] }, + scope: { turnId: "turn_0" }, + }, + ...old.filter((event) => event.type === "turn.settled"), + ...own.slice(1), + ]; + vi.spyOn(globalThis, "fetch") + .mockResolvedValueOnce( + Response.json({ ok: true, sessionId: "session_1", deliveryId: "d_1" }, { status: 202 }), + ) + .mockResolvedValueOnce( + new Response(events.map(encodeTestLine).join(""), { + headers: { [EVE_STREAM_VERSION_HEADER]: EVE_MESSAGE_STREAM_VERSION }, + }), + ); + const manager = new EvalSessionManager({ client: new Client({ host: "https://eve.test" }) }); + const turn = await manager.attach("session_1").send("Queued message."); + expect(turn.reply).toBe("Queued reply."); + expect(turn.status).toBe("completed"); + }); + it("attaches without I/O and advances the same session cursor across nextTurn and watch", async () => { const first = testTurnFacts(0, "First.", ["first"]); const second = testTurnFacts(1, "Second.", ["second"]); diff --git a/packages/eve/src/evals/session.ts b/packages/eve/src/evals/session.ts index 8b9716393b..91ee27952e 100644 --- a/packages/eve/src/evals/session.ts +++ b/packages/eve/src/evals/session.ts @@ -538,7 +538,9 @@ class EvalLiveTurn implements EveEvalLiveTurn { if (endsWith?.(event) === true) matched = true; const endsSegment = endsWith === undefined - ? (delivery?.observe(event) ?? false) || endsTurn + ? delivery === undefined + ? endsTurn + : delivery.observe(event) : matched && event.meta.endOfLine !== false; observe(event); this.#resolveWaiters(event); From 8e19b894bd7f7afafd125ae33ac53b456537e70b Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:29 +0000 Subject: [PATCH 07/17] fix(runtime): bind task-wait answers to the waiting turn delivery Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- .../eve/src/execution/proxied-deliver-step.ts | 18 ++++++++++++++---- .../session/edge-cases.integration.test.ts | 12 ++++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/eve/src/execution/proxied-deliver-step.ts b/packages/eve/src/execution/proxied-deliver-step.ts index 81fc2a1472..1fce6cb3f0 100644 --- a/packages/eve/src/execution/proxied-deliver-step.ts +++ b/packages/eve/src/execution/proxied-deliver-step.ts @@ -3,7 +3,7 @@ import { selectDeliveryPayloads } from "#harness/messages.js"; import { routeAnswer } from "#harness/session-machine/transitions.js"; import { storedProjection } from "#harness/session-machine/view.js"; import { principalOf } from "#execution/session/delivery-facts.js"; -import { interactionOwner } from "#protocol/session-projection/selectors.js"; +import { activeTurn, interactionOwner, waiting } from "#protocol/session-projection/selectors.js"; import type { ResponseSubmittedData } from "#protocol/session-events/families/response.js"; import type { SessionInboxAddress } from "#execution/session-inbox/address.js"; import { hasDelegatedSessionContext } from "#execution/delegated-session-context.js"; @@ -94,6 +94,17 @@ async function routeProxiedDeliver( const answerDeliveryIds: string[] = []; // Relayed answers belong to the asking turn's resumed work, even without message text. const tables = storedProjection(durableSession.state).view; + const currentTurn = activeTurn(tables); + const blocked = currentTurn === undefined ? undefined : waiting(tables, currentTurn.turnId); + const askedTurn = (row: (typeof tables.interactions)[string]): string | undefined => { + const owned = interactionOwner(tables, row).turnId; + if (owned !== undefined && tables.turns[owned]?.status === "running") return owned; + // Detached tasks have no turn owner, but a task-wait can hold the active turn on their prompts. + return blocked?.on === "person" && + blocked.interactions.some((request) => request.interactionId === row.interactionId) + ? currentTurn?.turnId + : undefined; + }; const routedDeliveries: Parameters[1]["deliveries"][number][] = []; const answers: ResponseSubmittedData[] = []; @@ -117,10 +128,9 @@ async function routeProxiedDeliver( .map((response) => tables.interactions[response.interactionId]) .find((row) => { if (row === undefined) return false; - const turnId = interactionOwner(tables, row).turnId; - return turnId !== undefined && tables.turns[turnId]?.status === "running"; + return askedTurn(row) !== undefined; }); - const turnId = askedIn === undefined ? undefined : interactionOwner(tables, askedIn).turnId; + const turnId = askedIn === undefined ? undefined : askedTurn(askedIn); if (routed.forSelf === undefined && turnId !== undefined) { answerDeliveryIds.push(...payloadMetadata.map((metadata) => metadata.deliveryId)); } diff --git a/packages/eve/src/execution/session/edge-cases.integration.test.ts b/packages/eve/src/execution/session/edge-cases.integration.test.ts index 6c06b5a951..f9ff72052d 100644 --- a/packages/eve/src/execution/session/edge-cases.integration.test.ts +++ b/packages/eve/src/execution/session/edge-cases.integration.test.ts @@ -1185,9 +1185,17 @@ describe("work delegated to a child session", () => { tools: [{ approval: true, name: "deploy" }], }, async (session) => { - await session.until("relayed", (event) => event.type === "interaction.opened"); + await session.next("relayed approval pause"); await session.answer(session.approvals()[0]!, "approve"); - await session.until("done", (event) => event.type === "turn.settled"); + const answered = await session.until( + "answer settlement", + (event) => event.type === "delivery.settled", + ); + expect(ofType(answered, "delivery.settled").at(-1)?.data).toMatchObject({ + outcome: "handled", + turnId: "turn_0", + }); + expect(turnOutcomes(answered)).toEqual(["turn_0:completed"]); }, ); From 97418896529c5d8e1423ad84691b5cbe51cfd9e0 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:29 +0000 Subject: [PATCH 08/17] fix(fixtures): observe queued budget work before answering its prompt Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- .../hitl/relayed-question-answered.eval.ts | 8 ++++++- .../proxied-approvals-one-at-a-time.eval.ts | 10 ++++++-- .../evals/hitl/ask-question-answer.eval.ts | 8 ++++++- .../message-waits-behind-budget.eval.ts | 24 +++++++++++++------ .../hitl/budget/no-step-behind-budget.eval.ts | 10 +++++--- .../budget/typed-continue-answers.eval.ts | 4 ++-- 6 files changed, 48 insertions(+), 16 deletions(-) diff --git a/e2e/fixtures/agent-subagents-hitl/evals/hitl/relayed-question-answered.eval.ts b/e2e/fixtures/agent-subagents-hitl/evals/hitl/relayed-question-answered.eval.ts index 71af073ef0..476aa5be1f 100644 --- a/e2e/fixtures/agent-subagents-hitl/evals/hitl/relayed-question-answered.eval.ts +++ b/e2e/fixtures/agent-subagents-hitl/evals/hitl/relayed-question-answered.eval.ts @@ -17,9 +17,15 @@ export default defineEval({ const production = request.request.options?.find((option) => option.label === "Production"); if (production === undefined) throw new Error("Expected a Production option."); - const answered = await session.respond([ + const live = await session.startRespond([ { optionId: production.id, interactionId: request.interactionId }, ]); + let answered; + try { + answered = await live.result(); + } finally { + t.log(JSON.stringify(live.events.slice(-16))); + } answered.event("interaction.settled", { count: 1, data: { diff --git a/e2e/fixtures/agent-subagents-hitl/evals/proxied-approvals-one-at-a-time.eval.ts b/e2e/fixtures/agent-subagents-hitl/evals/proxied-approvals-one-at-a-time.eval.ts index fc3d0360db..cbc03564d0 100644 --- a/e2e/fixtures/agent-subagents-hitl/evals/proxied-approvals-one-at-a-time.eval.ts +++ b/e2e/fixtures/agent-subagents-hitl/evals/proxied-approvals-one-at-a-time.eval.ts @@ -23,7 +23,13 @@ export default defineEval({ // The reply answers only the first approval. The subagent admits it: the answer stands while // the rest of its batch waits, and the parent relays that admission before Alice replies // to the next prompt, so her next reply can't revise it. - const approved = await blocked.send("approve"); + const liveApproval = await blocked.start("approve"); + let approved; + try { + approved = await liveApproval.result(); + } finally { + t.log(JSON.stringify(liveApproval.events.slice(-16))); + } approved.noFailedCalls(); const admittedInTurn = approved.events.some((event) => event.type === "response.admitted"); if (!admittedInTurn) { @@ -38,7 +44,7 @@ export default defineEval({ throw new Error(`The first reply resolved ${JSON.stringify(resolved)}.`); } - const cancelled = await approved.session.send("cancel"); + const cancelled = await approved.session.send("deny"); cancelled.noFailedCalls(); const completed = cancelled.reply?.includes(MARKER) ? cancelled diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts index 1b5d6ed4c5..968e9a14f6 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/ask-question-answer.eval.ts @@ -23,9 +23,15 @@ export default defineEval({ const production = choice.request.options?.find((option) => /production/i.test(option.label)); if (production === undefined) throw new Error("ask_question did not offer Production."); - const chosen = await session.respond([ + const liveChoice = await session.startRespond([ { optionId: production.id, interactionId: choice.interactionId }, ]); + let chosen; + try { + chosen = await liveChoice.result(); + } finally { + t.log(JSON.stringify(liveChoice.events.slice(-16))); + } chosen.expectOk(); chosen.calledTool("ask_question", { count: 1, diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/message-waits-behind-budget.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/message-waits-behind-budget.eval.ts index d60f5fdcb5..71fd2d8474 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/message-waits-behind-budget.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/message-waits-behind-budget.eval.ts @@ -1,6 +1,7 @@ import { defineEval } from "eve/evals"; +import { equals } from "eve/evals/expect"; -import { REPLY, SAY, answers, asAlice, budgetQuestion, expectHeld } from "../helpers.ts"; +import { REPLY, SAY, answers, asAlice, budgetQuestion } from "../helpers.ts"; /** * While the budget question waits, Alice adds that the note should mention @@ -18,12 +19,20 @@ export default defineEval({ async test(t) { const { request, session } = await budgetQuestion(t); - const added = await session.send(SAY.deadline, asAlice); - added.notEvent("delivery.consumed"); - expectHeld(added); - added.notEvent("interaction.settled"); - added.notEvent("model.requested"); - added.notEvent("content.completed"); + const pending = await session.start(SAY.deadline, asAlice); + await pending.waitForEvent("turn.paused"); + const added = { events: [...pending.events] }; + t.check( + added.events.filter((event) => + [ + "delivery.consumed", + "interaction.settled", + "model.requested", + "content.completed", + ].includes(event.type), + ).length, + equals(0), + ); const resumed = (await session.respond(answers("approve", request), asAlice)).expectOk(); resumed.event("delivery.consumed", { @@ -34,5 +43,6 @@ export default defineEval({ data: { phase: "reply", value: REPLY.statusNoteWithDeadline }, }); resumed.notEvent("turn.started"); + (await pending.result()).expectOk(); }, }); diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/no-step-behind-budget.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/no-step-behind-budget.eval.ts index b1362f9922..aae55119d9 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/no-step-behind-budget.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/no-step-behind-budget.eval.ts @@ -1,6 +1,7 @@ import { defineEval } from "eve/evals"; +import { equals } from "eve/evals/expect"; -import { SAY, asAlice, budgetQuestion, expectNoModelCallWhileOpen } from "../helpers.ts"; +import { SAY, answers, asAlice, budgetQuestion, expectNoModelCallWhileOpen } from "../helpers.ts"; /** * While the budget question waits, Alice adds a message. The turn must stay @@ -14,8 +15,11 @@ export default defineEval({ async test(t) { const { request, session } = await budgetQuestion(t); - const added = await session.send(SAY.deadline, asAlice); - added.notEvent("model.started"); + const added = await session.start(SAY.deadline, asAlice); + await added.waitForEvent("turn.paused"); + t.check(added.events.filter((event) => event.type === "model.started").length, equals(0)); + (await session.respond(answers("approve", request), asAlice)).expectOk(); + (await added.result()).expectOk(); expectNoModelCallWhileOpen(session, request.interactionId); }, }); diff --git a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/typed-continue-answers.eval.ts b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/typed-continue-answers.eval.ts index 164ef041eb..9c568b258c 100644 --- a/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/typed-continue-answers.eval.ts +++ b/e2e/fixtures/agent-tools-hitl/evals/hitl/budget/typed-continue-answers.eval.ts @@ -2,7 +2,7 @@ import { defineEval } from "eve/evals"; import { REPLY, asAlice, budgetQuestion, expectResolved } from "../helpers.ts"; -/** Alice types "continue": it names the question's option, so it answers it like the button. */ +/** Alice types "approve": it names the question's option, so it answers it like the button. */ export default defineEval({ description: "A typed reply naming a budget option answers the budget question.", tags: ["hitl", "budget", "text-reply"], @@ -10,7 +10,7 @@ export default defineEval({ async test(t) { const { request, session } = await budgetQuestion(t); - const typed = (await session.send("continue", asAlice)).expectOk(); + const typed = (await session.send("approve", asAlice)).expectOk(); expectResolved(typed, request, "accepted"); typed.event("interaction.settled", { data: { interactionId: request.interactionId, response: { optionId: "approve" } }, From 4f9d76590b9c38beaacd5ef907a72b379b7b307b Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:30 +0000 Subject: [PATCH 09/17] fix(runtime): preserve authenticated identity on the initial delivery Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- .changeset/initial-delivery-auth.md | 5 +++++ .../eve/src/execution/session/entry.test.ts | 20 +++++++++++++++++++ packages/eve/src/execution/session/entry.ts | 8 +++++++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .changeset/initial-delivery-auth.md diff --git a/.changeset/initial-delivery-auth.md b/.changeset/initial-delivery-auth.md new file mode 100644 index 0000000000..4496f0d043 --- /dev/null +++ b/.changeset/initial-delivery-auth.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +Preserve the authenticated session creator on the first message delivery. Initial tools and delegated work now run with the same caller identity as authenticated follow-up messages instead of losing it during admission. diff --git a/packages/eve/src/execution/session/entry.test.ts b/packages/eve/src/execution/session/entry.test.ts index 6d0b2fbd50..c4939df145 100644 --- a/packages/eve/src/execution/session/entry.test.ts +++ b/packages/eve/src/execution/session/entry.test.ts @@ -107,6 +107,26 @@ beforeEach(() => { }); describe("bootInitialOwner timeout startup", () => { + it("preserves the authenticated creator on the first delivery", async () => { + vi.mocked(createSessionInbox).mockReturnValue(createInbox() as never); + const auth = { + attributes: {}, + authenticator: "test", + principalId: "alice", + principalType: "user", + }; + const boot = await bootInitialOwner( + initialInput({ + serializedContext: { "eve.auth": auth, "eve.bundle": { source: { kind: "bundled" } } }, + }), + "session-1", + ); + expect(boot?.session.start.kind).toBe("turn"); + expect( + boot?.session.start.kind === "turn" ? boot.session.start.input?.auth : undefined, + ).toEqual(auth); + }); + it("prepares controls and starts the timeout while session creation is pending", async () => { const sessionCreation = deferred(); const timeoutStartup = deferred<{ runId: string }>(); diff --git a/packages/eve/src/execution/session/entry.ts b/packages/eve/src/execution/session/entry.ts index 8c3aa65939..e02ff51014 100644 --- a/packages/eve/src/execution/session/entry.ts +++ b/packages/eve/src/execution/session/entry.ts @@ -2,7 +2,12 @@ import { readSerializedSessionSchedule } from "#context/session-schedule.js"; import { failSession, runPreparedSession, type SessionBoot } from "#execution/session/program.js"; import { getWorkflowMetadata, getWritable } from "#compiled/@workflow/core/index.js"; -import type { DeliverHookPayload, RunInput, SessionCapabilities } from "#channel/types.js"; +import type { + DeliverHookPayload, + RunInput, + SessionCapabilities, + SessionAuthContext, +} from "#channel/types.js"; import { readChannelRequestId, readRootSessionId } from "#execution/eve-workflow-attributes.js"; import type { DurableCompiledArtifactsSource } from "#runtime/durable-compiled-artifacts-source.js"; import { resolveInitialTurnCallerStep } from "#subagents/parent-notification.js"; @@ -318,6 +323,7 @@ function createInitialDelivery( ): DeliverHookPayload | undefined { if (input.input.message === undefined) return undefined; return { + auth: serializedContext["eve.auth"] as SessionAuthContext | null | undefined, schedule: readSerializedSessionSchedule(serializedContext), deliveryMetadata: serializedContext["eve.channelDelivery"] === undefined From c9758adbda6b30edac224d00f6ae5e76d571d3d2 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:31 +0000 Subject: [PATCH 10/17] fix(runtime): accept authenticated workflow answers and remote receipts Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- .changeset/authenticated-workflow-answers.md | 5 ++++ .../evals/gated-deploy.approval.eval.ts | 2 +- .../execution/agent-sessions/remote.test.ts | 28 +++++++++---------- .../src/execution/agent-sessions/remote.ts | 19 +++++++++---- .../hook-cancellation.integration.test.ts | 7 +++++ .../src/execution/tools/workflow/messages.ts | 8 +++--- 6 files changed, 43 insertions(+), 26 deletions(-) create mode 100644 .changeset/authenticated-workflow-answers.md diff --git a/.changeset/authenticated-workflow-answers.md b/.changeset/authenticated-workflow-answers.md new file mode 100644 index 0000000000..c85db4b346 --- /dev/null +++ b/.changeset/authenticated-workflow-answers.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +Fix authenticated answers to workflow questions being discarded by an outdated control-message validator. Remote agent dispatch now accepts named delivery receipts while preserving support for older peers' creation acknowledgements. diff --git a/e2e/fixtures/agent-workflow-tools/evals/gated-deploy.approval.eval.ts b/e2e/fixtures/agent-workflow-tools/evals/gated-deploy.approval.eval.ts index dbb197a31d..23ba689b7c 100644 --- a/e2e/fixtures/agent-workflow-tools/evals/gated-deploy.approval.eval.ts +++ b/e2e/fixtures/agent-workflow-tools/evals/gated-deploy.approval.eval.ts @@ -17,7 +17,7 @@ export default defineEval({ status: "failed", }); - for (const decision of ["approve", "cancel"] as const) { + for (const decision of ["approve", "deny"] as const) { const session = await t.session(); const parked = await session.send( "WORKFLOW-APPROVAL-START Alice asks Bob to review the API release before deployment.", diff --git a/packages/eve/src/execution/agent-sessions/remote.test.ts b/packages/eve/src/execution/agent-sessions/remote.test.ts index 7079e27e79..1fadb08d24 100644 --- a/packages/eve/src/execution/agent-sessions/remote.test.ts +++ b/packages/eve/src/execution/agent-sessions/remote.test.ts @@ -172,19 +172,17 @@ describe("startRemoteAgentSession", () => { const { agentDelegation } = await import("#tracing/eve/delegation.js"); const manager = new AsyncLocalStorageContextManager().enable(); context.setGlobalContextManager(manager); - const fetchMock = vi - .fn() - .mockResolvedValue( - Response.json( - { - ok: true, - protocolVersion: REMOTE_AGENT_PROTOCOL_VERSION, - sessionId: "remote-session", - status: "accepted", - }, - { status: 202 }, - ), - ); + const fetchMock = vi.fn().mockResolvedValue( + Response.json( + { + ok: true, + protocolVersion: REMOTE_AGENT_PROTOCOL_VERSION, + sessionId: "remote-session", + status: "accepted", + }, + { status: 202 }, + ), + ); vi.stubGlobal("fetch", fetchMock); try { await agentDelegation.resume( @@ -234,14 +232,14 @@ describe("startRemoteAgentSession", () => { vi.unstubAllEnvs(); }); - it("carries a replay-stable operation id so the receiver can create once", async () => { + it("carries a replay-stable operation id and accepts the receiver's delivery receipt", async () => { const fetchMock = vi.fn().mockResolvedValue( Response.json( { ok: true, protocolVersion: REMOTE_AGENT_PROTOCOL_VERSION, sessionId: "remote-session", - status: "accepted", + deliveryId: "delivery:operation-1", }, { status: 202 }, ), diff --git a/packages/eve/src/execution/agent-sessions/remote.ts b/packages/eve/src/execution/agent-sessions/remote.ts index ba7c073363..6a42a59f56 100644 --- a/packages/eve/src/execution/agent-sessions/remote.ts +++ b/packages/eve/src/execution/agent-sessions/remote.ts @@ -48,12 +48,19 @@ import { decisionToTraceContentCeiling } from "#shared/forwarded-trace-policy.js import { writeConversationBaggage } from "#tracing/eve/conversation-context.js"; import { agentDelegation } from "#tracing/eve/delegation.js"; -const CreateSessionResponseSchema = z.object({ - ok: z.literal(true), - protocolVersion: z.number().optional(), - sessionId: z.string().min(1), - status: z.literal("accepted"), -}); +const CreateSessionResponseSchema = z + .object({ + ok: z.literal(true), + protocolVersion: z.number().optional(), + sessionId: z.string().min(1), + }) + .and( + z.union([ + z.object({ deliveryId: z.string().min(1) }), + // Earlier protocol peers acknowledge creation without naming the delivery. + z.object({ status: z.literal("accepted") }), + ]), + ); const UNKNOWN_REMOTE_OUTCOME = "The request may have completed on the remote agent. Do not retry automatically; check the remote outcome before sending the request again."; diff --git a/packages/eve/src/execution/tools/workflow/hook-cancellation.integration.test.ts b/packages/eve/src/execution/tools/workflow/hook-cancellation.integration.test.ts index 73a04a510f..8f34758d83 100644 --- a/packages/eve/src/execution/tools/workflow/hook-cancellation.integration.test.ts +++ b/packages/eve/src/execution/tools/workflow/hook-cancellation.integration.test.ts @@ -45,6 +45,13 @@ describe("workflow hook cancellation", () => { expect(question).toBeDefined(); await resumeSessionInbox(sessionCommandHookToken(run.runId), { kind: "send", + auth: { + attributes: {}, + authenticator: "test", + principalId: "alice", + principalType: "user", + issuer: "test", + }, delivery: { channelKind: "http", channelName: "test", deliveryId: "answer-delivery" }, payload: mode === "button" diff --git a/packages/eve/src/execution/tools/workflow/messages.ts b/packages/eve/src/execution/tools/workflow/messages.ts index 60db32e302..b26189293a 100644 --- a/packages/eve/src/execution/tools/workflow/messages.ts +++ b/packages/eve/src/execution/tools/workflow/messages.ts @@ -271,11 +271,11 @@ function isWorkflowToolRunAnswer(value: unknown): value is WorkflowToolRunAnswer function isPrincipal(value: unknown): value is Principal { if (typeof value !== "object" || value === null) return false; - const { authenticator, principalId, principalType } = value as Record; + const { id, type, issuer } = value as Record; return ( - typeof authenticator === "string" && - typeof principalId === "string" && - typeof principalType === "string" + typeof id === "string" && + typeof type === "string" && + (issuer === undefined || typeof issuer === "string") ); } From 8c407f002a5e352c5fb0f12413c957a4e4cc81a4 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:31 +0000 Subject: [PATCH 11/17] fix(runtime): read delivery receipts from remote control routes Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- .changeset/remote-control-receipts.md | 5 ++++ .../execution/agent-sessions/remote.test.ts | 6 ++--- .../src/execution/agent-sessions/remote.ts | 27 +++++++++++++++++-- 3 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 .changeset/remote-control-receipts.md diff --git a/.changeset/remote-control-receipts.md b/.changeset/remote-control-receipts.md new file mode 100644 index 0000000000..b9cfe0f97b --- /dev/null +++ b/.changeset/remote-control-receipts.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +Accept delivery receipts from remote cancellation and reset routes, while continuing to read older peers' acknowledgements. Remote control responses still must name the requested session. diff --git a/packages/eve/src/execution/agent-sessions/remote.test.ts b/packages/eve/src/execution/agent-sessions/remote.test.ts index 1fadb08d24..b93e40d55a 100644 --- a/packages/eve/src/execution/agent-sessions/remote.test.ts +++ b/packages/eve/src/execution/agent-sessions/remote.test.ts @@ -1098,7 +1098,7 @@ describe("cancelRemoteAgentTurn", () => { .mockResolvedValueOnce(Response.json({ ok: true, status: "no_active_turn" }, { status: 200 })) .mockResolvedValueOnce( Response.json( - { ok: true, sessionId: "remote/session id", status: "accepted" }, + { ok: true, sessionId: "remote/session id", deliveryId: "cancel-delivery" }, { status: 202 }, ), ); @@ -1189,8 +1189,8 @@ describe("resetRemoteAgentSession", () => { .fn() .mockResolvedValue( Response.json( - { ok: true, previousSessionId: "remote/session id", status: "reset" }, - { status: 200 }, + { ok: true, sessionId: "remote/session id", deliveryId: "reset-delivery" }, + { status: 202 }, ), ); vi.stubGlobal("fetch", fetchMock); diff --git a/packages/eve/src/execution/agent-sessions/remote.ts b/packages/eve/src/execution/agent-sessions/remote.ts index 6a42a59f56..7eed0135df 100644 --- a/packages/eve/src/execution/agent-sessions/remote.ts +++ b/packages/eve/src/execution/agent-sessions/remote.ts @@ -48,6 +48,29 @@ import { decisionToTraceContentCeiling } from "#shared/forwarded-trace-policy.js import { writeConversationBaggage } from "#tracing/eve/conversation-context.js"; import { agentDelegation } from "#tracing/eve/delegation.js"; +const DeliveryReceiptResponseSchema = z.object({ + ok: z.literal(true), + sessionId: z.string().min(1), + deliveryId: z.string().min(1), +}); + +const RemoteCancelResponseSchema = z.union([ + DeliveryReceiptResponseSchema.transform(({ sessionId }) => ({ + sessionId, + status: "accepted" as const, + })), + CancelTurnResponseSchema, +]); + +const RemoteResetResponseSchema = z.union([ + DeliveryReceiptResponseSchema.transform(({ sessionId }) => ({ + ok: true as const, + previousSessionId: sessionId, + status: "reset" as const, + })), + ResetResponseSchema, +]); + const CreateSessionResponseSchema = z .object({ ok: z.literal(true), @@ -367,7 +390,7 @@ export async function cancelRemoteAgentTurn(input: { throw new Error(`Remote agent "${input.remote.name}" cancel-turn response was not valid JSON.`); } - const result = CancelTurnResponseSchema.safeParse(body); + const result = RemoteCancelResponseSchema.safeParse(body); if ( !result.success || (result.data.status === "accepted" && result.data.sessionId !== input.sessionId) @@ -412,7 +435,7 @@ export async function resetRemoteAgentSession(input: { `Remote agent "${input.remote.name}" reset-session request failed with HTTP ${response.status}.`, ); } - const result = ResetResponseSchema.safeParse(await response.json()); + const result = RemoteResetResponseSchema.safeParse(await response.json()); if ( !result.success || (result.data.status === "reset" && result.data.previousSessionId !== input.sessionId) From 974267d01924114e37a616dbc7b4d7f29a106363 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:32 +0000 Subject: [PATCH 12/17] fix(runtime): recognize admitted relayed answers as read input Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- .changeset/relayed-partial-approval.md | 5 ++ .../harness/session-machine/waiting.test.ts | 47 ++++++++++++------- .../protocol/session-projection/selectors.ts | 11 ++++- 3 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 .changeset/relayed-partial-approval.md diff --git a/.changeset/relayed-partial-approval.md b/.changeset/relayed-partial-approval.md new file mode 100644 index 0000000000..cb0956591c --- /dev/null +++ b/.changeset/relayed-partial-approval.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +Settle a relayed answer delivery as awaiting input when its asker admits the answer but still needs another approval. Parents no longer hang waiting for new model work after the child has already read the answer. diff --git a/packages/eve/src/harness/session-machine/waiting.test.ts b/packages/eve/src/harness/session-machine/waiting.test.ts index 96a934a6fe..087065ead6 100644 --- a/packages/eve/src/harness/session-machine/waiting.test.ts +++ b/packages/eve/src/harness/session-machine/waiting.test.ts @@ -128,25 +128,36 @@ describe("announceWaiting", () => { it("answers for now one answer of a batch still waiting for the rest", () => { const asked = [callRequested, approval, second, modelSettled]; const paused = viewOf(started, asked, announceWaiting(viewOf(started), viewOf(started, asked))); - const answered = viewOf( - started, - asked, - announceWaiting(viewOf(started), viewOf(started, asked)), - [ - { data: { deliveryId: "d_2" }, type: "delivery.admitted" }, - { - data: { - deliveryId: "d_2", - interactionId: "i_1", - responseId: "r_1", - value: { optionId: "approve" }, - }, - type: "response.submitted", + const consumed: SessionEvent[] = [ + { data: { deliveryId: "d_2" }, type: "delivery.admitted" }, + { + data: { deliveryId: "d_2", parts: [{ kind: "text", text: "approve" }], turnId }, + scope: { turnId }, + type: "delivery.consumed", + }, + ]; + const submitted: SessionEvent[] = [ + { + data: { + deliveryId: "d_2", + interactionId: "i_1", + responseId: "r_1", + value: { optionId: "approve" }, }, - { data: { responseId: "r_1" }, type: "response.admitted" }, - ], - ); - expect(announceWaiting(paused, answered)).toContainEqual({ + type: "response.submitted", + }, + ]; + const announced = announceWaiting(viewOf(started), viewOf(started, asked)); + const before = viewOf(started, asked, announced, consumed, submitted); + expect(announceWaiting(paused, before)).toEqual([]); + const answered = viewOf(started, asked, announced, consumed, submitted, [ + { data: { responseId: "r_1" }, type: "response.admitted" }, + ]); + expect(waiting(answered, turnId)).toMatchObject({ + interactions: [{ interactionId: "i_2" }], + on: "person", + }); + expect(announceWaiting(before, answered)).toContainEqual({ data: { deliveryId: "d_2", outcome: "awaiting-input", turnId }, type: "delivery.settled", }); diff --git a/packages/eve/src/protocol/session-projection/selectors.ts b/packages/eve/src/protocol/session-projection/selectors.ts index 5c4578f8d8..f36c047e7a 100644 --- a/packages/eve/src/protocol/session-projection/selectors.ts +++ b/packages/eve/src/protocol/session-projection/selectors.ts @@ -238,8 +238,8 @@ export function waiting( ) ) return undefined; - // Input the turn consumed after the session last decided anything for it, by requesting a - // model run or asking someone, is the session's to act on. + // Input consumed after the session last requested a model, asked someone, or weighed an + // answer is still its work. A forwarded answer admitted by its asker has already been read. const decided = Math.max( -1, ...Object.values(view.runs) @@ -250,6 +250,13 @@ export function waiting( (row) => interactionOwner(view, row).turnId === turnId || subjectTurn(view, row) === turnId, ) .map((row) => row.introducedAt), + ...Object.values(view.responses) + .filter( + (response) => + response.status !== "submitted" && + view.deliveries[response.deliveryId]?.turnId === turnId, + ) + .map((response) => response.introducedAt), ); const unread = Object.values(view.deliveries).some( (delivery) => From e93b89933facb17af86453955c002cdbbeeb3940 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:33 +0000 Subject: [PATCH 13/17] fix(evals): retain intervening facts and previously open interactions Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- .changeset/eval-interaction-cursor.md | 5 ++ packages/eve/src/evals/live-turn.test.ts | 72 ++++++++++++++++++++++++ packages/eve/src/evals/session.ts | 36 +++++++++--- 3 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 .changeset/eval-interaction-cursor.md diff --git a/.changeset/eval-interaction-cursor.md b/.changeset/eval-interaction-cursor.md new file mode 100644 index 0000000000..773447d4bc --- /dev/null +++ b/.changeset/eval-interaction-cursor.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +Keep eval session views in sync with all facts since their last read, including interactions published between deliveries. Answer results retain still-open questions from earlier reads, so nested approvals cannot be lost when a receipt settles. diff --git a/packages/eve/src/evals/live-turn.test.ts b/packages/eve/src/evals/live-turn.test.ts index 962d01984b..aa3cbbf9aa 100644 --- a/packages/eve/src/evals/live-turn.test.ts +++ b/packages/eve/src/evals/live-turn.test.ts @@ -86,6 +86,78 @@ describe("EveEvalLiveTurn", () => { expect(turn.status).toBe("completed"); }); + it("keeps earlier and intervening questions when an answer delivery settles", async () => { + const question = (interactionId: string): SessionEvent => ({ + type: "interaction.opened", + data: { + interactionId, + request: { kind: "question", prompt: interactionId, allowFreeform: true }, + subject: { turnId }, + }, + }); + const paused: SessionEvent = { + type: "turn.paused", + data: { turnId, awaiting: [{ interactionId: "first" }, { interactionId: "second" }] }, + }; + const response = (events: readonly SessionEvent[]) => + new Response(events.map(encodeTestLine).join(""), { + headers: { [EVE_STREAM_VERSION_HEADER]: EVE_MESSAGE_STREAM_VERSION }, + }); + const fetch = vi + .spyOn(globalThis, "fetch") + .mockResolvedValueOnce( + response([ + ...testTurnFacts(0, "", ["initial"]).slice(0, 3), + question("first"), + question("second"), + paused, + ]), + ) + .mockResolvedValueOnce( + Response.json({ ok: true, sessionId: "session_1", deliveryId: "answer" }, { status: 202 }), + ) + .mockResolvedValueOnce( + response([ + question("third"), + { type: "delivery.admitted", data: { deliveryId: "answer" } }, + { type: "delivery.consumed", data: { deliveryId: "answer", parts: [], turnId } }, + { + type: "response.submitted", + data: { + deliveryId: "answer", + interactionId: "first", + responseId: "r_1", + value: { text: "Alice's answer" }, + }, + }, + { type: "response.settled", data: { responseId: "r_1", outcome: "applied" } }, + { + type: "interaction.settled", + data: { interactionId: "first", outcome: "accepted", cause: { responseId: "r_1" } }, + }, + { + type: "delivery.settled", + data: { deliveryId: "answer", outcome: "awaiting-input", turnId }, + }, + ]), + ); + const manager = new EvalSessionManager({ client: new Client({ host: "https://eve.test" }) }); + const session = manager.attach("session_1"); + await session.nextTurn(); + expect(session.pendingInteractions.map((row) => row.interactionId)).toEqual([ + "first", + "second", + ]); + const answered = await session.respond([{ interactionId: "first", text: "Alice's answer" }]); + expect(answered.status).toBe("awaiting-input"); + expect(session.pendingInteractions.map((row) => row.interactionId)).toEqual([ + "second", + "third", + ]); + expect(answered.interactions.map((row) => row.interactionId)).toEqual(["second", "third"]); + expect(fetch).toHaveBeenCalledTimes(3); + }); + it("attaches without I/O and advances the same session cursor across nextTurn and watch", async () => { const first = testTurnFacts(0, "First.", ["first"]); const second = testTurnFacts(1, "Second.", ["second"]); diff --git a/packages/eve/src/evals/session.ts b/packages/eve/src/evals/session.ts index 91ee27952e..a9255bf382 100644 --- a/packages/eve/src/evals/session.ts +++ b/packages/eve/src/evals/session.ts @@ -7,7 +7,9 @@ import { readFile } from "node:fs/promises"; import { basename } from "node:path"; import { createTextWithFileContent } from "#client/file-parts.js"; -import type { ClientSession } from "#client/session.js"; +import { followClientSession, type ClientSession } from "#client/session.js"; +import { openInteractions } from "#protocol/session-projection/selectors.js"; +import { interaction } from "#protocol/session-reader.js"; import type { ClientSessionState, SendTurnInput, @@ -162,8 +164,11 @@ export class EvalSessionDriver implements EveEvalSession { } async compact(): Promise { - const delivery = await this.#session.compact(); - return this.consume(delivery, undefined, { deliveryId: delivery.deliveryId }).result(); + const startIndex = this.#session.state.streamIndex; + const delivery = await this.#session.compact({ signal: this.#signal }); + return this.consume(this.#session.stream({ startIndex, signal: this.#signal }), undefined, { + deliveryId: delivery.deliveryId, + }).result(); } agent(opened: ChildOpened): EveEvalAgentSession { @@ -254,12 +259,23 @@ export class EvalSessionDriver implements EveEvalSession { async #start(input: SendTurnPayload): Promise { const { answers, message, ...options } = attachSignal(input, this.#signal); + const startIndex = this.#session.state.streamIndex; const response = answers === undefined ? await this.#session.send(message!, options) : await this.#session.respond(answers, options); const deliveryId = response.deliveryId; - return this.consume(response, message, { deliveryId }); + // Delivery streams omit earlier facts, but the eval's cursor and view must not skip them. + return this.consume( + followClientSession(this.#session, { + startIndex, + signal: options.signal, + headers: options.headers, + streamReconnectPolicy: options.streamReconnectPolicy, + }), + message, + { deliveryId }, + ); } /** @internal */ @@ -353,14 +369,17 @@ export class EvalSessionDriver implements EveEvalSession { #recordTurn(input: { readonly data: unknown; readonly events: readonly SessionStreamEvent[]; - readonly interactions: readonly Interaction[]; readonly message: string | undefined; readonly sessionId: string; readonly sessionEnded: boolean; readonly status: RequestStatus; }): EveEvalTurn { this.#events.push(...input.events); - this.#pendingInteractions = input.status === "awaiting-input" ? input.interactions : []; + const view = this.view; + const interactions = openInteractions(view).flatMap( + (row) => interaction(view, row.interactionId) ?? [], + ); + this.#pendingInteractions = input.status === "awaiting-input" ? interactions : []; const derived = deriveRunFacts(input.events, { sessionId: input.sessionId, @@ -369,11 +388,11 @@ export class EvalSessionDriver implements EveEvalSession { const turn = new EvalTurn({ collector: this.#collector, data: input.data, - view: this.view, + view, sessionEnded: input.sessionEnded, derived, events: input.events, - interactions: input.interactions, + interactions, message: input.message, session: this, sessionId: input.sessionId, @@ -394,7 +413,6 @@ export class EvalSessionDriver implements EveEvalSession { return this.#recordTurn({ data: summary.result, events, - interactions: summary.interactions, message: summary.message, sessionEnded: summary.sessionEnded, sessionId, From f69749b8919af0a0756d0e302fcaaa65751a9a69 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:33 +0000 Subject: [PATCH 14/17] fix(evals): separate cursor catch-up from delivery event slices Signed-off-by: OwenKephart Co-Authored-By: owenkephart --- packages/eve/src/client/delivery-events.ts | 6 ++++- packages/eve/src/evals/live-turn.test.ts | 2 ++ packages/eve/src/evals/session.ts | 27 ++++++++++++++++++---- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/packages/eve/src/client/delivery-events.ts b/packages/eve/src/client/delivery-events.ts index 78efd1b9b1..351d67297b 100644 --- a/packages/eve/src/client/delivery-events.ts +++ b/packages/eve/src/client/delivery-events.ts @@ -6,6 +6,7 @@ export async function* deliveryEvents( source: AsyncIterable, deliveryId: string, signal?: AbortSignal, + beforeAdmission?: (event: SessionStreamEvent) => void, ): AsyncGenerator { const segment = new ResponseSegment({ deliveryId }); let started = false; @@ -14,7 +15,10 @@ export async function* deliveryEvents( if (!started) { if (event.type === "session.ended") throw new Error("The session ended before admitting the accepted delivery."); - if (admittingLine[0]?.meta.position.line !== event.meta.position.line) admittingLine = []; + if (admittingLine[0]?.meta.position.line !== event.meta.position.line) { + for (const earlier of admittingLine) beforeAdmission?.(earlier); + admittingLine = []; + } admittingLine.push(event); if (event.type !== "delivery.admitted" || event.data.deliveryId !== deliveryId) continue; started = true; diff --git a/packages/eve/src/evals/live-turn.test.ts b/packages/eve/src/evals/live-turn.test.ts index aa3cbbf9aa..03a2dc28e7 100644 --- a/packages/eve/src/evals/live-turn.test.ts +++ b/packages/eve/src/evals/live-turn.test.ts @@ -155,6 +155,8 @@ describe("EveEvalLiveTurn", () => { "third", ]); expect(answered.interactions.map((row) => row.interactionId)).toEqual(["second", "third"]); + expect(answered.events.some((event) => event.type === "interaction.opened")).toBe(false); + expect(session.view.interactions.third?.status).toBe("open"); expect(fetch).toHaveBeenCalledTimes(3); }); diff --git a/packages/eve/src/evals/session.ts b/packages/eve/src/evals/session.ts index a9255bf382..56be5c506e 100644 --- a/packages/eve/src/evals/session.ts +++ b/packages/eve/src/evals/session.ts @@ -21,6 +21,7 @@ import type { import type { TraceContext } from "#protocol/session-events/families/session.js"; import type { ChildOpened } from "#protocol/session-events/families/child.js"; import { failureOf, ResponseSegment, summarizeTurnEvents } from "#client/session-utils.js"; +import { deliveryEvents } from "#client/delivery-events.js"; import type { Answer } from "#shared/input.js"; import type { Interaction } from "#protocol/session-reader.js"; import { deriveRunFacts } from "#evals/runner/derive-run-facts.js"; @@ -293,7 +294,8 @@ export class EvalSessionDriver implements EveEvalSession { events, observe: (event) => this.#observeEvent(sessionId, event), initialView: this.view, - record: (observed) => { + record: (observed, earlier) => { + this.#events.push(...earlier); if (message !== undefined) { this.#lastInput = typeof message === "string" @@ -461,7 +463,10 @@ class EvalLiveTurn implements EveEvalLiveTurn { readonly events: AsyncIterable; readonly initialView: SessionView; readonly observe: (event: SessionStreamEvent) => void; - readonly record: (events: readonly SessionStreamEvent[]) => EveEvalTurn; + readonly record: ( + events: readonly SessionStreamEvent[], + earlier: readonly SessionStreamEvent[], + ) => EveEvalTurn; readonly session: EveEvalSession; readonly sessionId: string; }) { @@ -536,7 +541,10 @@ class EvalLiveTurn implements EveEvalLiveTurn { async #consume( source: AsyncIterable, observe: (event: SessionStreamEvent) => void, - record: (events: readonly SessionStreamEvent[]) => EveEvalTurn, + record: ( + events: readonly SessionStreamEvent[], + earlier: readonly SessionStreamEvent[], + ) => EveEvalTurn, ending: { readonly deliveryId?: string; readonly endsWith?: (event: SessionStreamEvent) => boolean; @@ -550,7 +558,16 @@ class EvalLiveTurn implements EveEvalLiveTurn { // delivery settles without one, as an answer routed to a child does. const turn = new ResponseSegment(); const delivery = deliveryId === undefined ? undefined : new ResponseSegment({ deliveryId }); - for await (const event of source) { + const earlier: SessionStreamEvent[] = []; + const events = + deliveryId === undefined + ? source + : deliveryEvents(source, deliveryId, undefined, (event) => { + earlier.push(event); + foldReceivedEvent(this.#initialView, event); + observe(event); + }); + for await (const event of events) { this.#events.push(event); const endsTurn = turn.observe(event); if (endsWith?.(event) === true) matched = true; @@ -584,7 +601,7 @@ class EvalLiveTurn implements EveEvalLiveTurn { throw new Error(`Stream for session "${this.sessionId}" closed before a turn boundary.`); } - return record(this.#events); + return record(this.#events, earlier); } catch (error) { const normalized = error instanceof Error ? error : new Error(String(error)); this.#closeWaiters(normalized); From 86ebf79eb7285e26f273b1a74f6fe32aa7432625 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:34 +0000 Subject: [PATCH 15/17] fix(api): finish authorization docs and remove orphan verifier options Signed-off-by: owenkephart Co-Authored-By: owenkephart --- .changeset/curated-public-api.md | 2 +- docs/connections/mcp.mdx | 2 +- docs/connections/openapi.mdx | 4 ++-- docs/connections/overview.mdx | 18 +++++++++--------- docs/guides/dynamic-capabilities.md | 2 +- docs/patterns/multi-tenant-auth.md | 6 +++--- .../eve/public-api/eve/channels/discord.json | 1 - .../eve/public-api/eve/channels/linear.json | 1 - .../eve/public-api/eve/channels/teams.json | 2 -- .../eve/public-api/eve/channels/telegram.json | 1 - .../eve/public-api/eve/channels/twilio.json | 1 - .../eve/src/public/channels/discord/index.ts | 1 - .../eve/src/public/channels/linear/index.ts | 5 +---- .../eve/src/public/channels/teams/index.ts | 6 +----- .../eve/src/public/channels/telegram/index.ts | 1 - .../eve/src/public/channels/twilio/index.ts | 1 - .../src/public/definitions/connections/mcp.ts | 2 +- .../public/definitions/connections/openapi.ts | 2 +- .../scenarios/compile-agent.scenario.test.ts | 2 +- 19 files changed, 22 insertions(+), 38 deletions(-) diff --git a/.changeset/curated-public-api.md b/.changeset/curated-public-api.md index ac8f126b8e..1c91022f3e 100644 --- a/.changeset/curated-public-api.md +++ b/.changeset/curated-public-api.md @@ -2,4 +2,4 @@ "eve": minor --- -Curate public exports and give history and sandbox APIs eve-owned types while retaining reusable authoring types. Use canonical import paths, `principalType` for connection authorization, `ChannelCors` across channels, and explicit local or remote self-modification mounts; scaffold integrations now live under `eve/internal/*`. +Curate public exports and give history and sandbox APIs eve-owned types while retaining reusable authoring types. Use canonical import paths, `principalType` for connection authorization, `ChannelCors` across channels, and explicit local or remote self-modification mounts; scaffold integrations now live under `eve/internal/*`, and verifier option types used only by internal helpers are no longer exported. diff --git a/docs/connections/mcp.mdx b/docs/connections/mcp.mdx index fabef35c25..df2e64ac9e 100644 --- a/docs/connections/mcp.mdx +++ b/docs/connections/mcp.mdx @@ -137,7 +137,7 @@ export default defineMcpClientConnection({ url: "https://mcp.example.com/mcp", description: "Social publishing: draft, schedule, and manage posts.", auth: { - credentialOwner: "app", + principalType: "app", getToken: async () => ({ token: process.env.SOCIAL_API_KEY! }), }, approval: ({ toolName, toolInput }) => { diff --git a/docs/connections/openapi.mdx b/docs/connections/openapi.mdx index cdcf6355a1..2e5cedd509 100644 --- a/docs/connections/openapi.mdx +++ b/docs/connections/openapi.mdx @@ -18,7 +18,7 @@ export default defineOpenAPIConnection({ spec: "https://petstore3.swagger.io/api/v3/openapi.json", description: "Pet store inventory and orders.", auth: { - credentialOwner: "app", + principalType: "app", getToken: async () => ({ token: process.env.PETSTORE_TOKEN! }), }, }); @@ -106,7 +106,7 @@ export default defineOpenAPIConnection({ spec: "https://petstore3.swagger.io/api/v3/openapi.json", description: "Pet store inventory and orders.", auth: { - credentialOwner: "app", + principalType: "app", getToken: async () => ({ token: process.env.PETSTORE_TOKEN! }), }, operations: { allow: ["getInventory", "placeOrder"] }, diff --git a/docs/connections/overview.mdx b/docs/connections/overview.mdx index 4d4f5c65d7..d5ae230858 100644 --- a/docs/connections/overview.mdx +++ b/docs/connections/overview.mdx @@ -93,7 +93,7 @@ export default defineMcpClientConnection({ url: "https://mcp.linear.app/mcp", description: "Linear workspace.", auth: { - credentialOwner: "app", + principalType: "app", getToken: async () => ({ token: process.env.LINEAR_API_TOKEN! }), }, }); @@ -101,7 +101,7 @@ export default defineMcpClientConnection({ If the token has a known TTL, set `expiresAt` (milliseconds since epoch) and eve refreshes ahead of time rather than waiting for a `401`. -When `getToken` is the only auth, `credentialOwner` defaults to `"app"`: one shared credential keyed across all sessions. Set `credentialOwner: "user"` when each end-user carries their own token. +When `getToken` is the only auth, `principalType` defaults to `"app"`: one shared credential keyed across all sessions. Set `principalType: "user"` when each end-user carries their own token. eve resolves and caches connection tokens per step; they never land in conversation history or reach the model. @@ -111,13 +111,13 @@ A connection credential can belong to the agent or to the person using it. This | Credential owner | Use when | Auth shape | | ---------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| App | The agent should use one shared service, bot, installation, or app credential. | `auth: { getToken }` defaults to `credentialOwner: "app"`, or use `connect({ connector: "linear/myagent", principalType: "app" })` with Vercel Connect. | -| User | Each end-user should authorize and use their own third-party account. | `connect("linear/myagent")`, `connect({ connector: "linear/myagent", principalType: "user" })`, or `auth: { credentialOwner: "user", getToken }`. | +| App | The agent should use one shared service, bot, installation, or app credential. | `auth: { getToken }` defaults to `principalType: "app"`, or use `connect({ connector: "linear/myagent", principalType: "app" })` with Vercel Connect. | +| User | Each end-user should authorize and use their own third-party account. | `connect("linear/myagent")`, `connect({ connector: "linear/myagent", principalType: "user" })`, or `auth: { principalType: "user", getToken }`. | | User from a job | Background work should use the same user's OAuth grant that started the work. | Start or resume the session through a channel whose route auth resolved that user, or pass an explicit user auth context when dispatching through a channel. | -`credentialOwner: "user"` does not mean "ask any human later." It means "key this credential to the authenticated user already attached to the eve session." If the run was started by a schedule, a same-project runtime token, `localDev()`, or another internal runtime path without an end-user principal, a user-scoped connection fails with `reason: "principal_required"` instead of starting OAuth. In that case, either authenticate the inbound channel as a user or configure the connection as app-scoped. +`principalType: "user"` does not mean "ask any human later." It means "key this credential to the authenticated user already attached to the eve session." If the run was started by a schedule, a same-project runtime token, `localDev()`, or another internal runtime path without an end-user principal, a user-scoped connection fails with `reason: "principal_required"` instead of starting OAuth. In that case, either authenticate the inbound channel as a user or configure the connection as app-scoped. -`credentialOwner` is eve's field for raw getToken-only connection auth. Existing non-interactive raw `principalType` definitions still work, but are deprecated. Interactive auth and `connect()` from `@vercel/connect/eve` continue to use `principalType: "user"`; that field also remains correct for route-auth session identities. +`principalType` selects credential ownership for both raw `getToken` auth and `connect()` from `@vercel/connect/eve`. The retired `credentialOwner` field is rejected. This ownership setting is separate from a route-auth session identity, where `principalType` describes the caller. ## No auth @@ -159,7 +159,7 @@ export default defineOpenAPIConnection({ spec: "https://warehouse.example.com/openapi.json", description: "The caller's tenant-scoped warehouse.", auth: (ctx) => ({ - credentialOwner: "user", + principalType: "user", getToken: async () => ({ token: await tenantToken(ctx.session.auth.current), }), @@ -178,7 +178,7 @@ headers: { } ``` -Use `credentialOwner: "user"` for per-user tokens so eve rejects unauthenticated callers and keys its step-local token cache by user. The resolver supplements route auth; it does not authenticate the inbound request. Static auth objects and header maps remain available when every caller shares the same configuration. +Use `principalType: "user"` for per-user tokens so eve rejects unauthenticated callers and keys its step-local token cache by user. The resolver supplements route auth; it does not authenticate the inbound request. Static auth objects and header maps remain available when every caller shares the same configuration. ## Per-connection approval @@ -264,7 +264,7 @@ App-scoped Connect auth is non-interactive. eve asks Vercel Connect for an app t ## Self-hosted interactive OAuth -To run your own OAuth, use `defineInteractiveAuthorization` from `eve/connections`, which takes a three-method form and needs no Vercel Connect. eve mints a callback URL, parks (durably suspends) the turn on a framework-owned webhook, and resumes once the token comes back. Interactive auth is always `principalType: "user"`; `credentialOwner` applies only to getToken-only auth, and the factory pins the interactive value for you. +To run your own OAuth, use `defineInteractiveAuthorization` from `eve/connections`, which takes a three-method form and needs no Vercel Connect. eve mints a callback URL, parks (durably suspends) the turn on a framework-owned webhook, and resumes once the token comes back. Interactive auth is always `principalType: "user"`; the factory pins that value for you. ```ts title="agent/connections/linear.ts" import { diff --git a/docs/guides/dynamic-capabilities.md b/docs/guides/dynamic-capabilities.md index 9672aa2778..cd1a4a3879 100644 --- a/docs/guides/dynamic-capabilities.md +++ b/docs/guides/dynamic-capabilities.md @@ -179,7 +179,7 @@ export default defineDynamic({ description: `${account.label} (${account.accountId})`, instanceKey: account.accountId, auth: { - credentialOwner: "user", + principalType: "user", getToken: ({ principal }) => mintAccountToken(principal, account), }, }), diff --git a/docs/patterns/multi-tenant-auth.md b/docs/patterns/multi-tenant-auth.md index f455e81d7c..4d1a58fae7 100644 --- a/docs/patterns/multi-tenant-auth.md +++ b/docs/patterns/multi-tenant-auth.md @@ -91,7 +91,7 @@ eve to require the authenticated user from route auth, key the step-local token cache by that user, and pass the projected principal into `getToken`: ```ts title="agent/lib/tenant-connection-auth.ts" -import type { ConnectionAuthProvider, ConnectionPrincipal } from "eve/connections"; +import type { AuthorizationDefinition, ConnectionPrincipal } from "eve/connections"; import { tenantCredentials, type TenantService } from "./tenant-credentials"; function requireTenantPrincipal(principal: ConnectionPrincipal): { @@ -107,9 +107,9 @@ function requireTenantPrincipal(principal: ConnectionPrincipal): { return { tenantId, userId: principal.id }; } -export function tenantBearerAuth(service: TenantService): ConnectionAuthProvider { +export function tenantBearerAuth(service: TenantService): AuthorizationDefinition { return { - credentialOwner: "user", + principalType: "user", async getToken({ principal }) { const { tenantId, userId } = requireTenantPrincipal(principal); const credential = await tenantCredentials.getBearer(tenantId, service, { userId }); diff --git a/packages/eve/public-api/eve/channels/discord.json b/packages/eve/public-api/eve/channels/discord.json index 0ff0678476..90542248aa 100644 --- a/packages/eve/public-api/eve/channels/discord.json +++ b/packages/eve/public-api/eve/channels/discord.json @@ -33,7 +33,6 @@ "DiscordReceiveTarget", "DiscordRequestOptions", "DiscordUser", - "DiscordVerifyOptions", "DiscordWebhookVerifier", "defaultDiscordAuth", "discordChannel", diff --git a/packages/eve/public-api/eve/channels/linear.json b/packages/eve/public-api/eve/channels/linear.json index d18f838d71..71d4e321e5 100644 --- a/packages/eve/public-api/eve/channels/linear.json +++ b/packages/eve/public-api/eve/channels/linear.json @@ -32,7 +32,6 @@ "LinearReceiveTarget", "LinearSessionContext", "LinearUser", - "LinearVerifyOptions", "LinearWebhookSecret", "LinearWebhookVerifier", "callLinearGraphQL", diff --git a/packages/eve/public-api/eve/channels/teams.json b/packages/eve/public-api/eve/channels/teams.json index 748e7560f7..df2f66016a 100644 --- a/packages/eve/public-api/eve/channels/teams.json +++ b/packages/eve/public-api/eve/channels/teams.json @@ -32,7 +32,6 @@ "TeamsInvokeActivity", "TeamsInvokeResult", "TeamsInvokeResultOrPromise", - "TeamsJwtVerifyOptions", "TeamsMention", "TeamsMessageActivity", "TeamsMessageBody", @@ -43,7 +42,6 @@ "TeamsTenantId", "TeamsThread", "TeamsTokenProvider", - "TeamsVerifyOptions", "TeamsWebhookVerifier", "defaultTeamsAuth", "teamsChannel", diff --git a/packages/eve/public-api/eve/channels/telegram.json b/packages/eve/public-api/eve/channels/telegram.json index b23be2a38f..9442e7a7ff 100644 --- a/packages/eve/public-api/eve/channels/telegram.json +++ b/packages/eve/public-api/eve/channels/telegram.json @@ -30,7 +30,6 @@ "TelegramReceiveTarget", "TelegramUpdate", "TelegramUser", - "TelegramVerifyOptions", "TelegramWebhookSecretToken", "TelegramWebhookVerifier", "defaultTelegramAuth", diff --git a/packages/eve/public-api/eve/channels/twilio.json b/packages/eve/public-api/eve/channels/twilio.json index e84ad41e43..ddb2de0ee2 100644 --- a/packages/eve/public-api/eve/channels/twilio.json +++ b/packages/eve/public-api/eve/channels/twilio.json @@ -26,7 +26,6 @@ "TwilioTextMessage", "TwilioUpdateCallInput", "TwilioVerifiedRequest", - "TwilioVerifyOptions", "TwilioVoiceCall", "TwilioVoiceConfig", "TwilioVoiceResult", diff --git a/packages/eve/src/public/channels/discord/index.ts b/packages/eve/src/public/channels/discord/index.ts index 6ac820d56f..620fd48480 100644 --- a/packages/eve/src/public/channels/discord/index.ts +++ b/packages/eve/src/public/channels/discord/index.ts @@ -56,6 +56,5 @@ export { defaultDiscordAuth } from "#public/channels/discord/defaults.js"; export { type DiscordPublicKey, - type DiscordVerifyOptions, type DiscordWebhookVerifier, } from "#public/channels/discord/verify.js"; diff --git a/packages/eve/src/public/channels/linear/index.ts b/packages/eve/src/public/channels/linear/index.ts index b267e08e46..fad49a5a9e 100644 --- a/packages/eve/src/public/channels/linear/index.ts +++ b/packages/eve/src/public/channels/linear/index.ts @@ -47,7 +47,4 @@ export { type LinearReceiveTarget, type LinearSessionContext, } from "#public/channels/linear/linearChannel.js"; -export { - type LinearVerifyOptions, - type LinearWebhookVerifier, -} from "#public/channels/linear/verify.js"; +export { type LinearWebhookVerifier } from "#public/channels/linear/verify.js"; diff --git a/packages/eve/src/public/channels/teams/index.ts b/packages/eve/src/public/channels/teams/index.ts index ab8db4ab99..2a5439a311 100644 --- a/packages/eve/src/public/channels/teams/index.ts +++ b/packages/eve/src/public/channels/teams/index.ts @@ -68,8 +68,4 @@ export { export { defaultTeamsAuth } from "#public/channels/teams/defaults.js"; -export { - type TeamsJwtVerifyOptions, - type TeamsVerifyOptions, - type TeamsWebhookVerifier, -} from "#public/channels/teams/verify.js"; +export { type TeamsWebhookVerifier } from "#public/channels/teams/verify.js"; diff --git a/packages/eve/src/public/channels/telegram/index.ts b/packages/eve/src/public/channels/telegram/index.ts index d4dfb015f0..20284ff01d 100644 --- a/packages/eve/src/public/channels/telegram/index.ts +++ b/packages/eve/src/public/channels/telegram/index.ts @@ -57,7 +57,6 @@ export { export { defaultTelegramAuth } from "#public/channels/telegram/defaults.js"; export { - type TelegramVerifyOptions, type TelegramWebhookSecretToken, type TelegramWebhookVerifier, } from "#public/channels/telegram/verify.js"; diff --git a/packages/eve/src/public/channels/twilio/index.ts b/packages/eve/src/public/channels/twilio/index.ts index 3ab7a996e3..7aa223d856 100644 --- a/packages/eve/src/public/channels/twilio/index.ts +++ b/packages/eve/src/public/channels/twilio/index.ts @@ -43,6 +43,5 @@ export { type TwilioGatherTwimlOptions } from "#public/channels/twilio/twiml.js" export { type TwilioAuthToken, type TwilioVerifiedRequest, - type TwilioVerifyOptions, type TwilioWebhookUrl, } from "#public/channels/twilio/verify.js"; diff --git a/packages/eve/src/public/definitions/connections/mcp.ts b/packages/eve/src/public/definitions/connections/mcp.ts index e01d03aeb2..35ce92762c 100644 --- a/packages/eve/src/public/definitions/connections/mcp.ts +++ b/packages/eve/src/public/definitions/connections/mcp.ts @@ -64,7 +64,7 @@ export interface McpClientConnectionDefinition { * * - `getToken`-only: covers static API keys, pre-provisioned * JWTs, and out-of-band OAuth. Defaults to - * `credentialOwner: "app"` when omitted. + * `principalType: "app"` when omitted. * - Three-method form: provide `startAuthorization` and * `completeAuthorization` together to opt into * interactive OAuth authorization. diff --git a/packages/eve/src/public/definitions/connections/openapi.ts b/packages/eve/src/public/definitions/connections/openapi.ts index 5bdc0fb710..39d82f0074 100644 --- a/packages/eve/src/public/definitions/connections/openapi.ts +++ b/packages/eve/src/public/definitions/connections/openapi.ts @@ -67,7 +67,7 @@ export interface OpenAPIConnectionDefinition { * `Authorization: Bearer `. * * - `getToken`-only: covers static API keys, pre-provisioned tokens, - * and out-of-band OAuth. Defaults to `credentialOwner: "app"` when + * and out-of-band OAuth. Defaults to `principalType: "app"` when * omitted. * - Three-method form: provide `startAuthorization` and * `completeAuthorization` together to opt into interactive OAuth. diff --git a/packages/eve/test/scenarios/compile-agent.scenario.test.ts b/packages/eve/test/scenarios/compile-agent.scenario.test.ts index c1f0c178b0..62488c7f44 100644 --- a/packages/eve/test/scenarios/compile-agent.scenario.test.ts +++ b/packages/eve/test/scenarios/compile-agent.scenario.test.ts @@ -630,7 +630,7 @@ describe("compileAgent", () => { ].join("\n"), "agent/instructions.md": "You are a precise assistant.\n", "agent/instrumentation/support.ts": [ - 'import { isChannel } from "eve/instrumentation";', + 'import { isChannel } from "eve/channels";', 'import { otelIntegration } from "eve/instrumentation/otel";', 'import supportChannel from "../channels/support.js";', "", From c750c1b4ab450264228ba6419602d6bf8903528b Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 22:27:35 +0000 Subject: [PATCH 16/17] chore(api): refresh assembled session contracts after restacking Signed-off-by: owenkephart Co-Authored-By: owenkephart --- packages/eve/extension-contracts/reports/channel/v62.json | 2 +- packages/eve/extension-contracts/reports/connection/v51.json | 2 +- packages/eve/extension-contracts/reports/state/v14.json | 2 +- packages/eve/extension-contracts/reports/tool/v93.json | 2 +- packages/eve/public-api/eve/client.json | 2 +- packages/eve/public-api/eve/context.json | 1 + packages/eve/public-api/eve/server.json | 2 +- 7 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/eve/extension-contracts/reports/channel/v62.json b/packages/eve/extension-contracts/reports/channel/v62.json index ab55ee397e..82f8fbf5ed 100644 --- a/packages/eve/extension-contracts/reports/channel/v62.json +++ b/packages/eve/extension-contracts/reports/channel/v62.json @@ -2,7 +2,7 @@ "kind": "eve-extension-capability-contract", "capability": "channel", "epoch": 62, - "sha256": "7ba8f04b8b2ca2cfe6b86a67bf1d77d68814e0c7bdbba0662787821a423b5267", + "sha256": "5dc0676a24ace81f1c3901e03ba8e8af17e55a982a4e81b20537530ee6777b24", "exports": [ "DELETE", "GET", diff --git a/packages/eve/extension-contracts/reports/connection/v51.json b/packages/eve/extension-contracts/reports/connection/v51.json index ebe33fd725..a7d89b415a 100644 --- a/packages/eve/extension-contracts/reports/connection/v51.json +++ b/packages/eve/extension-contracts/reports/connection/v51.json @@ -2,7 +2,7 @@ "kind": "eve-extension-capability-contract", "capability": "connection", "epoch": 51, - "sha256": "0f0fb3abff7ca278c7109f95aba438b50d83d84d1f7daf14c4ea82ff04f4e739", + "sha256": "6a8f192a2e498d9fbbcbbb6057e1f28dfa907f297fd21d999853754d5845ec40", "exports": [ "ConnectionAuthorizationFailedError", "ConnectionAuthorizationRequiredError", diff --git a/packages/eve/extension-contracts/reports/state/v14.json b/packages/eve/extension-contracts/reports/state/v14.json index 14bbcc018f..060316c12c 100644 --- a/packages/eve/extension-contracts/reports/state/v14.json +++ b/packages/eve/extension-contracts/reports/state/v14.json @@ -2,7 +2,7 @@ "kind": "eve-extension-capability-contract", "capability": "state", "epoch": 14, - "sha256": "63d8130fc01847a610166183e88bbf95a9df2fb33cb3ce4c2b3ed8b7d64ac073", + "sha256": "1deb234c8db57d7d1173cda85d8819b73905aed8a5ff725214e2d18a3e62c6e5", "exports": [ "HistoryMessage", "SessionAuth", diff --git a/packages/eve/extension-contracts/reports/tool/v93.json b/packages/eve/extension-contracts/reports/tool/v93.json index 3ec4902232..23f05ef29e 100644 --- a/packages/eve/extension-contracts/reports/tool/v93.json +++ b/packages/eve/extension-contracts/reports/tool/v93.json @@ -2,7 +2,7 @@ "kind": "eve-extension-capability-contract", "capability": "tool", "epoch": 93, - "sha256": "e39f85a0b12f6177441ee427b55cb145790caf646f9a6daa62ead80873593a79", + "sha256": "ba09dbf4d14666c0141425e2f9d641a5914907c3e512ed56e84c2375cb1162d4", "exports": [ "AgentRouterInput", "AgentRouterTool", diff --git a/packages/eve/public-api/eve/client.json b/packages/eve/public-api/eve/client.json index 47f66d9aeb..5ae84f1484 100644 --- a/packages/eve/public-api/eve/client.json +++ b/packages/eve/public-api/eve/client.json @@ -62,7 +62,7 @@ "SessionNotActiveError", "SessionSnapshot", "SessionStreamEvent", - "StreamOptions", + "SessionStreamOptions", "StreamReconnectPolicy", "StreamReconnectRetryPolicy", "TokenValue", diff --git a/packages/eve/public-api/eve/context.json b/packages/eve/public-api/eve/context.json index 45df7fef14..d00b95e38b 100644 --- a/packages/eve/public-api/eve/context.json +++ b/packages/eve/public-api/eve/context.json @@ -6,6 +6,7 @@ "SessionInfo", "SessionParent", "SessionPredecessor", + "SessionSchedule", "SessionTurn", "StateHandle", "defineState" diff --git a/packages/eve/public-api/eve/server.json b/packages/eve/public-api/eve/server.json index 604642b360..d58fd8ea17 100644 --- a/packages/eve/public-api/eve/server.json +++ b/packages/eve/public-api/eve/server.json @@ -1 +1 @@ -["SessionReader", "SessionStreamOptions", "sessions"] +["SessionReader", "sessions"] From 7484f5158aae8e63decbbd3db2cebae7f2617489 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Sun, 18 Oct 2026 01:44:32 +0000 Subject: [PATCH 17/17] fix(api): preserve delegated create acknowledgements and migrate docs Signed-off-by: owenkephart Co-Authored-By: owenkephart --- .changeset/delegated-create-compatibility.md | 5 ++ docs/channels/custom.mdx | 32 +++++-- docs/guides/self-modification.md | 2 +- packages/eve/src/discover/extensions.test.ts | 4 +- packages/eve/src/eve-channel/index.ts | 3 + .../channels/eve-session-routes.test.ts | 83 +++++++++++-------- 6 files changed, 83 insertions(+), 46 deletions(-) create mode 100644 .changeset/delegated-create-compatibility.md diff --git a/.changeset/delegated-create-compatibility.md b/.changeset/delegated-create-compatibility.md new file mode 100644 index 0000000000..8a8a698d03 --- /dev/null +++ b/.changeset/delegated-create-compatibility.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +Delegated session creation retains the accepted acknowledgement required by supported remote protocols. Browser/client creates still return the new delivery receipt without the legacy status field. diff --git a/docs/channels/custom.mdx b/docs/channels/custom.mdx index ea7178caac..1d15b2379f 100644 --- a/docs/channels/custom.mdx +++ b/docs/channels/custom.mdx @@ -30,8 +30,8 @@ export default defineChannel({ return Response.json(await source.reset({ reason: "User requested /new" })); } - const session = await source.send(body.message, { auth: null }); - return Response.json({ sessionId: session.id }); + const delivery = await source.send(body.message, { auth: null }); + return Response.json(delivery, { status: 202 }); }), POST("/threads/:threadId/cancel", async (_request, { from, params }) => @@ -44,8 +44,26 @@ export default defineChannel({ Response.json(await from(params.threadId).clear()), ), - GET("/sessions/:sessionId/stream", async (_request, { attachSession, params }) => { - const stream = await attachSession(params.sessionId).getEventStream(); + GET("/sessions/:sessionId/stream", async (request, { attachSession, params }) => { + const iterator = attachSession(params.sessionId) + .stream({ signal: request.signal }) + [Symbol.asyncIterator](); + // This custom event-NDJSON route is not the built-in eve/client wire endpoint. + // Read before sending headers so stranded-session errors reach the route. + const first = await iterator.next(); + let firstPending = true; + const encoder = new TextEncoder(); + const stream = new ReadableStream({ + async pull(controller) { + const next = firstPending ? first : await iterator.next(); + firstPending = false; + if (next.done) controller.close(); + else controller.enqueue(encoder.encode(JSON.stringify(next.value) + "\n")); + }, + async cancel() { + await iterator.return?.(); + }, + }); return new Response(stream, { headers: { "content-type": "application/x-ndjson; charset=utf-8" }, }); @@ -161,12 +179,12 @@ await session.cancel({ turnId }); await session.compact(); await session.clear(); await session.reset({ reason: "Retire this session" }); -await session.getEventStream({ startIndex: 12 }); +for await (const event of session.stream({ startIndex: 12 })) render(event); ``` For a stranded session, `Session.send()`, `Session.respond()`, and a following -`Session.getEventStream()` throw `SessionStrandedError` from `eve/channels` and deliver nothing. -`getEventStream({ follow: false })` still reads the recorded history up to the current tail without +read through `Session.stream()` throw `SessionStrandedError` from `eve/channels` and deliver nothing. +`stream({ follow: false })` still reads the recorded history up to the current tail without changing the session. `error.owner.eveVersion` names the eve release that built the session when it recorded one. Call `reset()` to end the stranded session, then start a new one: diff --git a/docs/guides/self-modification.md b/docs/guides/self-modification.md index 0d413f3244..c651cd395a 100644 --- a/docs/guides/self-modification.md +++ b/docs/guides/self-modification.md @@ -39,7 +39,7 @@ export default selfModification({ }); ``` -Mounts that import `eve/self-modification` still work; that specifier is an alias for `eve/self-modification/local`. Neither specifier accepts a `deployed` option. If your mount sets `deployed`, eve rejects it and asks you to move that configuration to an `eve/self-modification/remote` mount. +The bare `eve/self-modification` entry is removed. Mount `eve/self-modification/local` for development or `eve/self-modification/remote` for deployed source changes. The local mount does not accept a `deployed` option. If your mount sets `deployed`, eve rejects it and asks you to move that configuration to an `eve/self-modification/remote` mount. ## Propose changes from a deployed agent diff --git a/packages/eve/src/discover/extensions.test.ts b/packages/eve/src/discover/extensions.test.ts index 492783c44c..ee172179c3 100644 --- a/packages/eve/src/discover/extensions.test.ts +++ b/packages/eve/src/discover/extensions.test.ts @@ -28,7 +28,7 @@ describe("locateExtensionMountPackage", () => { name: "eve", eve: { builtInExtensions: { - "./self-modification": { dist: "dist/src/self-modification/extension" }, + "./self-modification/local": { dist: "dist/src/self-modification/extension" }, }, }, }), @@ -48,7 +48,7 @@ describe("locateExtensionMountPackage", () => { packageName: "eve", packageRoot, sourceRoot: `${packageRoot}/dist/src/self-modification/extension`, - specifier: "eve/self-modification", + specifier: "eve/self-modification/local", }); }); diff --git a/packages/eve/src/eve-channel/index.ts b/packages/eve/src/eve-channel/index.ts index 0e4b9f5ced..47e3dfc540 100644 --- a/packages/eve/src/eve-channel/index.ts +++ b/packages/eve/src/eve-channel/index.ts @@ -103,10 +103,13 @@ function createdSessionBody(sessionId: string, body: ParsedCreateBody, deliveryI const created: { ok: true; protocolVersion?: number; + status?: "accepted"; sessionId: string; deliveryId?: string; } = { ok: true, sessionId }; if (deliveryId !== undefined) created.deliveryId = deliveryId; + // Supported delegated protocols still require this wire acknowledgement. + if (body.callback !== undefined) created.status = "accepted"; if (body.protocolVersion !== undefined) created.protocolVersion = body.protocolVersion; return created; } diff --git a/packages/eve/src/public/channels/eve-session-routes.test.ts b/packages/eve/src/public/channels/eve-session-routes.test.ts index 2a271a4c80..3f71a554b7 100644 --- a/packages/eve/src/public/channels/eve-session-routes.test.ts +++ b/packages/eve/src/public/channels/eve-session-routes.test.ts @@ -168,46 +168,57 @@ describe("eve ID-addressed session routes", () => { expect(createSession).toHaveBeenCalledTimes(1); }); - it("continues a remote-agent trace for callback sessions", async () => { - const createSession = vi.fn().mockResolvedValue({ - events: new ReadableStream(), - sessionId: "wrun_A", - }); - const args = attachRouteSessionCreator(createArgs(), createSession); + it.each([undefined, 2, REMOTE_AGENT_PROTOCOL_VERSION])( + "preserves legacy create receipts and remote traces for protocol %s", + async (protocolVersion) => { + const createSession = vi.fn().mockResolvedValue({ + events: new ReadableStream(), + sessionId: "wrun_A", + }); + const args = attachRouteSessionCreator(createArgs(), createSession); - const response = await route("POST", "/eve/v1/session")( - new Request("https://eve.test/eve/v1/session", { - body: JSON.stringify({ - callback: { - callId: "call-1", - subagentName: "research", - token: "tok123", - url: "https://caller.example.com/eve/v1/callback/tok123", + const response = await route("POST", "/eve/v1/session")( + new Request("https://eve.test/eve/v1/session", { + body: JSON.stringify({ + callback: { + callId: "call-1", + subagentName: "research", + token: "tok123", + url: "https://caller.example.com/eve/v1/callback/tok123", + }, + message: "hello", + protocolVersion, + }), + headers: { + "content-type": "application/json", + traceparent: `00-${"1".repeat(32)}-${"2".repeat(16)}-01`, }, - message: "hello", - protocolVersion: REMOTE_AGENT_PROTOCOL_VERSION, + method: "POST", }), - headers: { - "content-type": "application/json", - traceparent: `00-${"1".repeat(32)}-${"2".repeat(16)}-01`, - }, - method: "POST", - }), - args, - ); + args, + ); - expect(response.status).toBe(202); - expect(createSession).toHaveBeenCalledWith( - expect.objectContaining({ - parentTraceContext: { - isRemote: true, - spanId: "2".repeat(16), - traceFlags: 1, - traceId: "1".repeat(32), - }, - }), - ); - }); + expect(response.status).toBe(202); + // Protocols 1–3 readers require this acknowledgement, even when a newer + // caller could identify the accepted delivery from its receipt instead. + await expect(response.json()).resolves.toMatchObject({ + ok: true, + status: "accepted", + sessionId: "wrun_A", + protocolVersion: protocolVersion ?? 1, + }); + expect(createSession).toHaveBeenCalledWith( + expect.objectContaining({ + parentTraceContext: { + isRemote: true, + spanId: "2".repeat(16), + traceFlags: 1, + traceId: "1".repeat(32), + }, + }), + ); + }, + ); it("preserves the dispatch caller when the transport trace advances through ingress", async () => { const createSession = vi.fn().mockResolvedValue({