diff --git a/.github/ci-path-filters.yml b/.github/ci-path-filters.yml index 5b3e7d5a6..40fc4b9e4 100644 --- a/.github/ci-path-filters.yml +++ b/.github/ci-path-filters.yml @@ -14,6 +14,12 @@ shared: - 'crates/core/src/**' - 'crates/types/Cargo.toml' - 'crates/types/src/**' + - 'crates/worker-proto/Cargo.toml' + - 'crates/worker-proto/build.rs' + - 'crates/worker-proto/proto/**' + - 'crates/worker-proto/src/**' + - 'crates/worker/Cargo.toml' + - 'crates/worker/src/**' - 'justfile' - 'rust-toolchain.toml' @@ -30,6 +36,12 @@ rust_package: - 'crates/plugin/src/**' - 'crates/types/Cargo.toml' - 'crates/types/src/**' + - 'crates/worker-proto/Cargo.toml' + - 'crates/worker-proto/build.rs' + - 'crates/worker-proto/proto/**' + - 'crates/worker-proto/src/**' + - 'crates/worker/Cargo.toml' + - 'crates/worker/src/**' - 'crates/ffi/Cargo.toml' - 'crates/ffi/build.rs' - 'crates/ffi/cbindgen.toml' @@ -112,6 +124,7 @@ dependencies: - 'Cargo.toml' - 'about.toml' - 'crates/**/Cargo.toml' + - 'crates/worker-proto/proto/**' - 'crates/**/package.json' - 'integrations/**/package.json' - 'package.json' @@ -149,6 +162,7 @@ rust: - 'Cargo.toml' - 'crates/**/Cargo.toml' - 'crates/**/*.rs' + - 'crates/worker-proto/proto/**' - 'crates/ffi/cbindgen.toml' - 'integrations/coding-agents/**' diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 308f4589d..21ec95c79 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -375,9 +375,11 @@ jobs: version="${{ github.ref_name }}" packages=( nemo-relay-types + nemo-relay-plugin + nemo-relay-worker-proto + nemo-relay-worker nemo-relay nemo-relay-adaptive - nemo-relay-plugin nemo-relay-pii-redaction nemo-relay-ffi nemo-relay-cli diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6abdcffce..251ba97d3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -262,7 +262,7 @@ publish:artifactory:cargo: artifactory = { index = "sparse+${NEMO_RELAY_CI_ARTIFACTORY_CARGO_URL}" } EOF export CARGO_REGISTRIES_ARTIFACTORY_TOKEN="Bearer ${NEMO_RELAY_CI_ARTIFACTORY_KEY}" - export NEMO_RELAY_ARTIFACTORY_CRATE_DIRS="types core adaptive plugin pii-redaction ffi cli" + export NEMO_RELAY_ARTIFACTORY_CRATE_DIRS="types plugin worker-proto worker core adaptive pii-redaction ffi cli" crates="$( uv run --no-project python - <<'PY' diff --git a/ATTRIBUTIONS-Rust.md b/ATTRIBUTIONS-Rust.md index afca1c0cd..7f8a2084b 100644 --- a/ATTRIBUTIONS-Rust.md +++ b/ATTRIBUTIONS-Rust.md @@ -11173,6 +11173,215 @@ limitations under the License. ``` +## fixedbitset - 0.5.7 +**Repository URL**: https://github.com/petgraph/fixedbitset +**License Type(s)**: Apache-2.0 +### License: https://spdx.org/licenses/Apache-2.0.html +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +``` + ## fnv - 1.0.7 **Repository URL**: https://github.com/servo/rust-fnv **License Type(s)**: Apache-2.0 @@ -11386,7 +11595,6 @@ limitations under the License. **Repository URL**: https://github.com/orlp/foldhash **License Type(s)**: Zlib ### License: https://spdx.org/licenses/Zlib.html -### License File: LICENSE ``` Copyright (c) 2024 Orson Peters @@ -14198,9 +14406,8 @@ DEALINGS IN THE SOFTWARE. ## hashbrown - 0.15.5 **Repository URL**: https://github.com/rust-lang/hashbrown -**License Type(s)**: MIT OR Apache-2.0 -### License: https://spdx.org/licenses/ -### License File: LICENSE-APACHE +**License Type(s)**: Apache-2.0 +### License: https://spdx.org/licenses/Apache-2.0.html ``` Apache License Version 2.0, January 2004 @@ -14403,35 +14610,7 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -``` - -### License File: LICENSE-MIT -``` -Copyright (c) 2016 Amanieu d'Antras - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. ``` ## hashbrown - 0.17.0 @@ -21293,19 +21472,228 @@ THE SOFTWARE. ``` -## napi - 2.16.17 -**Repository URL**: https://github.com/napi-rs/napi-rs -**License Type(s)**: MIT -### License: https://spdx.org/licenses/MIT.html +## multimap - 0.10.1 +**Repository URL**: https://github.com/havarnov/multimap +**License Type(s)**: Apache-2.0 +### License: https://spdx.org/licenses/Apache-2.0.html ``` -MIT License + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Copyright (c) +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +``` + +## napi - 2.16.17 +**Repository URL**: https://github.com/napi-rs/napi-rs +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial @@ -24303,38 +24691,247 @@ limitations under the License. ``` -## pin-project - 1.1.11 -**Repository URL**: https://github.com/taiki-e/pin-project +## petgraph - 0.8.3 +**Repository URL**: https://github.com/petgraph/petgraph **License Type(s)**: Apache-2.0 ### License: https://spdx.org/licenses/Apache-2.0.html ``` -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +``` + +## pin-project - 1.1.11 +**Repository URL**: https://github.com/taiki-e/pin-project +**License Type(s)**: Apache-2.0 +### License: https://spdx.org/licenses/Apache-2.0.html +``` +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. @@ -24892,213 +25489,83 @@ limitations under the License. ## prettyplease - 0.2.37 **Repository URL**: https://github.com/dtolnay/prettyplease -**License Type(s)**: MIT OR Apache-2.0 -### License: https://spdx.org/licenses/ -### License File: LICENSE-APACHE +**License Type(s)**: Apache-2.0 +### License: https://spdx.org/licenses/Apache-2.0.html ``` - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS -``` -### License File: LICENSE-MIT -``` -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: +APPENDIX: How to apply the Apache License to your work. -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. ``` ## proc-macro2 - 1.0.106 @@ -25391,7 +25858,7 @@ limitations under the License. ``` -## prost-derive - 0.14.3 +## prost-build - 0.14.3 **Repository URL**: https://github.com/tokio-rs/prost **License Type(s)**: Apache-2.0 ### License: https://spdx.org/licenses/Apache-2.0.html @@ -25457,146 +25924,1036 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +``` + +## prost-derive - 0.14.3 +**Repository URL**: https://github.com/tokio-rs/prost +**License Type(s)**: Apache-2.0 +### License: https://spdx.org/licenses/Apache-2.0.html +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +``` + +## prost-types - 0.14.3 +**Repository URL**: https://github.com/tokio-rs/prost +**License Type(s)**: Apache-2.0 +### License: https://spdx.org/licenses/Apache-2.0.html +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +``` + +## protoc-bin-vendored - 3.2.0 +**Repository URL**: https://github.com/stepancheg/rust-protoc-bin-vendored/ +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +``` + +## protoc-bin-vendored-linux-aarch_64 - 3.2.0 +**Repository URL**: https://github.com/stepancheg/rust-protoc-bin-vendored/ +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +``` + +## protoc-bin-vendored-linux-ppcle_64 - 3.2.0 +**Repository URL**: https://github.com/stepancheg/rust-protoc-bin-vendored/ +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +``` + +## protoc-bin-vendored-linux-s390_64 - 3.2.0 +**Repository URL**: https://github.com/stepancheg/rust-protoc-bin-vendored/ +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +``` + +## protoc-bin-vendored-linux-x86_32 - 3.2.0 +**Repository URL**: https://github.com/stepancheg/rust-protoc-bin-vendored/ +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +``` + +## protoc-bin-vendored-linux-x86_64 - 3.2.0 +**Repository URL**: https://github.com/stepancheg/rust-protoc-bin-vendored/ +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +``` + +## protoc-bin-vendored-macos-aarch_64 - 3.2.0 +**Repository URL**: https://github.com/stepancheg/rust-protoc-bin-vendored/ +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +``` + +## protoc-bin-vendored-macos-x86_64 - 3.2.0 +**Repository URL**: https://github.com/stepancheg/rust-protoc-bin-vendored/ +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +``` + +## protoc-bin-vendored-win32 - 3.2.0 +**Repository URL**: https://github.com/stepancheg/rust-protoc-bin-vendored/ +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +``` + +## pulldown-cmark - 0.13.4 +**Repository URL**: https://github.com/raphlinus/pulldown-cmark +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +The MIT License + +Copyright 2015 Google Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +``` + +## pulldown-cmark-to-cmark - 22.0.0 +**Repository URL**: https://github.com/Byron/pulldown-cmark-to-cmark +**License Type(s)**: Apache-2.0 +### License: https://spdx.org/licenses/Apache-2.0.html +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. -END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS -APPENDIX: How to apply the Apache License to your work. + APPENDIX: How to apply the Apache License to your work. - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. -Copyright [yyyy] [name of copyright owner] + Copyright 2018 "Sebastian Thiel ", "Dylan Owen ", "Alessandro Ogier ", "Zixian Cai <2891235+caizixian@users.noreply.github.com>" -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. ``` @@ -36866,18 +38223,45 @@ SOFTWARE. you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +``` + +## tonic - 0.14.5 +**Repository URL**: https://github.com/hyperium/tonic +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +Copyright (c) 2025 Lucio Franco + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. ``` -## tonic - 0.14.5 +## tonic-build - 0.14.6 **Repository URL**: https://github.com/hyperium/tonic **License Type(s)**: MIT ### License: https://spdx.org/licenses/MIT.html @@ -36930,6 +38314,32 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. ``` +## tonic-prost-build - 0.14.6 +**Repository URL**: https://github.com/hyperium/tonic +**License Type(s)**: MIT +### License: https://spdx.org/licenses/MIT.html +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +``` + ## tower - 0.5.3 **Repository URL**: https://github.com/tower-rs/tower **License Type(s)**: MIT @@ -38025,6 +39435,215 @@ See the License for the specific language governing permissions and limitations under the License. ``` +## unicase - 2.9.0 +**Repository URL**: https://github.com/seanmonstar/unicase +**License Type(s)**: Apache-2.0 +### License: https://spdx.org/licenses/Apache-2.0.html +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +``` + ## unicode-ident - 1.0.24 **Repository URL**: https://github.com/dtolnay/unicode-ident **License Type(s)**: Apache-2.0 diff --git a/Cargo.lock b/Cargo.lock index c44679e4d..091a88c93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -628,6 +628,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "fnv" version = "1.0.7" @@ -1266,6 +1272,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "napi" version = "2.16.17" @@ -1535,6 +1547,37 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "nemo-relay-worker" +version = "0.5.0" +dependencies = [ + "futures-util", + "hyper-util", + "nemo-relay-types", + "nemo-relay-worker-proto", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tonic", + "tower", +] + +[[package]] +name = "nemo-relay-worker-proto" +version = "0.5.0" +dependencies = [ + "prost", + "prost-build", + "protoc-bin-vendored", + "serde", + "serde_json", + "tonic", + "tonic-prost", + "tonic-prost-build", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1763,6 +1806,17 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + [[package]] name = "pin-project" version = "1.1.11" @@ -1842,6 +1896,27 @@ dependencies = [ "prost-derive", ] +[[package]] +name = "prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "regex", + "syn", + "tempfile", +] + [[package]] name = "prost-derive" version = "0.14.3" @@ -1855,6 +1930,99 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + +[[package]] +name = "protoc-bin-vendored" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" +dependencies = [ + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-s390_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-aarch_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", +] + +[[package]] +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" + +[[package]] +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" + +[[package]] +name = "protoc-bin-vendored-linux-s390_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" + +[[package]] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" + +[[package]] +name = "protoc-bin-vendored-linux-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" + +[[package]] +name = "protoc-bin-vendored-macos-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" + +[[package]] +name = "protoc-bin-vendored-macos-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" + +[[package]] +name = "protoc-bin-vendored-win32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" + +[[package]] +name = "pulldown-cmark" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" +dependencies = [ + "pulldown-cmark", +] + [[package]] name = "pyo3" version = "0.28.3" @@ -2857,8 +3025,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", + "axum", "base64", "bytes", + "h2", "http", "http-body", "http-body-util", @@ -2867,6 +3037,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", + "socket2", "sync_wrapper", "tokio", "tokio-stream", @@ -2876,6 +3047,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tonic-prost" version = "0.14.5" @@ -2887,6 +3070,22 @@ dependencies = [ "tonic", ] +[[package]] +name = "tonic-prost-build" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn", + "tempfile", + "tonic-build", +] + [[package]] name = "tower" version = "0.5.3" @@ -3019,6 +3218,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/Cargo.toml b/Cargo.toml index 355411fa6..21bf4657e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ members = [ "crates/core", "crates/types", "crates/plugin", + "crates/worker-proto", + "crates/worker", "crates/adaptive", "crates/pii-redaction", "crates/cli", @@ -28,6 +30,8 @@ repository = "https://github.com/NVIDIA/NeMo-Relay" nemo-relay = { version = "0.5.0", path = "crates/core", default-features = false } nemo-relay-types = { version = "0.5.0", path = "crates/types" } nemo-relay-plugin = { version = "0.5.0", path = "crates/plugin" } +nemo-relay-worker-proto = { version = "0.5.0", path = "crates/worker-proto" } +nemo-relay-worker = { version = "0.5.0", path = "crates/worker" } nemo-relay-adaptive = { version = "0.5.0", path = "crates/adaptive" } nemo-relay-pii-redaction = { version = "0.5.0", path = "crates/pii-redaction" } nemo-relay-ffi = { version = "0.5.0", path = "crates/ffi" } diff --git a/RELEASING.md b/RELEASING.md index e711940a9..1f2dc130d 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -30,7 +30,7 @@ The release pipeline publishes these package surfaces from a tag push: | Ecosystem | Published Surface | |---|---| -| crates.io | `nemo-relay-types`, `nemo-relay`, `nemo-relay-adaptive`, `nemo-relay-plugin`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, `nemo-relay-cli` | +| crates.io | `nemo-relay-types`, `nemo-relay-plugin`, `nemo-relay-worker-proto`, `nemo-relay-worker`, `nemo-relay`, `nemo-relay-adaptive`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, `nemo-relay-cli` | | PyPI | `nemo-relay` | | npm | `nemo-relay-node`, `nemo-relay-openclaw`, `nemo-relay-wasm` | | GitHub Releases | CLI binaries and `SHA256SUMS` | @@ -51,9 +51,10 @@ NeMo Relay versions are anchored on the workspace SemVer in the repository root - The root `Cargo.toml` `workspace.package.version` is the canonical release version for the Rust workspace. - The root `Cargo.toml` `workspace.dependencies` entries for - `nemo-relay-types`, `nemo-relay`, `nemo-relay-plugin`, - `nemo-relay-adaptive`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, and - `nemo-relay-cli` must stay aligned with that same version. + `nemo-relay-types`, `nemo-relay-plugin`, `nemo-relay-worker-proto`, + `nemo-relay-worker`, `nemo-relay`, `nemo-relay-adaptive`, + `nemo-relay-pii-redaction`, `nemo-relay-ffi`, and `nemo-relay-cli` must + stay aligned with that same version. - `crates/node/package.json` carries the base npm version for the Node.js package. The repository-root `package-lock.json` carries the npm workspace lock entries and must be updated with it. @@ -133,8 +134,9 @@ Before you create a release tag, confirm the following: 3. The working tree you use for local validation is clean or disposable. 4. Registry credentials and repository settings are in place: - GitHub Actions `id-token: write` access for the top-level crates.io publish job - - crates.io trusted publishers for `nemo-relay-types`, `nemo-relay`, - `nemo-relay-adaptive`, `nemo-relay-plugin`, `nemo-relay-pii-redaction`, + - crates.io trusted publishers for `nemo-relay-types`, + `nemo-relay-plugin`, `nemo-relay-worker-proto`, `nemo-relay-worker`, + `nemo-relay`, `nemo-relay-adaptive`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, and `nemo-relay-cli` are configured for the top-level [`.github/workflows/ci.yaml`](.github/workflows/ci.yaml) workflow - GitHub Actions `id-token: write` access is available for the top-level npm publish job @@ -155,9 +157,9 @@ The helper updates: 1. The root [`Cargo.toml`](Cargo.toml) workspace version. 2. The root [`Cargo.toml`](Cargo.toml) `workspace.dependencies` versions for - `nemo-relay-types`, `nemo-relay`, `nemo-relay-plugin`, - `nemo-relay-adaptive`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, and - `nemo-relay-cli`. + `nemo-relay-types`, `nemo-relay-plugin`, `nemo-relay-worker-proto`, + `nemo-relay-worker`, `nemo-relay`, `nemo-relay-adaptive`, + `nemo-relay-pii-redaction`, `nemo-relay-ffi`, and `nemo-relay-cli`. 3. [`crates/node/package.json`](crates/node/package.json) and the `crates/node` entry in the root [`package-lock.json`](package-lock.json) to the same release version. @@ -247,8 +249,9 @@ The release pipeline then: 4. Publishes packages from the top-level workflow after the reusable packaging jobs complete: - `publish-rust` stamps Cargo workspace versions from the release tag, then - runs `cargo publish --package` for `nemo-relay-types`, `nemo-relay`, - `nemo-relay-adaptive`, `nemo-relay-plugin`, `nemo-relay-pii-redaction`, + runs `cargo publish --package` for `nemo-relay-types`, + `nemo-relay-plugin`, `nemo-relay-worker-proto`, `nemo-relay-worker`, + `nemo-relay`, `nemo-relay-adaptive`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, and `nemo-relay-cli` through trusted publishing from the top-level workflow - `publish-python` uploads the wheel artifacts to PyPI with trusted @@ -315,9 +318,10 @@ for that tag. After the release is live, verify: -1. The `nemo-relay-types`, `nemo-relay`, `nemo-relay-adaptive`, - `nemo-relay-plugin`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, and - `nemo-relay-cli` crates are visible on crates.io. +1. The `nemo-relay-types`, `nemo-relay-plugin`, `nemo-relay-worker-proto`, + `nemo-relay-worker`, `nemo-relay`, `nemo-relay-adaptive`, + `nemo-relay-pii-redaction`, `nemo-relay-ffi`, and `nemo-relay-cli` crates + are visible on crates.io. 2. The `nemo-relay` wheel is visible on PyPI. 3. The `nemo-relay-node`, `nemo-relay-openclaw`, and `nemo-relay-wasm` packages are visible on npm. diff --git a/about.toml b/about.toml index 0d2dc5079..847c1d97a 100644 --- a/about.toml +++ b/about.toml @@ -13,6 +13,7 @@ accepted = [ "MIT", "MPL-2.0", "Unicode-3.0", + "Zlib", ] ignore-build-dependencies = false diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 000000000..cd243b169 --- /dev/null +++ b/buf.yaml @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +version: v2 +modules: + - path: crates/worker-proto/proto +lint: + use: + - STANDARD diff --git a/codecov.yml b/codecov.yml index 851f4f9a3..f79453093 100644 --- a/codecov.yml +++ b/codecov.yml @@ -54,6 +54,28 @@ component_management: threshold: 0.5% base: auto if_ci_failed: error + - component_id: plugin_sdk + name: Plugin SDK + paths: + - "crates/plugin/src" + - "crates/worker-proto/src" + - "crates/worker/src" + statuses: + - type: project + target: 95% + threshold: 0.5% + base: auto + if_ci_failed: error + - component_id: shared_types + name: Shared DTO Types + paths: + - "crates/types/src" + statuses: + - type: project + target: auto + threshold: 0.5% + base: auto + if_ci_failed: error - component_id: cli name: CLI paths: diff --git a/crates/cli/tests/cli_tests.rs b/crates/cli/tests/cli_tests.rs index 100c4721f..291aab3e0 100644 --- a/crates/cli/tests/cli_tests.rs +++ b/crates/cli/tests/cli_tests.rs @@ -77,7 +77,7 @@ kind = "worker" [compat] relay = "0.5" -worker_protocol = "1" +worker_protocol = "grpc-v1" [defaults] enabled = false diff --git a/crates/cli/tests/coverage/config_tests.rs b/crates/cli/tests/coverage/config_tests.rs index 35824947a..d69c03c91 100644 --- a/crates/cli/tests/coverage/config_tests.rs +++ b/crates/cli/tests/coverage/config_tests.rs @@ -75,7 +75,7 @@ kind = "worker" [compat] relay = "0.1" -worker_protocol = "1" +worker_protocol = "grpc-v1" [defaults] enabled = false diff --git a/crates/cli/tests/coverage/doctor_tests.rs b/crates/cli/tests/coverage/doctor_tests.rs index 06485da99..1c78093c2 100644 --- a/crates/cli/tests/coverage/doctor_tests.rs +++ b/crates/cli/tests/coverage/doctor_tests.rs @@ -533,13 +533,14 @@ async fn collect_agents_filters_target_and_records_version() { std::fs::write(&codex, "#!/bin/sh\nprintf 'codex 1.2.3\\n'\n").unwrap(); make_executable(&codex); - let _env = EnvScope::set(&[("PATH", Some(temp.path().as_os_str()))]); - let resolved = ResolvedConfig::default(); + let mut resolved = ResolvedConfig::default(); + resolved.agents.codex.command = Some(codex.to_string_lossy().into_owned()); let agents = collect_agents(Some(CodingAgent::Codex), &resolved).await; assert_eq!(agents.len(), 1); assert_eq!(agents[0].name, "codex"); - assert_eq!(agents[0].status, Status::Warn); + assert_eq!(agents[0].status, Status::Pass); + assert_eq!(agents[0].path.as_deref(), Some(codex.as_path())); assert_eq!(agents[0].version.as_deref(), Some("codex 1.2.3")); } diff --git a/crates/core/src/plugin/dynamic.rs b/crates/core/src/plugin/dynamic.rs index cd86e72fd..a15af4081 100644 --- a/crates/core/src/plugin/dynamic.rs +++ b/crates/core/src/plugin/dynamic.rs @@ -48,6 +48,10 @@ pub enum DynamicPluginKind { pub enum WorkerRuntime { /// Python worker runtime. Python, + /// Rust worker executable runtime. + Rust, + /// Generic executable worker runtime. + Command, } /// Relay-enforced capability declared by a dynamic plugin. diff --git a/crates/core/src/plugin/dynamic/manifest.rs b/crates/core/src/plugin/dynamic/manifest.rs index f71bf8daf..f8cac46a8 100644 --- a/crates/core/src/plugin/dynamic/manifest.rs +++ b/crates/core/src/plugin/dynamic/manifest.rs @@ -19,6 +19,8 @@ use super::{ }; use crate::plugin::{PluginError, Result}; +const SUPPORTED_WORKER_PROTOCOL: &str = "grpc-v1"; + /// Authored `relay-plugin.toml` manifest. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -431,6 +433,8 @@ fn required_trimmed_string<'a>(value: Option<&'a str>, field: &str) -> Result<&' "{field} must not be empty" ))); } + // Validate trimmed content while returning the original slice so each caller + // can decide whether preserving or normalizing whitespace is correct. Ok(value) } @@ -511,7 +515,7 @@ fn validate_load_shape(kind: DynamicPluginKind, load: &DynamicPluginManifestLoad (DynamicPluginKind::Worker, DynamicPluginManifestLoad::Worker(load)) => { required_trimmed_string(load.entrypoint.as_deref(), "load.entrypoint")?; match load.runtime { - Some(WorkerRuntime::Python) => {} + Some(WorkerRuntime::Python | WorkerRuntime::Rust | WorkerRuntime::Command) => {} None => { return Err(PluginError::InvalidConfig( "worker plugins must declare load.runtime".into(), @@ -570,7 +574,15 @@ fn validate_compat_shape( } } DynamicPluginKind::Worker => { - required_trimmed_string(compat.worker_protocol.as_deref(), "compat.worker_protocol")?; + let worker_protocol = required_trimmed_string( + compat.worker_protocol.as_deref(), + "compat.worker_protocol", + )?; + if worker_protocol.trim() != SUPPORTED_WORKER_PROTOCOL { + return Err(PluginError::InvalidConfig(format!( + "worker plugins must declare compat.worker_protocol = \"{SUPPORTED_WORKER_PROTOCOL}\"" + ))); + } if compat.native_api.is_some() { return Err(PluginError::InvalidConfig( "worker plugins must not declare compat.native_api".into(), diff --git a/crates/core/tests/unit/plugin_dynamic_tests.rs b/crates/core/tests/unit/plugin_dynamic_tests.rs index fde4b7ba5..4f9465e7c 100644 --- a/crates/core/tests/unit/plugin_dynamic_tests.rs +++ b/crates/core/tests/unit/plugin_dynamic_tests.rs @@ -43,7 +43,7 @@ fn sample_record() -> DynamicPluginRecord { }, compatibility: DynamicPluginCompatibility::Worker(DynamicPluginWorkerCompatibility { relay: ">=0.1.0,<0.2.0".into(), - worker_protocol: "1".into(), + worker_protocol: "grpc-v1".into(), }), load: DynamicPluginLoadContract::Worker(DynamicPluginWorkerLoadContract { runtime: WorkerRuntime::Python, @@ -265,7 +265,7 @@ fn registry_rejects_missing_raw_record_relay_compatibility() { let mut record = sample_record(); record.compatibility = DynamicPluginCompatibility::Worker(DynamicPluginWorkerCompatibility { relay: String::new(), - worker_protocol: "1".into(), + worker_protocol: "grpc-v1".into(), }); let err = registry @@ -286,7 +286,7 @@ fn registry_add_canonicalizes_required_record_strings_before_storage() { record.metadata.id = " acme.guardrails.pii ".into(); record.compatibility = DynamicPluginCompatibility::Worker(DynamicPluginWorkerCompatibility { relay: " >=0.1.0,<0.2.0 ".into(), - worker_protocol: " 1 ".into(), + worker_protocol: " grpc-v1 ".into(), }); record.load = DynamicPluginLoadContract::Worker(DynamicPluginWorkerLoadContract { runtime: WorkerRuntime::Python, @@ -299,7 +299,7 @@ fn registry_add_canonicalizes_required_record_strings_before_storage() { stored.compatibility, DynamicPluginCompatibility::Worker(DynamicPluginWorkerCompatibility { relay: ">=0.1.0,<0.2.0".into(), - worker_protocol: "1".into(), + worker_protocol: "grpc-v1".into(), }) ); assert_eq!( @@ -478,7 +478,7 @@ kind = "worker" [compat] relay = ">=0.1.0,<0.2.0" -worker_protocol = "1" +worker_protocol = "grpc-v1" [defaults] enabled = false @@ -587,7 +587,7 @@ kind = "worker" [compat] relay = ">=0.1.0,<0.2.0" -worker_protocol = "1" +worker_protocol = "grpc-v1" [defaults] enabled = false @@ -626,7 +626,7 @@ kind = "worker" [compat] relay = " >=0.1.0,<0.2.0 " -worker_protocol = " 1 " +worker_protocol = " grpc-v1 " [defaults] enabled = false @@ -649,7 +649,7 @@ entrypoint = " acme_guardrails.plugin:register " record.compatibility, DynamicPluginCompatibility::Worker(DynamicPluginWorkerCompatibility { relay: ">=0.1.0,<0.2.0".into(), - worker_protocol: "1".into(), + worker_protocol: "grpc-v1".into(), }) ); assert_eq!( @@ -661,6 +661,85 @@ entrypoint = " acme_guardrails.plugin:register " ); } +#[test] +fn manifest_parse_accepts_rust_and_command_worker_runtimes() { + for (runtime, expected) in [ + ("rust", WorkerRuntime::Rust), + ("command", WorkerRuntime::Command), + ] { + let manifest = DynamicPluginManifest::parse_toml(&format!( + r#" +manifest_version = 1 + +[plugin] +id = "acme.guardrails.{runtime}" +kind = "worker" + +[compat] +relay = ">=0.1.0,<0.2.0" +worker_protocol = "grpc-v1" + +[defaults] +enabled = false + +[capabilities] +items = ["plugin_worker"] + +[load] +runtime = "{runtime}" +entrypoint = "acme_guardrails.plugin:register" +"# + )) + .expect("parse worker manifest"); + + let record = manifest + .into_record(None) + .expect("manifest converts into record"); + assert_eq!( + record.load, + DynamicPluginLoadContract::Worker(DynamicPluginWorkerLoadContract { + runtime: expected, + entrypoint: "acme_guardrails.plugin:register".into(), + }) + ); + } +} + +#[test] +fn manifest_rejects_unsupported_worker_protocol() { + let err = DynamicPluginManifest::parse_toml( + r#" +manifest_version = 1 + +[plugin] +id = "acme.guardrails.future-worker" +kind = "worker" + +[compat] +relay = ">=0.1.0,<0.2.0" +worker_protocol = "grpc-v2" + +[defaults] +enabled = false + +[capabilities] +items = ["plugin_worker"] + +[load] +runtime = "python" +entrypoint = "acme_guardrails.plugin:register" +"#, + ) + .expect_err("unsupported worker protocol should fail"); + + match err { + PluginError::InvalidConfig(message) => { + assert!(message.contains("grpc-v1"), "{message}"); + } + other => panic!("unexpected worker protocol error: {other}"), + } +} + #[test] fn manifest_parse_and_conversion_supports_rust_dynamic_lane() { let manifest = @@ -768,7 +847,7 @@ kind = "worker" [compat] relay = ">=0.1.0,<0.2.0" -worker_protocol = "1" +worker_protocol = "grpc-v1" [defaults] enabled = false @@ -981,7 +1060,7 @@ kind = "worker" [compat] relay = ">=0.1.0,<0.2.0" -worker_protocol = "1" +worker_protocol = "grpc-v1" [defaults] enabled = true @@ -1016,7 +1095,7 @@ kind = "worker" [compat] relay = ">=0.1.0,<0.2.0" -worker_protocol = "1" +worker_protocol = "grpc-v1" [defaults] enabled = false @@ -1150,7 +1229,7 @@ kind = "worker" [compat] relay = ">=0.1.0,<0.2.0" -worker_protocol = "1" +worker_protocol = "grpc-v1" [defaults] enabled = false @@ -1185,7 +1264,7 @@ kind = "worker" [compat] relay = ">=0.1.0,<0.2.0" -worker_protocol = "1" +worker_protocol = "grpc-v1" [defaults] enabled = false @@ -1223,7 +1302,7 @@ kind = "worker" [compat] relay = ">=0.1.0,<0.2.0" -worker_protocol = "1" +worker_protocol = "grpc-v1" [defaults] enabled = false diff --git a/crates/plugin/src/lib.rs b/crates/plugin/src/lib.rs index 1d764b852..31f0c4e40 100644 --- a/crates/plugin/src/lib.rs +++ b/crates/plugin/src/lib.rs @@ -845,10 +845,10 @@ impl LlmStream { if self.finished { return Ok(None); } - let Some(next) = self.raw.next else { - self.finished = true; - return Err("LLM stream next callback was null".into()); - }; + let next = self + .raw + .next + .expect("LLM stream next callback is validated on construction"); let mut out = ptr::null_mut(); let status = unsafe { next(self.raw.user_data, &mut out) }; match status { @@ -1860,13 +1860,9 @@ fn finish_typed_registration( } fn status_error(host: &NemoRelayNativeHostApiV1, status: NemoRelayStatus, label: &str) -> String { - match status { - NemoRelayStatus::Ok => format!("{label} succeeded"), - other => { - set_last_error(host, &format!("{label} failed: {other:?}")); - format!("{label} failed: {other:?}") - } - } + debug_assert_ne!(status, NemoRelayStatus::Ok); + set_last_error(host, &format!("{label} failed: {status:?}")); + format!("{label} failed: {status:?}") } fn callback_error(host: &NemoRelayNativeHostApiV1, message: String) -> NemoRelayStatus { @@ -2607,13 +2603,7 @@ fn write_json( return NemoRelayStatus::NullPointer; } unsafe { *out = ptr::null_mut() }; - let json = match serde_json::to_value(value) { - Ok(value) => value, - Err(error) => { - set_last_error(host, &format!("failed to serialize JSON: {error}")); - return NemoRelayStatus::Internal; - } - }; + let json = serde_json::to_value(value).expect("Relay DTOs and serde_json::Value serialize"); let Some(handle) = HostString::from_json(host, &json) else { set_last_error(host, "failed to allocate host string"); return NemoRelayStatus::Internal; diff --git a/crates/plugin/tests/typed_callbacks.rs b/crates/plugin/tests/typed_callbacks.rs index 214827c13..d3a502ffb 100644 --- a/crates/plugin/tests/typed_callbacks.rs +++ b/crates/plugin/tests/typed_callbacks.rs @@ -2143,7 +2143,7 @@ fn typed_tool_intercept_registration_rewrites_json() { assert_eq!(registration.name, "tool"); assert_eq!(registration.priority, 17); assert!(registration.break_chain); - let name = host_string(&host, "tool"); + let name = host_string(&host, ""); let payload = json_host_string(&host, json!({ "input": "value" })); let mut out = ptr::null_mut(); let status = unsafe { @@ -2416,6 +2416,23 @@ fn typed_callbacks_reject_null_abi_pointers_before_decoding_inputs() { registration.free(); } + let mut ctx = test_context(&host); + ctx.register_tool_request_intercept("tool", 0, false, |_name, value| Ok(value)) + .unwrap(); + let registration = take_tool_json_registration(); + let name = host_string(&host, "tool"); + let payload = json_host_string(&host, json!({})); + let mut out = ptr::null_mut(); + assert_eq!( + unsafe { (registration.cb)(ptr::null_mut(), name, payload, &mut out) }, + NemoRelayStatus::NullPointer + ); + unsafe { + (host.string_free)(name); + (host.string_free)(payload); + registration.free(); + } + let mut ctx = test_context(&host); ctx.register_tool_conditional_execution_guardrail("tool-conditional", 0, |_name, _value| { Ok(None) @@ -2712,6 +2729,489 @@ fn typed_callbacks_reject_null_abi_pointers_before_decoding_inputs() { } } +#[test] +fn typed_callbacks_report_invalid_json_for_each_decoder_family() { + let _guard = begin_test(); + let host = test_host(); + + let mut ctx = test_context(&host); + ctx.register_subscriber("events", |_event: &Event| {}) + .unwrap(); + let registration = take_subscriber_registration(); + let event = host_string(&host, "{not json"); + assert_eq!( + unsafe { (registration.cb)(registration.user_data as *mut c_void, event) }, + NemoRelayStatus::InvalidJson + ); + unsafe { + (host.string_free)(event); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_tool_sanitize_request_guardrail("tool-sanitize", 0, |_name, value| value) + .unwrap(); + let registration = take_tool_json_registration(); + let name = host_string(&host, "tool"); + let payload = host_string(&host, "{not json"); + let stale_out = host_string(&host, r#"{"stale":true}"#); + let mut out = stale_out; + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + payload, + &mut out, + ) + }, + NemoRelayStatus::InvalidJson + ); + assert!(out.is_null()); + unsafe { + (host.string_free)(stale_out); + (host.string_free)(name); + (host.string_free)(payload); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_tool_conditional_execution_guardrail("tool-conditional", 0, |_name, _value| { + Ok(None) + }) + .unwrap(); + let registration = take_tool_conditional_registration(); + let name = host_string(&host, "tool"); + let payload = host_string(&host, "{not json"); + let stale_reason = host_string(&host, "stale"); + let mut reason = stale_reason; + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + payload, + &mut reason, + ) + }, + NemoRelayStatus::InvalidJson + ); + assert!(reason.is_null()); + unsafe { + (host.string_free)(stale_reason); + (host.string_free)(name); + (host.string_free)(payload); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_tool_execution_intercept("tool-exec", 0, |_name, value, _next| Ok(value)) + .unwrap(); + let registration = take_tool_execution_registration(); + let name = host_string(&host, "tool"); + let payload = host_string(&host, "{not json"); + let stale_out = host_string(&host, r#"{"stale":true}"#); + let mut out = stale_out; + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + payload, + fake_tool_next, + ptr::null_mut(), + &mut out, + ) + }, + NemoRelayStatus::InvalidJson + ); + assert!(out.is_null()); + unsafe { + (host.string_free)(stale_out); + (host.string_free)(name); + (host.string_free)(payload); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_llm_sanitize_request_guardrail("llm-request", 0, |request| request) + .unwrap(); + let registration = take_llm_request_registration(); + let request = host_string(&host, "{not json"); + let stale_out = host_string(&host, r#"{"stale":true}"#); + let mut out = stale_out; + assert_eq!( + unsafe { (registration.cb)(registration.user_data as *mut c_void, request, &mut out) }, + NemoRelayStatus::InvalidJson + ); + assert!(out.is_null()); + unsafe { + (host.string_free)(stale_out); + (host.string_free)(request); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_llm_sanitize_response_guardrail("llm-response", 0, |value| value) + .unwrap(); + let registration = take_llm_json_registration(); + let response = host_string(&host, "{not json"); + let stale_out = host_string(&host, r#"{"stale":true}"#); + let mut out = stale_out; + assert_eq!( + unsafe { (registration.cb)(registration.user_data as *mut c_void, response, &mut out) }, + NemoRelayStatus::InvalidJson + ); + assert!(out.is_null()); + unsafe { + (host.string_free)(stale_out); + (host.string_free)(response); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_llm_conditional_execution_guardrail("llm-conditional", 0, |_request| Ok(None)) + .unwrap(); + let registration = take_llm_conditional_registration(); + let request = host_string(&host, "{not json"); + let stale_reason = host_string(&host, "stale"); + let mut reason = stale_reason; + assert_eq!( + unsafe { (registration.cb)(registration.user_data as *mut c_void, request, &mut reason) }, + NemoRelayStatus::InvalidJson + ); + assert!(reason.is_null()); + unsafe { + (host.string_free)(stale_reason); + (host.string_free)(request); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_llm_request_intercept("llm-request", 0, false, |_name, request, ann| { + Ok((request, ann)) + }) + .unwrap(); + let registration = take_llm_request_intercept_registration(); + let name = host_string(&host, "llm"); + let bad_request = host_string(&host, "{not json"); + let mut out_request = ptr::null_mut(); + let mut out_annotated = ptr::null_mut(); + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + bad_request, + ptr::null(), + &mut out_request, + &mut out_annotated, + ) + }, + NemoRelayStatus::InvalidJson + ); + let request = json_host_string(&host, serde_json::to_value(test_llm_request()).unwrap()); + let bad_annotation = host_string(&host, "{not json"); + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + request, + bad_annotation, + &mut out_request, + &mut out_annotated, + ) + }, + NemoRelayStatus::InvalidJson + ); + unsafe { + (host.string_free)(name); + (host.string_free)(bad_request); + (host.string_free)(request); + (host.string_free)(bad_annotation); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_llm_execution_intercept( + "llm-exec", + 0, + |_name, request, _next| Ok(request.content), + ) + .unwrap(); + let registration = take_llm_execution_registration(); + let name = host_string(&host, "llm"); + let request = host_string(&host, "{not json"); + let stale_out = host_string(&host, r#"{"stale":true}"#); + let mut out = stale_out; + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + request, + failing_llm_next, + ptr::null_mut(), + &mut out, + ) + }, + NemoRelayStatus::InvalidJson + ); + assert!(out.is_null()); + unsafe { + (host.string_free)(stale_out); + (host.string_free)(name); + (host.string_free)(request); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_llm_stream_execution_intercept("llm-stream", 0, |_name, _request, _next| { + Ok(Box::new(std::iter::empty())) + }) + .unwrap(); + let registration = take_llm_stream_execution_registration(); + let name = host_string(&host, "llm"); + let request = host_string(&host, "{not json"); + let mut stream = NemoRelayNativeLlmStreamV1::default(); + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + request, + fake_llm_stream_next, + ptr::null_mut(), + &mut stream, + ) + }, + NemoRelayStatus::InvalidJson + ); + unsafe { + (host.string_free)(name); + (host.string_free)(request); + registration.free(); + } +} + +#[test] +fn typed_callbacks_map_additional_callback_errors() { + let _guard = begin_test(); + let host = test_host(); + + let mut ctx = test_context(&host); + ctx.register_tool_conditional_execution_guardrail("tool-conditional", 0, |_name, _value| { + Err("tool conditional failed".into()) + }) + .unwrap(); + let registration = take_tool_conditional_registration(); + let name = host_string(&host, "tool"); + let args = json_host_string(&host, json!({})); + let mut reason = ptr::null_mut(); + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + args, + &mut reason, + ) + }, + NemoRelayStatus::Internal + ); + assert_eq!( + LAST_ERROR.lock().unwrap().as_deref(), + Some("tool conditional failed") + ); + unsafe { + (host.string_free)(name); + (host.string_free)(args); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_tool_execution_intercept("tool-exec", 0, |_name, _value, _next| { + Err("tool execution failed".into()) + }) + .unwrap(); + let registration = take_tool_execution_registration(); + let name = host_string(&host, "tool"); + let args = json_host_string(&host, json!({})); + let mut out = ptr::null_mut(); + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + args, + fake_tool_next, + ptr::null_mut(), + &mut out, + ) + }, + NemoRelayStatus::Internal + ); + assert_eq!( + LAST_ERROR.lock().unwrap().as_deref(), + Some("tool execution failed") + ); + unsafe { + (host.string_free)(name); + (host.string_free)(args); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_llm_conditional_execution_guardrail("llm-conditional", 0, |_request| { + Err("llm conditional failed".into()) + }) + .unwrap(); + let registration = take_llm_conditional_registration(); + let request = json_host_string(&host, serde_json::to_value(test_llm_request()).unwrap()); + let mut reason = ptr::null_mut(); + assert_eq!( + unsafe { (registration.cb)(registration.user_data as *mut c_void, request, &mut reason) }, + NemoRelayStatus::Internal + ); + assert_eq!( + LAST_ERROR.lock().unwrap().as_deref(), + Some("llm conditional failed") + ); + unsafe { + (host.string_free)(request); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_llm_request_intercept("llm-request", 0, false, |_name, _request, _ann| { + Err("llm request failed".into()) + }) + .unwrap(); + let registration = take_llm_request_intercept_registration(); + let name = host_string(&host, "llm"); + let request = json_host_string(&host, serde_json::to_value(test_llm_request()).unwrap()); + let mut out_request = ptr::null_mut(); + let mut out_annotated = ptr::null_mut(); + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + request, + ptr::null(), + &mut out_request, + &mut out_annotated, + ) + }, + NemoRelayStatus::Internal + ); + assert_eq!( + LAST_ERROR.lock().unwrap().as_deref(), + Some("llm request failed") + ); + unsafe { + (host.string_free)(name); + (host.string_free)(request); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_llm_execution_intercept("llm-exec", 0, |_name, _request, _next| { + Err("llm execution failed".into()) + }) + .unwrap(); + let registration = take_llm_execution_registration(); + let name = host_string(&host, "llm"); + let request = json_host_string(&host, serde_json::to_value(test_llm_request()).unwrap()); + let mut out = ptr::null_mut(); + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + request, + failing_llm_next, + ptr::null_mut(), + &mut out, + ) + }, + NemoRelayStatus::Internal + ); + assert_eq!( + LAST_ERROR.lock().unwrap().as_deref(), + Some("llm execution failed") + ); + unsafe { + (host.string_free)(name); + (host.string_free)(request); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_llm_execution_intercept( + "llm-exec", + 0, + |_name, request, _next| Ok(request.content), + ) + .unwrap(); + let registration = take_llm_execution_registration(); + let name = host_string(&host, "llm"); + let request = json_host_string(&host, serde_json::to_value(test_llm_request()).unwrap()); + let mut out = ptr::null_mut(); + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + request, + failing_llm_next, + ptr::null_mut(), + &mut out, + ) + }, + NemoRelayStatus::Ok + ); + assert_eq!(read_json_and_free(&host, out), json!({ "input": true })); + unsafe { + (host.string_free)(name); + (host.string_free)(request); + registration.free(); + } + + let mut ctx = test_context(&host); + ctx.register_llm_stream_execution_intercept("llm-stream", 0, |_name, _request, _next| { + Err("llm stream failed".into()) + }) + .unwrap(); + let registration = take_llm_stream_execution_registration(); + let name = host_string(&host, "llm"); + let request = json_host_string(&host, serde_json::to_value(test_llm_request()).unwrap()); + let mut stream = NemoRelayNativeLlmStreamV1::default(); + assert_eq!( + unsafe { + (registration.cb)( + registration.user_data as *mut c_void, + name, + request, + fake_llm_stream_next, + ptr::null_mut(), + &mut stream, + ) + }, + NemoRelayStatus::Internal + ); + assert_eq!( + LAST_ERROR.lock().unwrap().as_deref(), + Some("llm stream failed") + ); + unsafe { + (host.string_free)(name); + (host.string_free)(request); + registration.free(); + } +} + struct NextState { host: NemoRelayNativeHostApiV1, called: Arc, @@ -3465,6 +3965,16 @@ fn typed_llm_stream_execution_wraps_next_chunks() { assert_eq!(status, NemoRelayStatus::Ok); assert_eq!(called.load(Ordering::SeqCst), 1); + let mut out = ptr::null_mut(); + assert_eq!( + unsafe { stream.next.unwrap()(ptr::null_mut(), &mut out) }, + NemoRelayStatus::NullPointer + ); + assert_eq!( + unsafe { stream.next.unwrap()(stream.user_data, ptr::null_mut()) }, + NemoRelayStatus::NullPointer + ); + let (status, chunk) = poll_stream_chunk(&host, &stream); assert_eq!(status, NemoRelayStatus::Ok); assert_eq!(chunk.unwrap()["wrapped"], json!(true)); @@ -3476,6 +3986,17 @@ fn typed_llm_stream_execution_wraps_next_chunks() { let (status, chunk) = poll_stream_chunk(&host, &stream); assert_eq!(status, NemoRelayStatus::StreamEnd); assert!(chunk.is_none()); + let (status, chunk) = poll_stream_chunk(&host, &stream); + assert_eq!(status, NemoRelayStatus::StreamEnd); + assert!(chunk.is_none()); + assert_eq!( + unsafe { stream.cancel.unwrap()(stream.user_data) }, + NemoRelayStatus::Ok + ); + assert_eq!( + unsafe { stream.cancel.unwrap()(ptr::null_mut()) }, + NemoRelayStatus::NullPointer + ); unsafe { drop_stream(&mut stream); @@ -4405,6 +4926,14 @@ fn exported_entry_symbol_validates_args_before_constructor() { assert!(plugin.validate.is_none()); assert!(plugin.register.is_none()); assert!(plugin.drop.is_none()); + + let mut short_host = host; + short_host.struct_size = size_of::() - 1; + assert_eq!( + unsafe { constructor_counting_entry(&short_host, &mut plugin) }, + NemoRelayStatus::InvalidArg + ); + assert_eq!(CONSTRUCTOR_CALLS.load(Ordering::SeqCst), 0); } #[test] diff --git a/crates/worker-proto/Cargo.toml b/crates/worker-proto/Cargo.toml new file mode 100644 index 000000000..8499bfd96 --- /dev/null +++ b/crates/worker-proto/Cargo.toml @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "nemo-relay-worker-proto" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "gRPC protocol definitions for NeMo Relay out-of-process worker plugins." + +[lints] +workspace = true + +[dependencies] +prost = "0.14" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tonic = { version = "0.14.2", default-features = false, features = ["codegen", "transport"] } +tonic-prost = "0.14.2" + +[build-dependencies] +prost-build = "0.14" +protoc-bin-vendored = "3.2.0" +tonic-prost-build = "0.14.2" diff --git a/crates/worker-proto/build.rs b/crates/worker-proto/build.rs new file mode 100644 index 000000000..5fddbc74e --- /dev/null +++ b/crates/worker-proto/build.rs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Build script for the NeMo Relay worker gRPC protocol crate. + +fn main() -> Result<(), Box> { + let proto = "proto/nemo/relay/worker/v1/plugin_worker.proto"; + let include = "proto"; + let mut prost = prost_build::Config::new(); + prost.protoc_executable(protoc_bin_vendored::protoc_bin_path()?); + + tonic_prost_build::configure().compile_with_config(prost, &[proto], &[include])?; + Ok(()) +} diff --git a/crates/worker-proto/proto/nemo/relay/worker/v1/plugin_worker.proto b/crates/worker-proto/proto/nemo/relay/worker/v1/plugin_worker.proto new file mode 100644 index 000000000..134154584 --- /dev/null +++ b/crates/worker-proto/proto/nemo/relay/worker/v1/plugin_worker.proto @@ -0,0 +1,298 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package nemo.relay.worker.v1; + +service PluginWorker { + rpc Handshake(HandshakeRequest) returns (HandshakeResponse); + rpc Health(HealthRequest) returns (HealthResponse); + rpc Validate(ValidateRequest) returns (ValidateResponse); + rpc Register(RegisterRequest) returns (RegisterResponse); + rpc Invoke(InvokeRequest) returns (InvokeResponse); + rpc InvokeStream(InvokeRequest) returns (stream StreamChunk); + rpc CancelInvocation(CancelInvocationRequest) returns (WorkerAck); + rpc Shutdown(ShutdownRequest) returns (WorkerAck); +} + +service RelayHostRuntime { + rpc EmitMark(EmitMarkRequest) returns (HostAck); + rpc PushScope(PushScopeRequest) returns (PushScopeResponse); + rpc PopScope(PopScopeRequest) returns (HostAck); + rpc CreateScopeStack(CreateScopeStackRequest) returns (CreateScopeStackResponse); + rpc DropScopeStack(DropScopeStackRequest) returns (HostAck); + rpc ToolNext(ToolNextRequest) returns (JsonResult); + rpc LlmNext(LlmNextRequest) returns (JsonResult); + rpc LlmStreamNext(LlmStreamNextRequest) returns (stream StreamChunk); +} + +message JsonEnvelope { + string schema = 1; + bytes json = 2; +} + +enum RegistrationSurface { + REGISTRATION_SURFACE_UNSPECIFIED = 0; + SUBSCRIBER = 1; + TOOL_SANITIZE_REQUEST_GUARDRAIL = 10; + TOOL_SANITIZE_RESPONSE_GUARDRAIL = 11; + TOOL_CONDITIONAL_EXECUTION_GUARDRAIL = 12; + TOOL_REQUEST_INTERCEPT = 13; + TOOL_EXECUTION_INTERCEPT = 14; + LLM_SANITIZE_REQUEST_GUARDRAIL = 20; + LLM_SANITIZE_RESPONSE_GUARDRAIL = 21; + LLM_CONDITIONAL_EXECUTION_GUARDRAIL = 22; + LLM_REQUEST_INTERCEPT = 23; + LLM_EXECUTION_INTERCEPT = 24; + LLM_STREAM_EXECUTION_INTERCEPT = 25; +} + +enum ScopeType { + SCOPE_TYPE_UNSPECIFIED = 0; + AGENT = 1; + FUNCTION = 2; + TOOL = 3; + LLM = 4; + RETRIEVER = 5; + EMBEDDER = 6; + RERANKER = 7; + GUARDRAIL = 8; + EVALUATOR = 9; + CUSTOM = 10; + UNKNOWN = 11; +} + +message HandshakeRequest { + string activation_id = 1; + string plugin_id = 2; + string relay_version = 3; + string worker_protocol = 4; + string auth_token = 5; + string host_endpoint = 6; +} + +message HandshakeResponse { + string plugin_id = 1; + string plugin_kind = 2; + bool allows_multiple_components = 3; + string worker_protocol = 4; + string sdk_name = 5; + string sdk_version = 6; + string runtime_name = 7; + string runtime_version = 8; + repeated RegistrationSurface supported_surfaces = 9; +} + +message HealthRequest { + string activation_id = 1; + string auth_token = 2; +} + +message HealthResponse { + bool ok = 1; + string message = 2; + string plugin_id = 3; + string worker_protocol = 4; + string sdk_name = 5; + string sdk_version = 6; + string runtime_name = 7; + string runtime_version = 8; +} + +message ValidateRequest { + string activation_id = 1; + string plugin_id = 2; + string auth_token = 3; + JsonEnvelope config = 4; +} + +message ValidateResponse { + JsonEnvelope diagnostics = 1; + WorkerError error = 2; +} + +message RegisterRequest { + string activation_id = 1; + string plugin_id = 2; + string auth_token = 3; + JsonEnvelope config = 4; +} + +message RegisterResponse { + repeated Registration registrations = 1; + WorkerError error = 2; +} + +message Registration { + string local_name = 1; + RegistrationSurface surface = 2; + int32 priority = 3; + bool break_chain = 4; +} + +message InvokeRequest { + string activation_id = 1; + string invocation_id = 2; + string registration_name = 3; + RegistrationSurface surface = 4; + string continuation_id = 5; + ScopeContext scope = 6; + string auth_token = 7; + + oneof payload { + JsonEnvelope event = 10; + ToolInvocation tool = 11; + LlmInvocation llm = 12; + } +} + +message ToolInvocation { + string tool_name = 1; + JsonEnvelope value = 2; +} + +message LlmInvocation { + string model_name = 1; + JsonEnvelope request = 2; + JsonEnvelope annotated_request = 3; + JsonEnvelope response = 4; +} + +message InvokeResponse { + oneof result { + EmptyResult empty = 1; + JsonResult json = 2; + GuardrailResult guardrail = 3; + LlmRequestInterceptResult llm_request = 4; + WorkerError error = 5; + } +} + +message EmptyResult {} + +message JsonResult { + JsonEnvelope value = 1; + WorkerError error = 2; +} + +message GuardrailResult { + string block_reason = 1; +} + +message LlmRequestInterceptResult { + JsonEnvelope request = 1; + JsonEnvelope annotated_request = 2; + bool has_annotated_request = 3; +} + +message StreamChunk { + oneof item { + JsonEnvelope value = 1; + WorkerError error = 2; + } +} + +message CancelInvocationRequest { + string activation_id = 1; + string invocation_id = 2; + string auth_token = 3; + string reason = 4; +} + +message ShutdownRequest { + string activation_id = 1; + string auth_token = 2; + string reason = 3; +} + +message WorkerAck { + bool accepted = 1; + string message = 2; +} + +message HostAck { + bool ok = 1; + WorkerError error = 2; +} + +message WorkerError { + string code = 1; + string message = 2; + bool retryable = 3; +} + +message ScopeContext { + string scope_stack_id = 1; + string parent_scope_id = 2; +} + +message EmitMarkRequest { + string activation_id = 1; + string auth_token = 2; + ScopeContext scope = 3; + string name = 4; + JsonEnvelope data = 5; + JsonEnvelope metadata = 6; +} + +message PushScopeRequest { + string activation_id = 1; + string auth_token = 2; + ScopeContext scope = 3; + string name = 4; + ScopeType scope_type = 5; + JsonEnvelope data = 6; + JsonEnvelope metadata = 7; + JsonEnvelope input = 8; +} + +message PushScopeResponse { + string scope_handle_id = 1; + WorkerError error = 2; +} + +message PopScopeRequest { + string activation_id = 1; + string auth_token = 2; + string scope_handle_id = 3; + JsonEnvelope output = 4; + JsonEnvelope metadata = 5; +} + +message CreateScopeStackRequest { + string activation_id = 1; + string auth_token = 2; +} + +message CreateScopeStackResponse { + string scope_stack_id = 1; + WorkerError error = 2; +} + +message DropScopeStackRequest { + string activation_id = 1; + string auth_token = 2; + string scope_stack_id = 3; +} + +message ToolNextRequest { + string activation_id = 1; + string auth_token = 2; + string continuation_id = 3; + JsonEnvelope value = 4; +} + +message LlmNextRequest { + string activation_id = 1; + string auth_token = 2; + string continuation_id = 3; + JsonEnvelope request = 4; +} + +message LlmStreamNextRequest { + string activation_id = 1; + string auth_token = 2; + string continuation_id = 3; + JsonEnvelope request = 4; +} diff --git a/crates/worker-proto/src/lib.rs b/crates/worker-proto/src/lib.rs new file mode 100644 index 000000000..dd05930d2 --- /dev/null +++ b/crates/worker-proto/src/lib.rs @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![deny(rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links)] + +//! Versioned gRPC protocol for NeMo Relay out-of-process worker plugins. +//! +//! The protobuf schema owns transport control flow. Relay data transfer objects +//! are carried as JSON envelopes so `nemo-relay-types` remains the shared source +//! of truth for event, LLM, tool, scope, and plugin diagnostic data shapes. + +/// Stable worker protocol identifier accepted by `compat.worker_protocol`. +pub const WORKER_PROTOCOL_GRPC_V1: &str = "grpc-v1"; + +/// Generated protobuf and gRPC service definitions. +#[allow(missing_docs)] +pub mod v1 { + tonic::include_proto!("nemo.relay.worker.v1"); +} + +/// Creates a JSON envelope from a serializable DTO. +/// +/// # Errors +/// Returns a serde error when the supplied value cannot be serialized as JSON. +pub fn json_envelope( + schema: impl Into, + value: &T, +) -> Result { + Ok(v1::JsonEnvelope { + schema: schema.into(), + json: serde_json::to_vec(value)?, + }) +} + +/// Decodes a JSON envelope into the requested DTO type. +/// +/// # Errors +/// Returns a serde error when the envelope bytes are not valid JSON for `T`. +pub fn decode_json_envelope( + envelope: &v1::JsonEnvelope, +) -> Result { + serde_json::from_slice(&envelope.json) +} diff --git a/crates/worker-proto/tests/proto_tests.rs b/crates/worker-proto/tests/proto_tests.rs new file mode 100644 index 000000000..35a478c3e --- /dev/null +++ b/crates/worker-proto/tests/proto_tests.rs @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Tests for stable worker protocol helpers and enum values. + +use nemo_relay_worker_proto::v1::{ + HandshakeRequest, HealthRequest, InvokeRequest, JsonEnvelope, RegistrationSurface, ScopeType, +}; +use nemo_relay_worker_proto::{WORKER_PROTOCOL_GRPC_V1, decode_json_envelope, json_envelope}; +use prost::Message; +use serde_json::json; + +#[test] +fn worker_protocol_identifier_is_stable() { + assert_eq!(WORKER_PROTOCOL_GRPC_V1, "grpc-v1"); +} + +#[test] +fn registration_surface_values_are_stable() { + assert_eq!(RegistrationSurface::Subscriber as i32, 1); + assert_eq!(RegistrationSurface::ToolSanitizeRequestGuardrail as i32, 10); + assert_eq!( + RegistrationSurface::ToolSanitizeResponseGuardrail as i32, + 11 + ); + assert_eq!( + RegistrationSurface::ToolConditionalExecutionGuardrail as i32, + 12 + ); + assert_eq!(RegistrationSurface::ToolRequestIntercept as i32, 13); + assert_eq!(RegistrationSurface::ToolExecutionIntercept as i32, 14); + assert_eq!(RegistrationSurface::LlmSanitizeRequestGuardrail as i32, 20); + assert_eq!(RegistrationSurface::LlmSanitizeResponseGuardrail as i32, 21); + assert_eq!( + RegistrationSurface::LlmConditionalExecutionGuardrail as i32, + 22 + ); + assert_eq!(RegistrationSurface::LlmRequestIntercept as i32, 23); + assert_eq!(RegistrationSurface::LlmExecutionIntercept as i32, 24); + assert_eq!(RegistrationSurface::LlmStreamExecutionIntercept as i32, 25); +} + +#[test] +fn scope_type_values_are_stable() { + assert_eq!(ScopeType::Agent as i32, 1); + assert_eq!(ScopeType::Function as i32, 2); + assert_eq!(ScopeType::Tool as i32, 3); + assert_eq!(ScopeType::Llm as i32, 4); + assert_eq!(ScopeType::Retriever as i32, 5); + assert_eq!(ScopeType::Embedder as i32, 6); + assert_eq!(ScopeType::Reranker as i32, 7); + assert_eq!(ScopeType::Guardrail as i32, 8); + assert_eq!(ScopeType::Evaluator as i32, 9); + assert_eq!(ScopeType::Custom as i32, 10); + assert_eq!(ScopeType::Unknown as i32, 11); +} + +#[test] +fn request_field_numbers_are_stable() { + let handshake = HandshakeRequest { + activation_id: "act".into(), + plugin_id: "plugin".into(), + relay_version: "0.5.0".into(), + worker_protocol: WORKER_PROTOCOL_GRPC_V1.into(), + auth_token: "token".into(), + host_endpoint: "unix:///tmp/host.sock".into(), + }; + let encoded = handshake.encode_to_vec(); + assert_eq!( + encoded, + b"\x0a\x03act\x12\x06plugin\x1a\x050.5.0\x22\x07grpc-v1\x2a\x05token\x32\x15unix:///tmp/host.sock" + .to_vec() + ); + assert_eq!( + HandshakeRequest::decode(encoded.as_slice()).expect("decode handshake"), + handshake + ); + + let health = HealthRequest { + activation_id: "act".into(), + auth_token: "token".into(), + }; + let encoded = health.encode_to_vec(); + assert_eq!(encoded, b"\x0a\x03act\x12\x05token".to_vec()); + assert_eq!( + HealthRequest::decode(encoded.as_slice()).expect("decode health"), + health + ); + + let invoke = InvokeRequest { + activation_id: "act".into(), + invocation_id: "invoke".into(), + registration_name: "tool".into(), + surface: RegistrationSurface::ToolRequestIntercept as i32, + continuation_id: "next".into(), + scope: None, + auth_token: "token".into(), + payload: None, + }; + let encoded = invoke.encode_to_vec(); + assert_eq!( + encoded, + b"\x0a\x03act\x12\x06invoke\x1a\x04tool\x20\x0d\x2a\x04next\x3a\x05token".to_vec() + ); + assert_eq!( + InvokeRequest::decode(encoded.as_slice()).expect("decode invoke"), + invoke + ); +} + +#[test] +fn json_envelope_round_trips_payload() { + let payload = json!({"answer": 42}); + let envelope = json_envelope("nemo.relay.Json@1", &payload).unwrap(); + + assert_eq!(envelope.schema, "nemo.relay.Json@1"); + assert_eq!( + decode_json_envelope::(&envelope).unwrap(), + payload + ); +} + +#[test] +fn invalid_json_envelope_reports_decode_error() { + let envelope = JsonEnvelope { + schema: "nemo.relay.Json@1".into(), + json: b"{".to_vec(), + }; + + assert!(decode_json_envelope::(&envelope).is_err()); +} diff --git a/crates/worker/Cargo.toml b/crates/worker/Cargo.toml new file mode 100644 index 000000000..40a780e94 --- /dev/null +++ b/crates/worker/Cargo.toml @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "nemo-relay-worker" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Rust SDK for NeMo Relay out-of-process gRPC worker plugins." + +[lints] +workspace = true + +[dependencies] +futures-util = "0.3" +hyper-util = { version = "0.1", features = ["tokio"] } +nemo-relay-types.workspace = true +nemo-relay-worker-proto.workspace = true +serde_json = "1" +serde = "1" +thiserror = "2" +tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "sync"] } +tokio-stream = { version = "0.1", features = ["net", "sync"] } +tonic = { version = "0.14.2", default-features = false, features = ["codegen", "router", "transport"] } +tower = { version = "0.5", features = ["util"] } diff --git a/crates/worker/src/lib.rs b/crates/worker/src/lib.rs new file mode 100644 index 000000000..357f42657 --- /dev/null +++ b/crates/worker/src/lib.rs @@ -0,0 +1,1612 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![deny(rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links)] + +//! Rust SDK for NeMo Relay out-of-process gRPC worker plugins. + +use std::cell::RefCell; +use std::collections::HashMap; +use std::future::Future; +use std::net::{SocketAddr, ToSocketAddrs}; +#[cfg(unix)] +use std::os::unix::fs::FileTypeExt; +#[cfg(unix)] +use std::path::{Path, PathBuf}; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; + +use futures_util::{Stream, StreamExt}; +#[cfg(unix)] +use hyper_util::rt::TokioIo; +pub use nemo_relay_types::Json; +pub use nemo_relay_types::api::event::Event; +pub use nemo_relay_types::api::llm::LlmRequest; +pub use nemo_relay_types::api::scope::ScopeType; +use nemo_relay_types::codec::request::AnnotatedLlmRequest; +pub use nemo_relay_types::plugin::{ConfigDiagnostic, DiagnosticLevel}; +use nemo_relay_worker_proto::v1::plugin_worker_server::{PluginWorker, PluginWorkerServer}; +use nemo_relay_worker_proto::v1::relay_host_runtime_client::RelayHostRuntimeClient; +use nemo_relay_worker_proto::v1::{ + CancelInvocationRequest, CreateScopeStackRequest, DropScopeStackRequest, EmitMarkRequest, + EmptyResult, GuardrailResult, HandshakeRequest, HandshakeResponse, HealthRequest, + HealthResponse, InvokeRequest, InvokeResponse, JsonEnvelope, JsonResult, LlmNextRequest, + LlmRequestInterceptResult, LlmStreamNextRequest, PopScopeRequest, PushScopeRequest, + RegisterRequest, RegisterResponse, Registration, RegistrationSurface, ScopeContext, + ShutdownRequest, StreamChunk, ToolNextRequest, ValidateRequest, ValidateResponse, WorkerAck, + WorkerError, +}; +use nemo_relay_worker_proto::{WORKER_PROTOCOL_GRPC_V1, decode_json_envelope, json_envelope}; +#[cfg(unix)] +use tokio::net::{UnixListener, UnixStream}; +use tokio::sync::OnceCell; +#[cfg(unix)] +use tokio_stream::wrappers::UnixListenerStream; +use tonic::transport::{Channel, Endpoint, Server}; +use tonic::{Request, Response, Status}; +#[cfg(unix)] +use tower::service_fn; + +/// SDK result type. +pub type Result = std::result::Result; + +/// Boxed future returned by async worker callbacks. +pub type BoxFutureResult = Pin> + Send>>; + +/// Boxed JSON stream returned by streaming worker callbacks. +pub type JsonStream = Pin> + Send>>; + +tokio::task_local! { + static TASK_SCOPE_CONTEXT: Option; +} + +thread_local! { + static THREAD_SCOPE_CONTEXT: RefCell> = const { RefCell::new(None) }; +} + +/// Error returned by worker SDK callbacks and runtime helpers. +#[derive(Debug, thiserror::Error)] +pub enum WorkerSdkError { + /// Invalid host-provided input. + #[error("invalid input: {0}")] + InvalidInput(String), + /// Worker callback failed. + #[error("callback failed: {0}")] + Callback(String), + /// Worker transport failed. + #[error("transport failed: {0}")] + Transport(String), + /// JSON serialization failed. + #[error("serialization failed: {0}")] + Serialization(#[from] serde_json::Error), +} + +/// Trait implemented by Rust out-of-process worker plugins. +pub trait WorkerPlugin: Send + Sync + 'static { + /// Stable plugin id/kind returned to the Relay host. + fn plugin_id(&self) -> &str; + + /// Whether multiple configured components of this plugin kind are allowed. + fn allows_multiple_components(&self) -> bool { + false + } + + /// Validates component config. + fn validate(&self, _config: &Json) -> Vec { + Vec::new() + } + + /// Registers callbacks into the worker context. + fn register(&self, ctx: &mut PluginContext, config: &Json) -> Result<()>; +} + +type SubscriberFn = Arc; +type ToolSanitizeFn = Arc Json + Send + Sync>; +type ToolConditionalFn = Arc Result> + Send + Sync>; +type ToolRequestFn = Arc Result + Send + Sync>; +type ToolExecutionFn = Arc BoxFutureResult + Send + Sync>; +type LlmSanitizeRequestFn = Arc LlmRequest + Send + Sync>; +type LlmSanitizeResponseFn = Arc Json + Send + Sync>; +type LlmConditionalFn = Arc Result> + Send + Sync>; +type LlmRequestFn = Arc< + dyn Fn( + &str, + LlmRequest, + Option, + ) -> Result<(LlmRequest, Option)> + + Send + + Sync, +>; +type LlmExecutionFn = Arc BoxFutureResult + Send + Sync>; +type LlmStreamExecutionFn = + Arc BoxFutureResult + Send + Sync>; + +#[derive(Default)] +struct WorkerHandlers { + registrations: Vec, + subscribers: HashMap, + tool_sanitize_requests: HashMap, + tool_sanitize_responses: HashMap, + tool_conditionals: HashMap, + tool_requests: HashMap, + tool_executions: HashMap, + llm_sanitize_requests: HashMap, + llm_sanitize_responses: HashMap, + llm_conditionals: HashMap, + llm_requests: HashMap, + llm_executions: HashMap, + llm_stream_executions: HashMap, +} + +/// Registration context passed to [`WorkerPlugin::register`]. +pub struct PluginContext { + handlers: WorkerHandlers, + runtime: Option, +} + +impl PluginContext { + /// Creates an empty worker registration context. + pub fn new() -> Self { + Self { + handlers: WorkerHandlers::default(), + runtime: None, + } + } + + /// Creates an empty worker registration context with a host runtime handle. + pub fn with_runtime(runtime: PluginRuntime) -> Self { + Self { + handlers: WorkerHandlers::default(), + runtime: Some(runtime), + } + } + + /// Returns the host runtime handle for event and scope operations. + pub fn runtime(&self) -> Option { + self.runtime.clone() + } + + /// Registers an event subscriber. + pub fn register_subscriber(&mut self, name: &str, callback: F) + where + F: Fn(&Event) + Send + Sync + 'static, + { + self.push_registration(name, RegistrationSurface::Subscriber, 0, false); + self.handlers + .subscribers + .insert(name.into(), Arc::new(callback)); + } + + /// Registers a tool sanitize-request guardrail. + pub fn register_tool_sanitize_request_guardrail( + &mut self, + name: &str, + priority: i32, + callback: F, + ) where + F: Fn(&str, Json) -> Json + Send + Sync + 'static, + { + self.push_registration( + name, + RegistrationSurface::ToolSanitizeRequestGuardrail, + priority, + false, + ); + self.handlers + .tool_sanitize_requests + .insert(name.into(), Arc::new(callback)); + } + + /// Registers a tool sanitize-response guardrail. + pub fn register_tool_sanitize_response_guardrail( + &mut self, + name: &str, + priority: i32, + callback: F, + ) where + F: Fn(&str, Json) -> Json + Send + Sync + 'static, + { + self.push_registration( + name, + RegistrationSurface::ToolSanitizeResponseGuardrail, + priority, + false, + ); + self.handlers + .tool_sanitize_responses + .insert(name.into(), Arc::new(callback)); + } + + /// Registers a tool conditional-execution guardrail. + pub fn register_tool_conditional_execution_guardrail( + &mut self, + name: &str, + priority: i32, + callback: F, + ) where + F: Fn(&str, &Json) -> Result> + Send + Sync + 'static, + { + self.push_registration( + name, + RegistrationSurface::ToolConditionalExecutionGuardrail, + priority, + false, + ); + self.handlers + .tool_conditionals + .insert(name.into(), Arc::new(callback)); + } + + /// Registers a tool request intercept. + pub fn register_tool_request_intercept( + &mut self, + name: &str, + priority: i32, + break_chain: bool, + callback: F, + ) where + F: Fn(&str, Json) -> Result + Send + Sync + 'static, + { + self.push_registration( + name, + RegistrationSurface::ToolRequestIntercept, + priority, + break_chain, + ); + self.handlers + .tool_requests + .insert(name.into(), Arc::new(callback)); + } + + /// Registers a tool execution intercept. + pub fn register_tool_execution_intercept( + &mut self, + name: &str, + priority: i32, + callback: F, + ) where + F: Fn(&str, Json, ToolNext) -> Fut + Send + Sync + 'static, + Fut: Future> + Send + 'static, + { + self.push_registration( + name, + RegistrationSurface::ToolExecutionIntercept, + priority, + false, + ); + self.handlers.tool_executions.insert( + name.into(), + Arc::new(move |tool, value, next| Box::pin(callback(tool, value, next))), + ); + } + + /// Registers an LLM sanitize-request guardrail. + pub fn register_llm_sanitize_request_guardrail( + &mut self, + name: &str, + priority: i32, + callback: F, + ) where + F: Fn(LlmRequest) -> LlmRequest + Send + Sync + 'static, + { + self.push_registration( + name, + RegistrationSurface::LlmSanitizeRequestGuardrail, + priority, + false, + ); + self.handlers + .llm_sanitize_requests + .insert(name.into(), Arc::new(callback)); + } + + /// Registers an LLM sanitize-response guardrail. + pub fn register_llm_sanitize_response_guardrail( + &mut self, + name: &str, + priority: i32, + callback: F, + ) where + F: Fn(Json) -> Json + Send + Sync + 'static, + { + self.push_registration( + name, + RegistrationSurface::LlmSanitizeResponseGuardrail, + priority, + false, + ); + self.handlers + .llm_sanitize_responses + .insert(name.into(), Arc::new(callback)); + } + + /// Registers an LLM conditional-execution guardrail. + pub fn register_llm_conditional_execution_guardrail( + &mut self, + name: &str, + priority: i32, + callback: F, + ) where + F: Fn(&LlmRequest) -> Result> + Send + Sync + 'static, + { + self.push_registration( + name, + RegistrationSurface::LlmConditionalExecutionGuardrail, + priority, + false, + ); + self.handlers + .llm_conditionals + .insert(name.into(), Arc::new(callback)); + } + + /// Registers an LLM request intercept. + pub fn register_llm_request_intercept( + &mut self, + name: &str, + priority: i32, + break_chain: bool, + callback: F, + ) where + F: Fn( + &str, + LlmRequest, + Option, + ) -> Result<(LlmRequest, Option)> + + Send + + Sync + + 'static, + { + self.push_registration( + name, + RegistrationSurface::LlmRequestIntercept, + priority, + break_chain, + ); + self.handlers + .llm_requests + .insert(name.into(), Arc::new(callback)); + } + + /// Registers an LLM execution intercept. + pub fn register_llm_execution_intercept( + &mut self, + name: &str, + priority: i32, + callback: F, + ) where + F: Fn(&str, LlmRequest, LlmNext) -> Fut + Send + Sync + 'static, + Fut: Future> + Send + 'static, + { + self.push_registration( + name, + RegistrationSurface::LlmExecutionIntercept, + priority, + false, + ); + self.handlers.llm_executions.insert( + name.into(), + Arc::new(move |model, request, next| Box::pin(callback(model, request, next))), + ); + } + + /// Registers an LLM stream execution intercept. + pub fn register_llm_stream_execution_intercept( + &mut self, + name: &str, + priority: i32, + callback: F, + ) where + F: Fn(&str, LlmRequest, LlmStreamNext) -> Fut + Send + Sync + 'static, + Fut: Future> + Send + 'static, + { + self.push_registration( + name, + RegistrationSurface::LlmStreamExecutionIntercept, + priority, + false, + ); + self.handlers.llm_stream_executions.insert( + name.into(), + Arc::new(move |model, request, next| Box::pin(callback(model, request, next))), + ); + } + + fn push_registration( + &mut self, + name: &str, + surface: RegistrationSurface, + priority: i32, + break_chain: bool, + ) { + self.handlers.registrations.push(Registration { + local_name: name.into(), + surface: surface as i32, + priority, + break_chain, + }); + } +} + +impl Default for PluginContext { + fn default() -> Self { + Self::new() + } +} + +/// Cloneable handle for calling the Relay host runtime from worker callbacks. +#[derive(Clone)] +pub struct PluginRuntime { + activation_id: String, + auth_token: String, + host_endpoint: String, + host_channel: Arc>, +} + +impl PluginRuntime { + /// Emits a mark event through the host runtime. + pub async fn emit_mark( + &self, + name: &str, + data: Option, + metadata: Option, + ) -> Result<()> { + let scope = self.current_scope_context(); + let mut client = self.host_client().await?; + let response = client + .emit_mark(Request::new(EmitMarkRequest { + activation_id: self.activation_id.clone(), + auth_token: self.auth_token.clone(), + scope, + name: name.into(), + data: optional_json_envelope(data)?, + metadata: optional_json_envelope(metadata)?, + })) + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string()))? + .into_inner(); + ack_to_result(response.ok, response.error) + } + + /// Creates an isolated host-owned scope stack. + pub async fn create_scope_stack(&self) -> Result { + let mut client = self.host_client().await?; + let response = client + .create_scope_stack(Request::new(CreateScopeStackRequest { + activation_id: self.activation_id.clone(), + auth_token: self.auth_token.clone(), + })) + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string()))? + .into_inner(); + if let Some(error) = response.error { + return Err(worker_error_to_sdk(error)); + } + Ok(response.scope_stack_id) + } + + /// Drops an isolated host-owned scope stack. + pub async fn drop_scope_stack(&self, scope_stack_id: &str) -> Result<()> { + let mut client = self.host_client().await?; + let response = client + .drop_scope_stack(Request::new(DropScopeStackRequest { + activation_id: self.activation_id.clone(), + auth_token: self.auth_token.clone(), + scope_stack_id: scope_stack_id.into(), + })) + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string()))? + .into_inner(); + ack_to_result(response.ok, response.error) + } + + /// Pushes a scope through the host runtime. + pub async fn push_scope( + &self, + scope_stack_id: Option<&str>, + name: &str, + scope_type: ScopeType, + data: Option, + metadata: Option, + input: Option, + ) -> Result { + let scope = scope_stack_id + .map(scope_context) + .or_else(|| self.current_scope_context()); + let mut client = self.host_client().await?; + let response = client + .push_scope(Request::new(PushScopeRequest { + activation_id: self.activation_id.clone(), + auth_token: self.auth_token.clone(), + scope, + name: name.into(), + scope_type: proto_scope_type(scope_type), + data: optional_json_envelope(data)?, + metadata: optional_json_envelope(metadata)?, + input: optional_json_envelope(input)?, + })) + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string()))? + .into_inner(); + if let Some(error) = response.error { + return Err(worker_error_to_sdk(error)); + } + Ok(response.scope_handle_id) + } + + /// Pops a scope through the host runtime. + pub async fn pop_scope( + &self, + scope_handle_id: &str, + output: Option, + metadata: Option, + ) -> Result<()> { + let mut client = self.host_client().await?; + let response = client + .pop_scope(Request::new(PopScopeRequest { + activation_id: self.activation_id.clone(), + auth_token: self.auth_token.clone(), + scope_handle_id: scope_handle_id.into(), + output: optional_json_envelope(output)?, + metadata: optional_json_envelope(metadata)?, + })) + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string()))? + .into_inner(); + ack_to_result(response.ok, response.error) + } + + async fn host_client(&self) -> Result> { + self.host_channel + .get_or_try_init(|| connect_host_endpoint(&self.host_endpoint)) + .await + .cloned() + .map(RelayHostRuntimeClient::new) + } + + fn current_scope_context(&self) -> Option { + current_scope_context() + } +} + +/// Explicit worker server configuration for tests and custom launchers. +#[derive(Debug, Clone)] +pub struct WorkerServerConfig { + /// Endpoint the worker listens on, such as `unix:///tmp/worker.sock` or `http://127.0.0.1:50051`. + pub worker_endpoint: String, + /// Relay host runtime endpoint used for callbacks and continuations. + pub host_endpoint: String, + /// Host-issued activation identifier accepted by this worker. + pub activation_id: String, + /// Host-issued bearer token accepted by this worker. + pub auth_token: String, +} + +/// Continuation handle for tool execution intercepts. +#[derive(Clone)] +pub struct ToolNext { + runtime: PluginRuntime, + continuation_id: String, +} + +impl ToolNext { + /// Calls the remaining tool execution chain. + pub async fn call(&self, value: Json) -> Result { + let mut client = self.runtime.host_client().await?; + let response = client + .tool_next(Request::new(ToolNextRequest { + activation_id: self.runtime.activation_id.clone(), + auth_token: self.runtime.auth_token.clone(), + continuation_id: self.continuation_id.clone(), + value: Some(json_envelope("nemo.relay.Json@1", &value)?), + })) + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string()))? + .into_inner(); + json_result_to_sdk(response) + } +} + +/// Continuation handle for LLM execution intercepts. +#[derive(Clone)] +pub struct LlmNext { + runtime: PluginRuntime, + continuation_id: String, +} + +impl LlmNext { + /// Calls the remaining LLM execution chain. + pub async fn call(&self, request: LlmRequest) -> Result { + let mut client = self.runtime.host_client().await?; + let response = client + .llm_next(Request::new(LlmNextRequest { + activation_id: self.runtime.activation_id.clone(), + auth_token: self.runtime.auth_token.clone(), + continuation_id: self.continuation_id.clone(), + request: Some(json_envelope("nemo.relay.LlmRequest@1", &request)?), + })) + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string()))? + .into_inner(); + json_result_to_sdk(response) + } +} + +/// Continuation handle for LLM stream execution intercepts. +#[derive(Clone)] +pub struct LlmStreamNext { + runtime: PluginRuntime, + continuation_id: String, +} + +impl LlmStreamNext { + /// Calls the remaining LLM streaming execution chain. + pub async fn call(&self, request: LlmRequest) -> Result { + let scope = self.runtime.current_scope_context(); + let mut client = self.runtime.host_client().await?; + let response = client + .llm_stream_next(Request::new(LlmStreamNextRequest { + activation_id: self.runtime.activation_id.clone(), + auth_token: self.runtime.auth_token.clone(), + continuation_id: self.continuation_id.clone(), + request: Some(json_envelope("nemo.relay.LlmRequest@1", &request)?), + })) + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string()))?; + let stream = response.into_inner().map(|chunk| match chunk { + Ok(chunk) => stream_chunk_to_json(chunk), + Err(err) => Err(WorkerSdkError::Transport(err.to_string())), + }); + Ok(Box::pin(ScopedJsonStream::new(Box::pin(stream), scope))) + } +} + +/// Serves a worker plugin using environment variables supplied by the Relay host. +/// +/// # Errors +/// Returns an error when required worker environment variables are missing or +/// the gRPC server fails. +pub async fn serve_plugin(plugin: impl WorkerPlugin) -> Result<()> { + serve_plugin_arc(Arc::new(plugin)).await +} + +/// Serves a shared worker plugin using environment variables supplied by the Relay host. +/// +/// # Errors +/// Returns an error when required worker environment variables are missing or +/// the gRPC server fails. +pub async fn serve_plugin_arc(plugin: Arc) -> Result<()> { + let config = WorkerServerConfig { + worker_endpoint: required_env("NEMO_RELAY_WORKER_SOCKET")?, + host_endpoint: required_env("NEMO_RELAY_HOST_SOCKET")?, + activation_id: required_env("NEMO_RELAY_WORKER_ID")?, + auth_token: required_env("NEMO_RELAY_WORKER_TOKEN")?, + }; + serve_plugin_arc_with_config(plugin, config).await +} + +/// Serves a shared worker plugin using explicit endpoint and authentication configuration. +/// +/// This is primarily useful for tests and custom worker launchers. Relay-spawned +/// workers should normally use [`serve_plugin`] or [`serve_plugin_arc`]. +/// +/// # Errors +/// Returns an error when the endpoint configuration is invalid or the gRPC +/// server fails. +pub async fn serve_plugin_arc_with_config( + plugin: Arc, + config: WorkerServerConfig, +) -> Result<()> { + let runtime = PluginRuntime { + activation_id: config.activation_id, + auth_token: config.auth_token, + host_endpoint: config.host_endpoint, + host_channel: Arc::new(OnceCell::new()), + }; + let service = WorkerService { + plugin, + runtime, + handlers: Arc::new(Mutex::new(WorkerHandlers::default())), + }; + serve_worker_service(service, &config.worker_endpoint).await +} + +#[cfg(unix)] +async fn serve_worker_service(service: WorkerService, endpoint: &str) -> Result<()> { + if endpoint.starts_with("unix://") { + let path = parse_unix_endpoint(endpoint)?; + remove_stale_socket(&path)?; + let listener = UnixListener::bind(&path).map_err(|err| { + WorkerSdkError::Transport(format!("failed to bind worker socket: {err}")) + })?; + return Server::builder() + .add_service(PluginWorkerServer::new(service)) + .serve_with_incoming(UnixListenerStream::new(listener)) + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string())); + } + serve_tcp_worker_service(service, endpoint).await +} + +#[cfg(not(unix))] +async fn serve_worker_service(service: WorkerService, endpoint: &str) -> Result<()> { + if endpoint.starts_with("unix://") { + return Err(WorkerSdkError::InvalidInput( + "unix endpoints are not supported on this platform".into(), + )); + } + serve_tcp_worker_service(service, endpoint).await +} + +async fn serve_tcp_worker_service(service: WorkerService, endpoint: &str) -> Result<()> { + let addr = parse_tcp_endpoint(endpoint)?; + Server::builder() + .add_service(PluginWorkerServer::new(service)) + .serve(addr) + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string())) +} + +struct WorkerService { + plugin: Arc, + runtime: PluginRuntime, + handlers: Arc>, +} + +#[tonic::async_trait] +impl PluginWorker for WorkerService { + async fn handshake( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + self.authorize(&request.activation_id, &request.auth_token)?; + Ok(Response::new(HandshakeResponse { + plugin_id: self.plugin.plugin_id().into(), + plugin_kind: self.plugin.plugin_id().into(), + allows_multiple_components: self.plugin.allows_multiple_components(), + worker_protocol: WORKER_PROTOCOL_GRPC_V1.into(), + sdk_name: "nemo-relay-worker".into(), + sdk_version: env!("CARGO_PKG_VERSION").into(), + runtime_name: "rust".into(), + runtime_version: rustc_version_runtime(), + supported_surfaces: all_surfaces() + .into_iter() + .map(|surface| surface as i32) + .collect(), + })) + } + + async fn health( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + self.authorize(&request.activation_id, &request.auth_token)?; + Ok(Response::new(HealthResponse { + ok: true, + message: "ready".into(), + plugin_id: self.plugin.plugin_id().into(), + worker_protocol: WORKER_PROTOCOL_GRPC_V1.into(), + sdk_name: "nemo-relay-worker".into(), + sdk_version: env!("CARGO_PKG_VERSION").into(), + runtime_name: "rust".into(), + runtime_version: rustc_version_runtime(), + })) + } + + async fn validate( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + self.authorize(&request.activation_id, &request.auth_token)?; + let config = request + .config + .as_ref() + .map(decode_json_envelope::) + .transpose() + .map_err(|err| Status::invalid_argument(format!("invalid config JSON: {err}")))? + .unwrap_or(Json::Null); + let diagnostics = self.plugin.validate(&config); + Ok(Response::new(ValidateResponse { + diagnostics: Some(infallible_json_envelope( + "nemo.relay.PluginDiagnostics@1", + &diagnostics, + )), + error: None, + })) + } + + async fn register( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + self.authorize(&request.activation_id, &request.auth_token)?; + let config = request + .config + .as_ref() + .map(decode_json_envelope::) + .transpose() + .map_err(|err| Status::invalid_argument(format!("invalid config JSON: {err}")))? + .unwrap_or(Json::Null); + let mut ctx = PluginContext::with_runtime(self.runtime.clone()); + if let Err(err) = self.plugin.register(&mut ctx, &config) { + return Ok(Response::new(RegisterResponse { + registrations: Vec::new(), + error: Some(sdk_error_to_worker(err)), + })); + } + let registrations = ctx.handlers.registrations.clone(); + *self + .handlers + .lock() + .map_err(|err| Status::internal(format!("handler lock poisoned: {err}")))? = + ctx.handlers; + Ok(Response::new(RegisterResponse { + registrations, + error: None, + })) + } + + async fn invoke( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + self.authorize(&request.activation_id, &request.auth_token)?; + let response = self.invoke_inner(request).await; + Ok(Response::new(response)) + } + + type InvokeStreamStream = + Pin> + Send>>; + + async fn invoke_stream( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + self.authorize(&request.activation_id, &request.auth_token)?; + let scope = invocation_scope_context(request.scope.as_ref()); + let surface = RegistrationSurface::try_from(request.surface) + .map_err(|_| Status::invalid_argument("unknown registration surface"))?; + if surface != RegistrationSurface::LlmStreamExecutionIntercept { + return Err(Status::invalid_argument( + "InvokeStream only supports LLM stream execution", + )); + } + let handler = self + .handlers + .lock() + .map_err(|err| Status::internal(format!("handler lock poisoned: {err}")))? + .llm_stream_executions + .get(&request.registration_name) + .cloned() + .ok_or_else(|| Status::not_found("stream execution handler not registered"))?; + let payload = llm_payload(request.payload).map_err(status_from_sdk)?; + let request_value = + required_json::(payload.request, "llm request").map_err(status_from_sdk)?; + let next = LlmStreamNext { + runtime: self.runtime.clone(), + continuation_id: request.continuation_id, + }; + let stream = TASK_SCOPE_CONTEXT + .scope(scope.clone(), async { + let future = + with_thread_scope(&scope, || handler(&payload.model_name, request_value, next)); + future.await + }) + .await + .map_err(|err| Status::internal(err.to_string()))?; + let stream = ScopedJsonStream::new(stream, scope); + let mapped = stream.map(|item| match item { + Ok(value) => Ok(StreamChunk { + item: Some(nemo_relay_worker_proto::v1::stream_chunk::Item::Value( + json_envelope("nemo.relay.Json@1", &value) + .map_err(|err| Status::internal(err.to_string()))?, + )), + }), + Err(err) => Ok(StreamChunk { + item: Some(nemo_relay_worker_proto::v1::stream_chunk::Item::Error( + sdk_error_to_worker(err), + )), + }), + }); + Ok(Response::new(Box::pin(mapped))) + } + + async fn cancel_invocation( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + self.authorize(&request.activation_id, &request.auth_token)?; + Ok(Response::new(WorkerAck { + accepted: false, + message: "cancel invocation is not implemented by the Rust worker SDK yet".into(), + })) + } + + async fn shutdown( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + self.authorize(&request.activation_id, &request.auth_token)?; + Ok(Response::new(WorkerAck { + accepted: false, + message: "shutdown is not implemented by the Rust worker SDK yet".into(), + })) + } +} + +impl WorkerService { + fn authorize(&self, activation_id: &str, auth_token: &str) -> std::result::Result<(), Status> { + if activation_id != self.runtime.activation_id { + return Err(Status::permission_denied("invalid worker activation")); + } + if auth_token != self.runtime.auth_token { + return Err(Status::permission_denied("invalid worker token")); + } + Ok(()) + } + + async fn invoke_inner(&self, request: InvokeRequest) -> InvokeResponse { + match self.invoke_result(request).await { + Ok(response) => response, + Err(err) => InvokeResponse { + result: Some(nemo_relay_worker_proto::v1::invoke_response::Result::Error( + sdk_error_to_worker(err), + )), + }, + } + } + + async fn invoke_result(&self, request: InvokeRequest) -> Result { + let scope = invocation_scope_context(request.scope.as_ref()); + TASK_SCOPE_CONTEXT + .scope(scope.clone(), self.invoke_result_scoped(request, scope)) + .await + } + + async fn invoke_result_scoped( + &self, + request: InvokeRequest, + scope: Option, + ) -> Result { + let surface = RegistrationSurface::try_from(request.surface) + .map_err(|_| WorkerSdkError::InvalidInput("unknown registration surface".into()))?; + match surface { + RegistrationSurface::Subscriber => { + let event = event_payload(request.payload)?; + let handler = self.subscriber(&request.registration_name)?; + with_thread_scope(&scope, || handler(&event)); + Ok(empty_response()) + } + RegistrationSurface::ToolSanitizeRequestGuardrail => { + let payload = tool_payload(request.payload)?; + let handler = self.tool_sanitize_request(&request.registration_name)?; + Ok(json_response(with_thread_scope(&scope, || { + handler(&payload.tool_name, payload.value) + }))) + } + RegistrationSurface::ToolSanitizeResponseGuardrail => { + let payload = tool_payload(request.payload)?; + let handler = self.tool_sanitize_response(&request.registration_name)?; + Ok(json_response(with_thread_scope(&scope, || { + handler(&payload.tool_name, payload.value) + }))) + } + RegistrationSurface::ToolConditionalExecutionGuardrail => { + let payload = tool_payload(request.payload)?; + let handler = self.tool_conditional(&request.registration_name)?; + Ok(guardrail_response(with_thread_scope(&scope, || { + handler(&payload.tool_name, &payload.value) + })?)) + } + RegistrationSurface::ToolRequestIntercept => { + let payload = tool_payload(request.payload)?; + let handler = self.tool_request(&request.registration_name)?; + Ok(json_response(with_thread_scope(&scope, || { + handler(&payload.tool_name, payload.value) + })?)) + } + RegistrationSurface::ToolExecutionIntercept => { + let payload = tool_payload(request.payload)?; + let handler = self.tool_execution(&request.registration_name)?; + let next = ToolNext { + runtime: self.runtime.clone(), + continuation_id: request.continuation_id, + }; + let future = + with_thread_scope(&scope, || handler(&payload.tool_name, payload.value, next)); + Ok(json_response(future.await?)) + } + RegistrationSurface::LlmSanitizeRequestGuardrail => { + let payload = llm_payload(request.payload)?; + let request_value = required_json::(payload.request, "llm request")?; + let handler = self.llm_sanitize_request(&request.registration_name)?; + let request = with_thread_scope(&scope, || handler(request_value)); + Ok(json_response( + serde_json::to_value(request).expect("LLM request is JSON serializable"), + )) + } + RegistrationSurface::LlmSanitizeResponseGuardrail => { + let payload = llm_payload(request.payload)?; + let response = required_json::(payload.response, "llm response")?; + let handler = self.llm_sanitize_response(&request.registration_name)?; + Ok(json_response(with_thread_scope(&scope, || { + handler(response) + }))) + } + RegistrationSurface::LlmConditionalExecutionGuardrail => { + let payload = llm_payload(request.payload)?; + let request_value = required_json::(payload.request, "llm request")?; + let handler = self.llm_conditional(&request.registration_name)?; + Ok(guardrail_response(with_thread_scope(&scope, || { + handler(&request_value) + })?)) + } + RegistrationSurface::LlmRequestIntercept => { + let payload = llm_payload(request.payload)?; + let request_value = required_json::(payload.request, "llm request")?; + let annotated = payload + .annotated_request + .map(|value| decode_json_envelope::(&value)) + .transpose()?; + let handler = self.llm_request(&request.registration_name)?; + let (request, annotated) = with_thread_scope(&scope, || { + handler(&payload.model_name, request_value, annotated) + })?; + Ok(llm_request_response(request, annotated)?) + } + RegistrationSurface::LlmExecutionIntercept => { + let payload = llm_payload(request.payload)?; + let request_value = required_json::(payload.request, "llm request")?; + let handler = self.llm_execution(&request.registration_name)?; + let next = LlmNext { + runtime: self.runtime.clone(), + continuation_id: request.continuation_id, + }; + let future = + with_thread_scope(&scope, || handler(&payload.model_name, request_value, next)); + Ok(json_response(future.await?)) + } + RegistrationSurface::LlmStreamExecutionIntercept | RegistrationSurface::Unspecified => { + Err(WorkerSdkError::InvalidInput( + "surface must use InvokeStream or is unspecified".into(), + )) + } + } + } + + fn subscriber(&self, name: &str) -> Result { + self.handlers + .lock() + .map_err(|err| WorkerSdkError::Callback(format!("handler lock poisoned: {err}")))? + .subscribers + .get(name) + .cloned() + .ok_or_else(|| { + WorkerSdkError::InvalidInput(format!("subscriber '{name}' not registered")) + }) + } + + fn tool_sanitize_request(&self, name: &str) -> Result { + self.handlers + .lock() + .map_err(|err| WorkerSdkError::Callback(format!("handler lock poisoned: {err}")))? + .tool_sanitize_requests + .get(name) + .cloned() + .ok_or_else(|| { + WorkerSdkError::InvalidInput(format!( + "tool request sanitizer '{name}' not registered" + )) + }) + } + + fn tool_sanitize_response(&self, name: &str) -> Result { + self.handlers + .lock() + .map_err(|err| WorkerSdkError::Callback(format!("handler lock poisoned: {err}")))? + .tool_sanitize_responses + .get(name) + .cloned() + .ok_or_else(|| { + WorkerSdkError::InvalidInput(format!( + "tool response sanitizer '{name}' not registered" + )) + }) + } + + fn tool_conditional(&self, name: &str) -> Result { + self.handlers + .lock() + .map_err(|err| WorkerSdkError::Callback(format!("handler lock poisoned: {err}")))? + .tool_conditionals + .get(name) + .cloned() + .ok_or_else(|| { + WorkerSdkError::InvalidInput(format!("tool conditional '{name}' not registered")) + }) + } + + fn tool_request(&self, name: &str) -> Result { + self.handlers + .lock() + .map_err(|err| WorkerSdkError::Callback(format!("handler lock poisoned: {err}")))? + .tool_requests + .get(name) + .cloned() + .ok_or_else(|| { + WorkerSdkError::InvalidInput(format!("tool request '{name}' not registered")) + }) + } + + fn tool_execution(&self, name: &str) -> Result { + self.handlers + .lock() + .map_err(|err| WorkerSdkError::Callback(format!("handler lock poisoned: {err}")))? + .tool_executions + .get(name) + .cloned() + .ok_or_else(|| { + WorkerSdkError::InvalidInput(format!("tool execution '{name}' not registered")) + }) + } + + fn llm_sanitize_request(&self, name: &str) -> Result { + self.handlers + .lock() + .map_err(|err| WorkerSdkError::Callback(format!("handler lock poisoned: {err}")))? + .llm_sanitize_requests + .get(name) + .cloned() + .ok_or_else(|| { + WorkerSdkError::InvalidInput(format!( + "llm request sanitizer '{name}' not registered" + )) + }) + } + + fn llm_sanitize_response(&self, name: &str) -> Result { + self.handlers + .lock() + .map_err(|err| WorkerSdkError::Callback(format!("handler lock poisoned: {err}")))? + .llm_sanitize_responses + .get(name) + .cloned() + .ok_or_else(|| { + WorkerSdkError::InvalidInput(format!( + "llm response sanitizer '{name}' not registered" + )) + }) + } + + fn llm_conditional(&self, name: &str) -> Result { + self.handlers + .lock() + .map_err(|err| WorkerSdkError::Callback(format!("handler lock poisoned: {err}")))? + .llm_conditionals + .get(name) + .cloned() + .ok_or_else(|| { + WorkerSdkError::InvalidInput(format!("llm conditional '{name}' not registered")) + }) + } + + fn llm_request(&self, name: &str) -> Result { + self.handlers + .lock() + .map_err(|err| WorkerSdkError::Callback(format!("handler lock poisoned: {err}")))? + .llm_requests + .get(name) + .cloned() + .ok_or_else(|| { + WorkerSdkError::InvalidInput(format!("llm request '{name}' not registered")) + }) + } + + fn llm_execution(&self, name: &str) -> Result { + self.handlers + .lock() + .map_err(|err| WorkerSdkError::Callback(format!("handler lock poisoned: {err}")))? + .llm_executions + .get(name) + .cloned() + .ok_or_else(|| { + WorkerSdkError::InvalidInput(format!("llm execution '{name}' not registered")) + }) + } +} + +struct ToolPayload { + tool_name: String, + value: Json, +} + +struct LlmPayload { + model_name: String, + request: Option, + annotated_request: Option, + response: Option, +} + +fn event_payload( + payload: Option, +) -> Result { + match payload { + Some(nemo_relay_worker_proto::v1::invoke_request::Payload::Event(value)) => { + Ok(decode_json_envelope::(&value)?) + } + _ => Err(WorkerSdkError::InvalidInput( + "expected event payload".into(), + )), + } +} + +fn tool_payload( + payload: Option, +) -> Result { + match payload { + Some(nemo_relay_worker_proto::v1::invoke_request::Payload::Tool(value)) => { + let json = required_json::(value.value, "tool value")?; + Ok(ToolPayload { + tool_name: value.tool_name, + value: json, + }) + } + _ => Err(WorkerSdkError::InvalidInput("expected tool payload".into())), + } +} + +fn llm_payload( + payload: Option, +) -> Result { + match payload { + Some(nemo_relay_worker_proto::v1::invoke_request::Payload::Llm(value)) => Ok(LlmPayload { + model_name: value.model_name, + request: value.request, + annotated_request: value.annotated_request, + response: value.response, + }), + _ => Err(WorkerSdkError::InvalidInput("expected llm payload".into())), + } +} + +fn required_json( + value: Option, + field: &str, +) -> Result { + let value = value.ok_or_else(|| WorkerSdkError::InvalidInput(format!("{field} is missing")))?; + Ok(decode_json_envelope::(&value)?) +} + +fn empty_response() -> InvokeResponse { + InvokeResponse { + result: Some(nemo_relay_worker_proto::v1::invoke_response::Result::Empty( + EmptyResult {}, + )), + } +} + +fn json_response(value: Json) -> InvokeResponse { + InvokeResponse { + result: Some(nemo_relay_worker_proto::v1::invoke_response::Result::Json( + JsonResult { + value: Some(infallible_json_envelope("nemo.relay.Json@1", &value)), + error: None, + }, + )), + } +} + +fn guardrail_response(reason: Option) -> InvokeResponse { + InvokeResponse { + result: Some( + nemo_relay_worker_proto::v1::invoke_response::Result::Guardrail(GuardrailResult { + block_reason: reason.unwrap_or_default(), + }), + ), + } +} + +fn llm_request_response( + request: LlmRequest, + annotated: Option, +) -> Result { + Ok(InvokeResponse { + result: Some( + nemo_relay_worker_proto::v1::invoke_response::Result::LlmRequest( + LlmRequestInterceptResult { + request: Some(json_envelope("nemo.relay.LlmRequest@1", &request)?), + annotated_request: annotated + .as_ref() + .map(|value| json_envelope("nemo.relay.AnnotatedLlmRequest@1", value)) + .transpose()?, + has_annotated_request: annotated.is_some(), + }, + ), + ), + }) +} + +fn stream_chunk_to_json(chunk: StreamChunk) -> Result { + match chunk.item { + Some(nemo_relay_worker_proto::v1::stream_chunk::Item::Value(value)) => { + Ok(decode_json_envelope::(&value)?) + } + Some(nemo_relay_worker_proto::v1::stream_chunk::Item::Error(error)) => { + Err(worker_error_to_sdk(error)) + } + None => Err(WorkerSdkError::InvalidInput("empty stream chunk".into())), + } +} + +fn json_result_to_sdk(result: JsonResult) -> Result { + if let Some(error) = result.error { + return Err(worker_error_to_sdk(error)); + } + required_json(result.value, "json result") +} + +fn optional_json_envelope(value: Option) -> Result> { + value + .as_ref() + .map(|value| json_envelope("nemo.relay.Json@1", value).map_err(WorkerSdkError::from)) + .transpose() +} + +fn infallible_json_envelope(schema: &str, value: &T) -> JsonEnvelope { + json_envelope(schema, value).expect("Relay DTOs and serde_json::Value are JSON serializable") +} + +fn sdk_error_to_worker(error: WorkerSdkError) -> WorkerError { + WorkerError { + code: "worker.error".into(), + message: error.to_string(), + retryable: false, + } +} + +fn worker_error_to_sdk(error: WorkerError) -> WorkerSdkError { + WorkerSdkError::Callback(format!("{}: {}", error.code, error.message)) +} + +fn status_from_sdk(error: WorkerSdkError) -> Status { + Status::internal(error.to_string()) +} + +fn ack_to_result(ok: bool, error: Option) -> Result<()> { + if ok { + Ok(()) + } else { + Err(error + .map(worker_error_to_sdk) + .unwrap_or_else(|| WorkerSdkError::Callback("host call failed".into()))) + } +} + +struct ScopedJsonStream { + inner: JsonStream, + scope: Option, +} + +impl ScopedJsonStream { + fn new(inner: JsonStream, scope: Option) -> Self { + Self { inner, scope } + } +} + +impl Stream for ScopedJsonStream { + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + let scope = this.scope.clone(); + TASK_SCOPE_CONTEXT.sync_scope(scope.clone(), || { + with_thread_scope(&scope, || this.inner.as_mut().poll_next(cx)) + }) + } +} + +fn invocation_scope_context(scope: Option<&ScopeContext>) -> Option { + scope + .filter(|scope| !scope.scope_stack_id.trim().is_empty()) + .cloned() +} + +fn current_scope_context() -> Option { + TASK_SCOPE_CONTEXT + .try_with(Clone::clone) + .ok() + .flatten() + .or_else(|| THREAD_SCOPE_CONTEXT.with(|scope| scope.borrow().clone())) +} + +fn with_thread_scope(scope: &Option, f: impl FnOnce() -> T) -> T { + let _guard = ThreadScopeBinding::new(scope.clone()); + f() +} + +struct ThreadScopeBinding { + previous: Option, +} + +impl ThreadScopeBinding { + fn new(scope: Option) -> Self { + let previous = THREAD_SCOPE_CONTEXT.with(|current| current.replace(scope)); + Self { previous } + } +} + +impl Drop for ThreadScopeBinding { + fn drop(&mut self) { + let previous = self.previous.take(); + THREAD_SCOPE_CONTEXT.with(|scope| { + scope.replace(previous); + }); + } +} + +fn scope_context(scope_stack_id: &str) -> ScopeContext { + ScopeContext { + scope_stack_id: scope_stack_id.into(), + parent_scope_id: String::new(), + } +} + +fn proto_scope_type(scope_type: ScopeType) -> i32 { + (match scope_type { + ScopeType::Agent => nemo_relay_worker_proto::v1::ScopeType::Agent, + ScopeType::Function => nemo_relay_worker_proto::v1::ScopeType::Function, + ScopeType::Tool => nemo_relay_worker_proto::v1::ScopeType::Tool, + ScopeType::Llm => nemo_relay_worker_proto::v1::ScopeType::Llm, + ScopeType::Retriever => nemo_relay_worker_proto::v1::ScopeType::Retriever, + ScopeType::Embedder => nemo_relay_worker_proto::v1::ScopeType::Embedder, + ScopeType::Reranker => nemo_relay_worker_proto::v1::ScopeType::Reranker, + ScopeType::Guardrail => nemo_relay_worker_proto::v1::ScopeType::Guardrail, + ScopeType::Evaluator => nemo_relay_worker_proto::v1::ScopeType::Evaluator, + ScopeType::Custom => nemo_relay_worker_proto::v1::ScopeType::Custom, + ScopeType::Unknown => nemo_relay_worker_proto::v1::ScopeType::Unknown, + }) as i32 +} + +fn all_surfaces() -> Vec { + vec![ + RegistrationSurface::Subscriber, + RegistrationSurface::ToolSanitizeRequestGuardrail, + RegistrationSurface::ToolSanitizeResponseGuardrail, + RegistrationSurface::ToolConditionalExecutionGuardrail, + RegistrationSurface::ToolRequestIntercept, + RegistrationSurface::ToolExecutionIntercept, + RegistrationSurface::LlmSanitizeRequestGuardrail, + RegistrationSurface::LlmSanitizeResponseGuardrail, + RegistrationSurface::LlmConditionalExecutionGuardrail, + RegistrationSurface::LlmRequestIntercept, + RegistrationSurface::LlmExecutionIntercept, + RegistrationSurface::LlmStreamExecutionIntercept, + ] +} + +async fn connect_host_endpoint(endpoint: &str) -> Result { + if endpoint.starts_with("unix://") { + return connect_uds(endpoint).await; + } + let endpoint = normalize_tcp_endpoint(endpoint)?; + Endpoint::from_shared(endpoint) + .map_err(|err| WorkerSdkError::InvalidInput(err.to_string()))? + .connect() + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string())) +} + +#[cfg(unix)] +async fn connect_uds(endpoint: &str) -> Result { + let path = Arc::new(parse_unix_endpoint(endpoint)?); + let endpoint = Endpoint::try_from("http://[::]:50051") + .map_err(|err| WorkerSdkError::Transport(err.to_string()))?; + endpoint + .connect_with_connector(service_fn(move |_| { + let path = path.clone(); + async move { + let stream = UnixStream::connect(&*path).await?; + Ok::<_, std::io::Error>(TokioIo::new(stream)) + } + })) + .await + .map_err(|err| WorkerSdkError::Transport(err.to_string())) +} + +#[cfg(not(unix))] +async fn connect_uds(_endpoint: &str) -> Result { + Err(WorkerSdkError::InvalidInput( + "unix endpoints are not supported on this platform".into(), + )) +} + +fn parse_tcp_endpoint(endpoint: &str) -> Result { + let endpoint = normalize_tcp_endpoint(endpoint)?; + let authority = endpoint + .strip_prefix("http://") + .expect("normalized TCP endpoints always use http scheme"); + if authority.contains('/') { + return Err(WorkerSdkError::InvalidInput(format!( + "unsupported TCP endpoint '{endpoint}'" + ))); + } + authority + .to_socket_addrs() + .map_err(|err| { + WorkerSdkError::InvalidInput(format!("invalid TCP endpoint '{endpoint}': {err}")) + })? + .next() + .ok_or_else(|| WorkerSdkError::InvalidInput(format!("invalid TCP endpoint '{endpoint}'"))) +} + +fn normalize_tcp_endpoint(endpoint: &str) -> Result { + if let Some(authority) = endpoint.strip_prefix("tcp://") { + if authority.is_empty() { + return Err(WorkerSdkError::InvalidInput(format!( + "unsupported endpoint '{endpoint}'" + ))); + } + return Ok(format!("http://{authority}")); + } + if endpoint.starts_with("http://") { + return Ok(endpoint.to_owned()); + } + Err(WorkerSdkError::InvalidInput(format!( + "unsupported endpoint '{endpoint}'" + ))) +} + +#[cfg(unix)] +fn parse_unix_endpoint(endpoint: &str) -> Result { + endpoint + .strip_prefix("unix://") + .map(PathBuf::from) + .ok_or_else(|| WorkerSdkError::InvalidInput(format!("unsupported endpoint '{endpoint}'"))) +} + +#[cfg(unix)] +fn remove_stale_socket(path: &Path) -> Result<()> { + let metadata = match std::fs::symlink_metadata(path) { + Ok(metadata) => metadata, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(err) => { + return Err(WorkerSdkError::Transport(format!( + "failed to inspect worker socket path '{}': {err}", + path.display() + ))); + } + }; + if !metadata.file_type().is_socket() { + return Err(WorkerSdkError::InvalidInput(format!( + "worker socket path '{}' exists and is not a socket", + path.display() + ))); + } + std::fs::remove_file(path).map_err(|err| { + WorkerSdkError::Transport(format!( + "failed to remove stale worker socket '{}': {err}", + path.display() + )) + }) +} + +fn required_env(name: &str) -> Result { + std::env::var(name).map_err(|_| { + WorkerSdkError::InvalidInput(format!("environment variable {name} is required")) + }) +} + +fn rustc_version_runtime() -> String { + option_env!("RUSTC_VERSION") + .unwrap_or("unknown") + .to_string() +} diff --git a/crates/worker/tests/worker_sdk_tests.rs b/crates/worker/tests/worker_sdk_tests.rs new file mode 100644 index 000000000..6941b71fb --- /dev/null +++ b/crates/worker/tests/worker_sdk_tests.rs @@ -0,0 +1,2111 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! End-to-end coverage for the Rust gRPC worker SDK service. + +use std::future::Future; +use std::net::{SocketAddr, TcpListener}; +#[cfg(unix)] +use std::path::{Path, PathBuf}; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; +use std::time::Duration; +#[cfg(unix)] +use std::time::{SystemTime, UNIX_EPOCH}; + +use futures_util::{Stream, StreamExt}; +#[cfg(unix)] +use hyper_util::rt::TokioIo; +use nemo_relay_types::api::event::{BaseEvent, Event, MarkEvent}; +use nemo_relay_worker::{ + Json, JsonStream, LlmNext, LlmRequest, LlmStreamNext, PluginContext, PluginRuntime, Result, + ScopeType, ToolNext, WorkerPlugin, WorkerSdkError, WorkerServerConfig, serve_plugin, + serve_plugin_arc, serve_plugin_arc_with_config, +}; +use nemo_relay_worker_proto::v1::plugin_worker_client::PluginWorkerClient; +use nemo_relay_worker_proto::v1::relay_host_runtime_server::{ + RelayHostRuntime, RelayHostRuntimeServer, +}; +use nemo_relay_worker_proto::v1::{ + CancelInvocationRequest, CreateScopeStackRequest, CreateScopeStackResponse, + DropScopeStackRequest, EmitMarkRequest, HandshakeRequest, HealthRequest, HostAck, + InvokeRequest, InvokeResponse, JsonEnvelope, JsonResult, LlmInvocation, LlmNextRequest, + LlmStreamNextRequest, PopScopeRequest, PushScopeRequest, PushScopeResponse, RegisterRequest, + RegistrationSurface, ScopeContext, ShutdownRequest, StreamChunk, ToolInvocation, + ToolNextRequest, ValidateRequest, WorkerError, +}; +use nemo_relay_worker_proto::{WORKER_PROTOCOL_GRPC_V1, decode_json_envelope, json_envelope}; +use serde_json::json; +#[cfg(unix)] +use tokio::net::{UnixListener, UnixStream}; +use tokio::task::JoinHandle; +#[cfg(unix)] +use tokio_stream::wrappers::UnixListenerStream; +#[cfg(unix)] +use tonic::transport::Endpoint; +use tonic::transport::{Channel, Server}; +use tonic::{Request, Response, Status}; +#[cfg(unix)] +use tower::service_fn; + +const ACTIVATION_ID: &str = "activation-1"; +const AUTH_TOKEN: &str = "secret-token"; +const PLUGIN_ID: &str = "acme.worker"; +const REQUIRED_WORKER_ENVS: &[&str] = &[ + "NEMO_RELAY_WORKER_SOCKET", + "NEMO_RELAY_HOST_SOCKET", + "NEMO_RELAY_WORKER_ID", + "NEMO_RELAY_WORKER_TOKEN", +]; +static ENV_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(()); + +#[tokio::test(flavor = "multi_thread")] +async fn worker_service_enforces_auth_and_reports_registrations() { + let (handle, mut client) = spawn_worker( + Arc::new(SurfacePlugin::default()), + "http://127.0.0.1:9".into(), + ) + .await; + + let bad_handshake = client + .handshake(Request::new(HandshakeRequest { + activation_id: ACTIVATION_ID.into(), + plugin_id: PLUGIN_ID.into(), + relay_version: "0.5.0".into(), + worker_protocol: WORKER_PROTOCOL_GRPC_V1.into(), + auth_token: "bad-token".into(), + host_endpoint: "http://127.0.0.1:9".into(), + })) + .await + .expect_err("invalid token should fail"); + assert_eq!(bad_handshake.code(), tonic::Code::PermissionDenied); + + let bad_activation = client + .handshake(Request::new(HandshakeRequest { + activation_id: "wrong-activation".into(), + plugin_id: PLUGIN_ID.into(), + relay_version: "0.5.0".into(), + worker_protocol: WORKER_PROTOCOL_GRPC_V1.into(), + auth_token: AUTH_TOKEN.into(), + host_endpoint: "http://127.0.0.1:9".into(), + })) + .await + .expect_err("invalid activation should fail"); + assert_eq!(bad_activation.code(), tonic::Code::PermissionDenied); + + let handshake = client + .handshake(Request::new(HandshakeRequest { + activation_id: ACTIVATION_ID.into(), + plugin_id: PLUGIN_ID.into(), + relay_version: "0.5.0".into(), + worker_protocol: WORKER_PROTOCOL_GRPC_V1.into(), + auth_token: AUTH_TOKEN.into(), + host_endpoint: "http://127.0.0.1:9".into(), + })) + .await + .expect("handshake succeeds") + .into_inner(); + assert_eq!(handshake.plugin_id, PLUGIN_ID); + assert!( + handshake + .supported_surfaces + .contains(&(RegistrationSurface::LlmStreamExecutionIntercept as i32)) + ); + + let bad_health = client + .health(Request::new(HealthRequest { + activation_id: ACTIVATION_ID.into(), + auth_token: "bad-token".into(), + })) + .await + .expect_err("health auth should fail"); + assert_eq!(bad_health.code(), tonic::Code::PermissionDenied); + + let health = client + .health(Request::new(HealthRequest { + activation_id: ACTIVATION_ID.into(), + auth_token: AUTH_TOKEN.into(), + })) + .await + .expect("health succeeds") + .into_inner(); + assert!(health.ok); + assert_eq!(health.message, "ready"); + assert_eq!(health.plugin_id, PLUGIN_ID); + assert_eq!(health.worker_protocol, WORKER_PROTOCOL_GRPC_V1); + assert_eq!(health.sdk_name, "nemo-relay-worker"); + assert_eq!(health.runtime_name, "rust"); + + let validate_err = client + .validate(Request::new(ValidateRequest { + activation_id: ACTIVATION_ID.into(), + plugin_id: PLUGIN_ID.into(), + auth_token: "bad-token".into(), + config: Some(json_env(json!({"diagnostic": true}))), + })) + .await + .expect_err("validate auth should fail"); + assert_eq!(validate_err.code(), tonic::Code::PermissionDenied); + + let invalid_validate_config = client + .validate(Request::new(ValidateRequest { + activation_id: ACTIVATION_ID.into(), + plugin_id: PLUGIN_ID.into(), + auth_token: AUTH_TOKEN.into(), + config: Some(invalid_json_env("nemo.relay.Json@1")), + })) + .await + .expect_err("invalid validate config should fail"); + assert_eq!(invalid_validate_config.code(), tonic::Code::InvalidArgument); + + let diagnostics = client + .validate(Request::new(ValidateRequest { + activation_id: ACTIVATION_ID.into(), + plugin_id: PLUGIN_ID.into(), + auth_token: AUTH_TOKEN.into(), + config: Some(json_env(json!({"diagnostic": true}))), + })) + .await + .expect("validate succeeds") + .into_inner() + .diagnostics + .expect("diagnostics envelope"); + let diagnostics: Vec = + decode_json_envelope(&diagnostics).expect("decode diagnostics"); + assert_eq!(diagnostics.len(), 1); + + let register_err = client + .register(Request::new(RegisterRequest { + activation_id: ACTIVATION_ID.into(), + plugin_id: PLUGIN_ID.into(), + auth_token: "bad-token".into(), + config: Some(json_env(json!({}))), + })) + .await + .expect_err("register auth should fail"); + assert_eq!(register_err.code(), tonic::Code::PermissionDenied); + + let invalid_register_config = client + .register(Request::new(RegisterRequest { + activation_id: ACTIVATION_ID.into(), + plugin_id: PLUGIN_ID.into(), + auth_token: AUTH_TOKEN.into(), + config: Some(invalid_json_env("nemo.relay.Json@1")), + })) + .await + .expect_err("invalid register config should fail"); + assert_eq!(invalid_register_config.code(), tonic::Code::InvalidArgument); + + let registrations = register_plugin(&mut client).await; + assert_eq!(registrations.len(), 16); + assert_eq!( + registrations + .iter() + .filter(|registration| registration.local_name == "tool-sanitize") + .count(), + 2 + ); + + let invoke_err = client + .invoke(Request::new(InvokeRequest { + auth_token: "bad-token".into(), + ..tool_invoke( + "tool-request", + RegistrationSurface::ToolRequestIntercept, + json!({}), + ) + })) + .await + .expect_err("invoke auth should fail"); + assert_eq!(invoke_err.code(), tonic::Code::PermissionDenied); + + let cancel = client + .cancel_invocation(Request::new(CancelInvocationRequest { + activation_id: ACTIVATION_ID.into(), + invocation_id: "invoke-1".into(), + auth_token: AUTH_TOKEN.into(), + reason: "test".into(), + })) + .await + .expect("cancel returns ack") + .into_inner(); + assert!(!cancel.accepted); + assert!(cancel.message.contains("not implemented")); + + let shutdown = client + .shutdown(Request::new(ShutdownRequest { + activation_id: ACTIVATION_ID.into(), + auth_token: AUTH_TOKEN.into(), + reason: "test".into(), + })) + .await + .expect("shutdown returns ack") + .into_inner(); + assert!(!shutdown.accepted); + assert!(shutdown.message.contains("not implemented")); + + handle.abort(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn worker_service_invokes_every_registration_surface() { + let host = MockHost::default(); + let (host_handle, host_endpoint) = spawn_host(host.clone()).await; + let plugin = Arc::new(SurfacePlugin::default()); + let events = plugin.events.clone(); + let (worker_handle, mut client) = spawn_worker(plugin, tcp_endpoint(&host_endpoint)).await; + register_plugin(&mut client).await; + + let subscriber_response = client + .invoke(Request::new(event_invoke("subscriber"))) + .await + .expect("subscriber invoke") + .into_inner(); + assert_empty_response(subscriber_response); + assert_eq!( + events.lock().expect("events lock").as_slice(), + ["subscriber-event"] + ); + + assert_json_field( + invoke_json( + &mut client, + tool_invoke( + "tool-sanitize", + RegistrationSurface::ToolSanitizeRequestGuardrail, + json!({}), + ), + ) + .await, + "phase", + "tool_sanitize_request", + ); + assert_json_field( + invoke_json( + &mut client, + tool_invoke( + "tool-sanitize", + RegistrationSurface::ToolSanitizeResponseGuardrail, + json!({}), + ), + ) + .await, + "phase", + "tool_sanitize_response", + ); + assert_eq!( + invoke_guardrail( + &mut client, + tool_invoke( + "tool-conditional", + RegistrationSurface::ToolConditionalExecutionGuardrail, + json!({"block": true}), + ), + ) + .await, + "blocked-tool" + ); + assert_json_field( + invoke_json( + &mut client, + tool_invoke( + "tool-request", + RegistrationSurface::ToolRequestIntercept, + json!({}), + ), + ) + .await, + "phase", + "tool_request", + ); + let tool_exec = invoke_json( + &mut client, + tool_invoke( + "tool-exec", + RegistrationSurface::ToolExecutionIntercept, + json!({}), + ), + ) + .await; + assert_json_field(tool_exec.clone(), "next", "tool"); + assert_json_field(tool_exec, "phase", "tool_exec"); + assert!( + invoke_json( + &mut client, + tool_invoke( + "tool-scope-types", + RegistrationSurface::ToolExecutionIntercept, + json!({}), + ), + ) + .await + .is_null() + ); + + let llm_sanitize = invoke_json( + &mut client, + llm_invoke( + "llm-sanitize-request", + RegistrationSurface::LlmSanitizeRequestGuardrail, + llm_request(), + None, + None, + ), + ) + .await; + assert_eq!( + llm_sanitize + .get("content") + .and_then(|value| value.get("phase")), + Some(&json!("llm_sanitize_request")) + ); + assert_json_field( + invoke_json( + &mut client, + llm_invoke( + "llm-sanitize-response", + RegistrationSurface::LlmSanitizeResponseGuardrail, + llm_request(), + None, + Some(json!({})), + ), + ) + .await, + "phase", + "llm_sanitize_response", + ); + assert_eq!( + invoke_guardrail( + &mut client, + llm_invoke( + "llm-conditional", + RegistrationSurface::LlmConditionalExecutionGuardrail, + llm_request_with_block(), + None, + None, + ), + ) + .await, + "blocked-llm" + ); + let intercepted = invoke_llm_request( + &mut client, + llm_invoke( + "llm-request", + RegistrationSurface::LlmRequestIntercept, + llm_request(), + None, + None, + ), + ) + .await; + assert_eq!( + intercepted.content.get("phase"), + Some(&json!("llm_request")) + ); + let llm_exec = invoke_json( + &mut client, + llm_invoke( + "llm-exec", + RegistrationSurface::LlmExecutionIntercept, + llm_request(), + None, + None, + ), + ) + .await; + assert_json_field(llm_exec.clone(), "next", "llm"); + assert_json_field(llm_exec, "phase", "llm_exec"); + + let mut stream = client + .invoke_stream(Request::new(llm_invoke( + "llm-stream", + RegistrationSurface::LlmStreamExecutionIntercept, + llm_request(), + None, + None, + ))) + .await + .expect("stream invoke") + .into_inner(); + let first = stream_json(stream.next().await.expect("first stream item").unwrap()); + assert_json_field(first, "phase", "stream_poll"); + let second = stream_json(stream.next().await.expect("second stream item").unwrap()); + assert_json_field(second, "next", "llm_stream"); + assert!(stream.next().await.is_none()); + + let stream_auth = client + .invoke_stream(Request::new(InvokeRequest { + auth_token: "bad-token".into(), + ..llm_invoke( + "llm-stream", + RegistrationSurface::LlmStreamExecutionIntercept, + llm_request(), + None, + None, + ) + })) + .await + .expect_err("stream auth should fail"); + assert_eq!(stream_auth.code(), tonic::Code::PermissionDenied); + + let calls = host.calls(); + assert!(calls.contains(&"mark:tool-exec:stack-1:parent-1".into())); + assert!(calls.contains(&"create_scope_stack".into())); + assert!(calls.contains(&"push:worker-scope:stack-1:parent-1".into())); + assert!(calls.contains(&"pop:scope-handle-1".into())); + assert!(calls.contains(&"drop:isolated-stack".into())); + assert!(calls.contains(&"tool_next:next-1".into())); + assert!(calls.contains(&"llm_next:next-1".into())); + assert!(calls.contains(&"llm_stream_next:next-1".into())); + assert!(calls.contains(&"mark:stream-poll:stack-1:parent-1".into())); + assert!(calls.contains(&"push:scope-agent:explicit-stack:".into())); + assert!(calls.contains(&"push:scope-unknown:explicit-stack:".into())); + + worker_handle.abort(); + host_handle.abort(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn worker_service_reports_structured_callback_and_payload_errors() { + let (handle, mut client) = spawn_worker( + Arc::new(SurfacePlugin::default()), + "http://127.0.0.1:9".into(), + ) + .await; + register_plugin(&mut client).await; + + assert_worker_error( + client + .invoke(Request::new(InvokeRequest { + surface: 999, + ..tool_invoke( + "tool-request", + RegistrationSurface::ToolRequestIntercept, + json!({}), + ) + })) + .await + .expect("unknown surface returns structured error") + .into_inner(), + "unknown registration surface", + ); + assert_worker_error( + client + .invoke(Request::new(tool_invoke( + "missing", + RegistrationSurface::ToolRequestIntercept, + json!({}), + ))) + .await + .expect("unknown registration returns structured error") + .into_inner(), + "not registered", + ); + assert_worker_error( + client + .invoke(Request::new(InvokeRequest { + payload: None, + ..tool_invoke( + "tool-request", + RegistrationSurface::ToolRequestIntercept, + json!({}), + ) + })) + .await + .expect("invalid payload returns structured error") + .into_inner(), + "expected tool payload", + ); + assert_worker_error( + client + .invoke(Request::new(InvokeRequest { + payload: Some(nemo_relay_worker_proto::v1::invoke_request::Payload::Tool( + ToolInvocation { + tool_name: "tool".into(), + value: Some(JsonEnvelope { + schema: "nemo.relay.Json@1".into(), + json: b"{".to_vec(), + }), + }, + )), + ..tool_invoke( + "tool-request", + RegistrationSurface::ToolRequestIntercept, + json!({}), + ) + })) + .await + .expect("invalid JSON returns structured error") + .into_inner(), + "EOF while parsing", + ); + assert_worker_error( + client + .invoke(Request::new(tool_invoke( + "tool-error", + RegistrationSurface::ToolRequestIntercept, + json!({}), + ))) + .await + .expect("callback error returns structured error") + .into_inner(), + "boom", + ); + + let stream_err = client + .invoke_stream(Request::new(llm_invoke( + "llm-stream-error", + RegistrationSurface::LlmStreamExecutionIntercept, + llm_request(), + None, + None, + ))) + .await + .expect("stream error invoke") + .into_inner() + .next() + .await + .expect("stream item") + .expect("stream chunk"); + match stream_err.item.expect("stream item") { + nemo_relay_worker_proto::v1::stream_chunk::Item::Error(error) => { + assert!(error.message.contains("stream boom")); + } + other => panic!("unexpected stream item: {other:?}"), + } + + let stream_surface_err = client + .invoke_stream(Request::new(tool_invoke( + "tool-request", + RegistrationSurface::ToolRequestIntercept, + json!({}), + ))) + .await + .expect_err("wrong stream surface should fail transport call"); + assert_eq!(stream_surface_err.code(), tonic::Code::InvalidArgument); + + handle.abort(); +} + +#[test] +fn worker_plugin_defaults_and_context_construction_are_covered() { + let plugin = MinimalPlugin; + assert_eq!(plugin.plugin_id(), "minimal"); + assert!(!plugin.allows_multiple_components()); + assert!(plugin.validate(&Json::Null).is_empty()); + + assert!(PluginContext::new().runtime().is_none()); + assert!(PluginContext::default().runtime().is_none()); +} + +#[tokio::test(flavor = "current_thread")] +async fn worker_service_validates_env_and_endpoints() { + let _env_guard = ENV_LOCK.lock().await; + let snapshot = EnvSnapshot::capture(REQUIRED_WORKER_ENVS); + + clear_required_envs(); + let err = serve_plugin(MinimalPlugin) + .await + .expect_err("missing worker socket env fails"); + assert_error_contains(err, "NEMO_RELAY_WORKER_SOCKET"); + + for missing in REQUIRED_WORKER_ENVS { + set_required_envs(); + remove_env(missing); + let err = serve_plugin_arc(Arc::new(MinimalPlugin)) + .await + .expect_err("missing env fails"); + assert_error_contains(err, missing); + } + + set_required_envs(); + set_env("NEMO_RELAY_WORKER_SOCKET", "ftp://127.0.0.1:1"); + let err = serve_plugin_arc(Arc::new(MinimalPlugin)) + .await + .expect_err("valid env values reach endpoint validation"); + assert_error_contains(err, "unsupported endpoint"); + + snapshot.restore(); + + let unsupported = + serve_plugin_arc_with_config(Arc::new(MinimalPlugin), server_config("ftp://127.0.0.1:1")) + .await + .expect_err("unsupported worker endpoint fails"); + assert_error_contains(unsupported, "unsupported endpoint"); + + let empty_tcp = serve_plugin_arc_with_config(Arc::new(MinimalPlugin), server_config("tcp://")) + .await + .expect_err("empty tcp endpoint fails"); + assert_error_contains(empty_tcp, "unsupported endpoint"); + + let path_tcp = serve_plugin_arc_with_config( + Arc::new(MinimalPlugin), + server_config("http://127.0.0.1:1/path"), + ) + .await + .expect_err("tcp endpoint with path fails"); + assert_error_contains(path_tcp, "unsupported TCP endpoint"); + + let invalid_host = + serve_plugin_arc_with_config(Arc::new(MinimalPlugin), server_config("http://bad host")) + .await + .expect_err("invalid tcp host fails"); + assert_error_contains(invalid_host, "invalid TCP endpoint"); + + let busy_listener = TcpListener::bind("127.0.0.1:0").expect("bind busy listener"); + let busy_endpoint = format!( + "tcp://{}", + busy_listener.local_addr().expect("busy listener addr") + ); + let busy = serve_plugin_arc_with_config(Arc::new(MinimalPlugin), server_config(&busy_endpoint)) + .await + .expect_err("busy tcp endpoint fails"); + assert_error_contains(busy, "transport failed"); + drop(busy_listener); + + #[cfg(unix)] + { + let path = unique_temp_path("nrw-file"); + std::fs::write(&path, b"not a socket").expect("write regular file"); + let endpoint = format!("unix://{}", path.display()); + let err = serve_plugin_arc_with_config(Arc::new(MinimalPlugin), server_config(&endpoint)) + .await + .expect_err("regular file must not be removed as socket"); + assert_error_contains(err, "not a socket"); + assert!(path.exists()); + std::fs::remove_file(path).expect("remove regular file"); + + let missing_parent = unique_temp_path("nrw-missing-parent").join("worker.sock"); + let missing_parent_endpoint = format!("unix://{}", missing_parent.display()); + let bind_err = serve_plugin_arc_with_config( + Arc::new(MinimalPlugin), + server_config(&missing_parent_endpoint), + ) + .await + .expect_err("unix endpoint with missing parent fails during bind"); + assert_error_contains(bind_err, "failed to bind worker socket"); + } +} + +#[cfg(unix)] +#[tokio::test(flavor = "multi_thread")] +async fn worker_service_supports_unix_socket_worker_and_host_endpoints() { + let host = MockHost::default(); + let host_path = unique_temp_path("nrw-host"); + let worker_path = unique_temp_path("nrw-worker"); + let stale_worker_socket = + UnixListener::bind(&worker_path).expect("bind stale worker socket before test"); + drop(stale_worker_socket); + + let host_handle = spawn_unix_host(host.clone(), host_path.clone()).await; + let host_endpoint = format!("unix://{}", host_path.display()); + let worker_endpoint = format!("unix://{}", worker_path.display()); + let worker_handle = tokio::spawn(serve_plugin_arc_with_config( + Arc::new(SurfacePlugin::default()), + WorkerServerConfig { + worker_endpoint: worker_endpoint.clone(), + host_endpoint, + activation_id: ACTIVATION_ID.into(), + auth_token: AUTH_TOKEN.into(), + }, + )); + wait_for_unix_socket(&worker_path).await; + + let mut client = connect_worker_uds(&worker_endpoint).await; + register_plugin(&mut client).await; + let tool_exec = invoke_json( + &mut client, + tool_invoke( + "tool-exec", + RegistrationSurface::ToolExecutionIntercept, + json!({}), + ), + ) + .await; + assert_json_field(tool_exec, "phase", "tool_exec"); + assert!(host.calls().contains(&"tool_next:next-1".into())); + + worker_handle.abort(); + host_handle.abort(); + let _ = std::fs::remove_file(host_path); + let _ = std::fs::remove_file(worker_path); +} + +#[tokio::test(flavor = "multi_thread")] +async fn worker_service_reports_missing_handlers_and_malformed_payloads() { + let (handle, mut client) = spawn_worker( + Arc::new(SurfacePlugin::default()), + "http://127.0.0.1:9".into(), + ) + .await; + register_plugin(&mut client).await; + + for (request, expected) in [ + (event_invoke("missing-subscriber"), "subscriber"), + ( + tool_invoke( + "missing-tool-sanitize-request", + RegistrationSurface::ToolSanitizeRequestGuardrail, + json!({}), + ), + "tool request sanitizer", + ), + ( + tool_invoke( + "missing-tool-sanitize-response", + RegistrationSurface::ToolSanitizeResponseGuardrail, + json!({}), + ), + "tool response sanitizer", + ), + ( + tool_invoke( + "missing-tool-conditional", + RegistrationSurface::ToolConditionalExecutionGuardrail, + json!({}), + ), + "tool conditional", + ), + ( + tool_invoke( + "missing-tool-request", + RegistrationSurface::ToolRequestIntercept, + json!({}), + ), + "tool request", + ), + ( + tool_invoke( + "missing-tool-execution", + RegistrationSurface::ToolExecutionIntercept, + json!({}), + ), + "tool execution", + ), + ( + llm_invoke( + "missing-llm-sanitize-request", + RegistrationSurface::LlmSanitizeRequestGuardrail, + llm_request(), + None, + None, + ), + "llm request sanitizer", + ), + ( + llm_invoke( + "missing-llm-sanitize-response", + RegistrationSurface::LlmSanitizeResponseGuardrail, + llm_request(), + None, + Some(json!({})), + ), + "llm response sanitizer", + ), + ( + llm_invoke( + "missing-llm-conditional", + RegistrationSurface::LlmConditionalExecutionGuardrail, + llm_request(), + None, + None, + ), + "llm conditional", + ), + ( + llm_invoke( + "missing-llm-request", + RegistrationSurface::LlmRequestIntercept, + llm_request(), + None, + None, + ), + "llm request", + ), + ( + llm_invoke( + "missing-llm-execution", + RegistrationSurface::LlmExecutionIntercept, + llm_request(), + None, + None, + ), + "llm execution", + ), + ] { + assert_worker_error( + client + .invoke(Request::new(request)) + .await + .expect("missing handler returns structured error") + .into_inner(), + expected, + ); + } + + assert_worker_error( + client + .invoke(Request::new(InvokeRequest { + payload: None, + ..event_invoke("subscriber") + })) + .await + .expect("missing event payload returns structured error") + .into_inner(), + "expected event payload", + ); + assert_worker_error( + client + .invoke(Request::new(InvokeRequest { + payload: None, + ..llm_invoke( + "llm-exec", + RegistrationSurface::LlmExecutionIntercept, + llm_request(), + None, + None, + ) + })) + .await + .expect("missing llm payload returns structured error") + .into_inner(), + "expected llm payload", + ); + assert_worker_error( + client + .invoke(Request::new(llm_invoke_without_request( + "llm-sanitize-request", + RegistrationSurface::LlmSanitizeRequestGuardrail, + ))) + .await + .expect("missing llm request returns structured error") + .into_inner(), + "llm request is missing", + ); + assert_worker_error( + client + .invoke(Request::new(llm_invoke( + "llm-sanitize-response", + RegistrationSurface::LlmSanitizeResponseGuardrail, + llm_request(), + None, + None, + ))) + .await + .expect("missing llm response returns structured error") + .into_inner(), + "llm response is missing", + ); + assert_worker_error( + client + .invoke(Request::new(InvokeRequest { + surface: RegistrationSurface::Unspecified as i32, + ..tool_invoke( + "tool-request", + RegistrationSurface::ToolRequestIntercept, + json!({}), + ) + })) + .await + .expect("unspecified surface returns structured error") + .into_inner(), + "unspecified", + ); + assert_worker_error( + client + .invoke(Request::new(llm_invoke( + "llm-stream", + RegistrationSurface::LlmStreamExecutionIntercept, + llm_request(), + None, + None, + ))) + .await + .expect("stream surface on unary invoke returns structured error") + .into_inner(), + "InvokeStream", + ); + assert_worker_error( + client + .invoke(Request::new(llm_invoke_with_bad_annotation("llm-request"))) + .await + .expect("bad annotation returns structured error") + .into_inner(), + "EOF while parsing", + ); + + let unknown_stream_surface = client + .invoke_stream(Request::new(InvokeRequest { + surface: 999, + ..llm_invoke( + "llm-stream", + RegistrationSurface::LlmStreamExecutionIntercept, + llm_request(), + None, + None, + ) + })) + .await + .expect_err("unknown stream surface fails transport call"); + assert_eq!(unknown_stream_surface.code(), tonic::Code::InvalidArgument); + + let missing_stream = client + .invoke_stream(Request::new(llm_invoke( + "missing-llm-stream", + RegistrationSurface::LlmStreamExecutionIntercept, + llm_request(), + None, + None, + ))) + .await + .expect_err("missing stream handler fails transport call"); + assert_eq!(missing_stream.code(), tonic::Code::NotFound); + + let missing_stream_payload = client + .invoke_stream(Request::new(InvokeRequest { + payload: None, + ..llm_invoke( + "llm-stream", + RegistrationSurface::LlmStreamExecutionIntercept, + llm_request(), + None, + None, + ) + })) + .await + .expect_err("missing stream payload fails transport call"); + assert_status_message(missing_stream_payload, "expected llm payload"); + + let missing_stream_request = client + .invoke_stream(Request::new(llm_invoke_without_request( + "llm-stream", + RegistrationSurface::LlmStreamExecutionIntercept, + ))) + .await + .expect_err("missing stream request fails transport call"); + assert_status_message(missing_stream_request, "llm request is missing"); + + let open_error = client + .invoke_stream(Request::new(llm_invoke( + "llm-stream-open-error", + RegistrationSurface::LlmStreamExecutionIntercept, + llm_request(), + None, + None, + ))) + .await + .expect_err("stream open callback error fails transport call"); + assert_status_message(open_error, "stream open boom"); + + handle.abort(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn worker_service_propagates_host_runtime_errors() { + let host = MockHost::default(); + let (host_handle, host_endpoint) = spawn_host(host.clone()).await; + let (worker_handle, mut client) = spawn_worker( + Arc::new(SurfacePlugin::default()), + tcp_endpoint(&host_endpoint), + ) + .await; + register_plugin(&mut client).await; + + for (failures, expected) in [ + ( + MockHostFailures { + emit_mark: HostFailure::WorkerError, + ..Default::default() + }, + "emit mark failed", + ), + ( + MockHostFailures { + emit_mark: HostFailure::EmptyAck, + ..Default::default() + }, + "host call failed", + ), + ( + MockHostFailures { + create_scope_stack: true, + ..Default::default() + }, + "create scope stack failed", + ), + ( + MockHostFailures { + push_scope: true, + ..Default::default() + }, + "push scope failed", + ), + ( + MockHostFailures { + pop_scope: HostFailure::WorkerError, + ..Default::default() + }, + "pop scope failed", + ), + ( + MockHostFailures { + drop_scope_stack: HostFailure::WorkerError, + ..Default::default() + }, + "drop scope stack failed", + ), + ( + MockHostFailures { + tool_next: true, + ..Default::default() + }, + "tool next failed", + ), + ] { + host.set_failures(failures); + assert_worker_error( + client + .invoke(Request::new(tool_invoke( + "tool-exec", + RegistrationSurface::ToolExecutionIntercept, + json!({}), + ))) + .await + .expect("host failure returns structured error") + .into_inner(), + expected, + ); + } + + host.set_failures(MockHostFailures { + llm_next: true, + ..Default::default() + }); + assert_worker_error( + client + .invoke(Request::new(llm_invoke( + "llm-exec", + RegistrationSurface::LlmExecutionIntercept, + llm_request(), + None, + None, + ))) + .await + .expect("llm next failure returns structured error") + .into_inner(), + "llm next failed", + ); + + for (mode, expected) in [ + (MockStreamMode::WorkerError, "stream worker failed"), + (MockStreamMode::EmptyChunk, "empty stream chunk"), + (MockStreamMode::TransportError, "stream status failed"), + ] { + host.set_failures(MockHostFailures { + llm_stream_mode: mode, + ..Default::default() + }); + let mut stream = client + .invoke_stream(Request::new(llm_invoke( + "llm-stream", + RegistrationSurface::LlmStreamExecutionIntercept, + llm_request(), + None, + None, + ))) + .await + .expect("stream invoke") + .into_inner(); + let first = stream_json(stream.next().await.expect("stream poll item").unwrap()); + assert_json_field(first, "phase", "stream_poll"); + let second = stream.next().await.expect("host stream item").unwrap(); + assert_stream_error(second, expected); + } + + worker_handle.abort(); + host_handle.abort(); +} + +struct MinimalPlugin; + +impl WorkerPlugin for MinimalPlugin { + fn plugin_id(&self) -> &str { + "minimal" + } + + fn register(&self, _ctx: &mut PluginContext, _config: &Json) -> Result<()> { + Ok(()) + } +} + +#[derive(Default)] +struct SurfacePlugin { + events: Arc>>, +} + +impl WorkerPlugin for SurfacePlugin { + fn plugin_id(&self) -> &str { + PLUGIN_ID + } + + fn validate(&self, config: &Json) -> Vec { + if config + .get("diagnostic") + .and_then(serde_json::Value::as_bool) + .unwrap_or(false) + { + vec![nemo_relay_worker::ConfigDiagnostic { + level: nemo_relay_worker::DiagnosticLevel::Warning, + code: "diagnostic.requested".into(), + component: Some(PLUGIN_ID.into()), + field: Some("diagnostic".into()), + message: "diagnostic requested".into(), + }] + } else { + Vec::new() + } + } + + fn register(&self, ctx: &mut PluginContext, _config: &Json) -> Result<()> { + let runtime = ctx.runtime().expect("worker service provides runtime"); + let events = self.events.clone(); + ctx.register_subscriber("subscriber", move |event| { + events + .lock() + .expect("events lock") + .push(event.name().into()); + }); + ctx.register_tool_sanitize_request_guardrail("tool-sanitize", 1, |_, value| { + set_json_field(value, "phase", "tool_sanitize_request") + }); + ctx.register_tool_sanitize_response_guardrail("tool-sanitize", 1, |_, value| { + set_json_field(value, "phase", "tool_sanitize_response") + }); + ctx.register_tool_conditional_execution_guardrail("tool-conditional", 1, |_, value| { + Ok(value + .get("block") + .and_then(serde_json::Value::as_bool) + .and_then(|blocked| blocked.then(|| "blocked-tool".into()))) + }); + ctx.register_tool_request_intercept("tool-request", 1, false, |_, value| { + Ok(set_json_field(value, "phase", "tool_request")) + }); + ctx.register_tool_request_intercept("tool-error", 1, false, |_, _| { + Err(WorkerSdkError::Callback("boom".into())) + }); + + let tool_runtime = runtime.clone(); + ctx.register_tool_execution_intercept("tool-exec", 1, move |_, value, next: ToolNext| { + let runtime = tool_runtime.clone(); + async move { + runtime.emit_mark("tool-exec", None, None).await?; + let stack_id = runtime.create_scope_stack().await?; + let handle = runtime + .push_scope(None, "worker-scope", ScopeType::Function, None, None, None) + .await?; + runtime.pop_scope(&handle, None, None).await?; + runtime.drop_scope_stack(&stack_id).await?; + let next_value = next.call(value).await?; + Ok(set_json_field(next_value, "phase", "tool_exec")) + } + }); + let scope_runtime = runtime.clone(); + ctx.register_tool_execution_intercept("tool-scope-types", 1, move |_, _, _| { + let runtime = scope_runtime.clone(); + async move { + for scope_type in [ + ScopeType::Agent, + ScopeType::Function, + ScopeType::Tool, + ScopeType::Llm, + ScopeType::Retriever, + ScopeType::Embedder, + ScopeType::Reranker, + ScopeType::Guardrail, + ScopeType::Evaluator, + ScopeType::Custom, + ScopeType::Unknown, + ] { + let handle = runtime + .push_scope( + Some("explicit-stack"), + &format!("scope-{}", scope_type.as_str()), + scope_type, + None, + None, + None, + ) + .await?; + runtime.pop_scope(&handle, None, None).await?; + } + Ok(Json::Null) + } + }); + + ctx.register_llm_sanitize_request_guardrail("llm-sanitize-request", 1, |request| { + set_llm_phase(request, "llm_sanitize_request") + }); + ctx.register_llm_sanitize_response_guardrail("llm-sanitize-response", 1, |value| { + set_json_field(value, "phase", "llm_sanitize_response") + }); + ctx.register_llm_conditional_execution_guardrail("llm-conditional", 1, |request| { + Ok(request + .content + .get("block") + .and_then(serde_json::Value::as_bool) + .and_then(|blocked| blocked.then(|| "blocked-llm".into()))) + }); + ctx.register_llm_request_intercept("llm-request", 1, false, |_, request, annotated| { + Ok((set_llm_phase(request, "llm_request"), annotated)) + }); + + ctx.register_llm_execution_intercept( + "llm-exec", + 1, + move |_, request, next: LlmNext| async move { + let next_value = next.call(request).await?; + Ok(set_json_field(next_value, "phase", "llm_exec")) + }, + ); + + let stream_runtime = runtime.clone(); + ctx.register_llm_stream_execution_intercept( + "llm-stream", + 1, + move |_, request, next: LlmStreamNext| { + let runtime = stream_runtime.clone(); + async move { + let next_stream = next.call(request).await?; + Ok(Box::pin(RuntimeMarkThenStream::new(runtime, next_stream)) as JsonStream) + } + }, + ); + ctx.register_llm_stream_execution_intercept("llm-stream-error", 1, |_, _, _| async { + let stream: JsonStream = Box::pin(tokio_stream::iter(vec![Err( + WorkerSdkError::Callback("stream boom".into()), + )])); + Ok(stream) + }); + ctx.register_llm_stream_execution_intercept("llm-stream-open-error", 1, |_, _, _| async { + Err(WorkerSdkError::Callback("stream open boom".into())) + }); + Ok(()) + } +} + +struct RuntimeMarkThenStream { + runtime: Option, + pending: Option> + Send>>>, + inner: JsonStream, +} + +impl RuntimeMarkThenStream { + fn new(runtime: PluginRuntime, inner: JsonStream) -> Self { + Self { + runtime: Some(runtime), + pending: None, + inner, + } + } +} + +impl Unpin for RuntimeMarkThenStream {} + +impl Stream for RuntimeMarkThenStream { + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if self.runtime.is_some() || self.pending.is_some() { + if self.pending.is_none() { + let runtime = self.runtime.take().expect("runtime present"); + self.pending = Some(Box::pin(async move { + runtime.emit_mark("stream-poll", None, None).await?; + Ok(json!({"phase": "stream_poll"})) + })); + } + let future = self.pending.as_mut().expect("pending future"); + return match future.as_mut().poll(cx) { + Poll::Ready(result) => { + self.pending = None; + Poll::Ready(Some(result)) + } + Poll::Pending => Poll::Pending, + }; + } + self.inner.as_mut().poll_next(cx) + } +} + +#[derive(Clone, Copy, Default)] +enum HostFailure { + #[default] + None, + WorkerError, + EmptyAck, +} + +#[derive(Clone, Copy, Default)] +enum MockStreamMode { + #[default] + Value, + WorkerError, + TransportError, + EmptyChunk, +} + +#[derive(Clone, Copy, Default)] +struct MockHostFailures { + emit_mark: HostFailure, + create_scope_stack: bool, + push_scope: bool, + pop_scope: HostFailure, + drop_scope_stack: HostFailure, + tool_next: bool, + llm_next: bool, + llm_stream_mode: MockStreamMode, +} + +#[derive(Clone, Default)] +struct MockHost { + calls: Arc>>, + failures: Arc>, +} + +impl MockHost { + fn calls(&self) -> Vec { + self.calls.lock().expect("calls lock").clone() + } + + fn record(&self, call: impl Into) { + self.calls.lock().expect("calls lock").push(call.into()); + } + + fn failures(&self) -> MockHostFailures { + *self.failures.lock().expect("failures lock") + } + + fn set_failures(&self, failures: MockHostFailures) { + *self.failures.lock().expect("failures lock") = failures; + } +} + +#[tonic::async_trait] +impl RelayHostRuntime for MockHost { + async fn emit_mark( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + authorize_host(&request.activation_id, &request.auth_token)?; + let scope = request.scope.expect("scope context"); + self.record(format!( + "mark:{}:{}:{}", + request.name, scope.scope_stack_id, scope.parent_scope_id + )); + match self.failures().emit_mark { + HostFailure::None => {} + HostFailure::WorkerError => { + return Ok(Response::new(host_ack_error("emit mark failed"))); + } + HostFailure::EmptyAck => { + return Ok(Response::new(HostAck { + ok: false, + error: None, + })); + } + } + Ok(Response::new(host_ack())) + } + + async fn push_scope( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + authorize_host(&request.activation_id, &request.auth_token)?; + let scope = request.scope.expect("scope context"); + self.record(format!( + "push:{}:{}:{}", + request.name, scope.scope_stack_id, scope.parent_scope_id + )); + if self.failures().push_scope { + return Ok(Response::new(PushScopeResponse { + scope_handle_id: String::new(), + error: Some(worker_error("push scope failed")), + })); + } + Ok(Response::new(PushScopeResponse { + scope_handle_id: "scope-handle-1".into(), + error: None, + })) + } + + async fn pop_scope( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + authorize_host(&request.activation_id, &request.auth_token)?; + self.record(format!("pop:{}", request.scope_handle_id)); + match self.failures().pop_scope { + HostFailure::None => {} + HostFailure::WorkerError => { + return Ok(Response::new(host_ack_error("pop scope failed"))); + } + HostFailure::EmptyAck => { + return Ok(Response::new(HostAck { + ok: false, + error: None, + })); + } + } + Ok(Response::new(host_ack())) + } + + async fn create_scope_stack( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + authorize_host(&request.activation_id, &request.auth_token)?; + self.record("create_scope_stack"); + if self.failures().create_scope_stack { + return Ok(Response::new(CreateScopeStackResponse { + scope_stack_id: String::new(), + error: Some(worker_error("create scope stack failed")), + })); + } + Ok(Response::new(CreateScopeStackResponse { + scope_stack_id: "isolated-stack".into(), + error: None, + })) + } + + async fn drop_scope_stack( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + authorize_host(&request.activation_id, &request.auth_token)?; + self.record(format!("drop:{}", request.scope_stack_id)); + match self.failures().drop_scope_stack { + HostFailure::None => {} + HostFailure::WorkerError => { + return Ok(Response::new(host_ack_error("drop scope stack failed"))); + } + HostFailure::EmptyAck => { + return Ok(Response::new(HostAck { + ok: false, + error: None, + })); + } + } + Ok(Response::new(host_ack())) + } + + async fn tool_next( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + authorize_host(&request.activation_id, &request.auth_token)?; + self.record(format!("tool_next:{}", request.continuation_id)); + if self.failures().tool_next { + return Ok(Response::new(JsonResult { + value: None, + error: Some(worker_error("tool next failed")), + })); + } + Ok(Response::new(JsonResult { + value: Some(json_env(json!({"next": "tool"}))), + error: None, + })) + } + + async fn llm_next( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + authorize_host(&request.activation_id, &request.auth_token)?; + self.record(format!("llm_next:{}", request.continuation_id)); + if self.failures().llm_next { + return Ok(Response::new(JsonResult { + value: None, + error: Some(worker_error("llm next failed")), + })); + } + Ok(Response::new(JsonResult { + value: Some(json_env(json!({"next": "llm"}))), + error: None, + })) + } + + type LlmStreamNextStream = + Pin> + Send>>; + + async fn llm_stream_next( + &self, + request: Request, + ) -> std::result::Result, Status> { + let request = request.into_inner(); + authorize_host(&request.activation_id, &request.auth_token)?; + self.record(format!("llm_stream_next:{}", request.continuation_id)); + let chunks: Vec> = + match self.failures().llm_stream_mode { + MockStreamMode::Value => vec![Ok(StreamChunk { + item: Some(nemo_relay_worker_proto::v1::stream_chunk::Item::Value( + json_env(json!({"next": "llm_stream"})), + )), + })], + MockStreamMode::WorkerError => vec![Ok(StreamChunk { + item: Some(nemo_relay_worker_proto::v1::stream_chunk::Item::Error( + worker_error("stream worker failed"), + )), + })], + MockStreamMode::TransportError => { + vec![Err(Status::unavailable("stream status failed"))] + } + MockStreamMode::EmptyChunk => vec![Ok(StreamChunk { item: None })], + }; + let stream = tokio_stream::iter(chunks); + Ok(Response::new(Box::pin(stream))) + } +} + +async fn spawn_worker( + plugin: Arc, + host_endpoint: String, +) -> (JoinHandle>, PluginWorkerClient) { + let (worker_endpoint, connect_endpoint) = unused_worker_endpoints(); + let handle = tokio::spawn(serve_plugin_arc_with_config( + plugin, + WorkerServerConfig { + worker_endpoint, + host_endpoint, + activation_id: ACTIVATION_ID.into(), + auth_token: AUTH_TOKEN.into(), + }, + )); + let client = connect_worker(&connect_endpoint).await; + (handle, client) +} + +async fn spawn_host( + host: MockHost, +) -> ( + JoinHandle>, + String, +) { + let endpoint = unused_http_endpoint(); + let addr = endpoint + .strip_prefix("http://") + .expect("http endpoint") + .parse::() + .expect("socket address"); + let handle = tokio::spawn(async move { + Server::builder() + .add_service(RelayHostRuntimeServer::new(host)) + .serve(addr) + .await + }); + wait_for_port(&endpoint).await; + (handle, endpoint) +} + +#[cfg(unix)] +async fn spawn_unix_host( + host: MockHost, + path: PathBuf, +) -> JoinHandle> { + let _ = std::fs::remove_file(&path); + let listener = UnixListener::bind(&path).expect("bind host unix socket"); + let handle = tokio::spawn(async move { + Server::builder() + .add_service(RelayHostRuntimeServer::new(host)) + .serve_with_incoming(UnixListenerStream::new(listener)) + .await + }); + wait_for_unix_socket(&path).await; + handle +} + +async fn connect_worker(endpoint: &str) -> PluginWorkerClient { + for _ in 0..50 { + match PluginWorkerClient::connect(endpoint.to_owned()).await { + Ok(client) => return client, + Err(_) => std::thread::sleep(Duration::from_millis(20)), + } + } + panic!("worker did not start at {endpoint}"); +} + +#[cfg(unix)] +async fn connect_worker_uds(endpoint: &str) -> PluginWorkerClient { + let path = Arc::new( + endpoint + .strip_prefix("unix://") + .map(PathBuf::from) + .expect("unix endpoint"), + ); + for _ in 0..50 { + let path = path.clone(); + let channel = Endpoint::try_from("http://[::]:50051") + .expect("uds placeholder endpoint") + .connect_with_connector(service_fn(move |_| { + let path = path.clone(); + async move { + let stream = UnixStream::connect(&*path).await?; + Ok::<_, std::io::Error>(TokioIo::new(stream)) + } + })) + .await; + if let Ok(channel) = channel { + return PluginWorkerClient::new(channel); + } + std::thread::sleep(Duration::from_millis(20)); + } + panic!("worker did not start at {endpoint}"); +} + +async fn wait_for_port(endpoint: &str) { + for _ in 0..50 { + if Channel::from_shared(endpoint.to_owned()) + .expect("valid endpoint") + .connect() + .await + .is_ok() + { + return; + } + std::thread::sleep(Duration::from_millis(20)); + } + panic!("server did not start at {endpoint}"); +} + +#[cfg(unix)] +async fn wait_for_unix_socket(path: &Path) { + for _ in 0..50 { + if UnixStream::connect(path).await.is_ok() { + return; + } + std::thread::sleep(Duration::from_millis(20)); + } + panic!("server did not start at {}", path.display()); +} + +async fn register_plugin( + client: &mut PluginWorkerClient, +) -> Vec { + client + .register(Request::new(RegisterRequest { + activation_id: ACTIVATION_ID.into(), + plugin_id: PLUGIN_ID.into(), + auth_token: AUTH_TOKEN.into(), + config: Some(json_env(json!({}))), + })) + .await + .expect("register succeeds") + .into_inner() + .registrations +} + +fn unused_http_endpoint() -> String { + let listener = TcpListener::bind("127.0.0.1:0").expect("bind ephemeral port"); + let addr = listener.local_addr().expect("local addr"); + drop(listener); + format!("http://{addr}") +} + +fn unused_worker_endpoints() -> (String, String) { + let listener = TcpListener::bind("127.0.0.1:0").expect("bind ephemeral port"); + let addr = listener.local_addr().expect("local addr"); + drop(listener); + (format!("tcp://{addr}"), format!("http://{addr}")) +} + +fn tcp_endpoint(http_endpoint: &str) -> String { + format!( + "tcp://{}", + http_endpoint + .strip_prefix("http://") + .expect("http endpoint") + ) +} + +fn json_env(value: Json) -> JsonEnvelope { + json_envelope("nemo.relay.Json@1", &value).expect("encode JSON envelope") +} + +fn invalid_json_env(schema: &str) -> JsonEnvelope { + JsonEnvelope { + schema: schema.into(), + json: b"{".to_vec(), + } +} + +fn event_invoke(registration_name: &str) -> InvokeRequest { + let event = Event::Mark(MarkEvent::new( + BaseEvent::builder().name("subscriber-event").build(), + None, + None, + )); + InvokeRequest { + activation_id: ACTIVATION_ID.into(), + invocation_id: "invoke-1".into(), + registration_name: registration_name.into(), + surface: RegistrationSurface::Subscriber as i32, + continuation_id: "next-1".into(), + scope: Some(scope_context()), + auth_token: AUTH_TOKEN.into(), + payload: Some(nemo_relay_worker_proto::v1::invoke_request::Payload::Event( + json_envelope("nemo.relay.Event@1", &event).expect("encode event"), + )), + } +} + +fn tool_invoke( + registration_name: &str, + surface: RegistrationSurface, + value: Json, +) -> InvokeRequest { + InvokeRequest { + activation_id: ACTIVATION_ID.into(), + invocation_id: "invoke-1".into(), + registration_name: registration_name.into(), + surface: surface as i32, + continuation_id: "next-1".into(), + scope: Some(scope_context()), + auth_token: AUTH_TOKEN.into(), + payload: Some(nemo_relay_worker_proto::v1::invoke_request::Payload::Tool( + ToolInvocation { + tool_name: "tool".into(), + value: Some(json_env(value)), + }, + )), + } +} + +fn llm_invoke( + registration_name: &str, + surface: RegistrationSurface, + request: LlmRequest, + annotated_request: Option, + response: Option, +) -> InvokeRequest { + InvokeRequest { + activation_id: ACTIVATION_ID.into(), + invocation_id: "invoke-1".into(), + registration_name: registration_name.into(), + surface: surface as i32, + continuation_id: "next-1".into(), + scope: Some(scope_context()), + auth_token: AUTH_TOKEN.into(), + payload: Some(nemo_relay_worker_proto::v1::invoke_request::Payload::Llm( + LlmInvocation { + model_name: "model".into(), + request: Some( + json_envelope("nemo.relay.LlmRequest@1", &request).expect("encode request"), + ), + annotated_request: annotated_request.map(json_env), + response: response.map(json_env), + }, + )), + } +} + +fn llm_invoke_without_request( + registration_name: &str, + surface: RegistrationSurface, +) -> InvokeRequest { + InvokeRequest { + activation_id: ACTIVATION_ID.into(), + invocation_id: "invoke-1".into(), + registration_name: registration_name.into(), + surface: surface as i32, + continuation_id: "next-1".into(), + scope: Some(scope_context()), + auth_token: AUTH_TOKEN.into(), + payload: Some(nemo_relay_worker_proto::v1::invoke_request::Payload::Llm( + LlmInvocation { + model_name: "model".into(), + request: None, + annotated_request: None, + response: None, + }, + )), + } +} + +fn llm_invoke_with_bad_annotation(registration_name: &str) -> InvokeRequest { + let mut request = llm_invoke( + registration_name, + RegistrationSurface::LlmRequestIntercept, + llm_request(), + None, + None, + ); + if let Some(nemo_relay_worker_proto::v1::invoke_request::Payload::Llm(payload)) = + request.payload.as_mut() + { + payload.annotated_request = Some(JsonEnvelope { + schema: "nemo.relay.AnnotatedLlmRequest@1".into(), + json: b"{".to_vec(), + }); + } + request +} + +fn scope_context() -> ScopeContext { + ScopeContext { + scope_stack_id: "stack-1".into(), + parent_scope_id: "parent-1".into(), + } +} + +fn llm_request() -> LlmRequest { + LlmRequest { + headers: Default::default(), + content: json!({"prompt": "hello"}), + } +} + +fn llm_request_with_block() -> LlmRequest { + LlmRequest { + headers: Default::default(), + content: json!({"block": true}), + } +} + +fn set_json_field(mut value: Json, key: &str, field_value: &str) -> Json { + value + .as_object_mut() + .expect("JSON object") + .insert(key.into(), json!(field_value)); + value +} + +fn set_llm_phase(mut request: LlmRequest, phase: &str) -> LlmRequest { + request + .content + .as_object_mut() + .expect("request content object") + .insert("phase".into(), json!(phase)); + request +} + +async fn invoke_json(client: &mut PluginWorkerClient, request: InvokeRequest) -> Json { + let response = client + .invoke(Request::new(request)) + .await + .expect("invoke succeeds") + .into_inner(); + match response.result.expect("invoke result") { + nemo_relay_worker_proto::v1::invoke_response::Result::Json(result) => { + decode_json_envelope(&result.value.expect("json value")).expect("decode JSON result") + } + other => panic!("unexpected invoke result: {other:?}"), + } +} + +async fn invoke_guardrail( + client: &mut PluginWorkerClient, + request: InvokeRequest, +) -> String { + let response = client + .invoke(Request::new(request)) + .await + .expect("invoke succeeds") + .into_inner(); + match response.result.expect("invoke result") { + nemo_relay_worker_proto::v1::invoke_response::Result::Guardrail(result) => { + result.block_reason + } + other => panic!("unexpected invoke result: {other:?}"), + } +} + +async fn invoke_llm_request( + client: &mut PluginWorkerClient, + request: InvokeRequest, +) -> LlmRequest { + let response = client + .invoke(Request::new(request)) + .await + .expect("invoke succeeds") + .into_inner(); + match response.result.expect("invoke result") { + nemo_relay_worker_proto::v1::invoke_response::Result::LlmRequest(result) => { + decode_json_envelope(&result.request.expect("llm request")).expect("decode LLM request") + } + other => panic!("unexpected invoke result: {other:?}"), + } +} + +fn stream_json(chunk: StreamChunk) -> Json { + match chunk.item.expect("stream chunk item") { + nemo_relay_worker_proto::v1::stream_chunk::Item::Value(value) => { + decode_json_envelope(&value).expect("decode stream value") + } + other => panic!("unexpected stream chunk: {other:?}"), + } +} + +fn assert_empty_response(response: InvokeResponse) { + assert!(matches!( + response.result.expect("invoke result"), + nemo_relay_worker_proto::v1::invoke_response::Result::Empty(_) + )); +} + +fn assert_json_field(value: Json, key: &str, expected: &str) { + assert_eq!(value.get(key), Some(&json!(expected))); +} + +fn assert_worker_error(response: InvokeResponse, expected: &str) { + match response.result.expect("invoke result") { + nemo_relay_worker_proto::v1::invoke_response::Result::Error(error) => { + assert!( + error.message.contains(expected), + "expected '{expected}' in '{}'", + error.message + ); + } + other => panic!("unexpected invoke result: {other:?}"), + } +} + +fn assert_stream_error(chunk: StreamChunk, expected: &str) { + match chunk.item.expect("stream item") { + nemo_relay_worker_proto::v1::stream_chunk::Item::Error(error) => { + assert!( + error.message.contains(expected), + "expected '{expected}' in '{}'", + error.message + ); + } + other => panic!("unexpected stream item: {other:?}"), + } +} + +fn assert_status_message(status: Status, expected: &str) { + assert!( + status.message().contains(expected), + "expected '{expected}' in '{}'", + status.message() + ); +} + +fn assert_error_contains(error: WorkerSdkError, expected: &str) { + let message = error.to_string(); + assert!( + message.contains(expected), + "expected '{expected}' in '{message}'" + ); +} + +fn server_config(worker_endpoint: &str) -> WorkerServerConfig { + WorkerServerConfig { + worker_endpoint: worker_endpoint.into(), + host_endpoint: "http://127.0.0.1:9".into(), + activation_id: ACTIVATION_ID.into(), + auth_token: AUTH_TOKEN.into(), + } +} + +struct EnvSnapshot { + values: Vec<(&'static str, Option)>, +} + +impl EnvSnapshot { + fn capture(names: &'static [&'static str]) -> Self { + Self { + values: names + .iter() + .map(|name| (*name, std::env::var(name).ok())) + .collect(), + } + } + + fn restore(&self) { + for (name, value) in &self.values { + match value { + Some(value) => set_env(name, value), + None => remove_env(name), + } + } + } +} + +impl Drop for EnvSnapshot { + fn drop(&mut self) { + self.restore(); + } +} + +fn set_required_envs() { + set_env("NEMO_RELAY_WORKER_SOCKET", "tcp://127.0.0.1:1"); + set_env("NEMO_RELAY_HOST_SOCKET", "http://127.0.0.1:9"); + set_env("NEMO_RELAY_WORKER_ID", ACTIVATION_ID); + set_env("NEMO_RELAY_WORKER_TOKEN", AUTH_TOKEN); +} + +fn clear_required_envs() { + for name in REQUIRED_WORKER_ENVS { + remove_env(name); + } +} + +fn set_env(name: &str, value: &str) { + // Process environment mutation is guarded by ENV_LOCK in these tests. + unsafe { + std::env::set_var(name, value); + } +} + +fn remove_env(name: &str) { + // Process environment mutation is guarded by ENV_LOCK in these tests. + unsafe { + std::env::remove_var(name); + } +} + +#[cfg(unix)] +fn unique_temp_path(prefix: &str) -> std::path::PathBuf { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time after epoch") + .as_nanos(); + PathBuf::from(format!("/tmp/{prefix}-{}-{nanos}.sock", std::process::id())) +} + +fn host_ack() -> HostAck { + HostAck { + ok: true, + error: None, + } +} + +fn host_ack_error(message: &str) -> HostAck { + HostAck { + ok: false, + error: Some(worker_error(message)), + } +} + +fn worker_error(message: &str) -> WorkerError { + WorkerError { + code: "mock.error".into(), + message: message.into(), + retryable: false, + } +} + +fn authorize_host(activation_id: &str, auth_token: &str) -> std::result::Result<(), Status> { + if activation_id != ACTIVATION_ID || auth_token != AUTH_TOKEN { + return Err(Status::permission_denied("invalid host auth")); + } + Ok(()) +} diff --git a/deny.toml b/deny.toml index ca4c9576f..7dbda4aca 100644 --- a/deny.toml +++ b/deny.toml @@ -24,5 +24,6 @@ allow = [ "MIT", "MPL-2.0", "Unicode-3.0", + "Zlib", # required by foldhash through tonic-prost-build/prost-build ] private = { ignore = true } diff --git a/justfile b/justfile index 20fa8e36a..e8aa2d700 100644 --- a/justfile +++ b/justfile @@ -454,6 +454,8 @@ changed = [] found_workspace_version = False local_dependencies = ( "nemo-relay-types", + "nemo-relay-worker-proto", + "nemo-relay-worker", "nemo-relay", "nemo-relay-plugin", "nemo-relay-adaptive", @@ -654,9 +656,11 @@ PY published_cargo_packages() { printf '%s\n' \ nemo-relay-types \ + nemo-relay-plugin \ + nemo-relay-worker-proto \ + nemo-relay-worker \ nemo-relay \ nemo-relay-adaptive \ - nemo-relay-plugin \ nemo-relay-pii-redaction \ nemo-relay-ffi \ nemo-relay-cli @@ -1149,19 +1153,25 @@ package-rust: echo "Using explicit version $ref_name" set_cargo_workspace_version "$ref_name" fi - cargo_dirty_args=() - if [[ -n "$ref_name" ]]; then - cargo_dirty_args+=(--allow-dirty) - fi while IFS= read -r package; do cargo_package_config=() + cargo_package_args=(--locked --package "$package" --target-dir "$package_target_dir") + if [[ -n "$ref_name" ]]; then + cargo_package_args+=(--allow-dirty) + fi case "$package" in nemo-relay) cargo_package_config+=(--config 'patch.crates-io.nemo-relay-types.path="crates/types"') + cargo_package_config+=(--config 'patch.crates-io.nemo-relay-plugin.path="crates/plugin"') ;; nemo-relay-adaptive) cargo_package_config+=(--config 'patch.crates-io.nemo-relay-types.path="crates/types"') cargo_package_config+=(--config 'patch.crates-io.nemo-relay.path="crates/core"') + cargo_package_config+=(--config 'patch.crates-io.nemo-relay-plugin.path="crates/plugin"') + ;; + nemo-relay-worker) + cargo_package_config+=(--config 'patch.crates-io.nemo-relay-types.path="crates/types"') + cargo_package_config+=(--config 'patch.crates-io.nemo-relay-worker-proto.path="crates/worker-proto"') ;; nemo-relay-plugin) cargo_package_config+=(--config 'patch.crates-io.nemo-relay-types.path="crates/types"') @@ -1169,18 +1179,20 @@ package-rust: nemo-relay-pii-redaction) cargo_package_config+=(--config 'patch.crates-io.nemo-relay-types.path="crates/types"') cargo_package_config+=(--config 'patch.crates-io.nemo-relay.path="crates/core"') + cargo_package_config+=(--config 'patch.crates-io.nemo-relay-plugin.path="crates/plugin"') ;; nemo-relay-ffi|nemo-relay-cli) cargo_package_config+=(--config 'patch.crates-io.nemo-relay-types.path="crates/types"') cargo_package_config+=(--config 'patch.crates-io.nemo-relay.path="crates/core"') + cargo_package_config+=(--config 'patch.crates-io.nemo-relay-plugin.path="crates/plugin"') cargo_package_config+=(--config 'patch.crates-io.nemo-relay-adaptive.path="crates/adaptive"') cargo_package_config+=(--config 'patch.crates-io.nemo-relay-pii-redaction.path="crates/pii-redaction"') ;; esac if ((${#cargo_package_config[@]} == 0)); then - cargo package --locked --package "$package" "${cargo_dirty_args[@]}" --target-dir "$package_target_dir" + cargo package "${cargo_package_args[@]}" else - cargo package --locked --package "$package" "${cargo_dirty_args[@]}" --target-dir "$package_target_dir" "${cargo_package_config[@]}" + cargo package "${cargo_package_args[@]}" "${cargo_package_config[@]}" fi done < <(published_cargo_packages) find "$package_target_dir/package" -maxdepth 1 -type f -name '*.crate' -exec cp {} "$package_dir"/ \;