Commit 32712c6
authored
🤖 feat: Programmatic Tool Calling (PTC) via QuickJS sandbox (#1212)
## Overview
Implements **Programmatic Tool Calling (PTC)** - a `code_execution` tool
that enables AI models to orchestrate multi-tool workflows via
JavaScript code in a sandboxed QuickJS environment. Instead of N
inference round-trips for N tool calls, the model writes code that
executes all tools in a single round-trip.
**Gated behind experiment flags** (disabled by default):
- `PROGRAMMATIC_TOOL_CALLING` - Adds `code_execution` alongside existing
tools
- `PROGRAMMATIC_TOOL_CALLING_EXCLUSIVE` - Replaces all tools not
available within `code_execution` with just `code_execution`
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ code_execution tool │
├─────────────────────────────────────────────────────────────────┤
│ Static Analysis → TypeScript Validation → QuickJS Runtime │
│ (syntax, globals) (type checking) (sandboxed) │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────┴─────────────┐
│ Tool Bridge │
│ mux.bash(), mux.file_read │
│ mux.file_edit_*(), etc. │
└───────────────────────────┘
```
### Key Components
| Component | File | Purpose |
|-----------|------|---------|
| `IJSRuntime` | `ptc/runtime.ts` | Abstract interface for JS runtimes |
| `QuickJSRuntime` | `ptc/quickjsRuntime.ts` | QuickJS-emscripten
implementation with Asyncify |
| `ToolBridge` | `ptc/toolBridge.ts` | Exposes Mux tools under `mux.*`
namespace |
| `staticAnalysis` | `ptc/staticAnalysis.ts` | Pre-execution validation
(syntax, forbidden patterns) |
| `typeGenerator` | `ptc/typeGenerator.ts` | Generates `.d.ts` from Zod
schemas |
| `code_execution` | `tools/code_execution.ts` | Entry point tool
definition |
### Streaming Flow
Nested tool calls stream to the UI in real-time:
```
streamText() calls code_execution.execute()
↓
JS code runs: mux.file_read({...})
→ emit tool-call-start {parentToolCallId: "abc123"}
→ file_read executes
→ emit tool-call-end {parentToolCallId: "abc123", result: ...}
↓
JS code runs: mux.bash({...})
→ emit tool-call-start {parentToolCallId: "abc123"}
→ bash executes
→ emit tool-call-end {parentToolCallId: "abc123", result: ...}
↓
code_execution returns final result
```
The `StreamingMessageAggregator` handles `parentToolCallId` to nest
calls within the parent tool part.
## UI Components
- **CodeExecutionToolCall** - Main container with fieldset layout,
collapsible code/console sections
- **NestedToolRenderer** - Routes nested calls to specialized tool
components (BashToolCall, FileReadToolCall, etc.)
- **ConsoleOutput** - Displays console.log/warn/error output with
appropriate styling
## Security & Resource Limits
| Resource | Limit |
|----------|-------|
| Memory | 64MB |
| Timeout | 5 minutes |
| Sandbox | QuickJS WASM (no fs/net access except via tools) |
**Excluded from bridge:** `code_execution` (prevents recursion),
`ask_user_question`, `propose_plan`, `todo_*`, `status_set`
(UI-specific), provider-native tools (no `execute` function)
## Test Coverage
136 tests across:
- QuickJS runtime (49 tests) - marshaling, async functions, abort,
limits
- Static analysis (33 tests) - syntax, forbidden patterns, unavailable
globals
- Type generation (14 tests) - Zod → `.d.ts` conversion, caching
- Type validation (13 tests) - TypeScript error detection
- code_execution tool (20 tests) - end-to-end execution
- StreamingMessageAggregator (5 nested call tests) - parent/child
handling
## Known Limitations
1. **Sequential execution** - `Promise.all()` runs sequentially due to
Asyncify single-stack limitation
2. **Console output not streamed** - Appears only after code completes
---
_Generated with `mux` • Model: `anthropic:claude-opus-4-5` • Thinking:
`high`_1 parent a15e968 commit 32712c6
File tree
38 files changed
+6027
-102
lines changed- src
- browser
- components
- Messages
- tools
- hooks
- stories
- utils/messages
- common
- constants
- orpc/schemas
- types
- utils/tools
- node/services
- ptc
- tools
38 files changed
+6027
-102
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| |||
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| 53 | + | |
52 | 54 | | |
53 | 55 | | |
54 | 56 | | |
| |||
59 | 61 | | |
60 | 62 | | |
61 | 63 | | |
| 64 | + | |
| 65 | + | |
62 | 66 | | |
63 | 67 | | |
64 | 68 | | |
65 | 69 | | |
66 | 70 | | |
67 | 71 | | |
68 | 72 | | |
| 73 | + | |
69 | 74 | | |
70 | 75 | | |
71 | 76 | | |
| |||
153 | 158 | | |
154 | 159 | | |
155 | 160 | | |
156 | | - | |
157 | 161 | | |
158 | 162 | | |
159 | 163 | | |
| |||
196 | 200 | | |
197 | 201 | | |
198 | 202 | | |
| 203 | + | |
| 204 | + | |
199 | 205 | | |
200 | 206 | | |
201 | 207 | | |
| |||
734 | 740 | | |
735 | 741 | | |
736 | 742 | | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
737 | 753 | | |
738 | 754 | | |
739 | 755 | | |
| |||
746 | 762 | | |
747 | 763 | | |
748 | 764 | | |
| 765 | + | |
| 766 | + | |
749 | 767 | | |
750 | 768 | | |
751 | 769 | | |
| |||
1356 | 1374 | | |
1357 | 1375 | | |
1358 | 1376 | | |
| 1377 | + | |
| 1378 | + | |
1359 | 1379 | | |
1360 | 1380 | | |
1361 | 1381 | | |
| |||
2562 | 2582 | | |
2563 | 2583 | | |
2564 | 2584 | | |
| 2585 | + | |
| 2586 | + | |
2565 | 2587 | | |
2566 | 2588 | | |
2567 | 2589 | | |
| |||
3082 | 3104 | | |
3083 | 3105 | | |
3084 | 3106 | | |
| 3107 | + | |
| 3108 | + | |
| 3109 | + | |
| 3110 | + | |
3085 | 3111 | | |
3086 | 3112 | | |
3087 | 3113 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| 56 | + | |
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
| |||
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
| 93 | + | |
92 | 94 | | |
93 | 95 | | |
94 | 96 | | |
| |||
99 | 101 | | |
100 | 102 | | |
101 | 103 | | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
102 | 107 | | |
103 | 108 | | |
104 | 109 | | |
| |||
193 | 198 | | |
194 | 199 | | |
195 | 200 | | |
196 | | - | |
197 | 201 | | |
198 | 202 | | |
199 | 203 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| |||
136 | 137 | | |
137 | 138 | | |
138 | 139 | | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
139 | 149 | | |
140 | 150 | | |
141 | 151 | | |
| |||
329 | 339 | | |
330 | 340 | | |
331 | 341 | | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
332 | 355 | | |
333 | 356 | | |
334 | 357 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
0 commit comments