From 5947b6cb0f6b00f588489fc2a972773828120e1c Mon Sep 17 00:00:00 2001 From: Allen Zhou <46854522+allenzhou101@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:20:01 -0700 Subject: [PATCH] feat(eve): group agent summary channels by authored channel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the Vercel agent summary to schemaVersion 4. Version 3 emitted one flat channel entry per HTTP route, so a single authored channel (eg. channels/eve.ts with its session, follow-up, and stream routes) appeared several times under the same name and consumers over-counted channels unless they re-grouped by name themselves. Channels are now one entry per authored channel — name, display type, adapterKind, logicalPath — with route-level detail nested under a routes array, preserving first-occurrence order of both channels and routes. Signed-off-by: Allen Zhou <46854522+allenzhou101@users.noreply.github.com> --- .changeset/agent-summary-grouped-channels.md | 5 +++ .../host/build-vercel-agent-summary.test.ts | 40 ++++++++++------- .../nitro/host/build-vercel-agent-summary.ts | 45 +++++++++++++------ .../eve/src/internal/vercel-agent-summary.ts | 24 ++++++++-- 4 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 .changeset/agent-summary-grouped-channels.md diff --git a/.changeset/agent-summary-grouped-channels.md b/.changeset/agent-summary-grouped-channels.md new file mode 100644 index 000000000..c10bf3e62 --- /dev/null +++ b/.changeset/agent-summary-grouped-channels.md @@ -0,0 +1,5 @@ +--- +"eve": patch +--- + +The Vercel agent summary (`.eve/agent-summary.json`) now groups channels one entry per authored channel with routes nested under a `routes` array, instead of one flat entry per HTTP route. This is `schemaVersion` 4: a channel like `channels/eve.ts` reads as a single channel carrying its session, follow-up, and stream routes, so dashboards no longer over-count channels or repeat the same name per route. diff --git a/packages/eve/src/internal/nitro/host/build-vercel-agent-summary.test.ts b/packages/eve/src/internal/nitro/host/build-vercel-agent-summary.test.ts index 9ea759f03..8d470f3fa 100644 --- a/packages/eve/src/internal/nitro/host/build-vercel-agent-summary.test.ts +++ b/packages/eve/src/internal/nitro/host/build-vercel-agent-summary.test.ts @@ -70,16 +70,21 @@ function makeSchedule(name: string, cron: string): CompiledScheduleDefinition { }; } -function makeChannel(input: { name: string; adapterKind?: string }): CompiledChannelDefinition { +function makeChannel(input: { + name: string; + adapterKind?: string; + method?: CompiledChannelDefinition["method"]; + urlPath?: string; +}): CompiledChannelDefinition { return { adapterKind: input.adapterKind, kind: "channel", logicalPath: `channels/${input.name}.ts`, - method: "POST", + method: input.method ?? "POST", name: input.name, sourceId: `channels/${input.name}.ts`, sourceKind: "module", - urlPath: `/${input.name}`, + urlPath: input.urlPath ?? `/${input.name}`, }; } @@ -143,7 +148,13 @@ describe("buildVercelAgentSummary", () => { channels: [ makeChannel({ name: "slack", adapterKind: "slack" }), makeChannel({ name: "weather-bot", adapterKind: "weather-slack" }), - makeChannel({ name: "messages", adapterKind: "http" }), + makeChannel({ name: "messages", adapterKind: "http", urlPath: "/messages/v1/session" }), + makeChannel({ + name: "messages", + adapterKind: "http", + method: "GET", + urlPath: "/messages/v1/session/:sessionId/stream", + }), makeChannel({ name: "stripe", adapterKind: "stripe-webhook" }), makeChannel({ name: "mystery" }), { kind: "disabled", logicalPath: "channels/disabled.ts", name: "disabled" }, @@ -279,46 +290,45 @@ describe("buildVercelAgentSummary", () => { ]); // Disabled channels are dropped; remaining channels are normalized to - // the four-value display type. + // the four-value display type and grouped one entry per authored + // channel, with route-level detail nested under `routes`. expect(summary.channels).toEqual([ { adapterKind: "slack", logicalPath: "channels/slack.ts", - method: "POST", name: "slack", + routes: [{ method: "POST", urlPath: "/slack" }], type: "slack", - urlPath: "/slack", }, { adapterKind: "weather-slack", logicalPath: "channels/weather-bot.ts", - method: "POST", name: "weather-bot", + routes: [{ method: "POST", urlPath: "/weather-bot" }], type: "slack", - urlPath: "/weather-bot", }, { adapterKind: "http", logicalPath: "channels/messages.ts", - method: "POST", name: "messages", + routes: [ + { method: "POST", urlPath: "/messages/v1/session" }, + { method: "GET", urlPath: "/messages/v1/session/:sessionId/stream" }, + ], type: "http", - urlPath: "/messages", }, { adapterKind: "stripe-webhook", logicalPath: "channels/stripe.ts", - method: "POST", name: "stripe", + routes: [{ method: "POST", urlPath: "/stripe" }], type: "webhook", - urlPath: "/stripe", }, { logicalPath: "channels/mystery.ts", - method: "POST", name: "mystery", + routes: [{ method: "POST", urlPath: "/mystery" }], type: "unknown", - urlPath: "/mystery", }, ]); diff --git a/packages/eve/src/internal/nitro/host/build-vercel-agent-summary.ts b/packages/eve/src/internal/nitro/host/build-vercel-agent-summary.ts index 94bfdb348..c8b38c698 100644 --- a/packages/eve/src/internal/nitro/host/build-vercel-agent-summary.ts +++ b/packages/eve/src/internal/nitro/host/build-vercel-agent-summary.ts @@ -16,6 +16,7 @@ import { resolveInstalledPackageInfo } from "#internal/application/package.js"; import { type VercelEveAgentSummary, type VercelEveChannelEntry, + type VercelEveChannelRoute, type VercelEveConnectionEntry, type VercelEveInstructionsEntry, type VercelEveScheduleEntry, @@ -54,7 +55,7 @@ export function buildVercelAgentSummary(input: { tools: manifest.tools.map(toToolEntry), skills: manifest.skills.map(toSkillEntry), connections: manifest.connections.map(toConnectionEntry), - channels: manifest.channels.filter(isActiveChannel).map(toChannelEntry), + channels: groupChannelEntries(manifest.channels.filter(isActiveChannel)), sandbox: manifest.sandbox === null ? null @@ -167,20 +168,38 @@ function toConnectionEntry(connection: CompiledConnectionDefinition): VercelEveC return entry; } -function toChannelEntry(channel: CompiledChannelDefinition): VercelEveChannelEntry { - const entry: VercelEveChannelEntry = { - name: channel.name, - method: channel.method, - urlPath: channel.urlPath, - type: normalizeChannelKindForDisplay(channel.adapterKind), - logicalPath: channel.logicalPath, - }; - - if (channel.adapterKind !== undefined) { - return { ...entry, adapterKind: channel.adapterKind }; +/** + * Groups the manifest's route-level channel definitions into one summary + * entry per authored channel, preserving first-occurrence order of both + * channels and their routes. The manifest stores one definition per + * `(method, urlPath)` — the granularity the runtime mounts — but the + * summary's unit is the authored channel, with routes nested under it. + */ +function groupChannelEntries( + channels: readonly CompiledChannelDefinition[], +): VercelEveChannelEntry[] { + const grouped = new Map(); + + for (const channel of channels) { + const route: VercelEveChannelRoute = { method: channel.method, urlPath: channel.urlPath }; + const existing = grouped.get(channel.name); + if (existing !== undefined) { + existing.routes.push(route); + continue; + } + const entry = { + name: channel.name, + type: normalizeChannelKindForDisplay(channel.adapterKind), + logicalPath: channel.logicalPath, + routes: [route], + }; + grouped.set( + channel.name, + channel.adapterKind === undefined ? entry : { ...entry, adapterKind: channel.adapterKind }, + ); } - return entry; + return [...grouped.values()]; } function toSubagentEntry(subagent: CompiledSubagentNode): VercelEveSubagentEntry { diff --git a/packages/eve/src/internal/vercel-agent-summary.ts b/packages/eve/src/internal/vercel-agent-summary.ts index 060a5c3e3..0529398f9 100644 --- a/packages/eve/src/internal/vercel-agent-summary.ts +++ b/packages/eve/src/internal/vercel-agent-summary.ts @@ -37,7 +37,7 @@ export const VERCEL_EVE_AGENT_SUMMARY_KIND = "vercel-eve-agent-summary" as const * making semantic changes consumers must opt into. Adding optional fields * does not require a version bump. */ -export const VERCEL_EVE_AGENT_SUMMARY_VERSION = 3; +export const VERCEL_EVE_AGENT_SUMMARY_VERSION = 4; /** * Output path (relative to the agent's `appRoot`) where eve writes the @@ -140,18 +140,34 @@ export interface VercelEveConnectionEntry { }; } -export interface VercelEveChannelEntry { - readonly name: string; +/** + * One HTTP route mounted by a channel. + */ +export interface VercelEveChannelRoute { readonly method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "WEBSOCKET"; readonly urlPath: string; +} + +/** + * One authored channel, carrying every route it mounts. + * + * Schema version 4 groups routes under their authored channel: a channel + * module like `channels/eve.ts` compiles to one route per `(method, + * urlPath)` internally, but reads as a single channel in product UI. + * Version 3 emitted one flat entry per route, which made consumers + * over-count channels unless they re-grouped by `name` themselves. + */ +export interface VercelEveChannelEntry { + readonly name: string; readonly type: VercelEveChannelType; /** - * The raw `ChannelAdapter.kind` reported by the route, when present. + * The raw `ChannelAdapter.kind` reported by the channel, when present. * Useful when `type` is `"unknown"` and a consumer wants to render a * disambiguating label. */ readonly adapterKind?: string; readonly logicalPath: string; + readonly routes: readonly VercelEveChannelRoute[]; } export interface VercelEveSandboxEntry {