Skip to content

feat(channels)!: typed delivery hooks and canonical channel identity - #4932

Merged
OwenKephart merged 1 commit into
v27/lab-fixesfrom
api-cleanup/channel-delivery
Oct 20, 2026
Merged

OwenKephart merged 1 commit into
v27/lab-fixesfrom
api-cleanup/channel-delivery

Conversation

@OwenKephart

@OwenKephart OwenKephart commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Channel delivery hooks expose an untyped bag and harness input types, while hooks confuse an authored channel's identity with its durable adapter key. Introduce ChannelDelivery<TState> and let hooks return a replacement payload or nothing to preserve the input. Match authored channels by their path-derived identity without changing HTTP adapter rehydration. Remove kindHint and the deprecated metadata-audience fallback.

First PR in the independent API cleanup stack, now based on the final #4855 D8 head. Related to #4930. Approval and command API changes remain in the following slices. Capability snapshots are finalized at #4954 rather than regenerated for each intermediate PR.

Production code: 50 fewer lines (+94/−144). Typed delivery replacement and canonical resolver identity replace the harness-shaped delivery surface, unused kind hints, and deprecated audience fallback. Excludes tests/fixtures, generated copies, docs, and tooling.

Assembled checkpoint

Restacked onto #4855 at ab927ad6f, which adds the v27 follow-up commits in #4564, #4567, #4574, and #4726. This PR's own commits are unchanged. #4954 finalizes capability reports and completes consumer migrations. Before this restack, that assembled tip passed the invariant guard, all 59 workspace typecheck tasks, 538 focused unit tests, and 14 filtered integration cases; #4954 lists what ran after it. This is not a claim that each intermediate PR passes its standalone contract guard.

Validation

  • Revalidated the channel behavior on the assembled stack: 43 focused tests passed in 1.98 seconds.
    pnpm --filter eve exec vitest run --config vitest.unit.config.ts \
      src/public/definitions/channel-events.test.ts src/channel/instrumentation.test.ts \
      src/context/hook-lifecycle.test.ts src/public/channels/teams/teamsChannel.test.ts
  • Five regression cases were demonstrated failing against the original production owners.
  • Prefix package tsc -p tsconfig.json --noEmit passed after rebasing onto a1d230cd1.
  • Docs, declaration/JS builds, lint, formatting, and diff checks pass on the assembled stack.
  • Final review fixes also cover empty replacements with hidden client context and canonical identity in dynamic/schedule resolvers; both owning regressions failed before repair.
  • Fixture-owned channel delivery eval added; e2e runs in CI only. No full integration or scenario tier run locally.

Checklist

  • I added tests and documentation where relevant
  • I added a changeset if this touches the published eve package
  • DCO sign-off passes for every commit (git commit --signoff)

@vercel

vercel Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
eve-docs Error Error v0 Oct 16, 2026 8:12pm UTC
eve-pkg Error Error v0 Oct 16, 2026 8:12pm UTC

@github-actions

Copy link
Copy Markdown
Contributor

Bundle + Package Summary: apps/fixtures/weather-agent

Key takeaways

  • No notable deltas vs v27/lab-fixes (4ae2083).

Delta vs v27/lab-fixes (4ae2083)

Area Metric Baseline Current Delta
Package Packed tarball 7.67 MB 7.67 MB +324 B ⚠️
Package Unpacked publish size 26.91 MB 26.91 MB +1.2 kB ⚠️
Package Installed footprint 75.01 MB 75.01 MB +1.2 kB ⚠️
Package Published files 4230 4232 +2
Package Installed files 8239 8241 +2
Package Installed package instances 34 34 0
Package Distinct installed package names 32 32 0
Package Installed dependency edges 51 51 0
Package Installed optional peer edges 9 9 0
Runtime Unique function payloads 2 2 0
Runtime Total function bytes 20.95 MB 20.95 MB -2.0 kB ✅
Runtime Public routes 17 17 0
Changed function payloads vs v27/lab-fixes (4ae2083) (2)
Function Status Baseline Current Delta Route changes
functions/__server.func changed 10.48 MB 10.48 MB -976 B ✅ none
functions/.well-known/workflow/v1/flow.func changed 10.48 MB 10.48 MB -976 B ✅ none

eve init install

Metric Baseline Current Delta
Installed footprint 113.39 MB 113.39 MB +1.2 kB ⚠️
Installed packages 97 97 0
dependencies 4 4 0
devDependencies 2 2 0
Dependency package bytes 45.15 MB 45.15 MB +1.2 kB ⚠️
devDependency package bytes 5.11 MB 5.11 MB 0 B ➖
Build Metadata
  • Preset: vercel
  • Nitro: nitro@3.0.260903-beta
  • Output directory: apps/fixtures/weather-agent/.vercel/output
  • Build metadata timestamp: 2026-10-15T23:16:30.490Z
  • Route aliases: 17 public, 1 internal (18 total aliases)
  • Vercel routes in config: 20
  • Severity legend: 🔴 dominant/large, 🟠 notable, 🟡 watch, ⚪ small
Package Drill-Down

Package Details

  • Package: eve@0.79.2
  • Package directory: packages/eve
  • Tarball: 7.67 MB (eve-0.79.2.tgz)
  • Unpacked payload: 26.91 MB across 4232 published files
  • Installed footprint: 75.01 MB across 8241 installed files
  • Installed root package: 26.91 MB
  • Installed dependencies: 48.10 MB
  • Installed package instances: 34
  • Distinct installed package names: 32
  • Installed dependency edges: 51
  • Installed optional peer edges: 9
  • Runtime dependencies: 2
  • Peer dependencies: 7 (6 optional)

Installed footprint is measured from an isolated temporary npm install of the packed tarball.
Graph metrics read only package.json files in package directories directly beneath a node_modules boundary, including nested boundaries. Each directory is one package instance; distinct names come from those manifests. Dependency edges count each unique name in dependencies or optionalDependencies per instance; optional peer edges count peerDependencies marked optional.

Heavy installed dependencies

  • eve: 26.91 MB (35.9%)
  • @rolldown/binding-linux-x64-gnu: 19.81 MB (26.4%)
  • ai: 8.20 MB (10.9%)
  • zod: 6.15 MB (8.2%)
  • undici: 3.58 MB (4.8%)
Publish payload breakdown
Published file size
🟠 dist/src/compiled/shadcn-registry/index.js       [#####...................] 3.85 MB 14.3%
🟠 dist/src/compiled/@photon-ai/chat-adapter-ime... [###.....................] 2.29 MB 8.5%
🟠 dist/src/compiled/@ai-sdk/code-mode/index.js     [#.......................] 1.03 MB 3.8%
🟡 dist/src/compiled/@vercel/blob/index.js          [#.......................] 604.9 kB 2.2%
🟡 dist/src/compiled/_chunks/workflow/undici-D-I... [#.......................] 521.9 kB 1.9%
🔴 Other published files                            [########################] 18.62 MB 69.2%
Installed footprint breakdown
Installed package size
🔴 eve                             [########################] 26.91 MB 35.9%
🔴 @rolldown/binding-linux-x64-gnu [##################......] 19.81 MB 26.4%
🔴 ai                              [#######.................] 8.20 MB 10.9%
🔴 zod                             [#####...................] 6.15 MB 8.2%
🟠 undici                          [###.....................] 3.58 MB 4.8%
🟠 nitro                           [##......................] 1.89 MB 2.5%
🔴 Other installed packages        [########................] 8.46 MB 11.3%
Runtime dependencies (2)
Package Range Notes
nitro 3.0.260903-beta
undici 8.10.2
Peer dependencies (7)
Package Range Notes
@opentelemetry/api ^1.0.0 optional peer
ai catalog:
braintrust ^3.0.0 optional peer
chat ^4.42.0 optional peer
dd-trace ^6.13.0 optional peer
just-bash ^3.1.0 optional peer
microsandbox ^0.5.0 optional peer
eve init install drill-down

eve init install details

  • Command: eve init my-agent
  • Package manager: npm
  • Installed footprint: 113.39 MB across 10158 installed files
  • Installed packages: 97 total (91 transitive-only)
  • dependencies: 4 direct packages totaling 45.15 MB
  • devDependencies: 2 direct packages totaling 5.11 MB
  • Other transitive package files: 63.13 MB

Installed footprint is measured from an isolated temporary eve init my-agent using the current packed eve tarball.

Heavy installed dependencies

  • @typescript/typescript-linux-x64: 27.95 MB (24.6%)
  • eve: 26.91 MB (23.7%)
  • @rolldown/binding-linux-x64-gnu: 19.81 MB (17.5%)
  • zod: 9.76 MB (8.6%)
  • ai: 8.20 MB (7.2%)
Installed footprint breakdown
Installed package size
🔴 @typescript/typescript-linux-x64 [########################] 27.95 MB 24.6%
🔴 eve                              [#######################.] 26.91 MB 23.7%
🔴 @rolldown/binding-linux-x64-gnu  [#################.......] 19.81 MB 17.5%
🔴 zod                              [########................] 9.76 MB 8.6%
🔴 ai                               [#######.................] 8.20 MB 7.2%
🟠 undici                           [###.....................] 3.58 MB 3.2%
🔴 Other installed packages         [###############.........] 17.19 MB 15.2%
dependencies (4)
Package Range Installed size Share
@vercel/connect 2.5.2 276.3 kB 0.2%
ai ^7.0.137 8.20 MB 7.2%
eve file:eve-0.79.2.tgz 26.91 MB 23.7%
zod 4.5.4 9.76 MB 8.6%
devDependencies (2)
Package Range Installed size Share
@types/node 24.x 2.61 MB 2.3%
typescript 7.0.2 2.50 MB 2.2%
Function Drill-Down

Payload Size Graph

Unique function payload size and share of total
🔴 functions/.well-known/workflow/v1/flow.func     [########################] 10.48 MB 50.0%
🔴 functions/__server.func                         [########################] 10.48 MB 50.0%

Top Function Payloads

🟠 functions/.well-known/workflow/v1/flow.func • 1 public route • 10.48 MB
Metric Value
Public routes /.well-known/workflow/v1/flow
Runtime nodejs24.x
Handler index.mjs
Payload 10.48 MB
Function files 10.48 MB across 125 files
Traced dependencies 0 B
Signal 🟠 Bundled file _chunks/vercel.web.mjs is 2.46 MB (23.5%)

🟠 🔎 Dependency Analysis

📦 Bundled files:

Bundled file size
🟠 _chunks/vercel.web.mjs                         [############............] 2.46 MB 23.5%
🟠 _libs/undici.mjs                               [#####...................] 1.01 MB 9.6%
🟡 _chunks/sandbox3.mjs                           [####....................] 815.4 kB 7.8%
🟡 _chunks/compiled-artifacts-instrumentation.mjs [####....................] 733.9 kB 7.0%
🟡 _chunks/token-util-Bla5Z0G9.mjs                [####....................] 704.7 kB 6.7%
🔴 Other bundled files                            [########################] 4.75 MB 45.4%

🧾 Vercel Config

{
  "handler": "index.mjs",
  "launcherType": "Nodejs",
  "shouldAddHelpers": false,
  "supportsResponseStreaming": true,
  "runtime": "nodejs24.x",
  "maxDuration": "max",
  "experimentalTriggers": [
    {
      "type": "queue/v2beta",
      "topic": "__eve776561746865722d6167656e74_wkf_workflow_*",
      "consumer": "default",
      "retryAfterSeconds": 5,
      "initialDelaySeconds": 0
    }
  ],
  "environment": {
    "WORKFLOW_PRECONDITION_GUARD": "1"
  }
}

🟠 functions/__server.func • 16 public routes, 1 internal alias • 10.48 MB
Metric Value
Public routes /
/.well-known/workflow/v1/webhook/[token]
/eve/v1/callback/[token]
/eve/v1/connections/[name]/callback/[attemptId]/[token]
/eve/v1/connections/[name]/callback/[token]
/eve/v1/health
/eve/v1/info
/eve/v1/session
/eve/v1/session/[parentSessionId]/subagents/[callId]/[childSessionId]/stream
/eve/v1/session/[sessionId]
/eve/v1/session/[sessionId]/cancel
/eve/v1/session/[sessionId]/clear
/eve/v1/session/[sessionId]/compact
/eve/v1/session/[sessionId]/reset
/eve/v1/session/[sessionId]/stream
/eve/v1/session/[sessionId]/stubs
Internal aliases /__server
Runtime nodejs24.x
Handler index.mjs
Payload 10.48 MB
Function files 10.48 MB across 125 files
Traced dependencies 0 B
Signal 🟠 Bundled file _chunks/vercel.web.mjs is 2.46 MB (23.5%)

🟠 🔎 Dependency Analysis

📦 Bundled files:

Bundled file size
🟠 _chunks/vercel.web.mjs                         [############............] 2.46 MB 23.5%
🟠 _libs/undici.mjs                               [#####...................] 1.01 MB 9.6%
🟡 _chunks/sandbox3.mjs                           [####....................] 815.4 kB 7.8%
🟡 _chunks/compiled-artifacts-instrumentation.mjs [####....................] 733.9 kB 7.0%
🟡 _chunks/token-util-Bla5Z0G9.mjs                [####....................] 704.7 kB 6.7%
🔴 Other bundled files                            [########################] 4.75 MB 45.4%

🧾 Vercel Config

{
  "handler": "index.mjs",
  "launcherType": "Nodejs",
  "shouldAddHelpers": false,
  "supportsResponseStreaming": true,
  "runtime": "nodejs24.x"
}

Build Timing: e2e/fixtures/agent-tools-sandbox

This is an informational timing measurement inside eve build, from preflight through publication. Output-size measurement and profile writing are excluded.

Build mode: deployable Vercel build with sandbox template prewarm included.

  • Build pipeline: 3.83 s -> 3.69 s (-142.4 ms) vs v27/lab-fixes (4ae2083).
  • Timing is informational: shared GitHub runners are too variable for a hard timing budget.
Detailed phase timings vs `v27/lab-fixes (4ae2083)`
Phase Baseline Current Delta
extension.check 0.5 ms 18.2 ms +17.7 ms
project.resolve 0.4 ms 6.0 ms +5.6 ms
workspace.create 0.6 ms 1.0 ms +0.4 ms
host.prepare 803.1 ms 726.3 ms -76.8 ms
vercel.service-prefix.resolve 2.1 ms 2.1 ms 0.0 ms
nitro.create 689.2 ms 697.7 ms +8.5 ms
sandbox.prewarm 308.6 ms 307.0 ms -1.6 ms
nitro.cache.prepare 0.3 ms 0.3 ms 0.0 ms
nitro.prepare 0.9 ms 0.8 ms -0.1 ms
nitro.public-assets 0.8 ms 0.8 ms 0.0 ms
nitro.prerender 0.6 ms 0.5 ms -0.1 ms
nitro.bundle 1.88 s 1.79 s -99.5 ms
nitro.cache.write 0.4 ms 0.4 ms 0.0 ms
vercel.workflow-function.materialize 58.0 ms 57.6 ms -0.4 ms
agent-summary.emit 0.7 ms 0.6 ms -0.1 ms
connect-manifest.emit 0.3 ms 0.4 ms +0.1 ms
nitro.close 0.1 ms 0.1 ms 0.0 ms
output.publish 3.8 ms 3.7 ms -0.1 ms
workspace.remove 2.9 ms 3.0 ms +0.1 ms

Comment on lines +511 to +515
return defaultDeliverResult(
attachClientContext(
attachInputText({ ...(replacement ?? payload) }, readInputText(payload)),
readClientContext(payload),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning {} doesn't always ignore the delivery. The wrapper re-attaches the original payload's hidden client context (readClientContext(payload)) even when the hook returns an empty replacement. defaultDeliverResult then returns a context-only StepInput whenever that context is nonempty (channel/adapter.ts:249-256), and admission treats a defined input as consumed, not ignored (execution/session/delivery-facts.ts:81-84).

This can happen in practice: the eve session API can send() to an existing session with body.context attached as client context (eve-channel/index.ts:446-456), and that session's adapter may be a defineChannel one. Before this PR, a hook that returned undefined dropped the delivery completely, so {} is now a weaker ignore than the docs promise (docs/channels/custom.mdx:202, docs/concepts/upgrade-to-v27-events.md:201).

Suggested fix: short-circuit when the replacement has no message/answers/context/outputSchema and return undefined before re-attaching hidden fields, or only re-attach client context when the hook kept a deliverable payload. A regression test that uses client context would pin this.

Comment on lines +92 to +94
const kind =
ctx.get(ChannelInstrumentationKey)?.kind ??
(channelAdapter !== undefined ? getAdapterKind(channelAdapter) : undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes the public identity for hooks (and turn-step.ts:341 for admission), but buildResolveContext still sets channel.kind from getAdapterKind (context/dynamic-resolve-context.ts:51), and so do the connection resolver (context/dynamic-connection-lifecycle.ts:211) and the schedules client (public/experimental/schedules/client.ts:52). A route-only channel keeps adapter kind http (public/definitions/channel.ts:454, runtime/resolve-channel.ts:65), so isChannel(ctx.channel, support) now matches in a hook but still fails in a dynamic resolver. isChannel is documented to accept resolver contexts too (public/channels/index.ts:126-138).

Also, setChannelContext always sets ChannelInstrumentationKey together with ChannelKey (execution/channel-context.ts:20-21), so the ?? getAdapterKind(...) fallback copied here and in turn-step.ts looks unreachable. Could we add one helper, e.g. publicChannelKind(ctx) that reads ChannelInstrumentationKey.kind, and use it on every surface that exposes channel.kind? That gives one identity rule and removes the duplicated fallback.

warnDeprecatedMetadataAudience(adapter.kind);
return normalizeChannelAudience(audience);
}
if (project === undefined) return "unknown";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that the metadata fallback is gone, normalize only reads instrumentation.audience, but ChannelAudienceSource (lines 6-13) still declares kind, state, and instrumentation.metadata. Could we narrow it to { instrumentation?: { audience?: ChannelAudienceProjector } } so the contract matches what is actually read?

@vercel vercel Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Suggestions:

  1. A defineChannel deliver hook returning {} to ignore a delivery is instead marked consumed when the original payload carried a non-empty clientContext, contradicting the documented "return {} to ignore" contract.
  1. Dynamic resolver, connection, and schedule contexts set channel.kind from getAdapterKind instead of the canonical channel:<name> instrumentation kind, making isChannel(ctx.channel, ...) behave inconsistently with hook/admission contexts.

Fix on Vercel

import type { RouteDefinition } from "#channel/routes.js";
import type { Session, SessionHandle } from "#channel/session.js";
import type { SessionHandle } from "#channel/session.js";
import type { Delivery } from "#shared/delivery.js";

@vercel vercel Bot Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

channel.ts imports/re-exports Delivery/DeliveryResult from #shared/delivery.js, SessionNotActiveError from #channel/session-not-active-error.js, and SessionOperations from #channel/session.js — none of which exist, breaking the tsc -p tsconfig.build.json prepack step.

Fix on Vercel

…esolvers

Signed-off-by: OwenKephart <owen.kephart@vercel.com>
@OwenKephart
OwenKephart force-pushed the api-cleanup/channel-delivery branch from 03de07b to 4e0ea7c Compare October 16, 2026 20:10
@OwenKephart
OwenKephart merged commit 4e0ea7c into v27/lab-fixes Oct 20, 2026
12 of 148 checks passed
@OwenKephart
OwenKephart deleted the api-cleanup/channel-delivery branch October 20, 2026 22:58
@OwenKephart

Copy link
Copy Markdown
Contributor Author

Recovery note: this PR was automatically marked merged when its commits were included in #4855's branch. That consolidation has been unwound. #4855 is v27-only again; this change is independently reviewable in #5139, based on #4855. The original merged state remains historical GitHub metadata. Nothing was merged into main by this recovery.

This branch had an error being deployed

2 failed deployments
Preview – eve-pkg — 4e0ea7cd Deployed Oct 16, 2026 by vercel[bot]
Preview – eve-docs — 4e0ea7cd Deployed Oct 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant