feat: update opencode patch with ATOF JSONL export#62
feat: update opencode patch with ATOF JSONL export#62AnuradhaKaruppiah wants to merge 3 commits intoNVIDIA:mainfrom
Conversation
Refresh the OpenCode integration patch to preserve raw NeMo Flow ATOF events as JSONL sidecar output while keeping the existing optional direct ATIF export path. The patch registers an ATOF JSONL subscriber controlled by NEMO_FLOW_ATOF_DIR, keeps direct ATIF output controlled by NEMO_FLOW_ATIF_DIR, and documents both artifact paths in the OpenCode third-party README. Tool wrapping now returns OpenCode's original JavaScript tool results while NeMo Flow observes JSON-safe snapshots. This avoids structuredClone failures in OpenCode and prevents duplicate tool execution if the native observer fails after the wrapped callback has already run. Batch tool scopes are also popped with a finally block. Signed-off-by: Anuradha Karuppiah <26330987+AnuradhaKaruppiah@users.noreply.github.com>
WalkthroughThis PR integrates NemoFlow into OpenCode: adds an optional native dependency, feature flag/config, a NemoFlow runtime module and plugin, instruments session/agent/tool/LLM flows to push/pop scopes and wrap executions/streams, and exposes ATIF/JSONL exporters and extraHeaders passthrough for LLM calls. ChangesNemoFlow integration
Sequence Diagram(s)sequenceDiagram
participant Session as SessionProcessor
participant Nemo as NemoFlow
participant LLM as LLM Provider
participant Tool as Tool Executor
participant Export as Exporter
Session->>Nemo: init / isEnabled
Session->>Nemo: pushAgentScope(agentName)
alt LLM stream produced
Session->>Nemo: wrapLlmStream(streamInput)
Nemo->>LLM: forward streamFn()
LLM-->>Nemo: stream events
Nemo-->>Session: intercepted events (yield)
end
Session->>Nemo: wrapToolExecute(toolName, args, fn)
Nemo->>Tool: call fn(args)
Tool-->>Nemo: result
Nemo-->>Session: wrapped result
Note over Nemo,Export: on session idle
Nemo->>Export: exportTrajectory(sessionId)
Export-->>Nemo: filePath / jsonl
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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 (2)
patches/opencode/0001-add-nemo-flow-integration.patch (2)
9-21:⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy liftIsolate
nemo-flow-nodedependency fromcrates/nodedev tooling to keep the patch minimal.The
file:reference tocrates/nodepulls all its dev dependencies (c8,typedoc,@napi-rs/cli, etc.) into the locked manifest, causing unnecessary churn inbun.lockthat is decoupled from the actual integration code. This violates the guideline to keep patches minimal and reproducible—the patch now carries transitive dev-tool metadata that has nothing to do with the runtime behavior being patched.Move the
nemo-flow-nodedependency to a parent-repo install wrapper or packaged artifact instead of declaring it directly inthird_party/opencode's manifests.Also applies to: 92–93 (
ghostty-webrevision jump), 158–159, 271, 429–443🤖 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 `@patches/opencode/0001-add-nemo-flow-integration.patch` around lines 9 - 21, The patch adds "nemo-flow-node" as a file: reference under optionalDependencies which pulls dev-tooling from crates/node into the lockfile; instead remove the file:../../crates/node entries from optionalDependencies (both the top-level optionalDependencies block and the packages/app optionalDependencies) and replace them with a parent-repo install wrapper or a packaged artifact (e.g., publish a tarball or add a top-level installer script) that installs nemo-flow-node without referencing crates/node directly; apply the same change pattern to other offending entries mentioned (the ghostty-web revision and the other file: references flagged) so only the runtime artifact is consumed and dev-tooling from crates/node is not leaked into third_party/opencode manifests.
531-539:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winTrack when the LLM stream begins to prevent restart on error.
The catch block unconditionally calls
yield* streamFn(), which invokes a freshLLM.stream(streamInput)even if the stream already started within thetypedLlmStreamExecute()callback. If an error occurs after the callback runs, this causes a second provider request, resulting in double billing and duplicate tool calls.Add a flag to track whether the stream callback has been invoked. Only restart the stream in the catch block if it never started; otherwise, rethrow the error:
Suggested fix
export async function* wrapLlmStream( streamInput: any, streamFn: () => AsyncIterable<any>, ): AsyncGenerator<any> { if (!enabled || !typedLib) { yield* streamFn() return } + let streamStarted = false try { const request = { headers: {}, content: encodeStreamInput(streamInput), } const originals: any[] = [] const responseChunks: string[] = [] const toolCalls: any[] = [] const chunkCodec = { toJson(event: any): any { originals.push(event) if (event.type === "text-delta" && event.text) { responseChunks.push(event.text) } else if (event.type === "tool-call") { toolCalls.push({ id: event.toolCallId, type: "function", function: { name: event.toolName, arguments: JSON.stringify(event.input ?? {}), }, }) } return { _t: event.type ?? "chunk" } }, fromJson(_data: any): any { return originals.shift() }, } const responseCodec = new typedLib.JsonPassthrough() const collected: any[] = [] const nvStream = await typedLib.typedLlmStreamExecute( streamInput.model.providerID, request, (interceptedReq: any) => { + streamStarted = true applyIntercepted(streamInput, interceptedReq?.content) if (interceptedReq?.headers && Object.keys(interceptedReq.headers).length > 0) { streamInput.extraHeaders = interceptedReq.headers } return streamFn() }, (chunk: any) => { collected.push(chunk) }, () => { const result: any = { role: "assistant", content: responseChunks.join("") || null, } if (toolCalls.length > 0) { result.tool_calls = toolCalls } return result }, chunkCodec, responseCodec, { attributes: lib.LLM_ATTR_STREAMING, modelName: streamInput.model.id, }, ) while ((await nvStream.next()) !== null) { while (collected.length > 0) { yield collected.shift() } } while (collected.length > 0) { yield collected.shift() } } catch (e) { log.error("wrapLlmStream", { error: e }) + if (streamStarted) throw e yield* streamFn() } }🤖 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 `@patches/opencode/0001-add-nemo-flow-integration.patch` around lines 531 - 539, The LLM stream may be restarted on error even if the original stream callback already started, causing duplicate provider requests; add a boolean flag (e.g., started = false) around the async generator passed to NemoFlow.wrapLlmStream (the async function* that calls LLM.stream(streamInput)) and set it true immediately when the callback begins or right before/after awaiting LLM.stream; then modify the catch/restart logic so that it only calls yield* streamFn() (restarts the stream) if started is false, otherwise rethrow the error to avoid a second request. Ensure references: NemoFlow.wrapLlmStream, the async function* callback, LLM.stream(streamInput), and any catch block that currently unconditionally calls yield* streamFn().
🤖 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 `@patches/opencode/0001-add-nemo-flow-integration.patch`:
- Around line 724-740: The wrapToolExecute logic can re-run non-idempotent tools
on failure because executed is set only after await fn(args) resolves; change
the call inside lib.toolCallExecuteAsync so executed is set true immediately
before invoking fn (e.g., set executed = true; originalResult = await fn(args))
so the catch branch won't retry a failed-but-mutating tool; keep returning
originalResult (toJsonSafe wrapping remains) and only fall back to fn(args) if
executed is false. Separately, remove any local monorepo file references
(file:../../crates/node and file:../../../../crates/node) from the patch
artifact and move those dev-tool dependencies out of the third-party patch (or
use a separate lock file for the OpenCode integration) so the patch only
contains reproducible third-party changes.
---
Outside diff comments:
In `@patches/opencode/0001-add-nemo-flow-integration.patch`:
- Around line 9-21: The patch adds "nemo-flow-node" as a file: reference under
optionalDependencies which pulls dev-tooling from crates/node into the lockfile;
instead remove the file:../../crates/node entries from optionalDependencies
(both the top-level optionalDependencies block and the packages/app
optionalDependencies) and replace them with a parent-repo install wrapper or a
packaged artifact (e.g., publish a tarball or add a top-level installer script)
that installs nemo-flow-node without referencing crates/node directly; apply the
same change pattern to other offending entries mentioned (the ghostty-web
revision and the other file: references flagged) so only the runtime artifact is
consumed and dev-tooling from crates/node is not leaked into
third_party/opencode manifests.
- Around line 531-539: The LLM stream may be restarted on error even if the
original stream callback already started, causing duplicate provider requests;
add a boolean flag (e.g., started = false) around the async generator passed to
NemoFlow.wrapLlmStream (the async function* that calls LLM.stream(streamInput))
and set it true immediately when the callback begins or right before/after
awaiting LLM.stream; then modify the catch/restart logic so that it only calls
yield* streamFn() (restarts the stream) if started is false, otherwise rethrow
the error to avoid a second request. Ensure references: NemoFlow.wrapLlmStream,
the async function* callback, LLM.stream(streamInput), and any catch block that
currently unconditionally calls yield* streamFn().
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: a31835b6-8997-4314-a437-adb3ebe38955
⛔ Files ignored due to path filters (1)
third_party/README-opencode.mdis excluded by!third_party/**
📒 Files selected for processing (1)
patches/opencode/0001-add-nemo-flow-integration.patch
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/{patches,patch}/**/*.patch
📄 CodeRabbit inference engine (.agents/skills/contribute-integration/SKILL.md)
Keep the tracked patch artifact minimal and reproducible
Files:
patches/opencode/0001-add-nemo-flow-integration.patch
{./scripts/**,**/patches/**,**/*.patch}
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
For third-party integration or patch changes, run patch validation with
./scripts/apply-patches.sh --checkand relevant integration tests, keeping root./scripts/*.shwrappers for third-party flows
Files:
patches/opencode/0001-add-nemo-flow-integration.patch
patches/**/*.patch
⚙️ CodeRabbit configuration file
patches/**/*.patch: Patch changes should remain reviewable as regenerated diffs against the matching third_party submodule.
Check that integration behavior, package metadata, and tests remain consistent with the main NeMo Flow API.
Files:
patches/opencode/0001-add-nemo-flow-integration.patch
Avoid retrying provider streams or tool calls after their underlying callback has already started. This prevents duplicate model requests and duplicate execution of potentially non-idempotent tools when NeMo Flow observation fails after the underlying action has begun. Remove the local file: dependency on crates/node from the generated OpenCode patch. The integration now tries an installed nemo-flow-node package first, then falls back to the local NeMo Flow Node build resolved from the integration module path or NEMO_FLOW_NODE_DIR. This keeps the third-party patch focused on runtime instrumentation and removes unrelated bun.lock churn from the patch artifact. Signed-off-by: Anuradha Karuppiah <26330987+AnuradhaKaruppiah@users.noreply.github.com>
Retain the runtime safety fixes for tool and stream fallback paths while keeping the existing file: dependency model for nemo-flow-node. The broader dependency/install strategy is left for maintainer discussion instead of changing it in this PR. Signed-off-by: Anuradha Karuppiah <26330987+AnuradhaKaruppiah@users.noreply.github.com>
|
CR review response: |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
patches/opencode/0001-add-nemo-flow-integration.patch (1)
9-11:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftThe tracked patch still embeds local
file:dependencies.This is the same reproducibility issue raised earlier: these paths keep the patch tied to the author's monorepo layout and are what pull the
crates/nodedev-tool graph intobun.lock, which is why the patch now carries unrelated lock churn alongside the runtime changes. This still needs to be moved out of the tracked third-party patch before merge.As per coding guidelines, "Keep the tracked patch artifact minimal and reproducible" and "Patch changes should remain reviewable as regenerated diffs against the matching third_party submodule."
Also applies to: 19-21, 429-430, 441-442
🤖 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 `@patches/opencode/0001-add-nemo-flow-integration.patch` around lines 9 - 11, The tracked patch currently embeds local file: dependencies for the package key "nemo-flow-node" inside the optionalDependencies block (e.g. "nemo-flow-node": "file:../../crates/node") — remove any file:... local-path entries from the patch and replace them with a reproducible reference: either a published version specifier (semver) or move the local dependency change into the separate third_party/submodule patch/regeneration workflow so the main patch stays minimal; ensure you update all occurrences of the same pattern (the other "nemo-flow-node" file: entries referenced in this patch) and regenerate the lockfile/patch artifact from the third_party source instead of committing local file paths.
🤖 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 `@patches/opencode/0001-add-nemo-flow-integration.patch`:
- Around line 731-740: The wrapper currently sets executed = true before
awaiting fn(args), so if fn throws you end up returning originalResult! which
may be undefined; change the flow so executed is only set after fn(args)
successfully resolves (i.e., set executed = true and assign originalResult
immediately after await fn(args)), or alternatively in the catch block detect
executed && originalResult === undefined and rethrow the caught error instead of
returning originalResult; update the logic around lib.toolCallExecuteAsync, fn,
executed, and originalResult so tool exceptions are preserved and not
suppressed.
- Around line 916-923: In createAtOfJsonlExporter ensure the subscriber callback
is best-effort: wrap the body passed to lib.registerSubscriber (the anonymous
event handler referenced where atofJsonlSubscriberName is set) in a try/catch
and convert the event to a JSON-safe form using toJsonSafe before calling
JSON.stringify and fsSync.appendFileSync, swallowing/logging any serialization
or fs errors so they cannot propagate out of the subscriber.
---
Duplicate comments:
In `@patches/opencode/0001-add-nemo-flow-integration.patch`:
- Around line 9-11: The tracked patch currently embeds local file: dependencies
for the package key "nemo-flow-node" inside the optionalDependencies block (e.g.
"nemo-flow-node": "file:../../crates/node") — remove any file:... local-path
entries from the patch and replace them with a reproducible reference: either a
published version specifier (semver) or move the local dependency change into
the separate third_party/submodule patch/regeneration workflow so the main patch
stays minimal; ensure you update all occurrences of the same pattern (the other
"nemo-flow-node" file: entries referenced in this patch) and regenerate the
lockfile/patch artifact from the third_party source instead of committing local
file paths.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: b2117627-5d1f-4d40-b294-ae0861c942fd
⛔ Files ignored due to path filters (1)
third_party/README-opencode.mdis excluded by!third_party/**
📒 Files selected for processing (1)
patches/opencode/0001-add-nemo-flow-integration.patch
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/{patches,patch}/**/*.patch
📄 CodeRabbit inference engine (.agents/skills/contribute-integration/SKILL.md)
Keep the tracked patch artifact minimal and reproducible
Files:
patches/opencode/0001-add-nemo-flow-integration.patch
{./scripts/**,**/patches/**,**/*.patch}
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
For third-party integration or patch changes, run patch validation with
./scripts/apply-patches.sh --checkand relevant integration tests, keeping root./scripts/*.shwrappers for third-party flows
Files:
patches/opencode/0001-add-nemo-flow-integration.patch
patches/**/*.patch
⚙️ CodeRabbit configuration file
patches/**/*.patch: Patch changes should remain reviewable as regenerated diffs against the matching third_party submodule.
Check that integration behavior, package metadata, and tests remain consistent with the main NeMo Flow API.
Files:
patches/opencode/0001-add-nemo-flow-integration.patch
| + await lib.toolCallExecuteAsync(name, toJsonSafe(args), async () => { | ||
| + executed = true | ||
| + originalResult = await fn(args) | ||
| + return toJsonSafe(originalResult) | ||
| + }, null, null, null, null) | ||
| + return originalResult! | ||
| + } catch (e) { | ||
| + log.error("wrapToolExecute", { error: e }) | ||
| + if (executed) return originalResult! | ||
| + return fn(args) |
There was a problem hiding this comment.
Preserve tool exceptions after the wrapped callback starts.
Line 732 flips the guard before await fn(args), but if fn(args) rejects, Line 739 returns originalResult! before it was assigned. That suppresses the real tool failure and turns it into undefined/downstream corruption instead of rethrowing without retrying the tool.
Proposed fix
export async function wrapToolExecute<T>(
name: string,
args: any,
fn: (args: any) => Promise<T>,
): Promise<T> {
if (!enabled || !lib) return fn(args)
let originalResult: T
- let executed = false
+ let started = false
+ let completed = false
+ let callbackError: unknown = null
try {
// Keep OpenCode's tool execution on its original JS values. The native
// observer only needs JSON-safe snapshots; handing it OpenCode-owned
// objects can make later structuredClone(part) calls fail.
await lib.toolCallExecuteAsync(name, toJsonSafe(args), async () => {
- executed = true
- originalResult = await fn(args)
+ started = true
+ try {
+ originalResult = await fn(args)
+ completed = true
+ } catch (error) {
+ callbackError = error
+ throw error
+ }
return toJsonSafe(originalResult)
}, null, null, null, null)
return originalResult!
} catch (e) {
log.error("wrapToolExecute", { error: e })
- if (executed) return originalResult!
+ if (started) {
+ if (completed) return originalResult!
+ throw callbackError ?? e
+ }
return fn(args)
}
}🤖 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 `@patches/opencode/0001-add-nemo-flow-integration.patch` around lines 731 -
740, The wrapper currently sets executed = true before awaiting fn(args), so if
fn throws you end up returning originalResult! which may be undefined; change
the flow so executed is only set after fn(args) successfully resolves (i.e., set
executed = true and assign originalResult immediately after await fn(args)), or
alternatively in the catch block detect executed && originalResult === undefined
and rethrow the caught error instead of returning originalResult; update the
logic around lib.toolCallExecuteAsync, fn, executed, and originalResult so tool
exceptions are preserved and not suppressed.
| + export function createAtOfJsonlExporter(filePath: string): boolean { | ||
| + if (!enabled || !lib) return false | ||
| + if (atofJsonlSubscriberName) return true | ||
| + try { | ||
| + const subscriberName = `atof-jsonl-${process.pid}` | ||
| + lib.registerSubscriber(subscriberName, (event: any) => { | ||
| + fsSync.appendFileSync(filePath, JSON.stringify(event) + "\n") | ||
| + }) |
There was a problem hiding this comment.
Don't let JSONL export failures escape the subscriber callback.
Lines 921-923 call JSON.stringify and appendFileSync directly from the native subscriber. A single serialization or filesystem error can bubble out after observation has started and break the tracing path. This callback should stay best-effort like the rest of the NemoFlow bridge.
Proposed fix
export function createAtOfJsonlExporter(filePath: string): boolean {
if (!enabled || !lib) return false
if (atofJsonlSubscriberName) return true
try {
const subscriberName = `atof-jsonl-${process.pid}`
lib.registerSubscriber(subscriberName, (event: any) => {
- fsSync.appendFileSync(filePath, JSON.stringify(event) + "\n")
+ try {
+ fsSync.appendFileSync(filePath, JSON.stringify(toJsonSafe(event)) + "\n")
+ } catch (error) {
+ log.error("createAtOfJsonlExporter.write", { error, path: filePath })
+ }
})
atofJsonlSubscriberName = subscriberName
log.info("registered ATOF JSONL exporter", { path: filePath })
return trueAs per coding guidelines, "Convert objects to JSON-safe forms for observer via toJsonSafe; use try/catch to avoid breaking on non-serializable data."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| + export function createAtOfJsonlExporter(filePath: string): boolean { | |
| + if (!enabled || !lib) return false | |
| + if (atofJsonlSubscriberName) return true | |
| + try { | |
| + const subscriberName = `atof-jsonl-${process.pid}` | |
| + lib.registerSubscriber(subscriberName, (event: any) => { | |
| + fsSync.appendFileSync(filePath, JSON.stringify(event) + "\n") | |
| + }) | |
| export function createAtOfJsonlExporter(filePath: string): boolean { | |
| if (!enabled || !lib) return false | |
| if (atofJsonlSubscriberName) return true | |
| try { | |
| const subscriberName = `atof-jsonl-${process.pid}` | |
| lib.registerSubscriber(subscriberName, (event: any) => { | |
| try { | |
| fsSync.appendFileSync(filePath, JSON.stringify(toJsonSafe(event)) + "\n") | |
| } catch (error) { | |
| log.error("createAtOfJsonlExporter.write", { error, path: filePath }) | |
| } | |
| }) |
🤖 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 `@patches/opencode/0001-add-nemo-flow-integration.patch` around lines 916 -
923, In createAtOfJsonlExporter ensure the subscriber callback is best-effort:
wrap the body passed to lib.registerSubscriber (the anonymous event handler
referenced where atofJsonlSubscriberName is set) in a try/catch and convert the
event to a JSON-safe form using toJsonSafe before calling JSON.stringify and
fsSync.appendFileSync, swallowing/logging any serialization or fs errors so they
cannot propagate out of the subscriber.
|
reminder that we could update the commit in NeMo-Flow/third_party/sources.lock if staying current is a big deal here |
Overview
Refreshes the OpenCode integration patch so NeMo Flow can preserve raw ATOF events as a JSONL sidecar artifact while keeping the existing optional direct ATIF export path.
The raw ATOF JSONL artifact is intentionally preserved as the lossless publisher output. ATIF remains the normalized evaluation artifact, but keeping ATOF lets us validate the harness publisher independently, debug converter issues, and regenerate ATIF/evaluation artifacts offline without rerunning the agent task. This is especially useful while the NeMo-Flow harness integrations and ATOF-to-ATIF mapping are still evolving.
Details
NEMO_FLOW_ATOF_DIR, writingevents.jsonl.NEMO_FLOW_ATIF_DIRfor comparison/debug output.finallyblock.third_party/README-opencode.md.Where should the reviewer start?
Start with
patches/opencode/0001-add-nemo-flow-integration.patch, especially thesrc/nemo_flow/index.tsadditions for the ATOF JSONL subscriber and tool wrapper behavior. Then reviewthird_party/README-opencode.mdfor the documented contract.Validation run locally:
git diff --check./scripts/apply-patches.sh --checkbun run typecheckinthird_party/opencode/packages/opencodeuv run pre-commit run --files patches/opencode/0001-add-nemo-flow-integration.patch third_party/README-opencode.mdRelated Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)
Summary by CodeRabbit