langchain-quickjs 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/.gitignore +3 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/CHANGELOG.md +21 -4
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/PKG-INFO +4 -3
- langchain_quickjs-0.3.2/langchain_quickjs/__init__.py +21 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/langchain_quickjs/_format.py +13 -13
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/langchain_quickjs/_prompt.py +21 -14
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/langchain_quickjs/_ptc.py +33 -14
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/langchain_quickjs/_repl.py +153 -94
- langchain_quickjs-0.3.2/langchain_quickjs/_snapshot.py +125 -0
- langchain_quickjs-0.3.2/langchain_quickjs/_subagent.py +351 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/langchain_quickjs/middleware.py +47 -75
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/pyproject.toml +4 -3
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/_common.py +2 -2
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/benchmarks/_common.py +7 -7
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/benchmarks/test_quickjs_memory.py +11 -9
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/benchmarks/test_quickjs_throughput.py +10 -8
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/integration_tests/test_rlm.py +10 -10
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/smoke_tests/snapshots/quickjs_system_prompt_mixed_foreign_functions.md +14 -7
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/smoke_tests/snapshots/quickjs_system_prompt_mixed_foreign_functions_call.md +14 -7
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/smoke_tests/snapshots/quickjs_system_prompt_mixed_foreign_functions_turn.md +14 -7
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/smoke_tests/snapshots/quickjs_system_prompt_no_tools.md +9 -2
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/smoke_tests/snapshots/quickjs_system_prompt_no_tools_call.md +9 -2
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/smoke_tests/snapshots/quickjs_system_prompt_no_tools_turn.md +9 -2
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/test_end_to_end.py +11 -11
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/test_end_to_end_async.py +2 -2
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/test_ptc.py +78 -24
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/test_repl_middleware.py +88 -175
- langchain_quickjs-0.3.2/tests/unit_tests/test_snapshot.py +514 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/test_snapshot_persistence.py +77 -8
- langchain_quickjs-0.3.2/tests/unit_tests/test_subagent_events.py +229 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/test_thread_affinity.py +20 -21
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/uv.lock +65 -13
- langchain_quickjs-0.3.0/langchain_quickjs/__init__.py +0 -19
- langchain_quickjs-0.3.0/langchain_quickjs/_subagent.py +0 -153
- langchain_quickjs-0.3.0/langchain_quickjs/_swarm_task.py +0 -352
- langchain_quickjs-0.3.0/tests/unit_tests/test_swarm_task.py +0 -804
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/LICENSE +0 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/Makefile +0 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/README.md +0 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/langchain_quickjs/py.typed +0 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/__init__.py +0 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/integration_tests/__init__.py +0 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/integration_tests/test_postgres.py +0 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/__init__.py +0 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/smoke_tests/__init__.py +0 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/smoke_tests/conftest.py +0 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/smoke_tests/test_system_prompt.py +0 -0
- {langchain_quickjs-0.3.0 → langchain_quickjs-0.3.2}/tests/unit_tests/test_prompt_modes.py +0 -0
|
@@ -2,17 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
-
## [0.3.
|
|
5
|
+
## [0.3.2](https://github.com/langchain-ai/deepagents/compare/langchain-quickjs==0.3.1...langchain-quickjs==0.3.2) (2026-06-25)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* Dynamic subagents UI ([#4221](https://github.com/langchain-ai/deepagents/issues/4221)) ([10bcba2](https://github.com/langchain-ai/deepagents/commit/10bcba25600e51aba135f170b34aa6315c0f53d6))
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
6
12
|
|
|
13
|
+
* Ensure top-level title on subagent response schemas ([#4155](https://github.com/langchain-ai/deepagents/issues/4155)) ([08f917e](https://github.com/langchain-ai/deepagents/commit/08f917eea79513e7e894731f884e738c2d30383f))
|
|
14
|
+
* Normalize nested `undefined` tool args ([#3935](https://github.com/langchain-ai/deepagents/issues/3935)) ([1b461a0](https://github.com/langchain-ai/deepagents/commit/1b461a0d6c6a7a6d323db18505596fcce4326b92))
|
|
15
|
+
|
|
16
|
+
## [0.3.1](https://github.com/langchain-ai/deepagents/compare/langchain-quickjs==0.3.0...langchain-quickjs==0.3.1) (2026-06-22)
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* Persist top-level JS declarations across evals ([#4147](https://github.com/langchain-ai/deepagents/issues/4147)) ([7574fea](https://github.com/langchain-ai/deepagents/commit/7574fea88b51c77f7afa8279c4f561d6ecb47e3f))
|
|
21
|
+
* PTC tools in tools namespace are rendered without prepended `tools.` in system prompt and task as ptc duplicated task global ([#4075](https://github.com/langchain-ai/deepagents/issues/4075)) ([014a903](https://github.com/langchain-ai/deepagents/commit/014a9033af70b5b8b08ad2eb36f98590f5f1cca8))
|
|
22
|
+
|
|
23
|
+
## [0.3.0](https://github.com/langchain-ai/deepagents/compare/langchain-quickjs==0.2.0...langchain-quickjs==0.3.0) (2026-06-18)
|
|
7
24
|
|
|
8
25
|
### ⚠ BREAKING CHANGES
|
|
9
26
|
|
|
10
|
-
*
|
|
27
|
+
* Upgrade to 0.2.0 quickjs-rs ([#4067](https://github.com/langchain-ai/deepagents/issues/4067))
|
|
11
28
|
|
|
12
29
|
### Features
|
|
13
30
|
|
|
14
|
-
*
|
|
15
|
-
*
|
|
31
|
+
* Prompt tuning on task global ([#4066](https://github.com/langchain-ai/deepagents/issues/4066)) ([a47696f](https://github.com/langchain-ai/deepagents/commit/a47696f6d3e57eccb5ea19fb344305a7995ecc76))
|
|
32
|
+
* Upgrade to 0.2.0 quickjs-rs ([#4067](https://github.com/langchain-ai/deepagents/issues/4067)) ([4ffea88](https://github.com/langchain-ai/deepagents/commit/4ffea88690418207b5e4fa800ee8c1abfa454bec))
|
|
16
33
|
|
|
17
34
|
## [0.2.0](https://github.com/langchain-ai/deepagents/compare/langchain-quickjs==0.1.4...langchain-quickjs==0.2.0) (2026-06-12)
|
|
18
35
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain-quickjs
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: JavaScript REPL middleware for deepagents
|
|
5
5
|
Project-URL: Homepage, https://github.com/langchain-ai/deepagents/tree/main/libs/partners/quickjs
|
|
6
6
|
Project-URL: Repository, https://github.com/langchain-ai/deepagents
|
|
@@ -16,11 +16,12 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
18
|
Requires-Python: <4.0,>=3.11
|
|
19
|
-
Requires-Dist:
|
|
19
|
+
Requires-Dist: bsdiff4<2.0.0,>=1.2.6
|
|
20
|
+
Requires-Dist: deepagents<0.8.0,>=0.6.8
|
|
20
21
|
Requires-Dist: langchain-core<2.0.0,>=1.4.7
|
|
21
22
|
Requires-Dist: langchain<2.0.0,>=1.3.9
|
|
22
23
|
Requires-Dist: langgraph<2.0.0,>=1.2.5
|
|
23
|
-
Requires-Dist: quickjs-rs<0.3.0,>=0.2.
|
|
24
|
+
Requires-Dist: quickjs-rs<0.3.0,>=0.2.3
|
|
24
25
|
Description-Content-Type: text/markdown
|
|
25
26
|
|
|
26
27
|
# langchain-quickjs
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""langchain-quickjs: persistent JS REPL middleware for agents."""
|
|
2
|
+
|
|
3
|
+
from langchain_quickjs._ptc import PTCOption
|
|
4
|
+
from langchain_quickjs._subagent import (
|
|
5
|
+
SUBAGENT_STREAM_EVENT_TYPE,
|
|
6
|
+
SubagentCompleteEvent,
|
|
7
|
+
SubagentErrorEvent,
|
|
8
|
+
SubagentStartEvent,
|
|
9
|
+
SubagentStreamEvent,
|
|
10
|
+
)
|
|
11
|
+
from langchain_quickjs.middleware import CodeInterpreterMiddleware
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"SUBAGENT_STREAM_EVENT_TYPE",
|
|
15
|
+
"CodeInterpreterMiddleware",
|
|
16
|
+
"PTCOption",
|
|
17
|
+
"SubagentCompleteEvent",
|
|
18
|
+
"SubagentErrorEvent",
|
|
19
|
+
"SubagentStartEvent",
|
|
20
|
+
"SubagentStreamEvent",
|
|
21
|
+
]
|
|
@@ -17,7 +17,7 @@ _TRUNCATE_MARKER = "… [truncated {n} chars]"
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def format_handle(handle: Any) -> str:
|
|
20
|
-
"""Describe a
|
|
20
|
+
"""Describe a `Handle` value in REPL-style shorthand."""
|
|
21
21
|
kind = handle.type_of
|
|
22
22
|
if kind == "function":
|
|
23
23
|
try:
|
|
@@ -60,7 +60,7 @@ def _format_jsvalue(value: Any) -> str:
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
def _format_nested(value: Any) -> str:
|
|
63
|
-
"""Like
|
|
63
|
+
"""Like `_format_jsvalue` but quotes nested strings."""
|
|
64
64
|
if isinstance(value, str):
|
|
65
65
|
return f'"{value}"'
|
|
66
66
|
return _format_jsvalue(value)
|
|
@@ -84,8 +84,8 @@ def coerce_tool_output(value: Any) -> str:
|
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
# Scalar types the quickjs_rs binding marshals natively. Compound shapes
|
|
87
|
-
# (
|
|
88
|
-
#
|
|
87
|
+
# (`dict` / `list` / `tuple`) are walked recursively in
|
|
88
|
+
# `_coerce_for_marshal`; anything else becomes `str(value)` so the JS
|
|
89
89
|
# side can still see a usable value.
|
|
90
90
|
_NATIVE_JS_SCALARS = (str, bool, int, float, type(None))
|
|
91
91
|
|
|
@@ -93,18 +93,18 @@ _NATIVE_JS_SCALARS = (str, bool, int, float, type(None))
|
|
|
93
93
|
def coerce_tool_output_for_ptc(value: Any) -> Any:
|
|
94
94
|
"""Coerce a tool result for the PTC bridge, preserving native types.
|
|
95
95
|
|
|
96
|
-
The quickjs_rs
|
|
97
|
-
and
|
|
98
|
-
without an explicit
|
|
99
|
-
|
|
96
|
+
The quickjs_rs `register` bridge marshals Python primitives, `list`,
|
|
97
|
+
and `dict` directly to native JS values, so the model can use them
|
|
98
|
+
without an explicit `JSON.parse`. This helper unwraps LangChain's
|
|
99
|
+
`ToolMessage` / `Command` envelopes (matching `coerce_tool_output`'s
|
|
100
100
|
selection rules) and returns the underlying value typed.
|
|
101
101
|
|
|
102
102
|
Compound returns are walked recursively: nested values that the binding
|
|
103
|
-
cannot marshal natively (
|
|
104
|
-
are stringified in place via
|
|
103
|
+
cannot marshal natively (`datetime`, Pydantic models, custom classes)
|
|
104
|
+
are stringified in place via `str(value)` so the surrounding object
|
|
105
105
|
structure remains navigable from JS. Cyclic structures hit Python's
|
|
106
106
|
recursion limit and surface as a host error in the eval — same outcome
|
|
107
|
-
as
|
|
107
|
+
as `json.dumps` on a self-referencing dict.
|
|
108
108
|
"""
|
|
109
109
|
if isinstance(value, Command):
|
|
110
110
|
return coerce_tool_output_for_ptc(_extract_command_content(value))
|
|
@@ -129,14 +129,14 @@ def _coerce_for_marshal(value: Any) -> Any:
|
|
|
129
129
|
return [_coerce_for_marshal(v) for v in value]
|
|
130
130
|
# Pydantic models are dumped to a plain dict so the JS side sees the
|
|
131
131
|
# field shape its return-type signature advertises (rather than
|
|
132
|
-
#
|
|
132
|
+
# `str(model)`). Nested models / datetimes are handled by recursion.
|
|
133
133
|
if isinstance(value, BaseModel):
|
|
134
134
|
return _coerce_for_marshal(value.model_dump())
|
|
135
135
|
return str(value)
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
def _extract_command_content(command: Command) -> Any:
|
|
139
|
-
"""Return the trailing message content from a
|
|
139
|
+
"""Return the trailing message content from a `Command` update, if any."""
|
|
140
140
|
update = command.update
|
|
141
141
|
if isinstance(update, dict):
|
|
142
142
|
messages = update.get("messages")
|
|
@@ -45,6 +45,7 @@ flow, and synthesis - in plain JavaScript.
|
|
|
45
45
|
await task({
|
|
46
46
|
description, // full autonomous task prompt
|
|
47
47
|
subagentType, // configured subagent name
|
|
48
|
+
label, // optional short UI label for this dispatch
|
|
48
49
|
responseSchema, // optional JSON Schema for structured output
|
|
49
50
|
}); // -> Promise<unknown>
|
|
50
51
|
```
|
|
@@ -62,6 +63,10 @@ exploring, still pass its path and let the subagent read it; do not paste back
|
|
|
62
63
|
what you read. Each dispatch is stateless from the caller's perspective; you
|
|
63
64
|
cannot send follow-up messages to the same subagent run.
|
|
64
65
|
|
|
66
|
+
`label` is optional: when provided, it is shown in the live progress UI
|
|
67
|
+
instead of the default description-derived fallback. It is not sent to the
|
|
68
|
+
subagent and does not affect execution.
|
|
69
|
+
|
|
65
70
|
`responseSchema` is optional, but set it on any dispatch whose result feeds
|
|
66
71
|
later code. A deterministic, typed shape is what lets you compose the next
|
|
67
72
|
stage reliably — index it, sort it, compare fields, branch on it, merge it —
|
|
@@ -236,8 +241,10 @@ const findings = auditResults.flatMap((r) =>
|
|
|
236
241
|
r.findings.map((f) => ({ ...f, file: r.file }))
|
|
237
242
|
);
|
|
238
243
|
const verified = await Promise.all(findings.map((f) =>
|
|
239
|
-
task({
|
|
240
|
-
|
|
244
|
+
task({
|
|
245
|
+
description: "Verify this finding: " + f.evidence,
|
|
246
|
+
subagentType: "verifier",
|
|
247
|
+
}).then((v) => ({ ...f, ...v }))
|
|
241
248
|
));
|
|
242
249
|
```
|
|
243
250
|
|
|
@@ -260,10 +267,10 @@ def render_repl_system_prompt(
|
|
|
260
267
|
mode: Literal["thread", "turn", "call"],
|
|
261
268
|
ptc_attached: bool = False,
|
|
262
269
|
) -> str:
|
|
263
|
-
"""Render the base REPL system prompt text for
|
|
270
|
+
"""Render the base REPL system prompt text for `CodeInterpreterMiddleware`.
|
|
264
271
|
|
|
265
|
-
|
|
266
|
-
tools are exposed as the
|
|
272
|
+
`ptc_attached` controls the "external side effects" bullet: when host
|
|
273
|
+
tools are exposed as the `tools.*` namespace it points the model at the
|
|
267
274
|
API reference; otherwise it states the REPL is pure computation.
|
|
268
275
|
"""
|
|
269
276
|
if ptc_attached:
|
|
@@ -362,7 +369,7 @@ def render_eval_tool_description(*, mode: Literal["thread", "turn", "call"]) ->
|
|
|
362
369
|
|
|
363
370
|
|
|
364
371
|
def to_camel_case(name: str) -> str:
|
|
365
|
-
"""Convert
|
|
372
|
+
"""Convert `snake_case` / `kebab-case` → `camelCase`."""
|
|
366
373
|
return _CAMEL_SEP.sub(lambda m: m.group(1).upper(), name)
|
|
367
374
|
|
|
368
375
|
|
|
@@ -455,7 +462,7 @@ def _render_signature(
|
|
|
455
462
|
) -> str:
|
|
456
463
|
return_clause = f"Promise<{return_type}>"
|
|
457
464
|
default_signature = (
|
|
458
|
-
f"
|
|
465
|
+
f"tools.{fn_name}(input: Record<string, unknown>): {return_clause}"
|
|
459
466
|
)
|
|
460
467
|
if not schema or not isinstance(schema.get("properties"), dict):
|
|
461
468
|
return default_signature
|
|
@@ -471,20 +478,20 @@ def _render_signature(
|
|
|
471
478
|
body = "\n".join(fields) if fields else ""
|
|
472
479
|
if not body:
|
|
473
480
|
return default_signature
|
|
474
|
-
return f"
|
|
481
|
+
return f"tools.{fn_name}(input: {{\n{body}\n}}): {return_clause}"
|
|
475
482
|
|
|
476
483
|
|
|
477
484
|
# Return types come from the tool's underlying function annotation. We feed
|
|
478
|
-
# the annotation through
|
|
479
|
-
# render it through the same
|
|
480
|
-
# Compound shapes (TypedDict, BaseModel, recursive types) end up as
|
|
481
|
-
# in the schema and currently render as
|
|
482
|
-
# nested-model input args. Until that path resolves
|
|
485
|
+
# the annotation through `pydantic.TypeAdapter` to get a JSON Schema and
|
|
486
|
+
# render it through the same `_json_schema_to_ts` we use for input args.
|
|
487
|
+
# Compound shapes (TypedDict, BaseModel, recursive types) end up as `$ref`
|
|
488
|
+
# in the schema and currently render as `unknown` — same behaviour as
|
|
489
|
+
# nested-model input args. Until that path resolves `$ref` / `$defs`,
|
|
483
490
|
# the simpler unified renderer is the right trade-off here.
|
|
484
491
|
|
|
485
492
|
|
|
486
493
|
def _render_return_type(tool: BaseTool) -> str:
|
|
487
|
-
"""Render the return annotation as a TS type, defaulting to
|
|
494
|
+
"""Render the return annotation as a TS type, defaulting to `unknown`."""
|
|
488
495
|
target = getattr(tool, "func", None) or getattr(tool, "coroutine", None)
|
|
489
496
|
if target is None:
|
|
490
497
|
return "unknown"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"""Programmatic tool calling (PTC) support for
|
|
1
|
+
"""Programmatic tool calling (PTC) support for `CodeInterpreterMiddleware`.
|
|
2
2
|
|
|
3
3
|
PTC exposes the agent's LangChain tools inside the JavaScript REPL as
|
|
4
|
-
|
|
5
|
-
N serial tool calls, the model writes one
|
|
4
|
+
`tools.<camelCaseName>(input)` async functions. Instead of issuing
|
|
5
|
+
N serial tool calls, the model writes one `eval` that loops / parallelises
|
|
6
6
|
/ chains tools in-code:
|
|
7
7
|
|
|
8
8
|
const [a, b] = await Promise.all([
|
|
@@ -14,10 +14,10 @@ Two pieces live here:
|
|
|
14
14
|
|
|
15
15
|
- filtering — turn the live agent toolset into the subset exposed to PTC
|
|
16
16
|
- prompt rendering — render a short TS-ish API-reference block describing
|
|
17
|
-
|
|
17
|
+
each exposed tool, so the model knows the call shape
|
|
18
18
|
|
|
19
19
|
The host-function bridge that actually invokes each tool lives in
|
|
20
|
-
|
|
20
|
+
`_repl.py` next to the rest of the context wiring.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
from __future__ import annotations
|
|
@@ -34,6 +34,16 @@ if TYPE_CHECKING:
|
|
|
34
34
|
|
|
35
35
|
PTCOption = list[str | BaseTool]
|
|
36
36
|
|
|
37
|
+
_RESERVED_SUBAGENT_TASK_NAME = "task"
|
|
38
|
+
|
|
39
|
+
_TASK_IN_PTC_MSG = (
|
|
40
|
+
"The subagent `task` tool cannot be exposed via `ptc`. It is always "
|
|
41
|
+
"available as the top-level `task()` global inside the REPL (with "
|
|
42
|
+
"`subagentType`, `label`, and `responseSchema` support); exposing it through "
|
|
43
|
+
"the `tools.*` namespace would create a second, conflicting dispatch path "
|
|
44
|
+
'that drops `responseSchema`. Remove "task" from `ptc`.'
|
|
45
|
+
)
|
|
46
|
+
|
|
37
47
|
|
|
38
48
|
def filter_tools_for_ptc(
|
|
39
49
|
tools: Sequence[BaseTool],
|
|
@@ -41,23 +51,28 @@ def filter_tools_for_ptc(
|
|
|
41
51
|
*,
|
|
42
52
|
self_tool_name: str,
|
|
43
53
|
) -> list[BaseTool]:
|
|
44
|
-
"""Return the subset of
|
|
54
|
+
"""Return the subset of `tools` exposed inside the REPL.
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
to prevent the model from recursing
|
|
56
|
+
`self_tool_name` is the REPL's own tool name; it is *always* excluded
|
|
57
|
+
to prevent the model from recursing `tools.eval("tools.eval(...)")`.
|
|
48
58
|
If the model wants a nested eval, it can just write nested code in one
|
|
49
59
|
call — that's the whole point of PTC.
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
`config` is allowlist-only:
|
|
52
62
|
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
|
|
63
|
+
- `str` entries: expose matching tool names from `tools`.
|
|
64
|
+
- `BaseTool` entries: expose those tools directly (minus
|
|
65
|
+
`self_tool_name`).
|
|
56
66
|
|
|
57
|
-
Mixed lists are supported and merged. Explicit
|
|
67
|
+
Mixed lists are supported and merged. Explicit `BaseTool` entries
|
|
58
68
|
are included first, then name-matched agent tools are appended.
|
|
59
69
|
Duplicate tool names are deduplicated.
|
|
60
70
|
|
|
71
|
+
The subagent `task` tool is reserved and may not appear in `config`
|
|
72
|
+
(by name or instance) — it is always available as the `task()` global,
|
|
73
|
+
so a `tools.task` PTC variant would be a conflicting, degraded duplicate.
|
|
74
|
+
A `"task"` entry raises `ValueError`.
|
|
75
|
+
|
|
61
76
|
Warning:
|
|
62
77
|
PTC tool calls execute through the REPL bridge and currently do
|
|
63
78
|
not respect `interrupt_on` / HITL approval hooks for each
|
|
@@ -68,10 +83,14 @@ def filter_tools_for_ptc(
|
|
|
68
83
|
allow_names: set[str] = set()
|
|
69
84
|
for entry in config:
|
|
70
85
|
if isinstance(entry, BaseTool):
|
|
86
|
+
if entry.name == _RESERVED_SUBAGENT_TASK_NAME:
|
|
87
|
+
raise ValueError(_TASK_IN_PTC_MSG)
|
|
71
88
|
if entry.name != self_tool_name:
|
|
72
89
|
explicit_tools.append(entry)
|
|
73
90
|
continue
|
|
74
91
|
if isinstance(entry, str):
|
|
92
|
+
if entry == _RESERVED_SUBAGENT_TASK_NAME:
|
|
93
|
+
raise ValueError(_TASK_IN_PTC_MSG)
|
|
75
94
|
allow_names.add(entry)
|
|
76
95
|
continue
|
|
77
96
|
msg = "ptc list entries must be str or BaseTool"
|
|
@@ -98,7 +117,7 @@ def filter_tools_for_ptc(
|
|
|
98
117
|
|
|
99
118
|
|
|
100
119
|
def to_camel_case(name: str) -> str:
|
|
101
|
-
"""Convert
|
|
120
|
+
"""Convert `snake_case` / `kebab-case` → `camelCase`."""
|
|
102
121
|
return _prompt.to_camel_case(name)
|
|
103
122
|
|
|
104
123
|
|