Skip to content

feat(node): Add eveConversationHook() to link eve sessions as Sentry conversations - #24247

Open
mydea wants to merge 4 commits into
feat/e2e-eve-agentfrom
feat/eve-conversation-hook
Open

feat(node): Add eveConversationHook() to link eve sessions as Sentry conversations#24247
mydea wants to merge 4 commits into
feat/e2e-eve-agentfrom
feat/eve-conversation-hook

Conversation

@mydea

@mydea mydea commented Sep 9, 2026

Copy link
Copy Markdown
Member

Stacked on #24228 (base branch feat/e2e-eve-agent).

Adds eveConversationHook(), used as the default export of an eve agent/hooks/sentry.ts:

import * as Sentry from '@sentry/node';
import { defineHook } from 'eve/hooks';

export default defineHook(Sentry.eveConversationHook());

It tags every turn of an eve session with the durable session id (ctx.session.id) as the Sentry conversation id, so the session's AI spans — which land in separate traces, since each eve turn is its own durable workflow — group into one conversation in the Agents "Conversations" view.

Root cause / why this shape:

  • Scope, not the AI call. eve's session id never reaches the AI SDK's telemetry diagnostics channel (the channel event only carries recordInputs/recordOutputs/functionId; runtimeContext/metadata go to eve's own OTel integration, which Sentry bypasses). So the id can only be attached via the isolation scope, where the always-on conversationIdIntegration picks it up and stamps gen_ai.conversation.id onto the gen_ai spans.
  • Both turn.started and step.started. Each turn is a fresh request with its own isolation scope, and a turn that parks and resumes (approvals, compaction) resumes in another request where turn.started won't re-fire. step.started runs before every model call, so together they cover every request that produces spans. Re-setting the same id is idempotent.
  • Lives in @sentry/server-utils. The helper only needs setConversationId from core, so it belongs in the framework-agnostic shared layer, not @sentry/node. @sentry/node re-exports it (so every export * from '@sentry/node' SDK — astro, nitro (eve's own base), nestjs, hono, effect — surfaces it automatically); it is added explicitly to the node-based @sentry/bun, @sentry/aws-serverless, @sentry/google-cloud-serverless, and to the server-utils-based @sentry/deno and @sentry/cloudflare.
  • Structural typing. The eve hook context is typed structurally rather than imported from eve, so the SDK keeps no dependency on the framework; the shape is checked at the defineHook(...) call site. An optional getConversationId lets users override the default (e.g. to use a root/parent session id for subagents).

The node-eve e2e app now uses the hook and asserts gen_ai.conversation.id on each gen_ai span equals the eve session id returned by the session endpoint.

🤖 Generated with Claude Code

…y conversations

Adds `Sentry.eveConversationHook()`, used as the default export of an eve
`agent/hooks/sentry.ts`:

    export default defineHook(Sentry.eveConversationHook());

It tags every turn of an eve session with the durable session id as the Sentry
conversation id, so the session's AI spans — which land in separate traces
(each eve turn is its own durable workflow) — group into one conversation in
the Agents "Conversations" view.

The id is set on the isolation scope rather than on the AI call: eve's session
id never reaches the AI SDK's telemetry diagnostics channel, so the only way to
attach it is via the scope, where the always-on `conversationIdIntegration`
picks it up and stamps `gen_ai.conversation.id` onto the gen_ai spans.

Subscribes to both `turn.started` and `step.started`. Each turn is a fresh
request with its own isolation scope, and a turn that parks and resumes
(approvals, compaction) resumes in another request where `turn.started` won't
re-fire — `step.started` runs before every model call, so together they cover
each request that produces spans. Re-setting the same id is idempotent.

The eve hook context is typed structurally (not imported from `eve`) so
`@sentry/node` keeps no dependency on the framework; the shape is checked at the
`defineHook(...)` call site instead.

The node-eve e2e app uses the hook and asserts `gen_ai.conversation.id` on each
gen_ai span equals the eve session id.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 28.81 kB - -
@sentry/browser - with treeshaking flags 27.12 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 27.01 kB - -
@sentry/browser (incl. Tracing) 49.22 kB -0.12% -59 B 🔽
@sentry/browser (incl. Tracing + Span Streaming) 49.22 kB -0.11% -52 B 🔽
@sentry/browser (incl. Tracing, Profiling) 52.15 kB -0.13% -63 B 🔽
@sentry/browser (incl. Tracing, Replay) 88.76 kB -0.06% -46 B 🔽
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 77.94 kB -0.06% -43 B 🔽
@sentry/browser (incl. Tracing, Replay with Canvas) 93.44 kB -0.06% -50 B 🔽
@sentry/browser (incl. Tracing, Replay, Feedback) 106.38 kB -0.06% -57 B 🔽
@sentry/browser (incl. Feedback) 46.3 kB - -
@sentry/browser (incl. sendFeedback) 33.87 kB - -
@sentry/browser (incl. FeedbackAsync) 38.99 kB - -
@sentry/browser (incl. Metrics) 29.84 kB - -
@sentry/browser (incl. Logs) 30.1 kB - -
@sentry/browser (incl. Metrics & Logs) 30.78 kB - -
@sentry/react 30.57 kB - -
@sentry/react (incl. Tracing) 51.57 kB -0.13% -66 B 🔽
@sentry/vue 36.07 kB - -
@sentry/vue (incl. Tracing) 51.49 kB -0.13% -63 B 🔽
@sentry/svelte 28.85 kB - -
CDN Bundle 30.56 kB - -
CDN Bundle (incl. Tracing) 49.76 kB -0.1% -48 B 🔽
CDN Bundle (incl. Logs, Metrics) 32.83 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 51.7 kB -0.14% -72 B 🔽
CDN Bundle (incl. Replay, Logs, Metrics) 73.48 kB - -
CDN Bundle (incl. Tracing, Replay) 87.28 kB -0.09% -71 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 89.2 kB -0.07% -62 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback) 93.21 kB -0.07% -62 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 95.22 kB -0.06% -57 B 🔽
CDN Bundle - uncompressed 90.47 kB - -
CDN Bundle (incl. Tracing) - uncompressed 148.17 kB -0.13% -190 B 🔽
CDN Bundle (incl. Logs, Metrics) - uncompressed 97.04 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 154.13 kB -0.13% -190 B 🔽
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 226.3 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 267.76 kB -0.08% -190 B 🔽
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 273.71 kB -0.07% -190 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 281.46 kB -0.07% -190 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 287.4 kB -0.07% -190 B 🔽
@sentry/nextjs (client) 54.03 kB -0.1% -53 B 🔽
@sentry/sveltekit (client) 49.68 kB -0.1% -45 B 🔽
@sentry/core/server 36.99 kB - -
@sentry/core/browser 13.55 kB - -
@sentry/node 127.86 kB -0.02% -16 B 🔽
@sentry/node/import (ESM hook with diagnostics-channel injection) 81.61 kB - -
@sentry/node - without tracing 88.73 kB +0.03% +21 B 🔺
@sentry/node - without channel injection 107.11 kB -0.02% -14 B 🔽
@sentry/aws-serverless 97.12 kB +0.03% +27 B 🔺
@sentry/cloudflare (withSentry) - minified 201.98 kB -0.04% -74 B 🔽
@sentry/cloudflare (withSentry) 502.68 kB -0.05% -232 B 🔽

View base workflow run

@mydea
mydea added this pull request to stack #24250 September 9, 2026 13:29
mydea and others added 2 commits September 9, 2026 15:41
…e-export from every runtime SDK

The helper only needs `setConversationId` from core, so `@sentry/server-utils`
is its natural home — a framework-agnostic shared layer already re-exported by
the runtime SDKs — rather than living in `@sentry/node`.

- `@sentry/node` now re-exports it from `@sentry/server-utils` (like the other
  shared server helpers), so every node-based SDK that does
  `export * from '@sentry/node'` (astro, nitro — eve's own base —, nestjs,
  hono, effect) surfaces it automatically.
- Added to the explicit `@sentry/node` re-export blocks of `@sentry/bun`,
  `@sentry/aws-serverless` and `@sentry/google-cloud-serverless`.
- Added to the `@sentry/server-utils` re-export blocks of `@sentry/deno` and
  `@sentry/cloudflare`, which build on server-utils rather than node.

The unit test moves alongside the implementation into `@sentry/server-utils`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
It is a single-field options bag callers pass as an inline object literal, so
exporting the type name added public API surface across every runtime SDK for
no benefit. It stays declared (unexported) alongside the function, so the
signature is unaffected and callers still pass options structurally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mydea
mydea marked this pull request as ready for review September 9, 2026 14:43
@mydea
mydea requested review from a team as code owners September 9, 2026 14:43
@mydea
mydea requested review from JPeer264 and isaacs and removed request for a team September 9, 2026 14:43
Astro's runtime entry curates its `@sentry/node` re-exports (it can't
`export *`), so the helper has to be listed explicitly like the other SDKs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mydea
mydea requested a review from a team as a code owner September 9, 2026 14:51
@mydea
mydea requested review from nicohrubec and s1gr1d and removed request for a team September 9, 2026 14:51
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