Skip to content

perf: eliminate response.clone() memory doubling and cache lazy tool getters#1478

Open
skyhighbg22-jpg wants to merge 5 commits into
Gitlawb:mainfrom
skyhighbg22-jpg:perf/memory-and-io-optimizations
Open

perf: eliminate response.clone() memory doubling and cache lazy tool getters#1478
skyhighbg22-jpg wants to merge 5 commits into
Gitlawb:mainfrom
skyhighbg22-jpg:perf/memory-and-io-optimizations

Conversation

@skyhighbg22-jpg

@skyhighbg22-jpg skyhighbg22-jpg commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Performance optimizations — eliminates memory doubling from response cloning and caches lazy tool getters.

Optimizations

src/services/api/openaiShim.ts

Line Fix
2733 Replace response.clone() with response.text() → recreate Response immediately → then JSON.parse() for usage. Response body is preserved even if JSON.parse fails.

src/tools.ts

Line Fix
214, 217, 230 Cache lazy require() getter results in local IIFEs — avoids double-invocation of getSendMessageTool, getTeamCreateTool/DeleteTool, getPowerShellTool

Testing

  • Build passes: bun run build

Summary by CodeRabbit

  • Bug Fixes
    • Prevented “Body already used” errors in non-streaming API calls by defensively recreating the response body before parsing.
    • Improved resilience when parsing fails and preserved routing-related response metadata during body handling.
  • Tests
    • Added regression coverage for usage-parse failure handling.
    • Added regression coverage to ensure response routing metadata (e.g., response.url) is preserved.
  • Refactor
    • Reduced redundant lazy-loading invocations when assembling the full tool list, keeping behavior unchanged.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I found issues that need to be addressed before this is ready.

Findings

  • [P2] Preserve the response body when usage parsing fails
    src/services/api/openaiShim.ts:2733
    This now reads the live Response body before JSON.parse succeeds, but the catch leaves the original consumed response in place. If an OpenAI-compatible provider returns a successful non-streaming response with invalid JSON or an unexpected non-JSON body, the later caller path at response.json()/response.text() now fails with Body already used or loses the provider's original body, whereas the old response.clone().json() failure left the original response readable for normal conversion/error handling. Please recreate the Response immediately after text() succeeds, or otherwise only consume a clone/fallback buffer so downstream parsing can still read the body when usage extraction fails.

  • [P3] De-duplicate this scope with the older open PR
    src/services/api/openaiShim.ts:2733
    This PR repeats the same openaiShim.ts response-body optimization and the same src/tools.ts lazy getter caching that are already present in the older open #1473 from the same branch family, while #1473 also carries adjacent fixes in the same files. Keeping both PRs open makes reviewers resolve the same bug and tool-registry changes twice and risks merging one copy without the other PR's follow-up corrections. Please close/supersede one PR or remove the overlapping hunks so there is a single source of truth for these changes.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the changed paths and found an issue that still needs to be addressed.

Findings

  • [P2] Preserve the response body when usage parsing fails
    src/services/api/openaiShim.ts:2733
    The live Response body is still consumed before usage parsing has safely completed, and the replacement Response is only created after JSON.parse(bodyText) succeeds. If a provider returns a successful non-streaming response with invalid JSON or a 2xx non-JSON body, the catch swallows the parse failure and returns the original consumed response, so the downstream conversion/error path later hits Body already used instead of reading the provider body. Please recreate the response immediately after await response.text() succeeds, before parsing usage, or keep the usage probe on a clone/fallback buffer so parse failures leave the response readable.

…getters

openaiShim.ts:
- Replace response.clone() with response.text() + JSON.parse() + new Response()
  for non-streaming usage extraction — avoids doubling memory for large responses

tools.ts:
- Cache getSendMessageTool(), getTeamCreateTool/DeleteTool(), getPowerShellTool()
  results in local IIFEs — avoids double-invocation of lazy require() getters
@skyhighbg22-jpg skyhighbg22-jpg force-pushed the perf/memory-and-io-optimizations branch from 632203c to fc2f084 Compare June 2, 2026 14:56

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the changed paths and found one issue that still needs to be addressed.

Findings

  • [P3] Align the PR description with the current scope
    src/main.tsx:1280
    The PR description still says this change replaces the synchronous readFileSync path for --system-prompt-file / --append-system-prompt-file, but the current diff only changes src/services/api/openaiShim.ts and src/tools.ts; src/main.tsx still imports readFileSync and uses it for both prompt-file flags. That makes the review target and claimed behavior drift from the actual implementation. Please either include the async file-read change, or remove that row and summary claim so the PR description matches the code being merged.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 437a4609-3635-48e0-9f63-a791b15b20ad

📥 Commits

Reviewing files that changed from the base of the PR and between f210599 and ecfc5f8.

📒 Files selected for processing (1)
  • src/services/api/openaiShim.test.ts
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (13)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript with strict mode and ESM imports

Files:

  • src/services/api/openaiShim.test.ts
{src/commands/**/*.ts,src/services/**/*.ts,src/entrypoints/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use chalk for terminal color in CLI code

Files:

  • src/services/api/openaiShim.test.ts
{src/services/**/*.ts,src/utils/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use execa for child processes

Files:

  • src/services/api/openaiShim.test.ts
{src/integrations/**/*.ts,src/services/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Test the exact provider/model path you changed when possible for provider modifications

Files:

  • src/services/api/openaiShim.test.ts
**/*.{ts,tsx,js,jsx,py,json,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow the existing code style in the touched files

Files:

  • src/services/api/openaiShim.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add or update tests when the change affects behavior

Files:

  • src/services/api/openaiShim.test.ts
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Test the exact provider/model path you changed when possible

Files:

  • src/services/api/openaiShim.test.ts
**/*.{ts,tsx,js,jsx,py}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Keep comments useful and concise

Files:

  • src/services/api/openaiShim.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow TypeScript strict mode and type safety practices by running typecheck before submitting

Files:

  • src/services/api/openaiShim.test.ts
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the OpenClaude maintainer review rubric from AGENTS.md. Review the current diff, not stale discussion context. Separate real blockers from suggestions. Do not request changes for vague style churn. Treat approval as merge-ready from CodeRabbit's side, pending required human review and GitHub Checks. If checks are failing or unavailable, say so clearly instead of implying the PR is fully ready.

Files:

  • src/services/api/openaiShim.test.ts
{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}

⚙️ CodeRabbit configuration file

{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}: Review provider routing, model selection, env precedence, auth/token handling, OpenAI-compatible shims, retries, proxy behavior, and outbound HTTP behavior with high scrutiny. Block on silent default changes, hidden fallback expansion, credential reuse mistakes, hardcoded provider assumptions, or new network reach that is not intentional and documented.

Files:

  • src/services/api/openaiShim.test.ts
{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}

⚙️ CodeRabbit configuration file

{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}: Review tests for meaningful coverage of the changed behavior, isolation of global/env/config state, async cleanup, fake timers, provider profile leaks, and Windows-compatible assumptions. Block when risky runtime changes lack focused regression coverage or tests assert implementation details while missing the user-visible behavior.

Files:

  • src/services/api/openaiShim.test.ts
**

⚙️ CodeRabbit configuration file

**: # Contributing to OpenClaude

Thanks for contributing.

OpenClaude is a fast-moving open-source coding-agent CLI with support for multiple providers, local backends, MCP, and a terminal-first workflow. The best contributions here are focused, well-tested, and easy to review.

Before You Start

  • Search existing issues and discussions before opening a new thread.
  • Check open pull requests for work that overlaps with your contribution. If a PR already exists that addresses the same change, open an issue or discussion first to align on direction — duplicate PRs may be closed without review.
  • Use issues for confirmed bugs and actionable feature work.
  • Use discussions for setup help, ideas, and general community conversation.
  • For larger changes, open an issue first so the scope is clear before implementation.
  • For security reports, follow SECURITY.md.

Pull Requests

Every PR needs a reason. Your PR description must include:

  • what changed and why
  • the user or developer impact
  • the exact checks you ran
  • a linked issue when one exists, using Fixes fix: skip assertMinVersion for third-party providers #123, `Closes `#123, or another clear link
  • screenshots when the PR touches UI, terminal presentation, or the VS Code extension
  • which provider path was tested when the PR changes provider behavior

The PR author is responsible for ensuring their PR is merge-ready. PRs with merge conflicts will not be reviewed or approved until the conflicts are resolved.

Issues are the recommended starting point for anything non-trivial — opening one first helps avoid wasted effort if the change is out of scope or already being worked on. Small fixes, doc corrections, and obvious improvements can stand on their own without a linked issue, as long as the PR description explains the intent.

What Gets Closed Without Review

PRs may be closed without review...

Files:

  • src/services/api/openaiShim.test.ts
🔇 Additional comments (1)
src/services/api/openaiShim.test.ts (1)

192-6089: LGTM!


📝 Walkthrough

Walkthrough

The PR hardens non-streaming response handling in the OpenAI shim to preserve body content and routing metadata even when JSON parsing fails, adds two regression tests to validate error recovery and metadata preservation, and optimizes tool lazy-loading to eliminate redundant require() invocations.

Changes

Response Resilience and Tool Loading Optimization

Layer / File(s) Summary
Response body resilience: reconstruction and metadata preservation
src/services/api/openaiShim.ts, src/services/api/openaiShim.test.ts
Non-streaming response handling changed from clone().json() to reading body as text, recreating a Response with preserved status/statusText/headers and conditionally re-shadowed url/type properties, then manually parsing JSON. Failures during recreation or parsing are ignored to keep the response usable downstream. Two regression tests validate: (1) graceful recovery when JSON.parse fails during usage extraction, and (2) response.url metadata is preserved after body read/reconstruction for correct routing. Test fetch mocks are updated to cast to FetchType for consistent typing across the suite.
Tool lazy-loading optimization
src/tools.ts
Tool getter functions (getSendMessageTool(), getTeamCreateTool(), getTeamDeleteTool(), getPowerShellTool()) are refactored to cache results in local variables within immediately-invoked arrow functions, eliminating double-invocation of lazy require() statements while preserving original conditional filtering logic.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested Reviewers

  • jatmn
🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description covers optimizations but omits critical template sections: Impact analysis, Testing checklist completion status, and provider/model testing details. Complete the template by adding Impact section (user/developer-facing changes), checking Testing boxes, and specifying provider/model paths tested.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main changes: eliminating response.clone() memory overhead and caching lazy tool getters, matching the actual diff scope.
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.
Risk Surface Disclosed ✅ Passed PR touches provider routing (response.url preservation for /responses, /messages, Gemini routing). Risk surface is disclosed in code comments explaining routing impact and test cases verify routing...
No Hidden Policy Change ✅ Passed No hidden policy changes detected. PR contains performance optimizations (response.clone() elimination, lazy tool getter caching) and a bug fix that preserves pre-existing routing behavior in non-s...

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch perf/memory-and-io-optimizations

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 `@src/services/api/openaiShim.ts`:
- Around line 2733-2745: In _doOpenAIRequest, when params.stream is false, avoid
replacing the original fetch response (which loses response.url) — instead call
response.clone(), await clone.text() to get bodyText, parse JSON from that clone
to extract tokensIn/tokensOut (e.g.,
data.usage?.prompt_tokens/completion_tokens), and leave the original response
object intact so OpenAIShimMessages.create() can inspect response.url to choose
the correct non-stream branch; only recreate a Response for other code paths
that truly need it, but do not overwrite response in the non-stream success
path.
🪄 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 Plus

Run ID: 9f0b14e4-fec7-4b75-8030-be51d25a2a5c

📥 Commits

Reviewing files that changed from the base of the PR and between cfcc5d0 and 1d271c3.

📒 Files selected for processing (3)
  • src/services/api/openaiShim.test.ts
  • src/services/api/openaiShim.ts
  • src/tools.ts

Comment thread src/services/api/openaiShim.ts
@skyhighbg22-jpg skyhighbg22-jpg requested a review from jatmn June 3, 2026 15:38

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the changed paths and found issues that still need to be addressed.

Findings

  • [P2] Complete CodeRabbit's request to preserve response routing metadata
    src/services/api/openaiShim.ts:2737
    CodeRabbit's review thread is marked resolved, but the current patch still replaces the fetch response with new Response(bodyText, ...). That new response does not preserve response.url, while OpenAIShimMessages.create() uses response.url to choose the /responses, /messages, and Gemini non-stream conversion paths. Successful non-streaming requests to descriptor routes such as OpenCode /messages or Gemini endpoint paths can therefore fall through to the generic OpenAI converter and return the wrong message shape. Please complete that review request by keeping the original response metadata available, or by routing from explicit request metadata instead of the recreated response URL.

  • [P2] Fix the added regression test before merging
    src/services/api/openaiShim.test.ts:4043
    The new non-streaming: preserves response body when usage parsing fails test currently fails: bun test src/services/api/openaiShim.test.ts reports Expected: > 1, Received: 1 for parseCalls. That leaves the focused shim test file red on this branch, so the test needs to be corrected or rewritten to assert the intended readable-body failure mode without relying on a second JSON.parse call that does not occur in the current runtime path.

…ession test

Addresses PR review feedback (two P2s).

1. Preserve response.url and response.type when recreating the Response
   after reading the body for usage extraction. new Response(bodyText)
   drops url to empty string, which broke create()'s /responses,
   /messages, and Gemini routing — descriptor routes (OpenCode
   /messages, Gemini /models/gemini-*) fell through to the generic
   OpenAI converter and returned the wrong message shape. Restore the
   original metadata via Object.defineProperty (shadowing the read-only
   prototype getter), guarded by try/catch for runtime safety.

2. Fix the flaky 'preserves response body when usage parsing fails'
   test. The original mock threw on the first global JSON.parse call
   and asserted parseCalls > 1, but Bun's native Response.json() does
   not go through JS-level JSON.parse, so parseCalls stayed at 1 and
   the assertion failed. Rewrite to scope the failure to the response
   body text and assert usageParseFailed + content correctness instead,
   which works in both Bun (native Response.json) and Node (undici).

3. Add 'preserves response.url routing metadata after body read' test
   that pins an Anthropic-shaped body behind a /messages URL — fails
   without the url fix (content becomes []), passes with it.
@skyhighbg22-jpg skyhighbg22-jpg requested a review from jatmn June 18, 2026 12:57
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 18, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 18, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the previously discussed paths and do not see any remaining actionable issues from my side.

@kevincodex1 LGTM

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.

2 participants