Skip to content

feat(ai): generation middleware for media observability (#720)#760

Open
season179 wants to merge 2 commits into
TanStack:mainfrom
season179:feat/activity-observers
Open

feat(ai): generation middleware for media observability (#720)#760
season179 wants to merge 2 commits into
TanStack:mainfrom
season179:feat/activity-observers

Conversation

@season179

@season179 season179 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #720.

Adds activity-agnostic OpenTelemetry coverage for the media generation
activities: generateImage, generateVideo, generateAudio,
generateSpeech, and generateTranscription.

Following Alem's review feedback, this does not introduce a separate observer
prop/API. Media activities now accept the same middleware option shape as
chat, using a base GenerationMiddleware contract. ChatMiddleware remains the
richer chat-specific superset, while observe-only middleware authored against
GenerationMiddleware can run across both chat and media activities.

otelMiddleware() now satisfies both GenerationMiddleware and
ChatMiddleware, so the same middleware instance can be passed to chat() and
to any media activity. For media calls it emits one gen_ai.* client span per
activity, tagged with the appropriate gen_ai.operation.name:

  • generateImage -> image_generation
  • generateVideo -> video_generation
  • generateAudio -> audio_generation
  • generateSpeech -> text_to_speech
  • generateTranscription -> transcription

The media spans reuse the shared usage-attribute emission from chat, including
cost, total tokens, cache/reasoning detail, duration billing, upstream cost, and
media unit counts where reported. When a Meter is supplied, the middleware
also records gen_ai.client.operation.duration for media activity calls.

Streaming video is covered through the full create -> poll -> complete
lifecycle. If a consumer abandons the stream before completion, the middleware
receives onAbort and otelMiddleware() ends the span as cancelled rather than
leaking it.

Branch status

#747 has merged into main, and this branch has been refreshed on current
main so the PR diff only carries the #720 media generation middleware work.

Test plan

  • Unit: packages/ai/tests/middlewares/generation-middleware.test.ts covers
    media middleware wiring, lifecycle ordering, errors, and streaming-video abort
    behavior.
  • Unit: packages/ai/tests/middlewares/otel.test.ts covers otelMiddleware
    span/metric behavior for chat and media generation.
  • Type tests: packages/ai/tests/middleware-capabilities.test-d.ts verifies
    otelMiddleware() satisfies both GenerationMiddleware and ChatMiddleware,
    while chat-only middleware is not assignable to a media
    GenerationMiddleware slot.
  • E2E: testing/e2e route + spec exercises otelMiddleware() on
    generateImage through the mock transport.
  • Local verification after feat(ai): emit cost + full usage on otel spans #747 merge cleanup:
    • pnpm install
    • pnpm test:pr
    • pnpm --filter @tanstack/ai-e2e test:e2e (exit 0; 265 passed, 5 flaky
      after retries)

Summary by CodeRabbit

Release Notes

  • New Features
    • Added activity-agnostic generation middleware for media generation (image/video/audio/speech/transcription) with lifecycle hooks (start/usage/finish/abort/error) and a shared context.
    • Enhanced OpenTelemetry middleware to emit one CLIENT span per media call, using activity-specific operation names, including usage/cost attributes and optional duration histograms; streaming video now spans create→poll→complete and reports abandonment/cancellation correctly.
  • Documentation
    • Added guidance for observing non-chat media activities with OpenTelemetry.
  • Tests
    • Added coverage for media middleware behavior and OpenTelemetry media span attributes, including streaming scenarios.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a082c3e1-f331-4614-bc0b-6260fba49935

📥 Commits

Reviewing files that changed from the base of the PR and between fefbfc7 and 08bd5a4.

📒 Files selected for processing (3)
  • packages/ai/tests/middleware-capabilities.test.ts
  • packages/ai/tests/middlewares/fake-otel.ts
  • testing/e2e/src/routes/api.otel-media.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ai/tests/middleware-capabilities.test.ts

📝 Walkthrough

Walkthrough

Adds a shared GenerationMiddleware contract with onStart/onUsage/onFinish/onAbort/onError lifecycle hooks and wires it as an optional middleware option into all five media activities (generateImage, generateVideo, generateAudio, generateSpeech, generateTranscription). Extends otelMiddleware to implement GenerationMiddleware & ChatMiddleware, emitting one gen_ai.* CLIENT span per media call. Extracts usageAttributes, errorMessage, errorTypeName, and firstNumber into shared utilities.

Changes

Activity-Agnostic GenerationMiddleware for Media Activities

Layer / File(s) Summary
GenerationMiddleware types and shared utility primitives
packages/ai/src/activities/middleware/types.ts, packages/ai/src/utilities/errors.ts, packages/ai/src/utilities/numbers.ts, packages/ai/src/middlewares/usage-attributes.ts
Defines GenerationActivity discriminator union, GenerationMiddlewareContext with requestId, activity/provider/model metadata, and per-call context. Adds hook payload interfaces (GenerationUsageInfo, GenerationFinishInfo, GenerationAbortInfo, GenerationErrorInfo) and the GenerationMiddleware interface with optional lifecycle hooks. Introduces firstNumber(), errorMessage(), errorTypeName() shared utilities and the usageAttributes() helper that maps TokenUsage to gen_ai.usage.* span attributes.
Generation middleware runner and context factory
packages/ai/src/activities/middleware/run.ts, packages/ai/src/activities/middleware/index.ts, packages/ai/src/index.ts
Implements createGenerationContext() to build a stable GenerationMiddlewareContext with source: 'server'. Adds an internal sequential run helper and public lifecycle dispatchers (runGenerationStart, runGenerationUsage, runGenerationFinish, runGenerationAbort, runGenerationError) that await each middleware hook in registration order. Exports all types and runners from the shared middleware index and re-exports them from the package root.
ChatMiddlewareContext activity discriminator
packages/ai/src/activities/chat/middleware/types.ts, packages/ai/src/activities/chat/index.ts, packages/ai/tests/middleware-capabilities.test.ts, packages/ai/tests/middlewares/fake-otel.ts
Adds activity: 'chat' literal field to ChatMiddlewareContext so it structurally satisfies GenerationMiddlewareContext. Updates TextEngine constructor, test helpers (makeRunnerCtx), and fake OTEL test utilities (makeCtx) to set the new field with explicit defaults.
middleware option wired into all five media activities
packages/ai/src/activities/generateImage/index.ts, packages/ai/src/activities/generateAudio/index.ts, packages/ai/src/activities/generateSpeech/index.ts, packages/ai/src/activities/generateTranscription/index.ts, packages/ai/src/activities/generateVideo/index.ts
Adds optional middleware?: Array<GenerationMiddleware> to each activity's options type. Each run function creates a generation context via createGenerationContext and awaits runGenerationStart before the adapter invocation, runGenerationUsage when result.usage exists, and runGenerationFinish on success. On error, awaits runGenerationError before rethrowing. generateVideo streaming adds a settled flag and a finally block that calls runGenerationAbort on stream abandonment.
otelMiddleware extended to GenerationMiddleware & ChatMiddleware
packages/ai/src/middlewares/otel.ts
Changes return type to GenerationMiddleware & ChatMiddleware. Adds 'generation' to OtelSpanScope and a new 'generation' discriminant in OtelSpanInfo. Introduces OPERATION_NAME map for activity→span-name resolution. Implements mediaSpans storage and helpers to start CLIENT spans on media onStart, stamp usageAttributes on onUsage, and end spans on terminal hooks (finish/error/abort) with duration histograms. For chat, persists lastFinishReason from onChunk and uses chatCtx consistently in terminal hook callbacks. Replaces module-local utility implementations with shared imports (firstNumber, usageAttributes, errorMessage, errorTypeName).
Unit tests for generation middleware and otelMiddleware media spans
packages/ai/tests/middlewares/generation-middleware.test.ts, packages/ai/tests/middlewares/otel.test.ts, packages/ai/tests/middleware-capabilities.test-d.ts
Adds generation-middleware.test.ts validating hook ordering, metadata propagation, error rethrow, and video streaming lifecycle (finish/error/abort/early-stop/hook-throws) across all five activities. Extends otel.test.ts with media-activities suite covering CLIENT span creation, activity→operation-name mapping, usage/duration attribute attachment, error/abort histogram paths, concurrent context isolation, formatter/enricher application, and spanNameFormatter throw resilience. Adds a focused usageAttributes unit test. Updates middleware-capabilities type-level tests to verify otelMiddleware satisfies both GenerationMiddleware and ChatMiddleware and that chat-only middleware is not assignable to GenerationMiddleware.
E2E /api/otel-media route and Playwright media span assertion
testing/e2e/src/routes/api.otel-media.ts, testing/e2e/src/routeTree.gen.ts, testing/e2e/tests/middleware.spec.ts
Adds POST /api/otel-media endpoint with a per-request in-memory OpenTelemetry tracer that captures started spans (name, attributes, status, ended state), calls generateImage with otelMiddleware({ tracer }), and returns { ok: true, spans } on success. Registers the route in the generated route tree with full type coverage. Playwright spec asserts exactly one ended image_generation CLIENT span with expected gen_ai.system, gen_ai.operation.name, and gen_ai.request.model attributes.
Documentation and changeset
docs/advanced/otel.md, docs/config.json, .changeset/activity-observers.md
Adds "Beyond chat: media activities" section to OTel documentation describing single CLIENT span per media call, activity-specific gen_ai.operation.name mapping, usage and duration attribute attachment, streaming video span lifecycle, abort span termination semantics, and GenerationMiddleware extension contract. Updates docs/config.json OpenTelemetry updatedAt timestamp. Adds changeset for @tanstack/ai minor version release.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant runGenerateImage
  participant GenerationMiddlewareRunner as Generation Middleware Runner
  participant otelMiddleware
  participant Tracer
  participant Adapter

  Caller->>runGenerateImage: generateImage({ middleware: [otelMiddleware] })
  runGenerateImage->>GenerationMiddlewareRunner: runGenerationStart(middleware, mwCtx)
  GenerationMiddlewareRunner->>otelMiddleware: onStart(mwCtx)
  otelMiddleware->>Tracer: startSpan("image_generation", CLIENT)
  otelMiddleware->>otelMiddleware: mediaSpans.set(mwCtx, span)

  runGenerateImage->>Adapter: invoke image adapter
  Adapter-->>runGenerateImage: result + usage

  runGenerateImage->>GenerationMiddlewareRunner: runGenerationUsage(middleware, mwCtx, usage)
  GenerationMiddlewareRunner->>otelMiddleware: onUsage(mwCtx, usage)
  otelMiddleware->>otelMiddleware: span.setAttributes(usageAttributes(usage))

  runGenerateImage->>GenerationMiddlewareRunner: runGenerationFinish(middleware, mwCtx, {durationMs, usage})
  GenerationMiddlewareRunner->>otelMiddleware: onFinish(mwCtx, info)
  otelMiddleware->>Tracer: span.end()
  runGenerateImage-->>Caller: return result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • TanStack/ai#723: Adds TokenUsage.unitsBilled which this PR reads and emits as tanstack.ai.usage.units_billed in usageAttributes() for unit-billed media activities.
  • TanStack/ai#747: Both PRs modify packages/ai/src/middlewares/otel.ts around the usageAttributes(TokenUsage) helper and how OTel span attributes are populated from TokenUsage.
  • TanStack/ai#242: The main PR depends on the unified TokenUsage shape and protocol documentation that PR #242 introduces, which defines prompt/completion totals and optional breakdown fields consumed by usageAttributes().

Suggested reviewers

  • tombeckenham
  • jherr

🐇 Five activities now shine with spans so bright,
Image and video, speech in the night,
onStart, onFinish, the hooks form a chain,
OTel reports every gen_ai campaign,
No media call shall pass unobserved again! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(ai): generation middleware for media observability (#720)' accurately and concisely describes the main change: adding middleware-based observability to media generation activities.
Description check ✅ Passed The PR description provides comprehensive context on changes made, motivation, test coverage, and branch status. It addresses the required template sections despite not following the exact format.
Linked Issues check ✅ Passed The PR fully implements the objectives from issue #720: adds activity-agnostic GenerationMiddleware for media activities, ensures otelMiddleware satisfies both GenerationMiddleware and ChatMiddleware, emits correct gen_ai.operation.name for each activity, handles streaming-video abort via onAbort, and includes comprehensive test coverage.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to implementing generation middleware for media observability. Files modified include activity implementations, middleware types/runners, OTel integration, tests, and documentation updates—all aligned with the PR objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@season179 season179 marked this pull request as ready for review June 15, 2026 03:35

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/advanced/otel.md`:
- Line 194: The openaiImage adapter example in the documentation is using an
outdated model identifier. In the openaiImage function call, replace the model
parameter from 'gpt-image-1' to 'gpt-image-2' to reflect the latest OpenAI image
generation model as defined in the adapter's model-meta.ts file.

In `@packages/ai/src/activities/generateVideo/index.ts`:
- Around line 375-385: The observer lifecycle is mismanaged: notifyObserverStart
at lines 375 and around 395-397 occurs after yields, allowing early breaks to
emit onError without onStart, and notifyObserverFinish at lines 459-472 occurs
after yielding generation:result, causing the finally block (lines 518-539) to
see settled === false and emit a synthetic cancellation error. Move both
notifyObserverStart calls to execute before any yields in the function, and move
notifyObserverFinish to execute before the generation:result yield (or set
settled === true before yielding), so that the finally block's settled flag
accurately reflects whether the observer lifecycle was properly started and
completed.

In `@testing/e2e/src/routes/api.otel-media.ts`:
- Around line 117-127: The payload validation is missing before destructuring
properties from the data object. Currently, if body.forwardedProps, body.data,
or body is null or a primitive value, or if required fields (prompt, provider)
are missing, the destructuring assignment will throw an unhandled error
resulting in a 500 response instead of the proper error response. Add validation
logic before the destructuring of prompt, provider, testId, and aimockPort from
the data object to check that data is a valid object with the required
properties, and return { ok: false, error } with an appropriate error message if
validation fails. Only proceed to createImageAdapter and
createLocalCaptureTracer after successful validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3e1da7aa-1e1b-402b-98f8-1b412af95984

📥 Commits

Reviewing files that changed from the base of the PR and between 984ac3c and 2dd2912.

📒 Files selected for processing (27)
  • .changeset/activity-observers.md
  • .changeset/otel-full-usage-emission.md
  • docs/advanced/otel.md
  • docs/config.json
  • packages/ai/package.json
  • packages/ai/src/activities/generateAudio/index.ts
  • packages/ai/src/activities/generateImage/index.ts
  • packages/ai/src/activities/generateSpeech/index.ts
  • packages/ai/src/activities/generateTranscription/index.ts
  • packages/ai/src/activities/generateVideo/index.ts
  • packages/ai/src/index.ts
  • packages/ai/src/middlewares/otel.ts
  • packages/ai/src/observability/index.ts
  • packages/ai/src/observability/notify.ts
  • packages/ai/src/observability/otel.ts
  • packages/ai/src/observability/types.ts
  • packages/ai/src/observability/usage-attributes.ts
  • packages/ai/src/utilities/errors.ts
  • packages/ai/src/utilities/numbers.ts
  • packages/ai/tests/middlewares/otel.test.ts
  • packages/ai/tests/observability/activity-observers.test.ts
  • packages/ai/tests/observability/otel-observer.test.ts
  • packages/ai/vite.config.ts
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.otel-media.ts
  • testing/e2e/src/routes/api.otel-usage.ts
  • testing/e2e/tests/middleware.spec.ts

Comment thread docs/advanced/otel.md Outdated
Comment thread packages/ai/src/activities/generateVideo/index.ts Outdated
Comment thread testing/e2e/src/routes/api.otel-media.ts Outdated
@AlemTuzlak

Copy link
Copy Markdown
Contributor

I like the idea of adding this but not like an observer Prop, chat has ChatMiddleware, this should just be GenerationMiddleware passed in via middleware prop same as chat, the chat one would be a superset that just observes more hooks than the generation one. So that way the API is uniform across both

@tombeckenham

Copy link
Copy Markdown
Contributor

Thanks heaps for this @season179. Would you be able to resolve conflicts first before I review?

@nx-cloud

nx-cloud Bot commented Jun 16, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit a1f5bf9


☁️ Nx Cloud last updated this comment at 2026-06-16 21:46:24 UTC

@season179

Copy link
Copy Markdown
Contributor Author

Thank you, Alem for the feedback. I'll make the change accordingly.

@tombeckenham, yes, I'll make it conflict-free after addressing Alem's comment.

@season179 season179 marked this pull request as draft June 17, 2026 02:48
@season179 season179 changed the title feat(ai): activity-agnostic observability hook for media activities (#720) feat(ai): generation middleware for media observability (#720) Jun 17, 2026
@season179 season179 force-pushed the feat/activity-observers branch from e835e46 to fefbfc7 Compare June 17, 2026 04:46
@season179 season179 marked this pull request as ready for review June 17, 2026 05:14

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ai/tests/middlewares/fake-otel.ts (1)

242-275: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid casting an incomplete fake context to ChatMiddlewareContext.

makeCtx currently relies on as ChatMiddlewareContext while omitting required context fields (e.g., runId, threadId, capability accessors). This can mask contract drift and make tests fail later for the wrong reason.

Proposed fix (build a complete typed context)
 import { SpanStatusCode, trace as otelTrace } from '`@opentelemetry/api`'
 import type { ChatMiddlewareContext } from '../../src/activities/chat/middleware/types'
+import { CapabilityRegistry } from '../../src/activities/chat/middleware/capabilities'
 import type { ToolCall } from '../../src/types'
@@
 export function makeCtx(
   overrides: Partial<ChatMiddlewareContext> = {},
 ): ChatMiddlewareContext {
-  const base = {
+  const capabilities = new CapabilityRegistry()
+  const base: ChatMiddlewareContext = {
     requestId: 'req-1',
     streamId: 'stream-1',
+    runId: 'run-1',
+    threadId: 'thread-1',
     // Chat contexts always report `activity: 'chat'`; otelMiddleware branches
     // on it to choose the chat span tree over the single media span.
     activity: 'chat' as const,
@@
     messages: [],
     createId: (prefix: string) => `${prefix}-1`,
+    capabilities,
+    get: (capability) => capability[0](base),
+    getOptional: (capability) => capability[0](base, { optional: true }),
+    provide: (capability, value) => capability[1](base, value),
   }
-  return {
-    ...base,
-    ...overrides,
-  } as ChatMiddlewareContext
+  return { ...base, ...overrides }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai/tests/middlewares/fake-otel.ts` around lines 242 - 275, The
makeCtx function casts an incomplete object to ChatMiddlewareContext using "as
ChatMiddlewareContext" to hide missing required fields, which masks contract
drift and causes misleading test failures. Remove the type cast at the end of
the return statement in makeCtx and instead add all required fields to the base
object, including fields like runId, threadId, and any required capability
accessors. This ensures the object is fully typed without relying on casting to
suppress type errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai/tests/middleware-capabilities.test.ts`:
- Around line 1-17: The imports in the middleware-capabilities.test.ts file are
not ordered according to ESLint's sort-imports/import/order rules. Reorganize
the imports by grouping them in the following order: first place external
library imports (vitest) at the top, then all type imports together (type
imports from CapabilityContext, ChatMiddleware, and ChatMiddlewareContext),
followed by relative imports from ../src organized by path depth (starting with
deeper paths like ../src/activities/chat/middleware/capabilities, validate,
builder, define, compose, then ../src/logger/resolve, then ../src), and finally
place sibling imports starting with ./ (test-utils) at the end. Ensure imports
from the same source are grouped together and alphabetized within each group.

---

Outside diff comments:
In `@packages/ai/tests/middlewares/fake-otel.ts`:
- Around line 242-275: The makeCtx function casts an incomplete object to
ChatMiddlewareContext using "as ChatMiddlewareContext" to hide missing required
fields, which masks contract drift and causes misleading test failures. Remove
the type cast at the end of the return statement in makeCtx and instead add all
required fields to the base object, including fields like runId, threadId, and
any required capability accessors. This ensures the object is fully typed
without relying on casting to suppress type errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7019526b-080b-4464-abed-22bf041bc3cf

📥 Commits

Reviewing files that changed from the base of the PR and between a1f5bf9 and fefbfc7.

📒 Files selected for processing (26)
  • .changeset/activity-observers.md
  • docs/advanced/otel.md
  • docs/config.json
  • packages/ai/src/activities/chat/index.ts
  • packages/ai/src/activities/chat/middleware/types.ts
  • packages/ai/src/activities/generateAudio/index.ts
  • packages/ai/src/activities/generateImage/index.ts
  • packages/ai/src/activities/generateSpeech/index.ts
  • packages/ai/src/activities/generateTranscription/index.ts
  • packages/ai/src/activities/generateVideo/index.ts
  • packages/ai/src/activities/middleware/index.ts
  • packages/ai/src/activities/middleware/run.ts
  • packages/ai/src/activities/middleware/types.ts
  • packages/ai/src/index.ts
  • packages/ai/src/middlewares/otel.ts
  • packages/ai/src/middlewares/usage-attributes.ts
  • packages/ai/src/utilities/errors.ts
  • packages/ai/src/utilities/numbers.ts
  • packages/ai/tests/middleware-capabilities.test-d.ts
  • packages/ai/tests/middleware-capabilities.test.ts
  • packages/ai/tests/middlewares/fake-otel.ts
  • packages/ai/tests/middlewares/generation-middleware.test.ts
  • packages/ai/tests/middlewares/otel.test.ts
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.otel-media.ts
  • testing/e2e/tests/middleware.spec.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/ai/src/utilities/numbers.ts
  • .changeset/activity-observers.md
  • docs/config.json
  • docs/advanced/otel.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ai/src/utilities/errors.ts

@coderabbitai coderabbitai 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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ai/tests/middlewares/fake-otel.ts (1)

242-275: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid casting an incomplete fake context to ChatMiddlewareContext.

makeCtx currently relies on as ChatMiddlewareContext while omitting required context fields (e.g., runId, threadId, capability accessors). This can mask contract drift and make tests fail later for the wrong reason.

Proposed fix (build a complete typed context)
 import { SpanStatusCode, trace as otelTrace } from '`@opentelemetry/api`'
 import type { ChatMiddlewareContext } from '../../src/activities/chat/middleware/types'
+import { CapabilityRegistry } from '../../src/activities/chat/middleware/capabilities'
 import type { ToolCall } from '../../src/types'
@@
 export function makeCtx(
   overrides: Partial<ChatMiddlewareContext> = {},
 ): ChatMiddlewareContext {
-  const base = {
+  const capabilities = new CapabilityRegistry()
+  const base: ChatMiddlewareContext = {
     requestId: 'req-1',
     streamId: 'stream-1',
+    runId: 'run-1',
+    threadId: 'thread-1',
     // Chat contexts always report `activity: 'chat'`; otelMiddleware branches
     // on it to choose the chat span tree over the single media span.
     activity: 'chat' as const,
@@
     messages: [],
     createId: (prefix: string) => `${prefix}-1`,
+    capabilities,
+    get: (capability) => capability[0](base),
+    getOptional: (capability) => capability[0](base, { optional: true }),
+    provide: (capability, value) => capability[1](base, value),
   }
-  return {
-    ...base,
-    ...overrides,
-  } as ChatMiddlewareContext
+  return { ...base, ...overrides }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai/tests/middlewares/fake-otel.ts` around lines 242 - 275, The
makeCtx function casts an incomplete object to ChatMiddlewareContext using "as
ChatMiddlewareContext" to hide missing required fields, which masks contract
drift and causes misleading test failures. Remove the type cast at the end of
the return statement in makeCtx and instead add all required fields to the base
object, including fields like runId, threadId, and any required capability
accessors. This ensures the object is fully typed without relying on casting to
suppress type errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai/tests/middleware-capabilities.test.ts`:
- Around line 1-17: The imports in the middleware-capabilities.test.ts file are
not ordered according to ESLint's sort-imports/import/order rules. Reorganize
the imports by grouping them in the following order: first place external
library imports (vitest) at the top, then all type imports together (type
imports from CapabilityContext, ChatMiddleware, and ChatMiddlewareContext),
followed by relative imports from ../src organized by path depth (starting with
deeper paths like ../src/activities/chat/middleware/capabilities, validate,
builder, define, compose, then ../src/logger/resolve, then ../src), and finally
place sibling imports starting with ./ (test-utils) at the end. Ensure imports
from the same source are grouped together and alphabetized within each group.

---

Outside diff comments:
In `@packages/ai/tests/middlewares/fake-otel.ts`:
- Around line 242-275: The makeCtx function casts an incomplete object to
ChatMiddlewareContext using "as ChatMiddlewareContext" to hide missing required
fields, which masks contract drift and causes misleading test failures. Remove
the type cast at the end of the return statement in makeCtx and instead add all
required fields to the base object, including fields like runId, threadId, and
any required capability accessors. This ensures the object is fully typed
without relying on casting to suppress type errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7019526b-080b-4464-abed-22bf041bc3cf

📥 Commits

Reviewing files that changed from the base of the PR and between a1f5bf9 and fefbfc7.

📒 Files selected for processing (26)
  • .changeset/activity-observers.md
  • docs/advanced/otel.md
  • docs/config.json
  • packages/ai/src/activities/chat/index.ts
  • packages/ai/src/activities/chat/middleware/types.ts
  • packages/ai/src/activities/generateAudio/index.ts
  • packages/ai/src/activities/generateImage/index.ts
  • packages/ai/src/activities/generateSpeech/index.ts
  • packages/ai/src/activities/generateTranscription/index.ts
  • packages/ai/src/activities/generateVideo/index.ts
  • packages/ai/src/activities/middleware/index.ts
  • packages/ai/src/activities/middleware/run.ts
  • packages/ai/src/activities/middleware/types.ts
  • packages/ai/src/index.ts
  • packages/ai/src/middlewares/otel.ts
  • packages/ai/src/middlewares/usage-attributes.ts
  • packages/ai/src/utilities/errors.ts
  • packages/ai/src/utilities/numbers.ts
  • packages/ai/tests/middleware-capabilities.test-d.ts
  • packages/ai/tests/middleware-capabilities.test.ts
  • packages/ai/tests/middlewares/fake-otel.ts
  • packages/ai/tests/middlewares/generation-middleware.test.ts
  • packages/ai/tests/middlewares/otel.test.ts
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.otel-media.ts
  • testing/e2e/tests/middleware.spec.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/ai/src/utilities/numbers.ts
  • .changeset/activity-observers.md
  • docs/config.json
  • docs/advanced/otel.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ai/src/utilities/errors.ts
🛑 Comments failed to post (1)
packages/ai/tests/middleware-capabilities.test.ts (1)

1-17: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix import ordering to satisfy ESLint checks.

Line 1 through Line 17 currently violate sort-imports/import/order (as flagged by static analysis), which can block CI on lint-gated pipelines.

Suggested import reordering
-import { describe, it, expect, vi } from 'vitest'
+import { describe, expect, it, vi } from 'vitest'
 import {
-  createCapability,
   CapabilityRegistry,
+  createCapability,
 } from '../src/activities/chat/middleware/capabilities'
 import { validateCapabilities } from '../src/activities/chat/middleware/validate'
 import { createChatMiddleware } from '../src/activities/chat/middleware/builder'
 import { defineChatMiddleware } from '../src/activities/chat/middleware/define'
-import type { CapabilityContext } from '../src/activities/chat/middleware/capabilities'
 import { MiddlewareRunner } from '../src/activities/chat/middleware/compose'
 import { resolveDebugOption } from '../src/logger/resolve'
 import { chat } from '../src'
-import { createMockAdapter, ev, collectChunks } from './test-utils'
+import { collectChunks, createMockAdapter, ev } from './test-utils'
+import type { CapabilityContext } from '../src/activities/chat/middleware/capabilities'
 import type {
   ChatMiddleware,
   ChatMiddlewareContext,
 } from '../src/activities/chat/middleware/types'
🧰 Tools
🪛 ESLint

[error] 1-1: Member 'expect' of the import declaration should be sorted alphabetically.

(sort-imports)


[error] 4-4: Member 'CapabilityRegistry' of the import declaration should be sorted alphabetically.

(sort-imports)


[error] 9-9: ../src/activities/chat/middleware/capabilities type import should occur after import of ./test-utils

(import/order)


[error] 13-13: Member 'collectChunks' of the import declaration should be sorted alphabetically.

(sort-imports)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai/tests/middleware-capabilities.test.ts` around lines 1 - 17, The
imports in the middleware-capabilities.test.ts file are not ordered according to
ESLint's sort-imports/import/order rules. Reorganize the imports by grouping
them in the following order: first place external library imports (vitest) at
the top, then all type imports together (type imports from CapabilityContext,
ChatMiddleware, and ChatMiddlewareContext), followed by relative imports from
../src organized by path depth (starting with deeper paths like
../src/activities/chat/middleware/capabilities, validate, builder, define,
compose, then ../src/logger/resolve, then ../src), and finally place sibling
imports starting with ./ (test-utils) at the end. Ensure imports from the same
source are grouped together and alphabetized within each group.

Source: Linters/SAST tools

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.

feat(observability): activity-agnostic observability hook for non-chat activities (image/video/audio/speech/transcription)

3 participants