[PR #293] [MERGED] fix: update various issues in QuickJS REPL #333

Closed
opened 2026-06-05 17:22:42 -04:00 by yindo · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/langchain-ai/deepagentsjs/pull/293
Author: @maahir30
Created: 3/10/2026
Status: Merged
Merged: 3/11/2026
Merged by: @hntrl

Base: mainHead: quickjs-bugs


📝 Commits (6)

📊 Changes

5 files changed (+233 additions, -24 deletions)

View changed files

📝 libs/providers/quickjs/src/middleware.int.test.ts (+56 -1)
📝 libs/providers/quickjs/src/middleware.ts (+1 -0)
📝 libs/providers/quickjs/src/session.test.ts (+85 -0)
📝 libs/providers/quickjs/src/transform.test.ts (+49 -0)
📝 libs/providers/quickjs/src/transform.ts (+42 -23)

📄 Description

Summary

Fixes three bugs in the QuickJS REPL transformer discovered via structured-output eval traces. In the eval runs, these caused 4 unnecessary retries (~5K wasted tokens) from semicolon errors, and ~600 tokens of data re-embedding per cell.

Semicolons in auto-return cause SyntaxError

  • The transformer wraps the last expression in return (...) to capture its value
  • It was using the ExpressionStatement node range, which includes the trailing semicolon
  • So expr; became return (expr;) — a syntax error inside the parens
  • The LLM had to waste 4 retries discovering that removing all semicolons was the workaround
  • Fixed by using two different AST positions for the wrapping:
    • ExpressionStatement.start for the opening return ( — preserves any grouping parens like ({...})
    • The inner expression.end for the closing ) — sits before the semicolon in the AST
  • Now expr; becomes return (expr); — the semicolon stays outside the parens

TypeScript not stripped from variable initializers

  • The transformer strips TS in two phases: Phase 1 hoists variable declarations to globalThis, Phase 2 walks all other statements and strips TS annotations
  • Phase 2 explicitly skips VariableDeclaration nodes, assuming Phase 1 handles them
  • Phase 1 delegates to extractCleanInit, which was supposed to walk the init AST subtree and strip TS — but it only sliced the raw source text and returned it untouched
  • So const data = JSON.parse(raw) as { n: number } would emit the as { n: number } into QuickJS verbatim
  • Fixed by adding extractCleanSource:
    • Slices the init source into a separate MagicString copy
    • Walks the AST subtree over it, stripping all TS annotations
    • Adjusts every removal by an offset (node.start) to convert between absolute AST positions and the sliced MagicString's zero-based positions
  • The same helper is also used for destructuring patterns, which had the same gap

LLM re-embeds data instead of referencing REPL state

  • After a Promise.all cell that stored results in a variable like parsed, the LLM was copying the entire JSON response (~600 tokens) into the next js_eval call instead of just writing parsed.map(...)
  • Variables persist across cells via globalThis, but nothing reminded the LLM of that at decision time
  • Fixed with two reinforcements:
    • A system prompt hard rule: "reference variables by name instead of re-embedding data as inline JSON literals"
    • A state hint appended to every tool response listing which variables are available in the next cell (↳ State: results, parsed, cities (available in next cell))
  • The transformer now collects declared names during hoisting, the session accumulates them across evals, and formatReplResult includes them in the output

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/langchain-ai/deepagentsjs/pull/293 **Author:** [@maahir30](https://github.com/maahir30) **Created:** 3/10/2026 **Status:** ✅ Merged **Merged:** 3/11/2026 **Merged by:** [@hntrl](https://github.com/hntrl) **Base:** `main` ← **Head:** `quickjs-bugs` --- ### 📝 Commits (6) - [`f3d4efd`](https://github.com/langchain-ai/deepagentsjs/commit/f3d4efd1ae61d6893839d9358e666c55a64a836d) fix 3 bugs - [`04e1af4`](https://github.com/langchain-ai/deepagentsjs/commit/04e1af4877b053b39e9638f86986a3b8fccae750) fix bugs - [`c5c642b`](https://github.com/langchain-ai/deepagentsjs/commit/c5c642b6074e03032186da945f4e416a6b3db3a5) Merge branch 'main' into quickjs-bugs - [`3f06087`](https://github.com/langchain-ai/deepagentsjs/commit/3f06087416b021e00cfb463c4f7ded6dfa496dfb) remove state hint - [`f53412e`](https://github.com/langchain-ai/deepagentsjs/commit/f53412e96778cb664ee482a0c4ffec8c67663683) update - [`449e7fb`](https://github.com/langchain-ai/deepagentsjs/commit/449e7fbb5f7310cbf1c1e132b0226472b65f7e57) cr ### 📊 Changes **5 files changed** (+233 additions, -24 deletions) <details> <summary>View changed files</summary> 📝 `libs/providers/quickjs/src/middleware.int.test.ts` (+56 -1) 📝 `libs/providers/quickjs/src/middleware.ts` (+1 -0) 📝 `libs/providers/quickjs/src/session.test.ts` (+85 -0) 📝 `libs/providers/quickjs/src/transform.test.ts` (+49 -0) 📝 `libs/providers/quickjs/src/transform.ts` (+42 -23) </details> ### 📄 Description ## Summary Fixes three bugs in the QuickJS REPL transformer discovered via structured-output eval traces. In the eval runs, these caused 4 unnecessary retries (~5K wasted tokens) from semicolon errors, and ~600 tokens of data re-embedding per cell. ### Semicolons in auto-return cause SyntaxError - The transformer wraps the last expression in `return (...)` to capture its value - It was using the `ExpressionStatement` node range, which includes the trailing semicolon - So `expr;` became `return (expr;)` — a syntax error inside the parens - The LLM had to waste 4 retries discovering that removing all semicolons was the workaround - Fixed by using two different AST positions for the wrapping: - `ExpressionStatement.start` for the opening `return (` — preserves any grouping parens like `({...})` - The inner `expression.end` for the closing `)` — sits before the semicolon in the AST - Now `expr;` becomes `return (expr);` — the semicolon stays outside the parens ### TypeScript not stripped from variable initializers - The transformer strips TS in two phases: Phase 1 hoists variable declarations to `globalThis`, Phase 2 walks all other statements and strips TS annotations - Phase 2 explicitly skips `VariableDeclaration` nodes, assuming Phase 1 handles them - Phase 1 delegates to `extractCleanInit`, which was supposed to walk the init AST subtree and strip TS — but it only sliced the raw source text and returned it untouched - So `const data = JSON.parse(raw) as { n: number }` would emit the `as { n: number }` into QuickJS verbatim - Fixed by adding `extractCleanSource`: - Slices the init source into a separate MagicString copy - Walks the AST subtree over it, stripping all TS annotations - Adjusts every removal by an offset (`node.start`) to convert between absolute AST positions and the sliced MagicString's zero-based positions - The same helper is also used for destructuring patterns, which had the same gap ### LLM re-embeds data instead of referencing REPL state - After a `Promise.all` cell that stored results in a variable like `parsed`, the LLM was copying the entire JSON response (~600 tokens) into the next `js_eval` call instead of just writing `parsed.map(...)` - Variables persist across cells via `globalThis`, but nothing reminded the LLM of that at decision time - Fixed with two reinforcements: - A system prompt hard rule: "reference variables by name instead of re-embedding data as inline JSON literals" - A state hint appended to every tool response listing which variables are available in the next cell (`↳ State: results, parsed, cities (available in next cell)`) - The transformer now collects declared names during hoisting, the session accumulates them across evals, and `formatReplResult` includes them in the output --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
yindo added the pull-request label 2026-06-05 17:22:42 -04:00
yindo closed this issue 2026-06-05 17:22:42 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/deepagentsjs#333