Skip to content

feat: update opencode patch with ATOF JSONL export#62

Open
AnuradhaKaruppiah wants to merge 3 commits intoNVIDIA:mainfrom
AnuradhaKaruppiah:ak-harbor-opencode-nemoflow-smoke
Open

feat: update opencode patch with ATOF JSONL export#62
AnuradhaKaruppiah wants to merge 3 commits intoNVIDIA:mainfrom
AnuradhaKaruppiah:ak-harbor-opencode-nemoflow-smoke

Conversation

@AnuradhaKaruppiah
Copy link
Copy Markdown

@AnuradhaKaruppiah AnuradhaKaruppiah commented May 5, 2026

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.

  • I confirm this contribution is my own work, or I have the right to submit it under this project's license.
  • I searched existing issues and open pull requests, and this does not duplicate existing work.

Details

  • Adds a raw ATOF JSONL subscriber controlled by NEMO_FLOW_ATOF_DIR, writing events.jsonl.
  • Keeps direct ATIF export optional via NEMO_FLOW_ATIF_DIR for comparison/debug output.
  • Updates the OpenCode patch so wrapped tools return OpenCode's original JavaScript values while NeMo Flow observes JSON-safe snapshots.
  • Avoids duplicate tool execution if native event observation fails after the wrapped callback has already run.
  • Ensures batch tool scopes are popped with a finally block.
  • Documents the artifact paths and validation flow in third_party/README-opencode.md.

Where should the reviewer start?

Start with patches/opencode/0001-add-nemo-flow-integration.patch, especially the src/nemo_flow/index.ts additions for the ATOF JSONL subscriber and tool wrapper behavior. Then review third_party/README-opencode.md for the documented contract.

Validation run locally:

  • git diff --check
  • ./scripts/apply-patches.sh --check
  • bun run typecheck in third_party/opencode/packages/opencode
  • uv run pre-commit run --files patches/opencode/0001-add-nemo-flow-integration.patch third_party/README-opencode.md

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

  • Relates to NAT-6, Harbor / NAT integration smoke testing for OpenCode with NeMo Flow ATOF export

Summary by CodeRabbit

  • New Features
    • Optional NemoFlow integration for real-time tracing, monitoring, and agent/tool scope management
    • Session-based trajectory export (including ATIF JSONL) and NemoFlow-backed exporters
    • LLM requests now accept custom HTTP headers (extraHeaders passthrough)
  • Chores
    • Manifest updates to declare NemoFlow as an optional dependency and enable opt-in configuration flag

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>
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented May 5, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Walkthrough

This 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.

Changes

NemoFlow integration

Layer / File(s) Summary
Dependencies & Configuration
bun.lock, package.json (root + packages/opencode), packages/opencode/src/config/config.ts, packages/opencode/src/flag/flag.ts
Adds optional nemo-flow-node dependency; adds nemo_flow?: boolean to Config and exports NEMO_FLOW_ENABLED flag.
Plugin Registration
packages/opencode/src/plugin/index.ts, packages/opencode/src/plugin/nemo_flow.ts
Registers and implements NemoFlowPlugin which initializes NemoFlow, creates exporter directories, and hooks session idle/chat to create/export trajectories.
Runtime API / Core
packages/opencode/src/nemo_flow/index.ts
New NemoFlow module: conditional native load, init/isEnabled, scope push/pop (including SCOPE_ATTR_PARALLEL), wrapToolExecute, wrapLlmStream, exporter lifecycle, and JSONL exporter creation.
Session & Processor Wiring
packages/opencode/src/session/processor.ts, packages/opencode/src/session/prompt.ts
Imports NemoFlow; pushes agent scopes on first use; wraps tool executions via wrapToolExecute; ensures scope pop/cleanup.
LLM Stream & API
packages/opencode/src/session/llm.ts, packages/opencode/src/session/processor.ts
Extends LLM input with extraHeaders?: Record<string,string>; LLM streaming flow wrapped with NemoFlow.wrapLlmStream to intercept stream events.
Batch Tool Parallelism
packages/opencode/src/session/tool/batch.ts
Batch execution enters a NemoFlow parallel function scope (batch-parallel), runs tools via wrapToolExecute, and uses try/finally for proper scope pop.
Exports / ATIF JSONL
packages/opencode/src/nemo_flow/index.ts, packages/opencode/src/plugin/nemo_flow.ts
Exporter creation, trajectory export, and ATOF JSONL exporter creation and session-level export triggering implemented.

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
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits format with type 'feat', uses lowercase, provides a concise imperative summary, and is 50 characters—well under the 72-character limit.
Description check ✅ Passed The description includes all required template sections: Overview with confirmations, Details with specific changes, reviewer guidance, and Related Issues with action keyword 'Relates to'.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

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

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

@github-actions github-actions Bot added size:S PR is small feat PR introduces new feature or functionality labels May 5, 2026
@AnuradhaKaruppiah AnuradhaKaruppiah self-assigned this May 5, 2026
@AnuradhaKaruppiah
Copy link
Copy Markdown
Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@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

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 lift

Isolate nemo-flow-node dependency from crates/node dev tooling to keep the patch minimal.

The file: reference to crates/node pulls all its dev dependencies (c8, typedoc, @napi-rs/cli, etc.) into the locked manifest, causing unnecessary churn in bun.lock that 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-node dependency to a parent-repo install wrapper or packaged artifact instead of declaring it directly in third_party/opencode's manifests.

Also applies to: 92–93 (ghostty-web revision 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 win

Track when the LLM stream begins to prevent restart on error.

The catch block unconditionally calls yield* streamFn(), which invokes a fresh LLM.stream(streamInput) even if the stream already started within the typedLlmStreamExecute() 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

📥 Commits

Reviewing files that changed from the base of the PR and between 696bd99 and 94d5a27.

⛔ Files ignored due to path filters (1)
  • third_party/README-opencode.md is 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 --check and relevant integration tests, keeping root ./scripts/*.sh wrappers 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

Comment thread 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>
@github-actions github-actions Bot added size:L PR is large and removed size:S PR is small labels May 5, 2026
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>
@github-actions github-actions Bot added size:M PR is medium and removed size:L PR is large labels May 5, 2026
Copy link
Copy Markdown
Author

AnuradhaKaruppiah commented May 5, 2026

CR review response:
There is still a packaging/install strategy worth discussing separately: the current local file: dependency makes the third-party OpenCode workspace load nemo-flow-node cleanly, but it also pulls crates/node dev-tool metadata into the OpenCode lockfile and exposes unrelated Bun lock churn from OpenCode's moving ghostty-web#main dependency. One alternative for smoke harnesses is a wrapper install step such as bun install --no-save; another is a packaged/tarball artifact for nemo-flow-node. I did not want to make that policy change in this PR. What dependency strategy would maintainers prefer for third-party harness patches?

@AnuradhaKaruppiah AnuradhaKaruppiah marked this pull request as ready for review May 5, 2026 21:12
@AnuradhaKaruppiah AnuradhaKaruppiah requested a review from a team as a code owner May 5, 2026 21:12
Copy link
Copy Markdown

@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

♻️ Duplicate comments (1)
patches/opencode/0001-add-nemo-flow-integration.patch (1)

9-11: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

The 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/node dev-tool graph into bun.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

📥 Commits

Reviewing files that changed from the base of the PR and between 94d5a27 and 842c119.

⛔ Files ignored due to path filters (1)
  • third_party/README-opencode.md is 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 --check and relevant integration tests, keeping root ./scripts/*.sh wrappers 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

Comment on lines +731 to 740
+ 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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

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.

Comment on lines +916 to +923
+ 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")
+ })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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 true

As 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.

Suggested change
+ 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.

@bbednarski9
Copy link
Copy Markdown

reminder that we could update the commit in NeMo-Flow/third_party/sources.lock

if staying current is a big deal here

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

Labels

feat PR introduces new feature or functionality size:M PR is medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants