mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-22 01:35:28 -04:00
fix(quickjs): persist top-level JS declarations across evals (#4147)
Updates `langchain-quickjs` to require `quickjs-rs` 0.2.3 so the REPL can use the upstream runtime-level source transform for top-level declarations. The QuickJS runtime now enables `SourceTransform.TOP_LEVEL_CONST_TO_VAR`, keeping top-level `const` and `let` bindings visible across consecutive eval calls and matching the persistence behavior promised to agents. Changes: - Bumps `quickjs-rs` to 0.2.3 in `langchain-quickjs` and refreshes dependent lockfiles for the code and evals packages. - Configures `_Registry`-owned `Runtime` instances with `SourceTransform.TOP_LEVEL_CONST_TO_VAR`. - Adds sync and async regression coverage for same-turn eval persistence of top-level declarations.
This commit is contained in:
@@ -27,6 +27,7 @@ from quickjs_rs import (
|
||||
MemoryLimitError,
|
||||
Runtime,
|
||||
Snapshot,
|
||||
SourceTransform,
|
||||
ThreadWorker,
|
||||
)
|
||||
from quickjs_rs import (
|
||||
@@ -986,7 +987,10 @@ class _Registry:
|
||||
slot.worker.close()
|
||||
|
||||
async def _acreate_runtime(self) -> Runtime:
|
||||
return Runtime(memory_limit=self.memory_limit)
|
||||
return Runtime(
|
||||
memory_limit=self.memory_limit,
|
||||
transform_flags=SourceTransform.TOP_LEVEL_CONST_TO_VAR,
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
with self._lock:
|
||||
|
||||
@@ -24,7 +24,7 @@ keywords = ["deepagents", "langchain", "langgraph", "repl", "javascript", "quick
|
||||
|
||||
dependencies = [
|
||||
"deepagents>=0.6.8,<0.7.0",
|
||||
"quickjs-rs>=0.2.0,<0.3.0",
|
||||
"quickjs-rs>=0.2.3,<0.3.0",
|
||||
"langchain>=1.3.9,<2.0.0",
|
||||
"langchain-core>=1.4.7,<2.0.0",
|
||||
"langgraph>=1.2.5,<2.0.0",
|
||||
|
||||
@@ -233,3 +233,72 @@ async def test_repl_snapshot_persists_top_level_await_binding_between_turns(
|
||||
second_eval = _eval_tool_message(second)
|
||||
assert "<error" not in second_eval.content, second_eval.content
|
||||
assert "<result>hi</result>" in second_eval.content, second_eval.content
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invoke_mode",
|
||||
["invoke", "ainvoke"],
|
||||
ids=["sync_invoke", "async_ainvoke"],
|
||||
)
|
||||
async def test_repl_top_level_const_let_persist_across_evals_same_turn(
|
||||
invoke_mode: InvokeMode,
|
||||
) -> None:
|
||||
"""Top-level ``const``/``let`` bindings persist between evals.
|
||||
|
||||
The runtime is configured with
|
||||
``SourceTransform.TOP_LEVEL_CONST_TO_VAR`` so top-level lexical
|
||||
declarations are rewritten to ``var`` and survive on the global object.
|
||||
Without it, a second eval on the same live context would raise
|
||||
``ReferenceError`` because lexical bindings are dropped between evals.
|
||||
|
||||
This exercises the in-turn path (two evals, one context, no snapshot
|
||||
round-trip in between), which is what the bare-``const`` REPL
|
||||
persistence model promises.
|
||||
"""
|
||||
script = [
|
||||
AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"name": "eval",
|
||||
"args": {"code": "const greeting = 'hello'; let count = 41;"},
|
||||
"id": "call_1",
|
||||
"type": "tool_call",
|
||||
},
|
||||
],
|
||||
),
|
||||
AIMessage(
|
||||
content="",
|
||||
tool_calls=[
|
||||
{
|
||||
"name": "eval",
|
||||
"args": {"code": "greeting + (count + 1)"},
|
||||
"id": "call_2",
|
||||
"type": "tool_call",
|
||||
},
|
||||
],
|
||||
),
|
||||
AIMessage(content="done"),
|
||||
]
|
||||
agent = create_deep_agent(
|
||||
model=FakeChatModel(messages=iter(script)),
|
||||
middleware=[CodeInterpreterMiddleware()],
|
||||
checkpointer=InMemorySaver(),
|
||||
)
|
||||
config = {"configurable": {"thread_id": "quickjs-const-let-thread"}}
|
||||
|
||||
result = await _invoke_agent(
|
||||
agent,
|
||||
{"messages": [HumanMessage(content="declare then read const/let")]},
|
||||
config,
|
||||
invoke_mode,
|
||||
)
|
||||
eval_messages = [
|
||||
m for m in result["messages"] if isinstance(m, ToolMessage) and m.name == "eval"
|
||||
]
|
||||
assert len(eval_messages) == 2, eval_messages
|
||||
for msg in eval_messages:
|
||||
assert "<error" not in msg.content, msg.content
|
||||
assert "<result>hello42</result>" in eval_messages[-1].content, eval_messages[
|
||||
-1
|
||||
].content
|
||||
|
||||
Generated
+4
-4
@@ -975,7 +975,7 @@ requires-dist = [
|
||||
{ name = "langchain", specifier = ">=1.3.9,<2.0.0" },
|
||||
{ name = "langchain-core", specifier = ">=1.4.7,<2.0.0" },
|
||||
{ name = "langgraph", specifier = ">=1.2.5,<2.0.0" },
|
||||
{ name = "quickjs-rs", specifier = ">=0.2.0,<0.3.0" },
|
||||
{ name = "quickjs-rs", specifier = ">=0.2.3,<0.3.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
@@ -1779,14 +1779,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "quickjs-rs"
|
||||
version = "0.2.0"
|
||||
version = "0.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "wasmtime" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3f/59/263e19bcff54b7e7993de3f79da25c23b7d7296d84c13d31a3e38ea0ec7f/quickjs_rs-0.2.0.tar.gz", hash = "sha256:db1a8d72ddfc0e766772622adb510f91aa8626d87afd843893c1e2a360ee1ce8", size = 483392, upload-time = "2026-06-18T00:08:53.928Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/d5/3657636d9cdff1b2e76e17c210a52704158f13149e7f8175cb533bb64ca2/quickjs_rs-0.2.3.tar.gz", hash = "sha256:5715bb3b6c7583999a4f053a4547fb149c70a9ef0962aa93884cbfb89fb5a471", size = 829047, upload-time = "2026-06-22T20:40:56.372Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/22/7caf4552a7880f205c95099c3c3e6d36f146af4e34a0b3baa12d49c47ea1/quickjs_rs-0.2.0-py3-none-any.whl", hash = "sha256:cb661d40ee3ba6d3673a75729223315fd4f0b22087373156425fa4227fa2adc6", size = 454517, upload-time = "2026-06-18T00:08:52.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/fe/637b4128ee76febedc18350329099a764024736eafa79b72635b928a7b80/quickjs_rs-0.2.3-py3-none-any.whl", hash = "sha256:52879678d62dea10f7f30c5821f217a75a2c6d51a3f4c3059d0eacf319b9a889", size = 799751, upload-time = "2026-06-22T20:40:54.697Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user