Skip to content

feat(ai): add nitro stream logger#363

Open
hussainarslan wants to merge 3 commits into
HugoRCD:mainfrom
hussainarslan:fix/nitro-ai-stream-helper
Open

feat(ai): add nitro stream logger#363
hussainarslan wants to merge 3 commits into
HugoRCD:mainfrom
hussainarslan:fix/nitro-ai-stream-helper

Conversation

@hussainarslan
Copy link
Copy Markdown

@hussainarslan hussainarslan commented Jun 5, 2026

What
Adds createNitroAIStreamLogger() at evlog/ai/nitro for Nuxt/Nitro AI SDK streaming responses. The helper creates a correlated child request event, wraps streaming responses without buffering, records stream errors, and routes the child event through Nitro enrich/drain hooks including waitUntil.

Why
Issue #321 happens because AI SDK stream metadata can be written after Nitro has already emitted and sealed the parent request event. Keeping that lifecycle warning is useful, but streaming work needs its own event that can finish when the body closes.

How
The helper reads the parent logger with useLogger(event), creates a child request logger with a fresh requestId plus _parentRequestId, exposes createAILogger(childLog), and emits once when wrapResponse() observes stream close/error. Docs, README, playground usage, package exports, tsdown config, changeset, and the API snapshot were updated.

Validation
pnpm run dev:prepare
pnpm run lint
pnpm run typecheck
pnpm run test
pnpm test:coverage
pnpm api:snapshot

Fixes #321

Summary by CodeRabbit

  • New Features

    • Added a Nuxt/Nitro-friendly AI streaming logger to record stream metadata and avoid post-emit warnings for streamed responses.
  • Documentation

    • Updated AI SDK and framework guides with examples and guidance for streamed vs awaited AI calls using the new streaming logger; clarified wide-event lifecycle and Nitro logging behavior and post-emit guidance.
  • Tests

    • Added tests covering streaming logger behavior, metadata emission, stream/error handling, and drain semantics.
  • Chores

    • Packaging/exports and build mappings updated to expose the new integration.

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 5, 2026

@hussainarslan is attempting to deploy a commit to the HRCD Projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

Thank you for following the naming conventions! 🙏

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 5, 2026

Lost in the diff? Review this PR in Change Stack to follow the change map from intent to exact ranges.

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 574cf88b-a526-4d41-9b15-2e78f5700101

📥 Commits

Reviewing files that changed from the base of the PR and between 24df918 and ef420fb.

📒 Files selected for processing (2)
  • packages/evlog/src/ai/nitro.ts
  • packages/evlog/test/ai/nitro.test.ts

📝 Walkthrough

Walkthrough

Adds createNitroAIStreamLogger() and supporting types/exports, routes late AI stream metadata to a correlated child wide event via Nitro enrich/drain hooks, wraps streaming Responses to emit on completion/error, adds tests, updates package exports/tsdown, and revises docs and an example endpoint.

Changes

Nitro AI Stream Logger for Evlog

Layer / File(s) Summary
Core implementation: types, hooks, and main logger
packages/evlog/src/ai/nitro.ts, packages/evlog/tsdown.config.ts, packages/evlog/package.json, packages/evlog/src/logger.ts
Adds NitroAIStreamFields/NitroAIStreamLoggerOptions/NitroAIStreamLogger types; implements header sanitization, waitUntil derivation, error normalization, createObservedBody, enrichAndDrainNitroEvent, and createNitroAIStreamLogger(event, options) with idempotent emit() and wrapResponse() for streaming responses; exposes the new package subpath and tsdown entry; updates post-emit hint text.
Test suite for child event emission, response handling, and error cases
packages/evlog/test/ai/nitro.test.ts
Adds Vitest tests mocking Nitro app/hooks and headers; validates correlated child "ai-stream" emission with parent/child request ID linkage, hook ordering (evlog:enrich then evlog:drain), waitUntil behavior, wrapResponse preserving status/headers/body, error recording on stream failures, locked-body handling, and console error on handler failure.
Documentation, README, and release notes
apps/docs/..., packages/evlog/README.md, .changeset/nitro-ai-streams.md
Revises AI SDK docs and README to recommend createNitroAIStreamLogger(event) for Nuxt/Nitro streaming with wrapResponse and createEvlogIntegration(ai); updates wide-events guidance and post-emit hint text; adds a changeset for the minor release.
Example application: NuxthubPlayground chat endpoint
apps/nuxthub-playground/server/api/chat.post.ts
Refactors the example endpoint to use createNitroAIStreamLogger(event), derives { ai, log, wrapResponse }, wires ai.onUpdate to log.set, wraps the UI stream response with wrapResponse(...), and records final metadata via log.set in onFinish.

Sequence Diagram

sequenceDiagram
  participant Handler as Request handler
  participant NitroLogger as createNitroAIStreamLogger
  participant Stream as ReadableStream
  participant Enrich as evlog:enrich
  participant Drain as evlog:drain

  Handler->>NitroLogger: createNitroAIStreamLogger(event, options)
  Handler->>Stream: return wrapped Response body (wrapResponse)
  Stream->>NitroLogger: on close / error -> emit(childEvent)
  NitroLogger->>Enrich: call evlog:enrich(childEvent)
  Enrich->>Drain: pass enriched wide event
  NitroLogger->>Drain: call evlog:drain(childEvent) (await or waitUntil)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% 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): add nitro stream logger' clearly and concisely summarizes the main change: adding a new Nitro stream logger for AI SDK integration.
Description check ✅ Passed The PR description covers the What, Why, and How, includes validation steps, and fixes issue #321, though it lacks explicit checkbox completion and the linked issue format.
Linked Issues check ✅ Passed The PR directly addresses issue #321 by implementing createNitroAIStreamLogger() to emit a child event after stream completion, avoiding post-emit warnings for AI SDK metadata in Nuxt/Nitro streaming responses.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the Nitro AI stream logger feature: new implementation, documentation updates, examples, tests, package configuration, and a changeset entry.

✏️ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/evlog/src/ai/nitro.ts`:
- Around line 158-189: The function createObservedBody should defensively handle
the case where the incoming ReadableStream is already locked: before calling
body.getReader() check body.locked; if locked, call the provided onError with a
clear TypeError (e.g. "stream is already locked") and return the original body
unchanged (or rethrow if the caller expects an exception), otherwise proceed to
getReader as implemented; update any callers such as wrapResponse to expect this
early-return/error path so we don't attempt to lock an already-locked stream.

In `@packages/evlog/test/ai/nitro.test.ts`:
- Around line 107-124: The test currently mocks getHeaders to return an
authorization header which will be stripped by filterSafeHeaders; either remove
the authorization header from the mock or (preferred) add an explicit assertion
that the authorization header is absent in the data passed to
nitroRuntime.app.hooks.callHook (i.e. after invoking createNitroAIStreamLogger
in the first test assert that the hook call payload / event.headers does not
contain "authorization" or that filterSafeHeaders removed it), referencing the
existing getHeaders mock and the nitroRuntime.app.hooks.callHook mock used in
the test.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 231965f3-3349-4fc4-8a79-80fe56940b1e

📥 Commits

Reviewing files that changed from the base of the PR and between 1b17ff1 and 06b3350.

⛔ Files ignored due to path filters (1)
  • packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (13)
  • .changeset/nitro-ai-streams.md
  • apps/docs/content/2.learn/2.wide-events.md
  • apps/docs/content/3.integrate/frameworks/04.nitro.md
  • apps/docs/content/4.use-cases/2.ai-sdk/01.overview.md
  • apps/docs/content/4.use-cases/2.ai-sdk/02.usage.md
  • apps/docs/content/4.use-cases/2.ai-sdk/04.metadata.md
  • apps/nuxthub-playground/server/api/chat.post.ts
  • packages/evlog/README.md
  • packages/evlog/package.json
  • packages/evlog/src/ai/nitro.ts
  • packages/evlog/src/logger.ts
  • packages/evlog/test/ai/nitro.test.ts
  • packages/evlog/tsdown.config.ts

Comment thread packages/evlog/src/ai/nitro.ts
Comment thread packages/evlog/test/ai/nitro.test.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/evlog/src/ai/nitro.ts`:
- Around line 163-166: The locked-stream branch calls onError(new
TypeError('stream is already locked')) with no rejection handling; change that
call to attach a rejection handler so promise rejections can't become unhandled
(e.g., replace the current void onError(...) with onError(new TypeError('stream
is already locked')).catch(() => {/* optionally log */}) or await it inside a
try/catch in the enclosing async context). Update the call in the locked-stream
branch (where body.locked is checked) to handle .catch so any emit/plugin
failures are absorbed or logged.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 08e42136-bbc1-453a-acf6-2711d6accb33

📥 Commits

Reviewing files that changed from the base of the PR and between 06b3350 and 24df918.

📒 Files selected for processing (2)
  • packages/evlog/src/ai/nitro.ts
  • packages/evlog/test/ai/nitro.test.ts

Comment thread packages/evlog/src/ai/nitro.ts
@hussainarslan hussainarslan force-pushed the fix/nitro-ai-stream-helper branch from 24df918 to ef420fb Compare June 5, 2026 20:17
@HugoRCD
Copy link
Copy Markdown
Owner

HugoRCD commented Jun 7, 2026

Thanks @hussainarslan for the PR, it really helped me dig into this.

I'm not a huge fan of a Nitro-specific API here, I preferred fixing it lower down, at the streaming response body level (defer emit + late ai merge), while keeping createAILogger(log) everywhere.

That approach is in #367, feel free to try it with the pkg.pr.new preview and let me know if it covers your use case. I've also added you as a co-author on the other PR in recognition of your work on this one

@hussainarslan
Copy link
Copy Markdown
Author

Thanks @hussainarslan for the PR, it really helped me dig into this.

I'm not a huge fan of a Nitro-specific API here, I preferred fixing it lower down, at the streaming response body level (defer emit + late ai merge), while keeping createAILogger(log) everywhere.

That approach is in #367, feel free to try it with the pkg.pr.new preview and let me know if it covers your use case. I've also added you as a co-author on the other PR in recognition of your work on this one

That's great. Glad to be of help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] "[evlog] log.set() called after the wide event was emitted — Keys dropped: ai

2 participants