@mastra/quickjs 0.0.0-buttoned-spandex-20260824090603
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.
- package/CHANGELOG.md +70 -0
- package/LICENSE.md +30 -0
- package/dist/index.cjs +437 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +413 -0
- package/dist/index.js.map +1 -0
- package/dist/transport.d.ts +90 -0
- package/dist/transport.d.ts.map +1 -0
- package/package.json +67 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @mastra/quickjs
|
|
2
|
+
|
|
3
|
+
## 0.0.0-buttoned-spandex-20260824090603
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`79f04a7`](https://github.com/mastra-ai/mastra/commit/79f04a7f6c6829da541139f638f2f1d267916e08), [`65edab1`](https://github.com/mastra-ai/mastra/commit/65edab1c233d17b8f163bad12fca410d0e6f16b1), [`1e47b75`](https://github.com/mastra-ai/mastra/commit/1e47b7520cab4cfaa8daed52f17e2e6d14ff7539), [`ab20a38`](https://github.com/mastra-ai/mastra/commit/ab20a38d0275f8d85e0f3833bd87ef487bcc609f), [`fd4d5fe`](https://github.com/mastra-ai/mastra/commit/fd4d5fe4f943699b85db5e74404f190d5a6b8c2a), [`2c85f42`](https://github.com/mastra-ai/mastra/commit/2c85f428e04ccd63ea31a7ec80b5b327afdad555), [`11bbeb9`](https://github.com/mastra-ai/mastra/commit/11bbeb9b108ef2264e05acefc6dafb9cbb342921), [`1a485f3`](https://github.com/mastra-ai/mastra/commit/1a485f3538f5ec64d58bd8b5e1e99de0c695c87b), [`0d37487`](https://github.com/mastra-ai/mastra/commit/0d37487d9f349388a3f1cef6a536cf9dcc4b6273), [`8661d7d`](https://github.com/mastra-ai/mastra/commit/8661d7d7179f0a024456aabdd8679bcecd09ac28), [`dbbfeb8`](https://github.com/mastra-ai/mastra/commit/dbbfeb85ec949dc9ebc0755e1ad262e4f5eba8db), [`575e343`](https://github.com/mastra-ai/mastra/commit/575e343900451021d96110916497d334af7bc252), [`3cc9d00`](https://github.com/mastra-ai/mastra/commit/3cc9d00b2b4333e0377a5e9df5eff92c17ce7630), [`cacb839`](https://github.com/mastra-ai/mastra/commit/cacb8392d9e74189b56d857290b0615f98a2683d), [`c8e4cea`](https://github.com/mastra-ai/mastra/commit/c8e4ceac9a390d78c8327dff3cdb2861dd71957f), [`ed01e9a`](https://github.com/mastra-ai/mastra/commit/ed01e9a807514a904374bf687a7b8f18750f6f78), [`b47b26e`](https://github.com/mastra-ai/mastra/commit/b47b26e6fe95cb8a3482be2c5e52de157fe59d0b), [`0d37487`](https://github.com/mastra-ai/mastra/commit/0d37487d9f349388a3f1cef6a536cf9dcc4b6273), [`733a537`](https://github.com/mastra-ai/mastra/commit/733a537489a858b5880b2e98809334fba895a221), [`9207dfa`](https://github.com/mastra-ai/mastra/commit/9207dfab8062e5fc68b751684797ff86fe0b4e70), [`e737014`](https://github.com/mastra-ai/mastra/commit/e737014e0fc7035759762bb5b48baef1d6c0f6a7), [`f591643`](https://github.com/mastra-ai/mastra/commit/f591643becdf0be9bddce6ba1748e64bc30d77f1), [`b1ad324`](https://github.com/mastra-ai/mastra/commit/b1ad324d657f3544b0701332aef7eb10e9a36258), [`61c566d`](https://github.com/mastra-ai/mastra/commit/61c566dd2f2cde2b23ed8f139924e530d4202214), [`12c61d2`](https://github.com/mastra-ai/mastra/commit/12c61d280c8cb208bc3c8dbcbe5dcc60cf9d1cd0), [`c46eb09`](https://github.com/mastra-ai/mastra/commit/c46eb09ce4987509af57a0ac582c61241a6dd2f1), [`d6ce34a`](https://github.com/mastra-ai/mastra/commit/d6ce34aeceb06ddf3d595a1eed5cc74f481a46a1), [`f95f468`](https://github.com/mastra-ai/mastra/commit/f95f468cf1e7c2b924a13826494f98b8f2ccd581), [`30ed33e`](https://github.com/mastra-ai/mastra/commit/30ed33ee14084a26019aba15fceadda6d6ddefaf), [`9a12ef3`](https://github.com/mastra-ai/mastra/commit/9a12ef3fccf3f4186db0f294f4ee1f02cf4d8db2), [`91ad69d`](https://github.com/mastra-ai/mastra/commit/91ad69d64994c89199b0c55399e64ed91c61df2f), [`8dc408d`](https://github.com/mastra-ai/mastra/commit/8dc408d34438f9e13297f792c11a5cfd6cf952e1), [`c92def1`](https://github.com/mastra-ai/mastra/commit/c92def10a13c822972c96f0a4ca6ffc1f4258aed), [`63041eb`](https://github.com/mastra-ai/mastra/commit/63041eb4c50b520a0a80e03d4cd6ea99f67715a0), [`c5eaec5`](https://github.com/mastra-ai/mastra/commit/c5eaec5a860d80d0e3805e67db0414b87ac8cbed), [`e6f8450`](https://github.com/mastra-ai/mastra/commit/e6f845074d478527026b18d85031b23353e1d0a4), [`e66b2ba`](https://github.com/mastra-ai/mastra/commit/e66b2ba100db63eaeab6e21e1ea34b113f2ec781)]:
|
|
8
|
+
- @mastra/core@0.0.0-buttoned-spandex-20260824090603
|
|
9
|
+
|
|
10
|
+
## 0.1.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- Added `@mastra/quickjs`, a Code Mode transport that runs model-authored programs in an in-process QuickJS runtime compiled to WebAssembly. ([#21089](https://github.com/mastra-ai/mastra/pull/21089))
|
|
15
|
+
|
|
16
|
+
**Why**
|
|
17
|
+
|
|
18
|
+
Running Code Mode in-process previously meant `@mastra/isolated-vm`, which installs a native addon and requires the host process to start with `--no-node-snapshot`. Serverless platforms usually allow neither, leaving a full workspace sandbox as the only option. This transport needs neither, so Code Mode runs on those hosts.
|
|
19
|
+
|
|
20
|
+
**Usage**
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { createCodeMode } from '@mastra/core/tools';
|
|
24
|
+
import { QuickJsCodeModeTransport } from '@mastra/quickjs';
|
|
25
|
+
|
|
26
|
+
const { tool, instructions } = createCodeMode(
|
|
27
|
+
{ tools: { getTopProducts, getProductRatings } },
|
|
28
|
+
new QuickJsCodeModeTransport({ memoryLimitMb: 128 }),
|
|
29
|
+
);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Programs get the same boundary as the isolated-vm transport: no filesystem, network, process, or module access, and the allow-listed tools as their only capability. They run slower than in a V8 isolate, which affects compute-heavy code far more than code that awaits tool calls.
|
|
33
|
+
|
|
34
|
+
Closes https://github.com/mastra-ai/mastra/issues/20546
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [[`e7109ee`](https://github.com/mastra-ai/mastra/commit/e7109ee6f731bacc79c885906f3c7dca8d8f013a), [`b8ce7ec`](https://github.com/mastra-ai/mastra/commit/b8ce7ec96e39343c6c2f36d12d68a9ad816c09f7), [`2e4624e`](https://github.com/mastra-ai/mastra/commit/2e4624edb6917e61249cb60ee377735e7af7e4a9), [`45a9147`](https://github.com/mastra-ai/mastra/commit/45a914741f578754d79d8b7de7b4e4f304d8e14a), [`a3a3624`](https://github.com/mastra-ai/mastra/commit/a3a3624f646b98e409424d8defccbd334da9e8b8), [`6246914`](https://github.com/mastra-ai/mastra/commit/62469146636911f3cbbe0880bd011c6a897a59a7), [`6445eba`](https://github.com/mastra-ai/mastra/commit/6445eba6020abac681aba1cc9289f446cb400cbe), [`86b7b77`](https://github.com/mastra-ai/mastra/commit/86b7b777980d30f66e1fd134a37d2af4c22e54cc), [`1c75e32`](https://github.com/mastra-ai/mastra/commit/1c75e32f7fc0b9fb6f548b4407feaec8a1440212), [`296dc9a`](https://github.com/mastra-ai/mastra/commit/296dc9af29f3616e786c7825ec32e0df92d754c5), [`f59032a`](https://github.com/mastra-ai/mastra/commit/f59032a73699443555a08a479e7ac578975784f2), [`cdd5c33`](https://github.com/mastra-ai/mastra/commit/cdd5c33ac6c7118a9f139e6dc0e14e6a8ae31658), [`3f73c07`](https://github.com/mastra-ai/mastra/commit/3f73c076727e8c36b4fff7a1b40290fb68957fa8), [`772c0c8`](https://github.com/mastra-ai/mastra/commit/772c0c897cec383258de2e6178147f8014767c7b), [`d7cf7fa`](https://github.com/mastra-ai/mastra/commit/d7cf7fafc1ae1b50bd8462dd0e6c671a8606db93), [`7c1ebb1`](https://github.com/mastra-ai/mastra/commit/7c1ebb15690c4b3f0eabb19077cf8af573311e57), [`0f9a448`](https://github.com/mastra-ai/mastra/commit/0f9a448502157e59f7b76f24360ad497168f5ef8), [`578bf2e`](https://github.com/mastra-ai/mastra/commit/578bf2e6a88e9d5b8bf502204e15a95dfbb679ae), [`c47165c`](https://github.com/mastra-ai/mastra/commit/c47165c983c87594c6952f1fd2fa51a90205034c), [`289f4ce`](https://github.com/mastra-ai/mastra/commit/289f4ce16e3293370440172132c52ee787cbc09f), [`df31eb0`](https://github.com/mastra-ai/mastra/commit/df31eb0c7087d782a0d9346e467f9a4af4b0eef6), [`9571e3a`](https://github.com/mastra-ai/mastra/commit/9571e3a06ed2c5220196460bf82a2129255c3a8b), [`4f16ff8`](https://github.com/mastra-ai/mastra/commit/4f16ff824bf2f9b0ddc93f210477c10c8a4fb1ab), [`b4c89b4`](https://github.com/mastra-ai/mastra/commit/b4c89b4371b0c86da57403ad1a3b3ef0681f3128), [`e6534fa`](https://github.com/mastra-ai/mastra/commit/e6534fab031216f6cb48c4c9907cbfdce9d60bc6), [`210cb7a`](https://github.com/mastra-ai/mastra/commit/210cb7a167998c7bbf72cb3b93e6eb0563330239), [`06b2d87`](https://github.com/mastra-ai/mastra/commit/06b2d87e63bcdd0ed59215c6789692b9b12de376), [`1c67d85`](https://github.com/mastra-ai/mastra/commit/1c67d85e9da8285662f4dbbf47e0378c3fee0747), [`ac01d63`](https://github.com/mastra-ai/mastra/commit/ac01d6355974aec73fdb8781449ed12bac582094), [`80a3324`](https://github.com/mastra-ai/mastra/commit/80a33245d3110204de6f56d61211523ffe338692), [`e44e8f3`](https://github.com/mastra-ai/mastra/commit/e44e8f370b66c339ddcaba946d33da6d3c3f06cd), [`d9d2881`](https://github.com/mastra-ai/mastra/commit/d9d2881ede6dd6c023d144215fc812062aed0890), [`a810a05`](https://github.com/mastra-ai/mastra/commit/a810a058f62ad407cfc1701e0be36ae91145d7cf), [`ba24be6`](https://github.com/mastra-ai/mastra/commit/ba24be662439c331ab23a600041f93803c89eca8), [`842b5fe`](https://github.com/mastra-ai/mastra/commit/842b5fe22b6a7fa811bd14e48eb9af523ac989f2), [`990611b`](https://github.com/mastra-ai/mastra/commit/990611ba76eb876d86c9c594371ae5f02f94b432), [`80bdf3a`](https://github.com/mastra-ai/mastra/commit/80bdf3ae16ade6ff63bde0cb16fa2df8ab7dd4dd), [`c967a5e`](https://github.com/mastra-ai/mastra/commit/c967a5eec150c5dc5418c4a4388982d1fb7ad27c), [`dc4a25d`](https://github.com/mastra-ai/mastra/commit/dc4a25d41af4e2fe97a816070eaec6aa963ab53b), [`9ba1247`](https://github.com/mastra-ai/mastra/commit/9ba12470c77f1c03642d720ce67e517e878f666e), [`fd96298`](https://github.com/mastra-ai/mastra/commit/fd96298a8367622f4ebfcaa97b5b6c1fbbd14564), [`66bbfb5`](https://github.com/mastra-ai/mastra/commit/66bbfb5f05b473d39f88c0e4a481ccac41634f3a), [`dc4a25d`](https://github.com/mastra-ai/mastra/commit/dc4a25d41af4e2fe97a816070eaec6aa963ab53b), [`f8da216`](https://github.com/mastra-ai/mastra/commit/f8da21633e7eb0e31c9ce0fc30567870d19416d3), [`4a09a9c`](https://github.com/mastra-ai/mastra/commit/4a09a9c0474ef643558fcb5f0edc542b82f1cab0), [`5f798b3`](https://github.com/mastra-ai/mastra/commit/5f798b3362e9bdf4d690f85245606e146eef60b9), [`6a84954`](https://github.com/mastra-ai/mastra/commit/6a84954a2667f85b6d59da652dab1bbff007ccb0), [`1e83a47`](https://github.com/mastra-ai/mastra/commit/1e83a4734ab61ba5926af6793e3569a78b72ed37), [`52d8ef0`](https://github.com/mastra-ai/mastra/commit/52d8ef03801f1deb7ee48532fc4190dd4a33916c), [`cdd5c33`](https://github.com/mastra-ai/mastra/commit/cdd5c33ac6c7118a9f139e6dc0e14e6a8ae31658), [`7fdcaa6`](https://github.com/mastra-ai/mastra/commit/7fdcaa66105d64290f9b14432a12ec99f39c4d3a), [`d6c56f9`](https://github.com/mastra-ai/mastra/commit/d6c56f951db3213330b98b0abafa9778c8770e58), [`e08e789`](https://github.com/mastra-ai/mastra/commit/e08e789c1bf4cd2fe46363f7a4728536ceccc9bd), [`bf936e2`](https://github.com/mastra-ai/mastra/commit/bf936e2c89b2ff0dad5695b873ddc009ba96d41e), [`7fb580a`](https://github.com/mastra-ai/mastra/commit/7fb580ac73fbcacf2ff00872a3395f73ae1b9fa5), [`ed5d606`](https://github.com/mastra-ai/mastra/commit/ed5d606739c5e3fbdfa9f272df7809aa5ab43b1d), [`f53d5bd`](https://github.com/mastra-ai/mastra/commit/f53d5bd4885b29e4ac29a428a6044088ea8d6aa3), [`32980a3`](https://github.com/mastra-ai/mastra/commit/32980a3e2413d0274ac244d32c37d910edc13f00), [`01a2943`](https://github.com/mastra-ai/mastra/commit/01a2943a7d886edefdff072bfa51f055bab54437), [`82e3365`](https://github.com/mastra-ai/mastra/commit/82e3365ef7c9bf7bee2e7a7029035ea262d68895), [`6104347`](https://github.com/mastra-ai/mastra/commit/61043473ba6bfd0a25156824e853e13165562e6c), [`35cc901`](https://github.com/mastra-ai/mastra/commit/35cc90102cf834a84827acaf9eee0b6d6d1e2a3b), [`a8b4cf0`](https://github.com/mastra-ai/mastra/commit/a8b4cf02823cffebc4751a53337dfacf097c1ae1), [`9571e3a`](https://github.com/mastra-ai/mastra/commit/9571e3a06ed2c5220196460bf82a2129255c3a8b), [`333785c`](https://github.com/mastra-ai/mastra/commit/333785c93cbb01e42c60167e995457c28897ddbf), [`bda2235`](https://github.com/mastra-ai/mastra/commit/bda22353ee28f2df0eaea555f7cae1549f979c0b), [`efd5c81`](https://github.com/mastra-ai/mastra/commit/efd5c81cc25fde3c2ddd86fc1178deb4ec176e19), [`1b482c2`](https://github.com/mastra-ai/mastra/commit/1b482c2d89244dd758c41e5f927a2b44041388d2), [`45bfb88`](https://github.com/mastra-ai/mastra/commit/45bfb88fd52f1dd3be20e2a38905777c96499c90), [`ff28284`](https://github.com/mastra-ai/mastra/commit/ff2828416f14daff9d956e6a352fdaa23c950979), [`4bcdfaf`](https://github.com/mastra-ai/mastra/commit/4bcdfaf0eac3199d7cb171b0a19a92c9c341eea4), [`e3b9307`](https://github.com/mastra-ai/mastra/commit/e3b9307098daefbfae2a52ae2ef51bc9fc701190), [`d6834c5`](https://github.com/mastra-ai/mastra/commit/d6834c5a7866b16734d23900163c2414ed70d791), [`f33264f`](https://github.com/mastra-ai/mastra/commit/f33264f517ae603279afd5c4251e2b40f6dd3618), [`689f2c4`](https://github.com/mastra-ai/mastra/commit/689f2c4b6c0835fe455702b01d21daa8abcd9331), [`fcd0667`](https://github.com/mastra-ai/mastra/commit/fcd0667a4e378be35c9a1b1eb19cce78fbfd7282), [`cfd0d9e`](https://github.com/mastra-ai/mastra/commit/cfd0d9ec77ec3c69dd96f79cdb579e03d79f22ce), [`acc3513`](https://github.com/mastra-ai/mastra/commit/acc3513b19f79bf0a7ec2998694580edca54086c), [`1670533`](https://github.com/mastra-ai/mastra/commit/1670533986f6bacf567746245348125e3a106448), [`a7eb4a1`](https://github.com/mastra-ai/mastra/commit/a7eb4a11450f6170274ed5141bffe821d4fdd5a6), [`0976933`](https://github.com/mastra-ai/mastra/commit/0976933142333ec78451feef265b68bcb45aa5e7), [`242b945`](https://github.com/mastra-ai/mastra/commit/242b94558777bfbdeb42cbfea84afff0b6ad0633), [`c52d346`](https://github.com/mastra-ai/mastra/commit/c52d3462ec831a5d95926ecd3d3373f5928ad2e5), [`af4636a`](https://github.com/mastra-ai/mastra/commit/af4636a74463275d71c1d13a38f7d2b738f128bf), [`01a2943`](https://github.com/mastra-ai/mastra/commit/01a2943a7d886edefdff072bfa51f055bab54437), [`2eabc09`](https://github.com/mastra-ai/mastra/commit/2eabc097d86d52fbd0123da36a7c874154cc384f), [`0023e79`](https://github.com/mastra-ai/mastra/commit/0023e7919431078280abd11c89d1edeae35fcc69), [`c2ad51e`](https://github.com/mastra-ai/mastra/commit/c2ad51e2467f901eecba8c9f4a45e22a50bd7c18), [`25ca73d`](https://github.com/mastra-ai/mastra/commit/25ca73d25dee7ce9f0ca72939e3a505c4db7257e), [`2f9ef3f`](https://github.com/mastra-ai/mastra/commit/2f9ef3f4ca06fc2dcdd5088c26b7f4da6a016791), [`e7eefcb`](https://github.com/mastra-ai/mastra/commit/e7eefcb162cda7c493e8c3bf43050ead0efbcb2c), [`fea5cae`](https://github.com/mastra-ai/mastra/commit/fea5caedc7e2cfea51784a15e015952692027abf), [`4d7aca2`](https://github.com/mastra-ai/mastra/commit/4d7aca2fe75f225c83d1502d63079568e6ec163f), [`e1cead1`](https://github.com/mastra-ai/mastra/commit/e1cead17b5f3653cf00d2f90cc19b113119c02ba), [`01a2943`](https://github.com/mastra-ai/mastra/commit/01a2943a7d886edefdff072bfa51f055bab54437), [`d9d93b2`](https://github.com/mastra-ai/mastra/commit/d9d93b25e4a65ad5fa153fa35be7ed149c8d587f), [`c4ec889`](https://github.com/mastra-ai/mastra/commit/c4ec889561c0264c43f66d04d587bee4ce35e792), [`4b59f78`](https://github.com/mastra-ai/mastra/commit/4b59f786cbc9a7d1ef07a07517dbd4b96865e99d), [`eeae63e`](https://github.com/mastra-ai/mastra/commit/eeae63e7fbe8e1f237adc69bca6e2ac13c5ca907), [`3dc97ea`](https://github.com/mastra-ai/mastra/commit/3dc97ea415fad353b48a13095fad1835933cc12a), [`94e7ae9`](https://github.com/mastra-ai/mastra/commit/94e7ae970b37c888cd1244ef013292639a2fe6d1), [`e6a2860`](https://github.com/mastra-ai/mastra/commit/e6a2860649cc51f87d32d78b766ae2126446ba07), [`7010c5d`](https://github.com/mastra-ai/mastra/commit/7010c5d15728bf9c5dfe4fb6b1bf80ce23bf143a), [`bab06b1`](https://github.com/mastra-ai/mastra/commit/bab06b18923873a584bdfc71a6b4ec7fb4727fb7), [`3d01cd3`](https://github.com/mastra-ai/mastra/commit/3d01cd387321b6f9c5cac31d487c84bf51b19c78), [`7bf3086`](https://github.com/mastra-ai/mastra/commit/7bf308663f0115ca74ad20554ade740f06640859), [`4c186a0`](https://github.com/mastra-ai/mastra/commit/4c186a017275f45e6ed4c09de0f89550e2d09e8c), [`b0fa077`](https://github.com/mastra-ai/mastra/commit/b0fa077bcbc9b08551846fe372a0d3d15b71ed72), [`0282e16`](https://github.com/mastra-ai/mastra/commit/0282e16115538c8e9b248b90f0748eb01cb5dc98), [`a8dd139`](https://github.com/mastra-ai/mastra/commit/a8dd1391a9fe9a6632c25809ef236980afa9a020), [`6a667b4`](https://github.com/mastra-ai/mastra/commit/6a667b4b7cd6a93fe41fcdd357b08c5a8c09b9ab), [`9be8878`](https://github.com/mastra-ai/mastra/commit/9be8878dcf0388e84fc4873e0eec27bd49b881a4), [`e5786be`](https://github.com/mastra-ai/mastra/commit/e5786be02bb903073082bd9d6da880ebaacc343f), [`2440e09`](https://github.com/mastra-ai/mastra/commit/2440e096ea6c2def1ccc1eb2d0f3f5b88c4af940), [`2093fbd`](https://github.com/mastra-ai/mastra/commit/2093fbd53bb744bae19ec89f6d73db9a66fbe8a7), [`a59049b`](https://github.com/mastra-ai/mastra/commit/a59049b1652a13efff66ac826326b5ed9a550342), [`7bd85ea`](https://github.com/mastra-ai/mastra/commit/7bd85ea7588b71c25ce9f4019c88f8539be5dcbc), [`83fa004`](https://github.com/mastra-ai/mastra/commit/83fa0044bfda8b703a83883dbd8bef204844d13f), [`a463cdf`](https://github.com/mastra-ai/mastra/commit/a463cdf1c95c3059e70f0bff27959e8558bb899d), [`e7a5da4`](https://github.com/mastra-ai/mastra/commit/e7a5da4ef8e4dd452d2f232961b4e682a85ffe43), [`7b4393d`](https://github.com/mastra-ai/mastra/commit/7b4393d557411fdcf07b0e30e5acaf7cc85154ae), [`0ea6b80`](https://github.com/mastra-ai/mastra/commit/0ea6b8001408ce02b56e8be0536b0fd8cbaf8ad2)]:
|
|
39
|
+
- @mastra/core@1.58.0
|
|
40
|
+
|
|
41
|
+
## 0.1.0-alpha.0
|
|
42
|
+
|
|
43
|
+
### Minor Changes
|
|
44
|
+
|
|
45
|
+
- Added `@mastra/quickjs`, a Code Mode transport that runs model-authored programs in an in-process QuickJS runtime compiled to WebAssembly. ([#21089](https://github.com/mastra-ai/mastra/pull/21089))
|
|
46
|
+
|
|
47
|
+
**Why**
|
|
48
|
+
|
|
49
|
+
Running Code Mode in-process previously meant `@mastra/isolated-vm`, which installs a native addon and requires the host process to start with `--no-node-snapshot`. Serverless platforms usually allow neither, leaving a full workspace sandbox as the only option. This transport needs neither, so Code Mode runs on those hosts.
|
|
50
|
+
|
|
51
|
+
**Usage**
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { createCodeMode } from '@mastra/core/tools';
|
|
55
|
+
import { QuickJsCodeModeTransport } from '@mastra/quickjs';
|
|
56
|
+
|
|
57
|
+
const { tool, instructions } = createCodeMode(
|
|
58
|
+
{ tools: { getTopProducts, getProductRatings } },
|
|
59
|
+
new QuickJsCodeModeTransport({ memoryLimitMb: 128 }),
|
|
60
|
+
);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Programs get the same boundary as the isolated-vm transport: no filesystem, network, process, or module access, and the allow-listed tools as their only capability. They run slower than in a V8 isolate, which affects compute-heavy code far more than code that awaits tool calls.
|
|
64
|
+
|
|
65
|
+
Closes https://github.com/mastra-ai/mastra/issues/20546
|
|
66
|
+
|
|
67
|
+
### Patch Changes
|
|
68
|
+
|
|
69
|
+
- Updated dependencies [[`66bbfb5`](https://github.com/mastra-ai/mastra/commit/66bbfb5f05b473d39f88c0e4a481ccac41634f3a)]:
|
|
70
|
+
- @mastra/core@1.58.0-alpha.10
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Portions of this software are licensed as follows:
|
|
2
|
+
|
|
3
|
+
- All content that resides under any directory named "ee/" within this
|
|
4
|
+
repository, including but not limited to:
|
|
5
|
+
- `packages/core/src/auth/ee/`
|
|
6
|
+
- `packages/server/src/server/auth/ee/`
|
|
7
|
+
is licensed under the license defined in `ee/LICENSE`.
|
|
8
|
+
|
|
9
|
+
- All third-party components incorporated into the Mastra Software are
|
|
10
|
+
licensed under the original license provided by the owner of the
|
|
11
|
+
applicable component.
|
|
12
|
+
|
|
13
|
+
- Content outside of the above-mentioned directories or restrictions is
|
|
14
|
+
available under the "Apache License 2.0" as defined below.
|
|
15
|
+
|
|
16
|
+
# Apache License 2.0
|
|
17
|
+
|
|
18
|
+
Copyright (c) 2025 Kepler Software, Inc.
|
|
19
|
+
|
|
20
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
21
|
+
you may not use this file except in compliance with the License.
|
|
22
|
+
You may obtain a copy of the License at
|
|
23
|
+
|
|
24
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
25
|
+
|
|
26
|
+
Unless required by applicable law or agreed to in writing, software
|
|
27
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
28
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
29
|
+
See the License for the specific language governing permissions and
|
|
30
|
+
limitations under the License.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
let _mastra_core_tools = require("@mastra/core/tools");
|
|
25
|
+
let quickjs_emscripten = require("quickjs-emscripten");
|
|
26
|
+
let ts_blank_space = require("ts-blank-space");
|
|
27
|
+
ts_blank_space = __toESM(ts_blank_space, 1);
|
|
28
|
+
//#region src/transport.ts
|
|
29
|
+
/**
|
|
30
|
+
* Code Mode — QuickJS (WebAssembly) transport
|
|
31
|
+
*
|
|
32
|
+
* Runs the model's TypeScript program inside an in-process QuickJS interpreter
|
|
33
|
+
* compiled to WebAssembly, instead of a WorkspaceSandbox. The interpreter is a
|
|
34
|
+
* real security boundary: QuickJS starts with a bare global object, so the
|
|
35
|
+
* guest has no filesystem, network, process, timer, or module access — the only
|
|
36
|
+
* capabilities are the injected `external_*` functions, which call back into the
|
|
37
|
+
* host dispatcher (allow-list enforced host-side).
|
|
38
|
+
*
|
|
39
|
+
* This is the sibling of `@mastra/isolated-vm`, and the two are interchangeable.
|
|
40
|
+
* The difference is deployment cost:
|
|
41
|
+
*
|
|
42
|
+
* - `isolated-vm` is a native addon and, on Node 20+, requires the host process
|
|
43
|
+
* to be started with `--no-node-snapshot`. Neither is available on most
|
|
44
|
+
* serverless platforms.
|
|
45
|
+
* - QuickJS is WebAssembly. No native binary, no install step, no Node flags,
|
|
46
|
+
* and it also runs in the browser. The tradeoff is speed: an interpreter in
|
|
47
|
+
* WASM is materially slower than V8, which is an acceptable trade for the
|
|
48
|
+
* tool-orchestration workloads Code Mode targets.
|
|
49
|
+
*
|
|
50
|
+
* Host calls cross the WASM boundary as JSON strings in both directions, so no
|
|
51
|
+
* host object references are ever exposed to guest code.
|
|
52
|
+
*
|
|
53
|
+
* TypeScript is stripped on the host with `ts-blank-space` — a pure-JavaScript
|
|
54
|
+
* type eraser, deliberately chosen over esbuild, which is itself a native binary
|
|
55
|
+
* and would reintroduce the very dependency this package exists to avoid.
|
|
56
|
+
*
|
|
57
|
+
* ## Concurrency
|
|
58
|
+
*
|
|
59
|
+
* `quickjs-emscripten` ships an "asyncify" build whose host functions may be
|
|
60
|
+
* `async`, which looks like the natural fit. It is not usable here: an asyncified
|
|
61
|
+
* module can only suspend for one async call at a time, and re-entering a
|
|
62
|
+
* suspended module crashes it. Code Mode's whole value is batching tool calls
|
|
63
|
+
* with `Promise.all`.
|
|
64
|
+
*
|
|
65
|
+
* So this transport uses the regular synchronous build. Each `external_*` call
|
|
66
|
+
* hands the guest a deferred promise and returns control to the host
|
|
67
|
+
* immediately, without suspending. The host settles the deferred whenever the
|
|
68
|
+
* real dispatch finishes and pumps the guest's microtask queue. Any number of
|
|
69
|
+
* calls can therefore be in flight at once.
|
|
70
|
+
*/
|
|
71
|
+
/** Default interpreter heap limit, in MiB. */
|
|
72
|
+
const DEFAULT_MEMORY_LIMIT_MB = 128;
|
|
73
|
+
/** Default interpreter stack limit, in bytes. Guards runaway recursion. */
|
|
74
|
+
const DEFAULT_MAX_STACK_SIZE_BYTES = 1024 * 1024;
|
|
75
|
+
/** How long the guest's leftover job queue may run while being drained. */
|
|
76
|
+
const DRAIN_BUDGET_MS = 50;
|
|
77
|
+
/** Upper bound on drain passes, so a job queue that refills cannot spin forever. */
|
|
78
|
+
const MAX_DRAIN_PASSES = 10;
|
|
79
|
+
/**
|
|
80
|
+
* Code Mode transport that executes programs in an in-process QuickJS
|
|
81
|
+
* interpreter compiled to WebAssembly.
|
|
82
|
+
*
|
|
83
|
+
* No sandbox is required — the interpreter is the execution boundary
|
|
84
|
+
* (`requiresSandbox: false`).
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import { createCodeMode } from '@mastra/core/tools';
|
|
89
|
+
* import { QuickJsCodeModeTransport } from '@mastra/quickjs';
|
|
90
|
+
*
|
|
91
|
+
* const { tool, instructions } = createCodeMode(
|
|
92
|
+
* { tools: { getWeather, getForecast } },
|
|
93
|
+
* new QuickJsCodeModeTransport({ memoryLimitMb: 128 }),
|
|
94
|
+
* );
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
var QuickJsCodeModeTransport = class {
|
|
98
|
+
requiresSandbox = false;
|
|
99
|
+
#memoryLimitMb;
|
|
100
|
+
#maxStackSizeBytes;
|
|
101
|
+
#module;
|
|
102
|
+
constructor(options = {}) {
|
|
103
|
+
this.#memoryLimitMb = options.memoryLimitMb ?? DEFAULT_MEMORY_LIMIT_MB;
|
|
104
|
+
this.#maxStackSizeBytes = options.maxStackSizeBytes ?? DEFAULT_MAX_STACK_SIZE_BYTES;
|
|
105
|
+
this.#module = options.module;
|
|
106
|
+
}
|
|
107
|
+
async run(opts) {
|
|
108
|
+
const { program, toolIds, dispatch, timeout, abortSignal, onExternalCall, onExternalResult } = opts;
|
|
109
|
+
const externals = toolIds.map((toolId) => ({
|
|
110
|
+
toolId,
|
|
111
|
+
externalName: (0, _mastra_core_tools.sanitizeToolId)(toolId)
|
|
112
|
+
}));
|
|
113
|
+
const allowList = new Set(toolIds);
|
|
114
|
+
const logs = [];
|
|
115
|
+
const notifyCall = (tool, args) => {
|
|
116
|
+
try {
|
|
117
|
+
onExternalCall?.(tool, args);
|
|
118
|
+
} catch {}
|
|
119
|
+
};
|
|
120
|
+
const notifyResult = (tool, durationMs, error) => {
|
|
121
|
+
try {
|
|
122
|
+
onExternalResult?.(tool, durationMs, error);
|
|
123
|
+
} catch {}
|
|
124
|
+
};
|
|
125
|
+
let strippedProgram;
|
|
126
|
+
try {
|
|
127
|
+
strippedProgram = (0, ts_blank_space.default)(`(async () => {\n${program}\n})`);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return {
|
|
130
|
+
success: false,
|
|
131
|
+
logs,
|
|
132
|
+
error: {
|
|
133
|
+
message: error?.message ?? String(error),
|
|
134
|
+
name: "SyntaxError"
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const rpcHost = async (tool, argsJson) => {
|
|
139
|
+
const started = Date.now();
|
|
140
|
+
let args;
|
|
141
|
+
try {
|
|
142
|
+
args = JSON.parse(argsJson);
|
|
143
|
+
} catch {
|
|
144
|
+
args = void 0;
|
|
145
|
+
}
|
|
146
|
+
notifyCall(tool, args);
|
|
147
|
+
if (!allowList.has(tool)) {
|
|
148
|
+
notifyResult(tool, Date.now() - started, /* @__PURE__ */ new Error("not allowed"));
|
|
149
|
+
return JSON.stringify({
|
|
150
|
+
ok: false,
|
|
151
|
+
error: {
|
|
152
|
+
message: `Tool "${tool}" is not available in Code Mode`,
|
|
153
|
+
name: "NotAllowedError"
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const result = await dispatch(tool, args);
|
|
159
|
+
const json = JSON.stringify({
|
|
160
|
+
ok: true,
|
|
161
|
+
result
|
|
162
|
+
});
|
|
163
|
+
notifyResult(tool, Date.now() - started);
|
|
164
|
+
return json;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
const err = error;
|
|
167
|
+
notifyResult(tool, Date.now() - started, error instanceof Error ? error : new Error(String(error)));
|
|
168
|
+
return JSON.stringify({
|
|
169
|
+
ok: false,
|
|
170
|
+
error: {
|
|
171
|
+
message: err?.message ?? String(error),
|
|
172
|
+
name: err?.name
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const wasm = this.#module ?? await (0, quickjs_emscripten.getQuickJS)();
|
|
178
|
+
if (abortSignal?.aborted) return {
|
|
179
|
+
success: false,
|
|
180
|
+
logs,
|
|
181
|
+
error: {
|
|
182
|
+
message: "Code Mode execution aborted",
|
|
183
|
+
name: "AbortError"
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const runtime = wasm.newRuntime();
|
|
187
|
+
runtime.setMemoryLimit(this.#memoryLimitMb * 1024 * 1024);
|
|
188
|
+
runtime.setMaxStackSize(this.#maxStackSizeBytes);
|
|
189
|
+
const context = runtime.newContext();
|
|
190
|
+
const pendingDeferreds = /* @__PURE__ */ new Set();
|
|
191
|
+
let abandonedGuestResult;
|
|
192
|
+
let finished = false;
|
|
193
|
+
const drainJobs = () => {
|
|
194
|
+
const drainDeadline = Date.now() + DRAIN_BUDGET_MS;
|
|
195
|
+
runtime.setInterruptHandler(() => Date.now() >= drainDeadline);
|
|
196
|
+
for (let pass = 0; pass < MAX_DRAIN_PASSES && Date.now() < drainDeadline; pass++) {
|
|
197
|
+
const jobs = runtime.executePendingJobs();
|
|
198
|
+
jobs.error?.dispose();
|
|
199
|
+
if (!jobs.error && jobs.value === 0) break;
|
|
200
|
+
}
|
|
201
|
+
runtime.removeInterruptHandler();
|
|
202
|
+
};
|
|
203
|
+
const cleanup = () => {
|
|
204
|
+
if (finished) return;
|
|
205
|
+
finished = true;
|
|
206
|
+
for (const deferred of pendingDeferreds) deferred.dispose();
|
|
207
|
+
pendingDeferreds.clear();
|
|
208
|
+
abandonedGuestResult?.value?.dispose();
|
|
209
|
+
abandonedGuestResult?.error?.dispose();
|
|
210
|
+
abandonedGuestResult = void 0;
|
|
211
|
+
drainJobs();
|
|
212
|
+
context.dispose();
|
|
213
|
+
runtime.dispose();
|
|
214
|
+
};
|
|
215
|
+
const pumpJobs = () => {
|
|
216
|
+
if (finished) return;
|
|
217
|
+
runtime.executePendingJobs().error?.dispose();
|
|
218
|
+
};
|
|
219
|
+
const deadline = Date.now() + timeout;
|
|
220
|
+
let interrupted;
|
|
221
|
+
runtime.setInterruptHandler(() => {
|
|
222
|
+
if (abortSignal?.aborted) {
|
|
223
|
+
interrupted = "abort";
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
if (Date.now() >= deadline) {
|
|
227
|
+
interrupted = "timeout";
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
});
|
|
232
|
+
const timedOutResult = () => ({
|
|
233
|
+
success: false,
|
|
234
|
+
logs,
|
|
235
|
+
error: {
|
|
236
|
+
message: `Code Mode execution timed out after ${timeout}ms`,
|
|
237
|
+
name: "TimeoutError"
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
const abortedResult = () => ({
|
|
241
|
+
success: false,
|
|
242
|
+
logs,
|
|
243
|
+
error: {
|
|
244
|
+
message: "Code Mode execution aborted",
|
|
245
|
+
name: "AbortError"
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
let timer;
|
|
249
|
+
let onAbort;
|
|
250
|
+
try {
|
|
251
|
+
const hostCall = context.newFunction("__hostCall", (toolHandle, argsHandle) => {
|
|
252
|
+
const tool = context.getString(toolHandle);
|
|
253
|
+
const argsJson = context.getString(argsHandle);
|
|
254
|
+
const deferred = context.newPromise();
|
|
255
|
+
pendingDeferreds.add(deferred);
|
|
256
|
+
rpcHost(tool, argsJson).then((json) => {
|
|
257
|
+
if (finished) return;
|
|
258
|
+
const resultHandle = context.newString(json);
|
|
259
|
+
deferred.resolve(resultHandle);
|
|
260
|
+
resultHandle.dispose();
|
|
261
|
+
pendingDeferreds.delete(deferred);
|
|
262
|
+
deferred.dispose();
|
|
263
|
+
});
|
|
264
|
+
deferred.settled.then(pumpJobs);
|
|
265
|
+
return deferred.handle;
|
|
266
|
+
});
|
|
267
|
+
context.setProp(context.global, "__hostCall", hostCall);
|
|
268
|
+
hostCall.dispose();
|
|
269
|
+
const hostLog = context.newFunction("__hostLog", (messageHandle) => {
|
|
270
|
+
logs.push(context.getString(messageHandle));
|
|
271
|
+
});
|
|
272
|
+
context.setProp(context.global, "__hostLog", hostLog);
|
|
273
|
+
hostLog.dispose();
|
|
274
|
+
const bootstrap = context.evalCode(`
|
|
275
|
+
(() => {
|
|
276
|
+
const __hostCall = globalThis.__hostCall;
|
|
277
|
+
const __hostLog = globalThis.__hostLog;
|
|
278
|
+
delete globalThis.__hostCall;
|
|
279
|
+
delete globalThis.__hostLog;
|
|
280
|
+
|
|
281
|
+
const __rpc = (tool, args) => {
|
|
282
|
+
let argsJson;
|
|
283
|
+
try {
|
|
284
|
+
argsJson = JSON.stringify(args === undefined ? null : args);
|
|
285
|
+
} catch (e) {
|
|
286
|
+
return Promise.reject(
|
|
287
|
+
new Error('Arguments passed to an external_* function must be JSON-serializable'),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
return __hostCall(String(tool), argsJson).then((json) => {
|
|
291
|
+
const res = JSON.parse(json);
|
|
292
|
+
if (res.ok) return res.result;
|
|
293
|
+
const err = new Error((res.error && res.error.message) || 'external tool failed');
|
|
294
|
+
if (res.error && res.error.name) err.name = res.error.name;
|
|
295
|
+
throw err;
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const __safeStringify = (value) => {
|
|
300
|
+
try { return JSON.stringify(value); } catch { return String(value); }
|
|
301
|
+
};
|
|
302
|
+
const __log = (args) => {
|
|
303
|
+
__hostLog(args.map((a) => (typeof a === 'string' ? a : __safeStringify(a))).join(' '));
|
|
304
|
+
};
|
|
305
|
+
globalThis.console = Object.freeze({
|
|
306
|
+
log: (...args) => __log(args),
|
|
307
|
+
info: (...args) => __log(args),
|
|
308
|
+
warn: (...args) => __log(args),
|
|
309
|
+
error: (...args) => __log(args),
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
for (const { externalName, toolId } of ${JSON.stringify(externals)}) {
|
|
313
|
+
globalThis['external_' + externalName] = (input) => __rpc(toolId, input);
|
|
314
|
+
}
|
|
315
|
+
})()
|
|
316
|
+
`);
|
|
317
|
+
if (bootstrap.error) {
|
|
318
|
+
const err = context.dump(bootstrap.error);
|
|
319
|
+
bootstrap.error.dispose();
|
|
320
|
+
return {
|
|
321
|
+
success: false,
|
|
322
|
+
logs,
|
|
323
|
+
error: { message: err?.message ?? "Code Mode bootstrap failed" }
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
bootstrap.value.dispose();
|
|
327
|
+
const evalResult = context.evalCode(`
|
|
328
|
+
(async () => {
|
|
329
|
+
const __user = ${strippedProgram}
|
|
330
|
+
try {
|
|
331
|
+
const __result = await __user();
|
|
332
|
+
try {
|
|
333
|
+
const json = JSON.stringify({ ok: true, value: __result });
|
|
334
|
+
return json === undefined ? '{"ok":true}' : json;
|
|
335
|
+
} catch {
|
|
336
|
+
return JSON.stringify({
|
|
337
|
+
ok: false,
|
|
338
|
+
error: { message: 'Code Mode result is not JSON-serializable', name: 'TypeError' },
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
} catch (e) {
|
|
342
|
+
return JSON.stringify({
|
|
343
|
+
ok: false,
|
|
344
|
+
error: { message: (e && e.message) || String(e), name: e && e.name },
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
})()
|
|
348
|
+
`);
|
|
349
|
+
if (evalResult.error) {
|
|
350
|
+
const err = context.dump(evalResult.error);
|
|
351
|
+
evalResult.error.dispose();
|
|
352
|
+
if (interrupted === "abort" || abortSignal?.aborted) return abortedResult();
|
|
353
|
+
if (interrupted === "timeout") return timedOutResult();
|
|
354
|
+
return {
|
|
355
|
+
success: false,
|
|
356
|
+
logs,
|
|
357
|
+
error: {
|
|
358
|
+
message: err?.message ?? "Code Mode evaluation failed",
|
|
359
|
+
name: err?.name
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const guestPromise = context.resolvePromise(evalResult.value);
|
|
364
|
+
evalResult.value.dispose();
|
|
365
|
+
guestPromise.then((result) => {
|
|
366
|
+
if (finished) return;
|
|
367
|
+
abandonedGuestResult = result;
|
|
368
|
+
});
|
|
369
|
+
pumpJobs();
|
|
370
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
371
|
+
timer = setTimeout(() => resolve("timeout"), Math.max(0, deadline - Date.now()));
|
|
372
|
+
});
|
|
373
|
+
const abortPromise = new Promise((resolve) => {
|
|
374
|
+
if (!abortSignal) return;
|
|
375
|
+
if (abortSignal.aborted) return resolve("abort");
|
|
376
|
+
onAbort = () => resolve("abort");
|
|
377
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
378
|
+
});
|
|
379
|
+
const outcome = await Promise.race([
|
|
380
|
+
guestPromise.then((result) => ({
|
|
381
|
+
kind: "settled",
|
|
382
|
+
result
|
|
383
|
+
})),
|
|
384
|
+
timeoutPromise.then(() => ({
|
|
385
|
+
kind: "timeout",
|
|
386
|
+
result: void 0
|
|
387
|
+
})),
|
|
388
|
+
abortPromise.then(() => ({
|
|
389
|
+
kind: "abort",
|
|
390
|
+
result: void 0
|
|
391
|
+
}))
|
|
392
|
+
]);
|
|
393
|
+
if (outcome.kind !== "settled") {
|
|
394
|
+
drainJobs();
|
|
395
|
+
await Promise.resolve();
|
|
396
|
+
return outcome.kind === "abort" ? abortedResult() : timedOutResult();
|
|
397
|
+
}
|
|
398
|
+
const settled = outcome.result;
|
|
399
|
+
abandonedGuestResult = void 0;
|
|
400
|
+
if (settled.error) {
|
|
401
|
+
const err = context.dump(settled.error);
|
|
402
|
+
settled.error.dispose();
|
|
403
|
+
if (interrupted === "abort" || abortSignal?.aborted) return abortedResult();
|
|
404
|
+
if (interrupted === "timeout") return timedOutResult();
|
|
405
|
+
return {
|
|
406
|
+
success: false,
|
|
407
|
+
logs,
|
|
408
|
+
error: {
|
|
409
|
+
message: err?.message ?? "Code Mode evaluation failed",
|
|
410
|
+
name: err?.name
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
const json = context.getString(settled.value);
|
|
415
|
+
settled.value.dispose();
|
|
416
|
+
const envelope = JSON.parse(json);
|
|
417
|
+
return envelope.ok ? {
|
|
418
|
+
success: true,
|
|
419
|
+
result: envelope.value,
|
|
420
|
+
logs
|
|
421
|
+
} : {
|
|
422
|
+
success: false,
|
|
423
|
+
error: envelope.error,
|
|
424
|
+
logs
|
|
425
|
+
};
|
|
426
|
+
} finally {
|
|
427
|
+
if (timer) clearTimeout(timer);
|
|
428
|
+
if (onAbort) abortSignal?.removeEventListener("abort", onAbort);
|
|
429
|
+
runtime.removeInterruptHandler();
|
|
430
|
+
cleanup();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
//#endregion
|
|
435
|
+
exports.QuickJsCodeModeTransport = QuickJsCodeModeTransport;
|
|
436
|
+
|
|
437
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["#memoryLimitMb","#maxStackSizeBytes","#module","err"],"sources":["../src/transport.ts"],"sourcesContent":["/**\n * Code Mode — QuickJS (WebAssembly) transport\n *\n * Runs the model's TypeScript program inside an in-process QuickJS interpreter\n * compiled to WebAssembly, instead of a WorkspaceSandbox. The interpreter is a\n * real security boundary: QuickJS starts with a bare global object, so the\n * guest has no filesystem, network, process, timer, or module access — the only\n * capabilities are the injected `external_*` functions, which call back into the\n * host dispatcher (allow-list enforced host-side).\n *\n * This is the sibling of `@mastra/isolated-vm`, and the two are interchangeable.\n * The difference is deployment cost:\n *\n * - `isolated-vm` is a native addon and, on Node 20+, requires the host process\n * to be started with `--no-node-snapshot`. Neither is available on most\n * serverless platforms.\n * - QuickJS is WebAssembly. No native binary, no install step, no Node flags,\n * and it also runs in the browser. The tradeoff is speed: an interpreter in\n * WASM is materially slower than V8, which is an acceptable trade for the\n * tool-orchestration workloads Code Mode targets.\n *\n * Host calls cross the WASM boundary as JSON strings in both directions, so no\n * host object references are ever exposed to guest code.\n *\n * TypeScript is stripped on the host with `ts-blank-space` — a pure-JavaScript\n * type eraser, deliberately chosen over esbuild, which is itself a native binary\n * and would reintroduce the very dependency this package exists to avoid.\n *\n * ## Concurrency\n *\n * `quickjs-emscripten` ships an \"asyncify\" build whose host functions may be\n * `async`, which looks like the natural fit. It is not usable here: an asyncified\n * module can only suspend for one async call at a time, and re-entering a\n * suspended module crashes it. Code Mode's whole value is batching tool calls\n * with `Promise.all`.\n *\n * So this transport uses the regular synchronous build. Each `external_*` call\n * hands the guest a deferred promise and returns control to the host\n * immediately, without suspending. The host settles the deferred whenever the\n * real dispatch finishes and pumps the guest's microtask queue. Any number of\n * calls can therefore be in flight at once.\n */\n\nimport { sanitizeToolId } from '@mastra/core/tools';\nimport type { CodeModeToolResult, CodeModeTransport } from '@mastra/core/tools';\nimport { getQuickJS } from 'quickjs-emscripten';\nimport type { QuickJSDeferredPromise, QuickJSHandle, QuickJSWASMModule } from 'quickjs-emscripten';\nimport tsBlankSpace from 'ts-blank-space';\n\n/** Default interpreter heap limit, in MiB. */\nconst DEFAULT_MEMORY_LIMIT_MB = 128;\n\n/** Default interpreter stack limit, in bytes. Guards runaway recursion. */\nconst DEFAULT_MAX_STACK_SIZE_BYTES = 1024 * 1024;\n\n/** How long the guest's leftover job queue may run while being drained. */\nconst DRAIN_BUDGET_MS = 50;\n\n/** Upper bound on drain passes, so a job queue that refills cannot spin forever. */\nconst MAX_DRAIN_PASSES = 10;\n\nexport interface QuickJsCodeModeTransportOptions {\n /**\n * QuickJS heap limit in MiB. Exceeding it terminates the program.\n * Default: 128.\n */\n memoryLimitMb?: number;\n\n /**\n * QuickJS stack limit in bytes. Exceeding it terminates the program.\n * Default: 1 MiB.\n */\n maxStackSizeBytes?: number;\n\n /**\n * A pre-loaded QuickJS WebAssembly module.\n *\n * Defaults to the shared release build resolved by `getQuickJS()`, which is\n * what most callers want. Supply your own to pin a specific variant — for\n * example a single-file variant that suits your bundler, or the debug build\n * with its leak sanitizer during testing.\n */\n module?: QuickJSWASMModule;\n}\n\n/** The success and failure handles of a QuickJS call result, either of which must be freed. */\ninterface VmResultHandles {\n value?: QuickJSHandle;\n error?: QuickJSHandle;\n}\n\n/** Envelope returned by the guest program eval (as a JSON string). */\ninterface GuestResultEnvelope {\n ok: boolean;\n value?: unknown;\n error?: { message: string; name?: string };\n}\n\n/** Envelope returned by the host RPC handler (as a JSON string). */\ninterface HostRpcEnvelope {\n ok: boolean;\n result?: unknown;\n error?: { message: string; name?: string };\n}\n\n/**\n * Code Mode transport that executes programs in an in-process QuickJS\n * interpreter compiled to WebAssembly.\n *\n * No sandbox is required — the interpreter is the execution boundary\n * (`requiresSandbox: false`).\n *\n * @example\n * ```typescript\n * import { createCodeMode } from '@mastra/core/tools';\n * import { QuickJsCodeModeTransport } from '@mastra/quickjs';\n *\n * const { tool, instructions } = createCodeMode(\n * { tools: { getWeather, getForecast } },\n * new QuickJsCodeModeTransport({ memoryLimitMb: 128 }),\n * );\n * ```\n */\nexport class QuickJsCodeModeTransport implements CodeModeTransport {\n readonly requiresSandbox = false;\n\n readonly #memoryLimitMb: number;\n readonly #maxStackSizeBytes: number;\n readonly #module: QuickJSWASMModule | undefined;\n\n constructor(options: QuickJsCodeModeTransportOptions = {}) {\n this.#memoryLimitMb = options.memoryLimitMb ?? DEFAULT_MEMORY_LIMIT_MB;\n this.#maxStackSizeBytes = options.maxStackSizeBytes ?? DEFAULT_MAX_STACK_SIZE_BYTES;\n this.#module = options.module;\n }\n\n async run(opts: Parameters<CodeModeTransport['run']>[0]): Promise<CodeModeToolResult> {\n const { program, toolIds, dispatch, timeout, abortSignal, onExternalCall, onExternalResult } = opts;\n\n const externals = toolIds.map(toolId => ({ toolId, externalName: sanitizeToolId(toolId) }));\n const allowList = new Set(toolIds);\n const logs: string[] = [];\n\n // Observer hooks are caller-supplied and best-effort: a throwing hook must\n // never prevent the RPC from responding, or the matching in-guest promise\n // would hang until the timeout.\n const notifyCall = (tool: string, args: unknown): void => {\n try {\n onExternalCall?.(tool, args);\n } catch {\n /* observer errors are non-fatal */\n }\n };\n const notifyResult = (tool: string, durationMs: number, error?: Error): void => {\n try {\n onExternalResult?.(tool, durationMs, error);\n } catch {\n /* observer errors are non-fatal */\n }\n };\n\n // Strip TypeScript on the host; QuickJS runs plain JavaScript. Wrapping in\n // an async arrow makes top-level `return`/`await`/`const` legal, same as the\n // core runner's program module. ts-blank-space only erases types, so it\n // leaves genuine syntax errors alone — those surface from `evalCode` below\n // as a real SyntaxError, with QuickJS's own diagnostics.\n let strippedProgram: string;\n try {\n strippedProgram = tsBlankSpace(`(async () => {\\n${program}\\n})`);\n } catch (error) {\n const err = error as { message?: string };\n return {\n success: false,\n logs,\n error: { message: err?.message ?? String(error), name: 'SyntaxError' },\n };\n }\n\n // Host-side RPC handler. Crosses the boundary as JSON strings in both\n // directions so no host references or non-JSON values ever leak into the\n // guest. Errors are returned in the envelope (never thrown) so the guest can\n // rebuild them with the original error name.\n const rpcHost = async (tool: string, argsJson: string): Promise<string> => {\n const started = Date.now();\n let args: unknown;\n try {\n args = JSON.parse(argsJson);\n } catch {\n args = undefined;\n }\n notifyCall(tool, args);\n // Allow-list enforcement: never invoke a tool that wasn't exposed.\n if (!allowList.has(tool)) {\n notifyResult(tool, Date.now() - started, new Error('not allowed'));\n return JSON.stringify({\n ok: false,\n error: { message: `Tool \"${tool}\" is not available in Code Mode`, name: 'NotAllowedError' },\n } satisfies HostRpcEnvelope);\n }\n try {\n const result = await dispatch(tool, args);\n // Serialize before reporting success so a non-serializable result\n // (circular reference, BigInt) falls through to the error path without\n // emitting a success event first.\n const json = JSON.stringify({ ok: true, result } satisfies HostRpcEnvelope);\n notifyResult(tool, Date.now() - started);\n return json;\n } catch (error) {\n const err = error as { message?: string; name?: string };\n notifyResult(tool, Date.now() - started, error instanceof Error ? error : new Error(String(error)));\n return JSON.stringify({\n ok: false,\n error: { message: err?.message ?? String(error), name: err?.name },\n } satisfies HostRpcEnvelope);\n }\n };\n\n const wasm = this.#module ?? (await getQuickJS());\n\n if (abortSignal?.aborted) {\n return { success: false, logs, error: { message: 'Code Mode execution aborted', name: 'AbortError' } };\n }\n\n const runtime = wasm.newRuntime();\n runtime.setMemoryLimit(this.#memoryLimitMb * 1024 * 1024);\n runtime.setMaxStackSize(this.#maxStackSizeBytes);\n const context = runtime.newContext();\n\n // Every QuickJS handle must be disposed by hand, and an undisposed one\n // aborts the whole WASM module when the runtime is freed. All disposal\n // therefore happens here, once, covering deferreds that are still unsettled\n // because the run timed out or was aborted while a dispatch was in flight.\n const pendingDeferreds = new Set<QuickJSDeferredPromise>();\n let abandonedGuestResult: VmResultHandles | undefined;\n let finished = false;\n\n // A run that ends early — timed out, aborted, or interrupted mid-job — leaves\n // guest work sitting in the job queue, and those jobs still reference guest\n // objects. QuickJS asserts that no objects survive when a runtime is freed,\n // so the queue has to be drained first. The drain gets its own short\n // deadline, since the very work being drained may be the runaway code that\n // ended the run.\n const drainJobs = (): void => {\n const drainDeadline = Date.now() + DRAIN_BUDGET_MS;\n runtime.setInterruptHandler(() => Date.now() >= drainDeadline);\n for (let pass = 0; pass < MAX_DRAIN_PASSES && Date.now() < drainDeadline; pass++) {\n const jobs = runtime.executePendingJobs();\n jobs.error?.dispose();\n if (!jobs.error && jobs.value === 0) break;\n }\n runtime.removeInterruptHandler();\n };\n\n const cleanup = (): void => {\n if (finished) return;\n finished = true;\n for (const deferred of pendingDeferreds) deferred.dispose();\n pendingDeferreds.clear();\n abandonedGuestResult?.value?.dispose();\n abandonedGuestResult?.error?.dispose();\n abandonedGuestResult = undefined;\n\n drainJobs();\n context.dispose();\n runtime.dispose();\n };\n\n // Draining the guest's microtask queue can itself fail — most importantly\n // when the interrupt handler unwinds a job that ran away. That failure comes\n // back as a handle which, left undisposed, aborts the WASM module when the\n // runtime is freed.\n const pumpJobs = (): void => {\n if (finished) return;\n const jobs = runtime.executePendingJobs();\n jobs.error?.dispose();\n };\n\n const deadline = Date.now() + timeout;\n let interrupted: 'timeout' | 'abort' | undefined;\n // The only way to stop a runaway synchronous program: QuickJS calls this\n // between operations, and a truthy return unwinds the evaluation. It cannot\n // help while the guest is merely awaiting a host call — no guest code is\n // running then — which is what the host-side races below are for.\n runtime.setInterruptHandler(() => {\n if (abortSignal?.aborted) {\n interrupted = 'abort';\n return true;\n }\n if (Date.now() >= deadline) {\n interrupted = 'timeout';\n return true;\n }\n return false;\n });\n\n const timedOutResult = (): CodeModeToolResult => ({\n success: false,\n logs,\n error: { message: `Code Mode execution timed out after ${timeout}ms`, name: 'TimeoutError' },\n });\n const abortedResult = (): CodeModeToolResult => ({\n success: false,\n logs,\n error: { message: 'Code Mode execution aborted', name: 'AbortError' },\n });\n\n let timer: NodeJS.Timeout | undefined;\n let onAbort: (() => void) | undefined;\n\n try {\n // Host bridge for `external_*`. Returns a deferred promise to the guest\n // and returns immediately without suspending the module, so any number of\n // dispatches can be in flight at once and `Promise.all` behaves normally.\n const hostCall = context.newFunction('__hostCall', (toolHandle, argsHandle) => {\n const tool = context.getString(toolHandle);\n const argsJson = context.getString(argsHandle);\n const deferred = context.newPromise();\n pendingDeferreds.add(deferred);\n\n void rpcHost(tool, argsJson).then(json => {\n // The run may already be over (timeout/abort) and the context freed;\n // touching it here would crash the WASM module.\n if (finished) return;\n const resultHandle = context.newString(json);\n deferred.resolve(resultHandle);\n resultHandle.dispose();\n pendingDeferreds.delete(deferred);\n deferred.dispose();\n });\n\n // Settling a promise only queues the guest's continuation; QuickJS runs\n // it when jobs are pumped.\n void deferred.settled.then(pumpJobs);\n\n return deferred.handle;\n });\n context.setProp(context.global, '__hostCall', hostCall);\n hostCall.dispose();\n\n const hostLog = context.newFunction('__hostLog', messageHandle => {\n logs.push(context.getString(messageHandle));\n });\n context.setProp(context.global, '__hostLog', hostLog);\n hostLog.dispose();\n\n // Bootstrap: console capture and the `external_*` globals. The guest only\n // ever sees plain functions closing over the two host bridges.\n const bootstrap = context.evalCode(`\n (() => {\n const __hostCall = globalThis.__hostCall;\n const __hostLog = globalThis.__hostLog;\n delete globalThis.__hostCall;\n delete globalThis.__hostLog;\n\n const __rpc = (tool, args) => {\n let argsJson;\n try {\n argsJson = JSON.stringify(args === undefined ? null : args);\n } catch (e) {\n return Promise.reject(\n new Error('Arguments passed to an external_* function must be JSON-serializable'),\n );\n }\n return __hostCall(String(tool), argsJson).then((json) => {\n const res = JSON.parse(json);\n if (res.ok) return res.result;\n const err = new Error((res.error && res.error.message) || 'external tool failed');\n if (res.error && res.error.name) err.name = res.error.name;\n throw err;\n });\n };\n\n const __safeStringify = (value) => {\n try { return JSON.stringify(value); } catch { return String(value); }\n };\n const __log = (args) => {\n __hostLog(args.map((a) => (typeof a === 'string' ? a : __safeStringify(a))).join(' '));\n };\n globalThis.console = Object.freeze({\n log: (...args) => __log(args),\n info: (...args) => __log(args),\n warn: (...args) => __log(args),\n error: (...args) => __log(args),\n });\n\n for (const { externalName, toolId } of ${JSON.stringify(externals)}) {\n globalThis['external_' + externalName] = (input) => __rpc(toolId, input);\n }\n })()\n `);\n if (bootstrap.error) {\n const err = context.dump(bootstrap.error) as { message?: string };\n bootstrap.error.dispose();\n return { success: false, logs, error: { message: err?.message ?? 'Code Mode bootstrap failed' } };\n }\n bootstrap.value.dispose();\n\n // The guest returns a JSON envelope string so results and errors cross the\n // boundary as data, mirroring the frame protocol's JSON semantics.\n const evalResult = context.evalCode(`\n (async () => {\n const __user = ${strippedProgram}\n try {\n const __result = await __user();\n try {\n const json = JSON.stringify({ ok: true, value: __result });\n return json === undefined ? '{\"ok\":true}' : json;\n } catch {\n return JSON.stringify({\n ok: false,\n error: { message: 'Code Mode result is not JSON-serializable', name: 'TypeError' },\n });\n }\n } catch (e) {\n return JSON.stringify({\n ok: false,\n error: { message: (e && e.message) || String(e), name: e && e.name },\n });\n }\n })()\n `);\n\n if (evalResult.error) {\n const err = context.dump(evalResult.error) as { message?: string; name?: string };\n evalResult.error.dispose();\n if (interrupted === 'abort' || abortSignal?.aborted) return abortedResult();\n if (interrupted === 'timeout') return timedOutResult();\n return {\n success: false,\n logs,\n error: { message: err?.message ?? 'Code Mode evaluation failed', name: err?.name },\n };\n }\n\n const guestPromise = context.resolvePromise(evalResult.value);\n evalResult.value.dispose();\n\n // The guest can settle and still lose the race below — an abort or timeout\n // that lands in the same turn wins, and the result is discarded. Its handle\n // is real either way, so record it for disposal rather than stranding it.\n void guestPromise.then(result => {\n // Once the context is gone there is nothing left to free, and touching a\n // handle from it would crash the module.\n if (finished) return;\n abandonedGuestResult = result as VmResultHandles;\n });\n\n pumpJobs();\n\n // Covers time the guest spends awaiting rather than executing, where the\n // interrupt handler cannot reach: a dispatch that never resolves, or an\n // abort that lands between guest turns.\n const timeoutPromise = new Promise<'timeout'>(resolve => {\n timer = setTimeout(() => resolve('timeout'), Math.max(0, deadline - Date.now()));\n });\n const abortPromise = new Promise<'abort'>(resolve => {\n if (!abortSignal) return;\n // The signal can already have fired: pumping the job queue above runs\n // guest code, which can reach a dispatch that aborts synchronously, and\n // an `abort` listener added afterwards would never be called.\n if (abortSignal.aborted) return resolve('abort');\n onAbort = () => resolve('abort');\n abortSignal.addEventListener('abort', onAbort, { once: true });\n });\n\n const outcome = await Promise.race([\n guestPromise.then(result => ({ kind: 'settled' as const, result })),\n timeoutPromise.then(() => ({ kind: 'timeout' as const, result: undefined })),\n abortPromise.then(() => ({ kind: 'abort' as const, result: undefined })),\n ]);\n\n if (outcome.kind !== 'settled') {\n // The guest may be one job away from producing a result nobody will read.\n // Let it land while the context is still alive, so its handle is recorded\n // and freed rather than stranded.\n drainJobs();\n await Promise.resolve();\n return outcome.kind === 'abort' ? abortedResult() : timedOutResult();\n }\n\n const settled = outcome.result;\n abandonedGuestResult = undefined;\n if (settled.error) {\n const err = context.dump(settled.error) as { message?: string; name?: string };\n settled.error.dispose();\n if (interrupted === 'abort' || abortSignal?.aborted) return abortedResult();\n if (interrupted === 'timeout') return timedOutResult();\n return {\n success: false,\n logs,\n error: { message: err?.message ?? 'Code Mode evaluation failed', name: err?.name },\n };\n }\n\n const json = context.getString(settled.value);\n settled.value.dispose();\n const envelope = JSON.parse(json) as GuestResultEnvelope;\n return envelope.ok\n ? { success: true, result: envelope.value, logs }\n : { success: false, error: envelope.error, logs };\n } finally {\n if (timer) clearTimeout(timer);\n if (onAbort) abortSignal?.removeEventListener('abort', onAbort);\n runtime.removeInterruptHandler();\n cleanup();\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,KAAK,+BAA+B,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { sanitizeToolId } from "@mastra/core/tools";
|
|
2
|
+
import { getQuickJS } from "quickjs-emscripten";
|
|
3
|
+
import tsBlankSpace from "ts-blank-space";
|
|
4
|
+
//#region src/transport.ts
|
|
5
|
+
/**
|
|
6
|
+
* Code Mode — QuickJS (WebAssembly) transport
|
|
7
|
+
*
|
|
8
|
+
* Runs the model's TypeScript program inside an in-process QuickJS interpreter
|
|
9
|
+
* compiled to WebAssembly, instead of a WorkspaceSandbox. The interpreter is a
|
|
10
|
+
* real security boundary: QuickJS starts with a bare global object, so the
|
|
11
|
+
* guest has no filesystem, network, process, timer, or module access — the only
|
|
12
|
+
* capabilities are the injected `external_*` functions, which call back into the
|
|
13
|
+
* host dispatcher (allow-list enforced host-side).
|
|
14
|
+
*
|
|
15
|
+
* This is the sibling of `@mastra/isolated-vm`, and the two are interchangeable.
|
|
16
|
+
* The difference is deployment cost:
|
|
17
|
+
*
|
|
18
|
+
* - `isolated-vm` is a native addon and, on Node 20+, requires the host process
|
|
19
|
+
* to be started with `--no-node-snapshot`. Neither is available on most
|
|
20
|
+
* serverless platforms.
|
|
21
|
+
* - QuickJS is WebAssembly. No native binary, no install step, no Node flags,
|
|
22
|
+
* and it also runs in the browser. The tradeoff is speed: an interpreter in
|
|
23
|
+
* WASM is materially slower than V8, which is an acceptable trade for the
|
|
24
|
+
* tool-orchestration workloads Code Mode targets.
|
|
25
|
+
*
|
|
26
|
+
* Host calls cross the WASM boundary as JSON strings in both directions, so no
|
|
27
|
+
* host object references are ever exposed to guest code.
|
|
28
|
+
*
|
|
29
|
+
* TypeScript is stripped on the host with `ts-blank-space` — a pure-JavaScript
|
|
30
|
+
* type eraser, deliberately chosen over esbuild, which is itself a native binary
|
|
31
|
+
* and would reintroduce the very dependency this package exists to avoid.
|
|
32
|
+
*
|
|
33
|
+
* ## Concurrency
|
|
34
|
+
*
|
|
35
|
+
* `quickjs-emscripten` ships an "asyncify" build whose host functions may be
|
|
36
|
+
* `async`, which looks like the natural fit. It is not usable here: an asyncified
|
|
37
|
+
* module can only suspend for one async call at a time, and re-entering a
|
|
38
|
+
* suspended module crashes it. Code Mode's whole value is batching tool calls
|
|
39
|
+
* with `Promise.all`.
|
|
40
|
+
*
|
|
41
|
+
* So this transport uses the regular synchronous build. Each `external_*` call
|
|
42
|
+
* hands the guest a deferred promise and returns control to the host
|
|
43
|
+
* immediately, without suspending. The host settles the deferred whenever the
|
|
44
|
+
* real dispatch finishes and pumps the guest's microtask queue. Any number of
|
|
45
|
+
* calls can therefore be in flight at once.
|
|
46
|
+
*/
|
|
47
|
+
/** Default interpreter heap limit, in MiB. */
|
|
48
|
+
const DEFAULT_MEMORY_LIMIT_MB = 128;
|
|
49
|
+
/** Default interpreter stack limit, in bytes. Guards runaway recursion. */
|
|
50
|
+
const DEFAULT_MAX_STACK_SIZE_BYTES = 1024 * 1024;
|
|
51
|
+
/** How long the guest's leftover job queue may run while being drained. */
|
|
52
|
+
const DRAIN_BUDGET_MS = 50;
|
|
53
|
+
/** Upper bound on drain passes, so a job queue that refills cannot spin forever. */
|
|
54
|
+
const MAX_DRAIN_PASSES = 10;
|
|
55
|
+
/**
|
|
56
|
+
* Code Mode transport that executes programs in an in-process QuickJS
|
|
57
|
+
* interpreter compiled to WebAssembly.
|
|
58
|
+
*
|
|
59
|
+
* No sandbox is required — the interpreter is the execution boundary
|
|
60
|
+
* (`requiresSandbox: false`).
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* import { createCodeMode } from '@mastra/core/tools';
|
|
65
|
+
* import { QuickJsCodeModeTransport } from '@mastra/quickjs';
|
|
66
|
+
*
|
|
67
|
+
* const { tool, instructions } = createCodeMode(
|
|
68
|
+
* { tools: { getWeather, getForecast } },
|
|
69
|
+
* new QuickJsCodeModeTransport({ memoryLimitMb: 128 }),
|
|
70
|
+
* );
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
var QuickJsCodeModeTransport = class {
|
|
74
|
+
requiresSandbox = false;
|
|
75
|
+
#memoryLimitMb;
|
|
76
|
+
#maxStackSizeBytes;
|
|
77
|
+
#module;
|
|
78
|
+
constructor(options = {}) {
|
|
79
|
+
this.#memoryLimitMb = options.memoryLimitMb ?? DEFAULT_MEMORY_LIMIT_MB;
|
|
80
|
+
this.#maxStackSizeBytes = options.maxStackSizeBytes ?? DEFAULT_MAX_STACK_SIZE_BYTES;
|
|
81
|
+
this.#module = options.module;
|
|
82
|
+
}
|
|
83
|
+
async run(opts) {
|
|
84
|
+
const { program, toolIds, dispatch, timeout, abortSignal, onExternalCall, onExternalResult } = opts;
|
|
85
|
+
const externals = toolIds.map((toolId) => ({
|
|
86
|
+
toolId,
|
|
87
|
+
externalName: sanitizeToolId(toolId)
|
|
88
|
+
}));
|
|
89
|
+
const allowList = new Set(toolIds);
|
|
90
|
+
const logs = [];
|
|
91
|
+
const notifyCall = (tool, args) => {
|
|
92
|
+
try {
|
|
93
|
+
onExternalCall?.(tool, args);
|
|
94
|
+
} catch {}
|
|
95
|
+
};
|
|
96
|
+
const notifyResult = (tool, durationMs, error) => {
|
|
97
|
+
try {
|
|
98
|
+
onExternalResult?.(tool, durationMs, error);
|
|
99
|
+
} catch {}
|
|
100
|
+
};
|
|
101
|
+
let strippedProgram;
|
|
102
|
+
try {
|
|
103
|
+
strippedProgram = tsBlankSpace(`(async () => {\n${program}\n})`);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
logs,
|
|
108
|
+
error: {
|
|
109
|
+
message: error?.message ?? String(error),
|
|
110
|
+
name: "SyntaxError"
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const rpcHost = async (tool, argsJson) => {
|
|
115
|
+
const started = Date.now();
|
|
116
|
+
let args;
|
|
117
|
+
try {
|
|
118
|
+
args = JSON.parse(argsJson);
|
|
119
|
+
} catch {
|
|
120
|
+
args = void 0;
|
|
121
|
+
}
|
|
122
|
+
notifyCall(tool, args);
|
|
123
|
+
if (!allowList.has(tool)) {
|
|
124
|
+
notifyResult(tool, Date.now() - started, /* @__PURE__ */ new Error("not allowed"));
|
|
125
|
+
return JSON.stringify({
|
|
126
|
+
ok: false,
|
|
127
|
+
error: {
|
|
128
|
+
message: `Tool "${tool}" is not available in Code Mode`,
|
|
129
|
+
name: "NotAllowedError"
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const result = await dispatch(tool, args);
|
|
135
|
+
const json = JSON.stringify({
|
|
136
|
+
ok: true,
|
|
137
|
+
result
|
|
138
|
+
});
|
|
139
|
+
notifyResult(tool, Date.now() - started);
|
|
140
|
+
return json;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const err = error;
|
|
143
|
+
notifyResult(tool, Date.now() - started, error instanceof Error ? error : new Error(String(error)));
|
|
144
|
+
return JSON.stringify({
|
|
145
|
+
ok: false,
|
|
146
|
+
error: {
|
|
147
|
+
message: err?.message ?? String(error),
|
|
148
|
+
name: err?.name
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const wasm = this.#module ?? await getQuickJS();
|
|
154
|
+
if (abortSignal?.aborted) return {
|
|
155
|
+
success: false,
|
|
156
|
+
logs,
|
|
157
|
+
error: {
|
|
158
|
+
message: "Code Mode execution aborted",
|
|
159
|
+
name: "AbortError"
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const runtime = wasm.newRuntime();
|
|
163
|
+
runtime.setMemoryLimit(this.#memoryLimitMb * 1024 * 1024);
|
|
164
|
+
runtime.setMaxStackSize(this.#maxStackSizeBytes);
|
|
165
|
+
const context = runtime.newContext();
|
|
166
|
+
const pendingDeferreds = /* @__PURE__ */ new Set();
|
|
167
|
+
let abandonedGuestResult;
|
|
168
|
+
let finished = false;
|
|
169
|
+
const drainJobs = () => {
|
|
170
|
+
const drainDeadline = Date.now() + DRAIN_BUDGET_MS;
|
|
171
|
+
runtime.setInterruptHandler(() => Date.now() >= drainDeadline);
|
|
172
|
+
for (let pass = 0; pass < MAX_DRAIN_PASSES && Date.now() < drainDeadline; pass++) {
|
|
173
|
+
const jobs = runtime.executePendingJobs();
|
|
174
|
+
jobs.error?.dispose();
|
|
175
|
+
if (!jobs.error && jobs.value === 0) break;
|
|
176
|
+
}
|
|
177
|
+
runtime.removeInterruptHandler();
|
|
178
|
+
};
|
|
179
|
+
const cleanup = () => {
|
|
180
|
+
if (finished) return;
|
|
181
|
+
finished = true;
|
|
182
|
+
for (const deferred of pendingDeferreds) deferred.dispose();
|
|
183
|
+
pendingDeferreds.clear();
|
|
184
|
+
abandonedGuestResult?.value?.dispose();
|
|
185
|
+
abandonedGuestResult?.error?.dispose();
|
|
186
|
+
abandonedGuestResult = void 0;
|
|
187
|
+
drainJobs();
|
|
188
|
+
context.dispose();
|
|
189
|
+
runtime.dispose();
|
|
190
|
+
};
|
|
191
|
+
const pumpJobs = () => {
|
|
192
|
+
if (finished) return;
|
|
193
|
+
runtime.executePendingJobs().error?.dispose();
|
|
194
|
+
};
|
|
195
|
+
const deadline = Date.now() + timeout;
|
|
196
|
+
let interrupted;
|
|
197
|
+
runtime.setInterruptHandler(() => {
|
|
198
|
+
if (abortSignal?.aborted) {
|
|
199
|
+
interrupted = "abort";
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (Date.now() >= deadline) {
|
|
203
|
+
interrupted = "timeout";
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
});
|
|
208
|
+
const timedOutResult = () => ({
|
|
209
|
+
success: false,
|
|
210
|
+
logs,
|
|
211
|
+
error: {
|
|
212
|
+
message: `Code Mode execution timed out after ${timeout}ms`,
|
|
213
|
+
name: "TimeoutError"
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
const abortedResult = () => ({
|
|
217
|
+
success: false,
|
|
218
|
+
logs,
|
|
219
|
+
error: {
|
|
220
|
+
message: "Code Mode execution aborted",
|
|
221
|
+
name: "AbortError"
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
let timer;
|
|
225
|
+
let onAbort;
|
|
226
|
+
try {
|
|
227
|
+
const hostCall = context.newFunction("__hostCall", (toolHandle, argsHandle) => {
|
|
228
|
+
const tool = context.getString(toolHandle);
|
|
229
|
+
const argsJson = context.getString(argsHandle);
|
|
230
|
+
const deferred = context.newPromise();
|
|
231
|
+
pendingDeferreds.add(deferred);
|
|
232
|
+
rpcHost(tool, argsJson).then((json) => {
|
|
233
|
+
if (finished) return;
|
|
234
|
+
const resultHandle = context.newString(json);
|
|
235
|
+
deferred.resolve(resultHandle);
|
|
236
|
+
resultHandle.dispose();
|
|
237
|
+
pendingDeferreds.delete(deferred);
|
|
238
|
+
deferred.dispose();
|
|
239
|
+
});
|
|
240
|
+
deferred.settled.then(pumpJobs);
|
|
241
|
+
return deferred.handle;
|
|
242
|
+
});
|
|
243
|
+
context.setProp(context.global, "__hostCall", hostCall);
|
|
244
|
+
hostCall.dispose();
|
|
245
|
+
const hostLog = context.newFunction("__hostLog", (messageHandle) => {
|
|
246
|
+
logs.push(context.getString(messageHandle));
|
|
247
|
+
});
|
|
248
|
+
context.setProp(context.global, "__hostLog", hostLog);
|
|
249
|
+
hostLog.dispose();
|
|
250
|
+
const bootstrap = context.evalCode(`
|
|
251
|
+
(() => {
|
|
252
|
+
const __hostCall = globalThis.__hostCall;
|
|
253
|
+
const __hostLog = globalThis.__hostLog;
|
|
254
|
+
delete globalThis.__hostCall;
|
|
255
|
+
delete globalThis.__hostLog;
|
|
256
|
+
|
|
257
|
+
const __rpc = (tool, args) => {
|
|
258
|
+
let argsJson;
|
|
259
|
+
try {
|
|
260
|
+
argsJson = JSON.stringify(args === undefined ? null : args);
|
|
261
|
+
} catch (e) {
|
|
262
|
+
return Promise.reject(
|
|
263
|
+
new Error('Arguments passed to an external_* function must be JSON-serializable'),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return __hostCall(String(tool), argsJson).then((json) => {
|
|
267
|
+
const res = JSON.parse(json);
|
|
268
|
+
if (res.ok) return res.result;
|
|
269
|
+
const err = new Error((res.error && res.error.message) || 'external tool failed');
|
|
270
|
+
if (res.error && res.error.name) err.name = res.error.name;
|
|
271
|
+
throw err;
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const __safeStringify = (value) => {
|
|
276
|
+
try { return JSON.stringify(value); } catch { return String(value); }
|
|
277
|
+
};
|
|
278
|
+
const __log = (args) => {
|
|
279
|
+
__hostLog(args.map((a) => (typeof a === 'string' ? a : __safeStringify(a))).join(' '));
|
|
280
|
+
};
|
|
281
|
+
globalThis.console = Object.freeze({
|
|
282
|
+
log: (...args) => __log(args),
|
|
283
|
+
info: (...args) => __log(args),
|
|
284
|
+
warn: (...args) => __log(args),
|
|
285
|
+
error: (...args) => __log(args),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
for (const { externalName, toolId } of ${JSON.stringify(externals)}) {
|
|
289
|
+
globalThis['external_' + externalName] = (input) => __rpc(toolId, input);
|
|
290
|
+
}
|
|
291
|
+
})()
|
|
292
|
+
`);
|
|
293
|
+
if (bootstrap.error) {
|
|
294
|
+
const err = context.dump(bootstrap.error);
|
|
295
|
+
bootstrap.error.dispose();
|
|
296
|
+
return {
|
|
297
|
+
success: false,
|
|
298
|
+
logs,
|
|
299
|
+
error: { message: err?.message ?? "Code Mode bootstrap failed" }
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
bootstrap.value.dispose();
|
|
303
|
+
const evalResult = context.evalCode(`
|
|
304
|
+
(async () => {
|
|
305
|
+
const __user = ${strippedProgram}
|
|
306
|
+
try {
|
|
307
|
+
const __result = await __user();
|
|
308
|
+
try {
|
|
309
|
+
const json = JSON.stringify({ ok: true, value: __result });
|
|
310
|
+
return json === undefined ? '{"ok":true}' : json;
|
|
311
|
+
} catch {
|
|
312
|
+
return JSON.stringify({
|
|
313
|
+
ok: false,
|
|
314
|
+
error: { message: 'Code Mode result is not JSON-serializable', name: 'TypeError' },
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
} catch (e) {
|
|
318
|
+
return JSON.stringify({
|
|
319
|
+
ok: false,
|
|
320
|
+
error: { message: (e && e.message) || String(e), name: e && e.name },
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
})()
|
|
324
|
+
`);
|
|
325
|
+
if (evalResult.error) {
|
|
326
|
+
const err = context.dump(evalResult.error);
|
|
327
|
+
evalResult.error.dispose();
|
|
328
|
+
if (interrupted === "abort" || abortSignal?.aborted) return abortedResult();
|
|
329
|
+
if (interrupted === "timeout") return timedOutResult();
|
|
330
|
+
return {
|
|
331
|
+
success: false,
|
|
332
|
+
logs,
|
|
333
|
+
error: {
|
|
334
|
+
message: err?.message ?? "Code Mode evaluation failed",
|
|
335
|
+
name: err?.name
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
const guestPromise = context.resolvePromise(evalResult.value);
|
|
340
|
+
evalResult.value.dispose();
|
|
341
|
+
guestPromise.then((result) => {
|
|
342
|
+
if (finished) return;
|
|
343
|
+
abandonedGuestResult = result;
|
|
344
|
+
});
|
|
345
|
+
pumpJobs();
|
|
346
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
347
|
+
timer = setTimeout(() => resolve("timeout"), Math.max(0, deadline - Date.now()));
|
|
348
|
+
});
|
|
349
|
+
const abortPromise = new Promise((resolve) => {
|
|
350
|
+
if (!abortSignal) return;
|
|
351
|
+
if (abortSignal.aborted) return resolve("abort");
|
|
352
|
+
onAbort = () => resolve("abort");
|
|
353
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
354
|
+
});
|
|
355
|
+
const outcome = await Promise.race([
|
|
356
|
+
guestPromise.then((result) => ({
|
|
357
|
+
kind: "settled",
|
|
358
|
+
result
|
|
359
|
+
})),
|
|
360
|
+
timeoutPromise.then(() => ({
|
|
361
|
+
kind: "timeout",
|
|
362
|
+
result: void 0
|
|
363
|
+
})),
|
|
364
|
+
abortPromise.then(() => ({
|
|
365
|
+
kind: "abort",
|
|
366
|
+
result: void 0
|
|
367
|
+
}))
|
|
368
|
+
]);
|
|
369
|
+
if (outcome.kind !== "settled") {
|
|
370
|
+
drainJobs();
|
|
371
|
+
await Promise.resolve();
|
|
372
|
+
return outcome.kind === "abort" ? abortedResult() : timedOutResult();
|
|
373
|
+
}
|
|
374
|
+
const settled = outcome.result;
|
|
375
|
+
abandonedGuestResult = void 0;
|
|
376
|
+
if (settled.error) {
|
|
377
|
+
const err = context.dump(settled.error);
|
|
378
|
+
settled.error.dispose();
|
|
379
|
+
if (interrupted === "abort" || abortSignal?.aborted) return abortedResult();
|
|
380
|
+
if (interrupted === "timeout") return timedOutResult();
|
|
381
|
+
return {
|
|
382
|
+
success: false,
|
|
383
|
+
logs,
|
|
384
|
+
error: {
|
|
385
|
+
message: err?.message ?? "Code Mode evaluation failed",
|
|
386
|
+
name: err?.name
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
const json = context.getString(settled.value);
|
|
391
|
+
settled.value.dispose();
|
|
392
|
+
const envelope = JSON.parse(json);
|
|
393
|
+
return envelope.ok ? {
|
|
394
|
+
success: true,
|
|
395
|
+
result: envelope.value,
|
|
396
|
+
logs
|
|
397
|
+
} : {
|
|
398
|
+
success: false,
|
|
399
|
+
error: envelope.error,
|
|
400
|
+
logs
|
|
401
|
+
};
|
|
402
|
+
} finally {
|
|
403
|
+
if (timer) clearTimeout(timer);
|
|
404
|
+
if (onAbort) abortSignal?.removeEventListener("abort", onAbort);
|
|
405
|
+
runtime.removeInterruptHandler();
|
|
406
|
+
cleanup();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
//#endregion
|
|
411
|
+
export { QuickJsCodeModeTransport };
|
|
412
|
+
|
|
413
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["#memoryLimitMb","#maxStackSizeBytes","#module","err"],"sources":["../src/transport.ts"],"sourcesContent":["/**\n * Code Mode — QuickJS (WebAssembly) transport\n *\n * Runs the model's TypeScript program inside an in-process QuickJS interpreter\n * compiled to WebAssembly, instead of a WorkspaceSandbox. The interpreter is a\n * real security boundary: QuickJS starts with a bare global object, so the\n * guest has no filesystem, network, process, timer, or module access — the only\n * capabilities are the injected `external_*` functions, which call back into the\n * host dispatcher (allow-list enforced host-side).\n *\n * This is the sibling of `@mastra/isolated-vm`, and the two are interchangeable.\n * The difference is deployment cost:\n *\n * - `isolated-vm` is a native addon and, on Node 20+, requires the host process\n * to be started with `--no-node-snapshot`. Neither is available on most\n * serverless platforms.\n * - QuickJS is WebAssembly. No native binary, no install step, no Node flags,\n * and it also runs in the browser. The tradeoff is speed: an interpreter in\n * WASM is materially slower than V8, which is an acceptable trade for the\n * tool-orchestration workloads Code Mode targets.\n *\n * Host calls cross the WASM boundary as JSON strings in both directions, so no\n * host object references are ever exposed to guest code.\n *\n * TypeScript is stripped on the host with `ts-blank-space` — a pure-JavaScript\n * type eraser, deliberately chosen over esbuild, which is itself a native binary\n * and would reintroduce the very dependency this package exists to avoid.\n *\n * ## Concurrency\n *\n * `quickjs-emscripten` ships an \"asyncify\" build whose host functions may be\n * `async`, which looks like the natural fit. It is not usable here: an asyncified\n * module can only suspend for one async call at a time, and re-entering a\n * suspended module crashes it. Code Mode's whole value is batching tool calls\n * with `Promise.all`.\n *\n * So this transport uses the regular synchronous build. Each `external_*` call\n * hands the guest a deferred promise and returns control to the host\n * immediately, without suspending. The host settles the deferred whenever the\n * real dispatch finishes and pumps the guest's microtask queue. Any number of\n * calls can therefore be in flight at once.\n */\n\nimport { sanitizeToolId } from '@mastra/core/tools';\nimport type { CodeModeToolResult, CodeModeTransport } from '@mastra/core/tools';\nimport { getQuickJS } from 'quickjs-emscripten';\nimport type { QuickJSDeferredPromise, QuickJSHandle, QuickJSWASMModule } from 'quickjs-emscripten';\nimport tsBlankSpace from 'ts-blank-space';\n\n/** Default interpreter heap limit, in MiB. */\nconst DEFAULT_MEMORY_LIMIT_MB = 128;\n\n/** Default interpreter stack limit, in bytes. Guards runaway recursion. */\nconst DEFAULT_MAX_STACK_SIZE_BYTES = 1024 * 1024;\n\n/** How long the guest's leftover job queue may run while being drained. */\nconst DRAIN_BUDGET_MS = 50;\n\n/** Upper bound on drain passes, so a job queue that refills cannot spin forever. */\nconst MAX_DRAIN_PASSES = 10;\n\nexport interface QuickJsCodeModeTransportOptions {\n /**\n * QuickJS heap limit in MiB. Exceeding it terminates the program.\n * Default: 128.\n */\n memoryLimitMb?: number;\n\n /**\n * QuickJS stack limit in bytes. Exceeding it terminates the program.\n * Default: 1 MiB.\n */\n maxStackSizeBytes?: number;\n\n /**\n * A pre-loaded QuickJS WebAssembly module.\n *\n * Defaults to the shared release build resolved by `getQuickJS()`, which is\n * what most callers want. Supply your own to pin a specific variant — for\n * example a single-file variant that suits your bundler, or the debug build\n * with its leak sanitizer during testing.\n */\n module?: QuickJSWASMModule;\n}\n\n/** The success and failure handles of a QuickJS call result, either of which must be freed. */\ninterface VmResultHandles {\n value?: QuickJSHandle;\n error?: QuickJSHandle;\n}\n\n/** Envelope returned by the guest program eval (as a JSON string). */\ninterface GuestResultEnvelope {\n ok: boolean;\n value?: unknown;\n error?: { message: string; name?: string };\n}\n\n/** Envelope returned by the host RPC handler (as a JSON string). */\ninterface HostRpcEnvelope {\n ok: boolean;\n result?: unknown;\n error?: { message: string; name?: string };\n}\n\n/**\n * Code Mode transport that executes programs in an in-process QuickJS\n * interpreter compiled to WebAssembly.\n *\n * No sandbox is required — the interpreter is the execution boundary\n * (`requiresSandbox: false`).\n *\n * @example\n * ```typescript\n * import { createCodeMode } from '@mastra/core/tools';\n * import { QuickJsCodeModeTransport } from '@mastra/quickjs';\n *\n * const { tool, instructions } = createCodeMode(\n * { tools: { getWeather, getForecast } },\n * new QuickJsCodeModeTransport({ memoryLimitMb: 128 }),\n * );\n * ```\n */\nexport class QuickJsCodeModeTransport implements CodeModeTransport {\n readonly requiresSandbox = false;\n\n readonly #memoryLimitMb: number;\n readonly #maxStackSizeBytes: number;\n readonly #module: QuickJSWASMModule | undefined;\n\n constructor(options: QuickJsCodeModeTransportOptions = {}) {\n this.#memoryLimitMb = options.memoryLimitMb ?? DEFAULT_MEMORY_LIMIT_MB;\n this.#maxStackSizeBytes = options.maxStackSizeBytes ?? DEFAULT_MAX_STACK_SIZE_BYTES;\n this.#module = options.module;\n }\n\n async run(opts: Parameters<CodeModeTransport['run']>[0]): Promise<CodeModeToolResult> {\n const { program, toolIds, dispatch, timeout, abortSignal, onExternalCall, onExternalResult } = opts;\n\n const externals = toolIds.map(toolId => ({ toolId, externalName: sanitizeToolId(toolId) }));\n const allowList = new Set(toolIds);\n const logs: string[] = [];\n\n // Observer hooks are caller-supplied and best-effort: a throwing hook must\n // never prevent the RPC from responding, or the matching in-guest promise\n // would hang until the timeout.\n const notifyCall = (tool: string, args: unknown): void => {\n try {\n onExternalCall?.(tool, args);\n } catch {\n /* observer errors are non-fatal */\n }\n };\n const notifyResult = (tool: string, durationMs: number, error?: Error): void => {\n try {\n onExternalResult?.(tool, durationMs, error);\n } catch {\n /* observer errors are non-fatal */\n }\n };\n\n // Strip TypeScript on the host; QuickJS runs plain JavaScript. Wrapping in\n // an async arrow makes top-level `return`/`await`/`const` legal, same as the\n // core runner's program module. ts-blank-space only erases types, so it\n // leaves genuine syntax errors alone — those surface from `evalCode` below\n // as a real SyntaxError, with QuickJS's own diagnostics.\n let strippedProgram: string;\n try {\n strippedProgram = tsBlankSpace(`(async () => {\\n${program}\\n})`);\n } catch (error) {\n const err = error as { message?: string };\n return {\n success: false,\n logs,\n error: { message: err?.message ?? String(error), name: 'SyntaxError' },\n };\n }\n\n // Host-side RPC handler. Crosses the boundary as JSON strings in both\n // directions so no host references or non-JSON values ever leak into the\n // guest. Errors are returned in the envelope (never thrown) so the guest can\n // rebuild them with the original error name.\n const rpcHost = async (tool: string, argsJson: string): Promise<string> => {\n const started = Date.now();\n let args: unknown;\n try {\n args = JSON.parse(argsJson);\n } catch {\n args = undefined;\n }\n notifyCall(tool, args);\n // Allow-list enforcement: never invoke a tool that wasn't exposed.\n if (!allowList.has(tool)) {\n notifyResult(tool, Date.now() - started, new Error('not allowed'));\n return JSON.stringify({\n ok: false,\n error: { message: `Tool \"${tool}\" is not available in Code Mode`, name: 'NotAllowedError' },\n } satisfies HostRpcEnvelope);\n }\n try {\n const result = await dispatch(tool, args);\n // Serialize before reporting success so a non-serializable result\n // (circular reference, BigInt) falls through to the error path without\n // emitting a success event first.\n const json = JSON.stringify({ ok: true, result } satisfies HostRpcEnvelope);\n notifyResult(tool, Date.now() - started);\n return json;\n } catch (error) {\n const err = error as { message?: string; name?: string };\n notifyResult(tool, Date.now() - started, error instanceof Error ? error : new Error(String(error)));\n return JSON.stringify({\n ok: false,\n error: { message: err?.message ?? String(error), name: err?.name },\n } satisfies HostRpcEnvelope);\n }\n };\n\n const wasm = this.#module ?? (await getQuickJS());\n\n if (abortSignal?.aborted) {\n return { success: false, logs, error: { message: 'Code Mode execution aborted', name: 'AbortError' } };\n }\n\n const runtime = wasm.newRuntime();\n runtime.setMemoryLimit(this.#memoryLimitMb * 1024 * 1024);\n runtime.setMaxStackSize(this.#maxStackSizeBytes);\n const context = runtime.newContext();\n\n // Every QuickJS handle must be disposed by hand, and an undisposed one\n // aborts the whole WASM module when the runtime is freed. All disposal\n // therefore happens here, once, covering deferreds that are still unsettled\n // because the run timed out or was aborted while a dispatch was in flight.\n const pendingDeferreds = new Set<QuickJSDeferredPromise>();\n let abandonedGuestResult: VmResultHandles | undefined;\n let finished = false;\n\n // A run that ends early — timed out, aborted, or interrupted mid-job — leaves\n // guest work sitting in the job queue, and those jobs still reference guest\n // objects. QuickJS asserts that no objects survive when a runtime is freed,\n // so the queue has to be drained first. The drain gets its own short\n // deadline, since the very work being drained may be the runaway code that\n // ended the run.\n const drainJobs = (): void => {\n const drainDeadline = Date.now() + DRAIN_BUDGET_MS;\n runtime.setInterruptHandler(() => Date.now() >= drainDeadline);\n for (let pass = 0; pass < MAX_DRAIN_PASSES && Date.now() < drainDeadline; pass++) {\n const jobs = runtime.executePendingJobs();\n jobs.error?.dispose();\n if (!jobs.error && jobs.value === 0) break;\n }\n runtime.removeInterruptHandler();\n };\n\n const cleanup = (): void => {\n if (finished) return;\n finished = true;\n for (const deferred of pendingDeferreds) deferred.dispose();\n pendingDeferreds.clear();\n abandonedGuestResult?.value?.dispose();\n abandonedGuestResult?.error?.dispose();\n abandonedGuestResult = undefined;\n\n drainJobs();\n context.dispose();\n runtime.dispose();\n };\n\n // Draining the guest's microtask queue can itself fail — most importantly\n // when the interrupt handler unwinds a job that ran away. That failure comes\n // back as a handle which, left undisposed, aborts the WASM module when the\n // runtime is freed.\n const pumpJobs = (): void => {\n if (finished) return;\n const jobs = runtime.executePendingJobs();\n jobs.error?.dispose();\n };\n\n const deadline = Date.now() + timeout;\n let interrupted: 'timeout' | 'abort' | undefined;\n // The only way to stop a runaway synchronous program: QuickJS calls this\n // between operations, and a truthy return unwinds the evaluation. It cannot\n // help while the guest is merely awaiting a host call — no guest code is\n // running then — which is what the host-side races below are for.\n runtime.setInterruptHandler(() => {\n if (abortSignal?.aborted) {\n interrupted = 'abort';\n return true;\n }\n if (Date.now() >= deadline) {\n interrupted = 'timeout';\n return true;\n }\n return false;\n });\n\n const timedOutResult = (): CodeModeToolResult => ({\n success: false,\n logs,\n error: { message: `Code Mode execution timed out after ${timeout}ms`, name: 'TimeoutError' },\n });\n const abortedResult = (): CodeModeToolResult => ({\n success: false,\n logs,\n error: { message: 'Code Mode execution aborted', name: 'AbortError' },\n });\n\n let timer: NodeJS.Timeout | undefined;\n let onAbort: (() => void) | undefined;\n\n try {\n // Host bridge for `external_*`. Returns a deferred promise to the guest\n // and returns immediately without suspending the module, so any number of\n // dispatches can be in flight at once and `Promise.all` behaves normally.\n const hostCall = context.newFunction('__hostCall', (toolHandle, argsHandle) => {\n const tool = context.getString(toolHandle);\n const argsJson = context.getString(argsHandle);\n const deferred = context.newPromise();\n pendingDeferreds.add(deferred);\n\n void rpcHost(tool, argsJson).then(json => {\n // The run may already be over (timeout/abort) and the context freed;\n // touching it here would crash the WASM module.\n if (finished) return;\n const resultHandle = context.newString(json);\n deferred.resolve(resultHandle);\n resultHandle.dispose();\n pendingDeferreds.delete(deferred);\n deferred.dispose();\n });\n\n // Settling a promise only queues the guest's continuation; QuickJS runs\n // it when jobs are pumped.\n void deferred.settled.then(pumpJobs);\n\n return deferred.handle;\n });\n context.setProp(context.global, '__hostCall', hostCall);\n hostCall.dispose();\n\n const hostLog = context.newFunction('__hostLog', messageHandle => {\n logs.push(context.getString(messageHandle));\n });\n context.setProp(context.global, '__hostLog', hostLog);\n hostLog.dispose();\n\n // Bootstrap: console capture and the `external_*` globals. The guest only\n // ever sees plain functions closing over the two host bridges.\n const bootstrap = context.evalCode(`\n (() => {\n const __hostCall = globalThis.__hostCall;\n const __hostLog = globalThis.__hostLog;\n delete globalThis.__hostCall;\n delete globalThis.__hostLog;\n\n const __rpc = (tool, args) => {\n let argsJson;\n try {\n argsJson = JSON.stringify(args === undefined ? null : args);\n } catch (e) {\n return Promise.reject(\n new Error('Arguments passed to an external_* function must be JSON-serializable'),\n );\n }\n return __hostCall(String(tool), argsJson).then((json) => {\n const res = JSON.parse(json);\n if (res.ok) return res.result;\n const err = new Error((res.error && res.error.message) || 'external tool failed');\n if (res.error && res.error.name) err.name = res.error.name;\n throw err;\n });\n };\n\n const __safeStringify = (value) => {\n try { return JSON.stringify(value); } catch { return String(value); }\n };\n const __log = (args) => {\n __hostLog(args.map((a) => (typeof a === 'string' ? a : __safeStringify(a))).join(' '));\n };\n globalThis.console = Object.freeze({\n log: (...args) => __log(args),\n info: (...args) => __log(args),\n warn: (...args) => __log(args),\n error: (...args) => __log(args),\n });\n\n for (const { externalName, toolId } of ${JSON.stringify(externals)}) {\n globalThis['external_' + externalName] = (input) => __rpc(toolId, input);\n }\n })()\n `);\n if (bootstrap.error) {\n const err = context.dump(bootstrap.error) as { message?: string };\n bootstrap.error.dispose();\n return { success: false, logs, error: { message: err?.message ?? 'Code Mode bootstrap failed' } };\n }\n bootstrap.value.dispose();\n\n // The guest returns a JSON envelope string so results and errors cross the\n // boundary as data, mirroring the frame protocol's JSON semantics.\n const evalResult = context.evalCode(`\n (async () => {\n const __user = ${strippedProgram}\n try {\n const __result = await __user();\n try {\n const json = JSON.stringify({ ok: true, value: __result });\n return json === undefined ? '{\"ok\":true}' : json;\n } catch {\n return JSON.stringify({\n ok: false,\n error: { message: 'Code Mode result is not JSON-serializable', name: 'TypeError' },\n });\n }\n } catch (e) {\n return JSON.stringify({\n ok: false,\n error: { message: (e && e.message) || String(e), name: e && e.name },\n });\n }\n })()\n `);\n\n if (evalResult.error) {\n const err = context.dump(evalResult.error) as { message?: string; name?: string };\n evalResult.error.dispose();\n if (interrupted === 'abort' || abortSignal?.aborted) return abortedResult();\n if (interrupted === 'timeout') return timedOutResult();\n return {\n success: false,\n logs,\n error: { message: err?.message ?? 'Code Mode evaluation failed', name: err?.name },\n };\n }\n\n const guestPromise = context.resolvePromise(evalResult.value);\n evalResult.value.dispose();\n\n // The guest can settle and still lose the race below — an abort or timeout\n // that lands in the same turn wins, and the result is discarded. Its handle\n // is real either way, so record it for disposal rather than stranding it.\n void guestPromise.then(result => {\n // Once the context is gone there is nothing left to free, and touching a\n // handle from it would crash the module.\n if (finished) return;\n abandonedGuestResult = result as VmResultHandles;\n });\n\n pumpJobs();\n\n // Covers time the guest spends awaiting rather than executing, where the\n // interrupt handler cannot reach: a dispatch that never resolves, or an\n // abort that lands between guest turns.\n const timeoutPromise = new Promise<'timeout'>(resolve => {\n timer = setTimeout(() => resolve('timeout'), Math.max(0, deadline - Date.now()));\n });\n const abortPromise = new Promise<'abort'>(resolve => {\n if (!abortSignal) return;\n // The signal can already have fired: pumping the job queue above runs\n // guest code, which can reach a dispatch that aborts synchronously, and\n // an `abort` listener added afterwards would never be called.\n if (abortSignal.aborted) return resolve('abort');\n onAbort = () => resolve('abort');\n abortSignal.addEventListener('abort', onAbort, { once: true });\n });\n\n const outcome = await Promise.race([\n guestPromise.then(result => ({ kind: 'settled' as const, result })),\n timeoutPromise.then(() => ({ kind: 'timeout' as const, result: undefined })),\n abortPromise.then(() => ({ kind: 'abort' as const, result: undefined })),\n ]);\n\n if (outcome.kind !== 'settled') {\n // The guest may be one job away from producing a result nobody will read.\n // Let it land while the context is still alive, so its handle is recorded\n // and freed rather than stranded.\n drainJobs();\n await Promise.resolve();\n return outcome.kind === 'abort' ? abortedResult() : timedOutResult();\n }\n\n const settled = outcome.result;\n abandonedGuestResult = undefined;\n if (settled.error) {\n const err = context.dump(settled.error) as { message?: string; name?: string };\n settled.error.dispose();\n if (interrupted === 'abort' || abortSignal?.aborted) return abortedResult();\n if (interrupted === 'timeout') return timedOutResult();\n return {\n success: false,\n logs,\n error: { message: err?.message ?? 'Code Mode evaluation failed', name: err?.name },\n };\n }\n\n const json = context.getString(settled.value);\n settled.value.dispose();\n const envelope = JSON.parse(json) as GuestResultEnvelope;\n return envelope.ok\n ? { success: true, result: envelope.value, logs }\n : { success: false, error: envelope.error, logs };\n } finally {\n if (timer) clearTimeout(timer);\n if (onAbort) abortSignal?.removeEventListener('abort', onAbort);\n runtime.removeInterruptHandler();\n cleanup();\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Mode — QuickJS (WebAssembly) transport
|
|
3
|
+
*
|
|
4
|
+
* Runs the model's TypeScript program inside an in-process QuickJS interpreter
|
|
5
|
+
* compiled to WebAssembly, instead of a WorkspaceSandbox. The interpreter is a
|
|
6
|
+
* real security boundary: QuickJS starts with a bare global object, so the
|
|
7
|
+
* guest has no filesystem, network, process, timer, or module access — the only
|
|
8
|
+
* capabilities are the injected `external_*` functions, which call back into the
|
|
9
|
+
* host dispatcher (allow-list enforced host-side).
|
|
10
|
+
*
|
|
11
|
+
* This is the sibling of `@mastra/isolated-vm`, and the two are interchangeable.
|
|
12
|
+
* The difference is deployment cost:
|
|
13
|
+
*
|
|
14
|
+
* - `isolated-vm` is a native addon and, on Node 20+, requires the host process
|
|
15
|
+
* to be started with `--no-node-snapshot`. Neither is available on most
|
|
16
|
+
* serverless platforms.
|
|
17
|
+
* - QuickJS is WebAssembly. No native binary, no install step, no Node flags,
|
|
18
|
+
* and it also runs in the browser. The tradeoff is speed: an interpreter in
|
|
19
|
+
* WASM is materially slower than V8, which is an acceptable trade for the
|
|
20
|
+
* tool-orchestration workloads Code Mode targets.
|
|
21
|
+
*
|
|
22
|
+
* Host calls cross the WASM boundary as JSON strings in both directions, so no
|
|
23
|
+
* host object references are ever exposed to guest code.
|
|
24
|
+
*
|
|
25
|
+
* TypeScript is stripped on the host with `ts-blank-space` — a pure-JavaScript
|
|
26
|
+
* type eraser, deliberately chosen over esbuild, which is itself a native binary
|
|
27
|
+
* and would reintroduce the very dependency this package exists to avoid.
|
|
28
|
+
*
|
|
29
|
+
* ## Concurrency
|
|
30
|
+
*
|
|
31
|
+
* `quickjs-emscripten` ships an "asyncify" build whose host functions may be
|
|
32
|
+
* `async`, which looks like the natural fit. It is not usable here: an asyncified
|
|
33
|
+
* module can only suspend for one async call at a time, and re-entering a
|
|
34
|
+
* suspended module crashes it. Code Mode's whole value is batching tool calls
|
|
35
|
+
* with `Promise.all`.
|
|
36
|
+
*
|
|
37
|
+
* So this transport uses the regular synchronous build. Each `external_*` call
|
|
38
|
+
* hands the guest a deferred promise and returns control to the host
|
|
39
|
+
* immediately, without suspending. The host settles the deferred whenever the
|
|
40
|
+
* real dispatch finishes and pumps the guest's microtask queue. Any number of
|
|
41
|
+
* calls can therefore be in flight at once.
|
|
42
|
+
*/
|
|
43
|
+
import type { CodeModeToolResult, CodeModeTransport } from '@mastra/core/tools';
|
|
44
|
+
import type { QuickJSWASMModule } from 'quickjs-emscripten';
|
|
45
|
+
export interface QuickJsCodeModeTransportOptions {
|
|
46
|
+
/**
|
|
47
|
+
* QuickJS heap limit in MiB. Exceeding it terminates the program.
|
|
48
|
+
* Default: 128.
|
|
49
|
+
*/
|
|
50
|
+
memoryLimitMb?: number;
|
|
51
|
+
/**
|
|
52
|
+
* QuickJS stack limit in bytes. Exceeding it terminates the program.
|
|
53
|
+
* Default: 1 MiB.
|
|
54
|
+
*/
|
|
55
|
+
maxStackSizeBytes?: number;
|
|
56
|
+
/**
|
|
57
|
+
* A pre-loaded QuickJS WebAssembly module.
|
|
58
|
+
*
|
|
59
|
+
* Defaults to the shared release build resolved by `getQuickJS()`, which is
|
|
60
|
+
* what most callers want. Supply your own to pin a specific variant — for
|
|
61
|
+
* example a single-file variant that suits your bundler, or the debug build
|
|
62
|
+
* with its leak sanitizer during testing.
|
|
63
|
+
*/
|
|
64
|
+
module?: QuickJSWASMModule;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Code Mode transport that executes programs in an in-process QuickJS
|
|
68
|
+
* interpreter compiled to WebAssembly.
|
|
69
|
+
*
|
|
70
|
+
* No sandbox is required — the interpreter is the execution boundary
|
|
71
|
+
* (`requiresSandbox: false`).
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* import { createCodeMode } from '@mastra/core/tools';
|
|
76
|
+
* import { QuickJsCodeModeTransport } from '@mastra/quickjs';
|
|
77
|
+
*
|
|
78
|
+
* const { tool, instructions } = createCodeMode(
|
|
79
|
+
* { tools: { getWeather, getForecast } },
|
|
80
|
+
* new QuickJsCodeModeTransport({ memoryLimitMb: 128 }),
|
|
81
|
+
* );
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare class QuickJsCodeModeTransport implements CodeModeTransport {
|
|
85
|
+
#private;
|
|
86
|
+
readonly requiresSandbox = false;
|
|
87
|
+
constructor(options?: QuickJsCodeModeTransportOptions);
|
|
88
|
+
run(opts: Parameters<CodeModeTransport['run']>[0]): Promise<CodeModeToolResult>;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEhF,OAAO,KAAK,EAAyC,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAenG,MAAM,WAAW,+BAA+B;IAC9C;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAsBD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,wBAAyB,YAAW,iBAAiB;;IAChE,QAAQ,CAAC,eAAe,SAAS;gBAMrB,OAAO,GAAE,+BAAoC;IAMnD,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAmXtF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mastra/quickjs",
|
|
3
|
+
"version": "0.0.0-buttoned-spandex-20260824090603",
|
|
4
|
+
"description": "In-process WebAssembly Code Mode transport for Mastra, backed by QuickJS — no native binaries and no Node flags required",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"quickjs-emscripten": "^0.31.0",
|
|
24
|
+
"ts-blank-space": "^0.6.1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "22.20.1",
|
|
28
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
29
|
+
"eslint": "^10.7.0",
|
|
30
|
+
"tsdown": "0.22.9",
|
|
31
|
+
"typescript": "^6.0.3",
|
|
32
|
+
"vitest": "4.1.10",
|
|
33
|
+
"zod": "^4.4.3",
|
|
34
|
+
"@internal/types-builder": "0.0.0-buttoned-spandex-20260824090603",
|
|
35
|
+
"@mastra/core": "0.0.0-buttoned-spandex-20260824090603",
|
|
36
|
+
"@internal/lint": "0.0.0-buttoned-spandex-20260824090603"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@mastra/core": "0.0.0-buttoned-spandex-20260824090603"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"CHANGELOG.md"
|
|
44
|
+
],
|
|
45
|
+
"homepage": "https://mastra.ai",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/mastra-ai/mastra.git",
|
|
49
|
+
"directory": "code-mode/quickjs"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/mastra-ai/mastra/issues"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20.19.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsdown --silent --config tsdown.config.ts",
|
|
59
|
+
"build:lib": "pnpm build",
|
|
60
|
+
"build:watch": "pnpm build --watch",
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"test:watch": "vitest watch",
|
|
64
|
+
"lint": "oxlint . && eslint .",
|
|
65
|
+
"lint:fix": "oxlint --fix . && eslint --fix ."
|
|
66
|
+
}
|
|
67
|
+
}
|