diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..6f1c9fb --- /dev/null +++ b/.env.template @@ -0,0 +1,5 @@ +# Optional +CODE_INTERPRETER_URL=http://127.0.0.1:50051 +BEE_FRAMEWORK_LOG_PRETTY=true +BEE_FRAMEWORK_LOG_LEVEL="info" +BEE_FRAMEWORK_LOG_SINGLE_LINE="false" diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..df157ba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots / Code snippets** +If applicable, add screenshots or code snippets to help explain your problem. + +**Set-up:** + - Bee version: [e.g. v0.0.3] + - Model provider [e.g. watsonx] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4cd5ca1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,139 @@ +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +.idea +/experiments +.npmrc + +# Code interpreter data +/examples/tmp/code_interpreter/* +/examples/tmp/local/* diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..85e04b3 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +yarn lint-staged +CI=true yarn lint && yarn run test:unit && yarn copyright diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 0000000..325e2b5 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,5 @@ +{ + "*.{ts,js}": "eslint --fix", + "*.ts": "tsc-files --noEmit", + "*": "prettier --ignore-unknown --write" +} diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..b8e593f --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20.15.1 diff --git a/.release-it.json b/.release-it.json new file mode 100644 index 0000000..85efe4e --- /dev/null +++ b/.release-it.json @@ -0,0 +1,22 @@ +{ + "plugins": { + "@release-it/conventional-changelog": { + "preset": { + "name": "conventionalcommits" + }, + "header": "# Changelog", + "infile": "CHANGELOG.md" + } + }, + "npm": { + "skipChecks": true, + "publish": true + }, + "hooks": { + "before:init": ["yarn lint", "yarn ts:check", "yarn test:all"], + "after:bump": ["yarn build"] + }, + "github": { + "release": true + } +} diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f3e51c6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [IBM Cloud Support](https://www.ibm.com/cloud/support). +All complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff5bd02 --- /dev/null +++ b/README.md @@ -0,0 +1,173 @@ +

+ +

Bee Agent Framework

+

+ +

+ + + +

+ +Open-source framework for building, deploying, and serving powerful agentic workflows at scale. + +The Bee framework makes it easy to build agentic worfklows with leading proprietary and open-source models. We’re working on bringing model-agnostic support to any LLM to help developers avoid model provider lock-in and embrace the latest open-source LLMs. + +## Key Features + +- πŸ€– **AI agents**: Use our powerful Bee agent or build your own. +- πŸ› οΈ **Tools**: Use our built-in tools and create your own in Javascript or Python. +- πŸ‘©β€πŸ’» **Code interpreter**: Run code safely in a sandbox container. +- πŸ’Ύ **Memory**: Multiple strategies to optimize token spend. +- ⏸️ **Serialization** Handle complex agentic workflows and easily pause/resume them without losing state. +- πŸ” **Traceability**: Get full visibility of your agent’s inner workings [link to docs page], log all running events and use our MLflow integration to debug performance [link to instructions to setup MLflow]. +- πŸŽ›οΈ **Production-level** control with caching and error handling [link to docs] +- 🚧 (Coming soon) **Evaluation**: Run evaluation jobs with your own data source (custom csv or Airtable data). +- 🚧 (Coming soon) **Model-agnostic support**: Change model providers in 1 line of code without breaking your agent’s functionality. +- 🚧 (Coming soon) **Chat UI**: Serve your agent to users in a delightful GUI with built-in transparency, explainability, and user controls. +- ... more on our [Roadmap](#roadmap) + +## Get started with Bee + +### Installation + +```shell +npm install bee-agent-framework +``` + +```shell +yarn add bee-agent-framework +``` + +#### Example + +```typescript +import { BeeAgent } from "bee-agent-framework/agents/bee/agent.js"; +import { OllamaChatLLM } from "bee-agent-framework/adapters/ollama/chat.js"; +import { TokenMemory } from "bee-agent-framework/memory/tokenMemory.js"; +import { DuckDuckGoSearchTool } from "bee-agent-framework/tools/search/duckDuckGoSearch.js"; +import { OpenMeteoTool } from "bee-agent-framework/tools/weather/openMeteo.js"; + +const llm = new OllamaChatLLM(); // default is llama3.1 (7b), it is recommended to use 70b model +const agent = new BeeAgent({ + llm, // for more explore 'bee-agent-framework/adapters' + memory: new TokenMemory({ llm }), // for more explore 'bee-agent-framework/memory' + tools: [new DuckDuckGoSearchTool(), new OpenMeteoTool()], // for more explore 'bee-agent-framework/tools' +}); + +const response = await agent + .run({ prompt: "What's the current weather in Las Vegas?" }) + .observe((emitter) => { + emitter.on("update", async ({ data, update, meta }) => { + console.log(`Agent (${update.key}) πŸ€– : `, update.value); + }); + }); + +console.log(`Agent πŸ€– : `, response.result.text); +``` + +➑️ See a more [advanced example](./examples/agents/bee.ts). + +➑️ All examples can be found in the [examples](./examples) directory. + +### Local Installation (Python Interpreter + Interactive CLI) + +> _Note: `yarn` should be installed via Corepack ([tutorial](https://yarnpkg.com/corepack))_ + +> _Note: To make any asset available to a local code interpreter place them the following directory: ./examples/tmp/local_ + +> _Note: Docker distribution with support for compose is required, the following are supported:_ +> +> - [Rancher](https://www.rancher.com/) - recommended +> - [Docker](https://www.docker.com/) +> - [Podman](https://podman.io/) - requires [compose](https://podman-desktop.io/docs/compose/setting-up-compose) and **rootful machine** (if your current machine is rootless, please create a new one) + +1. Clone the repository `git clone git@github.com:i-am-bee/bee-agent-framework`. +2. Install dependencies `yarn install`. +3. Create `.env` (from `.env.template`) and fill in missing values (if any). +4. Start the code interpreter `yarn run infra:start-code-interpreter`. +5. Start the agent `yarn run start:bee` (it runs ./examples/agents/bee.ts file). + + +### πŸ› οΈ Tools + +| Name | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| `PythonTool` | Run arbitrary Python code in the remote environment. | +| `WikipediaTool` | Search for data on Wikipedia. | +| `DuckDuckGoTool` | Search for data on DuckDuckGo. | +| `LLMTool` | Uses an LLM to process input data. | +| `DynamicTool` | Construct to create dynamic tools. | +| `ArXivTool` | Retrieves research articles published on arXiv. | +| `WebCrawlerTool` | Retrieves content of an arbitrary website. | +| `CustomTool` | Runs your own Python function in the remote environment. | +| `OpenMeteoTool` | Retrieves current, previous, or upcoming weather for a given destination. | +| βž• [Request](https://github.com/i-am-bee/bee-agent-framework/discussions) | | + +### πŸ”ŒοΈ Adapters (LLM - Inference providers) + +| Name | Description | +|--------------------------------------------------------------------------|-----------------------------------------------------------------------------------------| +| `Ollama` | LLM + ChatLLM support ([example](./examples/llms/providers/ollama.ts)) | +| `LangChain` | Use any LLM that LangChain supports ([example](./examples/llms/providers/langchain.ts)) | +| `WatsonX` | LLM + ChatLLM support ([example](./examples/llms/providers/watsonx.ts)) | +| `BAM (IBM Internal)` | LLM + ChatLLM support ([example](./examples/llms/providers/bam.ts)) | +| βž• [Request](https://github.com/i-am-bee/bee-agent-framework/discussions) | | + + +### πŸ“¦ Modules + +The source directory (`src`) provides numerous modules that one can use. + +| Name | Description | +| -------------- |--------------------------------------------------------------------------------------------| +| **agents** | Base classes defining the common interface for agent. | +| **llms** | Base classes defining the common interface for text inference (standard or chat). | +| **template** | Prompt Templating system based on `Mustache` with various improvements_. | +| **memory** | Various types of memories to use with agent. | +| **tools** | Tools that an agent can use. | +| **cache** | Preset of different caching approaches that can be used together with tools. | +| **errors** | Base framework error classes used by each module. | +| **adapters** | Concrete implementations of given modules for different environments. | +| **logger** | Core component for logging all actions within the framework. | +| **serializer** | Core component for the ability to serialize/deserialize modules into the serialized format. | +| **version** | Constants representing the framework (e.g., latest version) | +| **internals** | Modules used by other modules within the framework. | + +To see more in-depth explanation see (docs)(./docs/overview.md). + +## Tutorials + +🚧 Coming soon 🚧 + +## Roadmap + +- Evaluation with MLFlow integration +- JSON encoder/decoder for model-agnostic support +- Chat Client (GUI) +- Structured outputs +- Improvements to base Bee agent +- Guardrails +- 🚧 TBD 🚧 + +## Contribution guidelines + +The Bee Agent Framework is an open-source project and we ❀️ contributions. + +## Feature contributions + +You can get started with any ticket market as β€œgood first issue”. + +Have an idea for a new feature? We recommend you first talk to a maintainer prior to spending a lot of time making a pull request that may not align with the project roadmap. + +## Bugs + +We are using [GitHub Issues](https://github.com/i-am-bee/bee-agent-framework/issues) to manage our public bugs. We keep a close eye on this, so before filing a new issue, please check to make sure it hasn't already been logged. + +## Code of conduct + +This project and everyone participating in it are governed by the [Code of Conduct](./CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please read the [full text](./CODE_OF_CONDUCT.md) so that you can read which actions may or may not be tolerated. + +## Legal notice + +All content in these repositories including code has been provided by IBM under the associated open source software license and IBM is under no obligation to provide enhancements, updates, or support. IBM developers produced this code as an open source project (not as an IBM product), and IBM makes no assertions as to the level of quality nor security, and will not be maintaining this code going forward. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8ac7286 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,60 @@ +services: + bee-code-interpreter: + image: "iambeeagent/bee-code-interpreter:latest" + environment: + KUBECONFIG: /root/kubeconfig.yaml + APP_EXECUTOR_IMAGE: "iambeeagent/bee-code-executor:latest" + APP_EXECUTOR_CONTAINER_RESOURCES: "{}" + APP_FILE_STORAGE_PATH: "/storage" + volumes: + - ${CODE_INTEPRETER_TMPDIR:-./examples/tmp/code_interpreter}:/storage + - k3s-kubeconfig:/kube + ports: + - "50051:50051" + entrypoint: ["/bin/sh", "-c"] + command: + - > + echo 'Updating kubeconfig' && + while [ ! -f $${KUBECONFIG} ] || ! kubectl get namespace default 2>/dev/null 1>&2; do + sleep 1; + sed 's|127.0.0.1|bee-code-interpreter-k3s|g' /kube/kubeconfig.yaml >$${KUBECONFIG} 2>/dev/null; + done && + echo 'Kubeconfig updated successfully' && + + echo "Pulling executor image $${APP_EXECUTOR_IMAGE}" && + if kubectl get job/pull-image 2>/dev/null 1>&2; then + echo "pull-image job already exists"; + else + kubectl create job pull-image --image "$${APP_EXECUTOR_IMAGE}" -- /bin/sh -c "echo done"; + fi && + kubectl wait --for=condition=complete job/pull-image --timeout 3600s && + echo 'Image pulled successfully' && + + python -m code_interpreter + depends_on: + - bee-code-interpreter-k3s + + bee-code-interpreter-k3s: + image: "rancher/k3s:v1.29.7-k3s1" + command: ["server", "--tls-san", "bee-code-interpreter-k3s"] + tmpfs: + - /run + - /var/run + ulimits: + nproc: 65535 + nofile: + soft: 65535 + hard: 65535 + privileged: true + restart: always + environment: + - K3S_TOKEN=secret-token + - K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml + - K3S_KUBECONFIG_MODE=644 + volumes: + - k3s-containerd:/var/lib/rancher/k3s/agent/containerd + - k3s-kubeconfig:/output + +volumes: + k3s-kubeconfig: + k3s-containerd: diff --git a/docs/assets/Bee_Dark.svg b/docs/assets/Bee_Dark.svg new file mode 100644 index 0000000..d80a408 --- /dev/null +++ b/docs/assets/Bee_Dark.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 0000000..3677473 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,183 @@ +# Overview + +## πŸ“¦ Modules + +The source directory (`src`) provides numerous modules that one can use. + +| Name | Description | +| -------------- |--------------------------------------------------------------------------------------------| +| **agents** | Base classes defining the common interface for agent. | +| **llms** | Base classes defining the common interface for text inference (standard or chat). | +| **template** | Prompt Templating system based on `Mustache` with various improvements_. | +| **memory** | Various types of memories to use with agent. | +| **tools** | Tools that an agent can use. | +| **cache** | Preset of different caching approaches that can be used together with tools. | +| **errors** | Base framework error classes used by each module. | +| **adapters** | Concrete implementations of given modules for different environments. | +| **logger** | Core component for logging all actions within the framework. | +| **serializer** | Core component for the ability to serialize/deserialize modules into the serialized format. | +| **version** | Constants representing the framework (e.g., latest version) | +| **internals** | Modules used by other modules within the framework. | + +### Emitter + +> Location within the framework `bee-agent-framework/emitter`. + +An emitter is a core functionality of the framework that gives you the ability to see what is happening under the hood. Because the vast majority of the frameworks components uses it, you can easily observe them. + +```typescript +import { Emitter, EventMeta } from "@/emitter/emitter.js"; + +Emitter.root.match("*.*", (data: unknown, event: EventMeta) => { + console.info(event.path, { data }) +}) +``` + +🚧 TBD + +**πŸ‘‰ [Emitter + Agent example](../examples/agents/bee.ts) πŸ‘ˆ** + +### LLMs + +A Large Language Model (LLM) is an AI designed to understand and generate human-like text. +Trained on extensive text data, LLMs learn language patterns, grammar, context, and basic reasoning to perform tasks like text completion, translation, summarization, and answering questions. + +The framework defines LLM via the `BaseLLM` class (`src/llms/base.ts`). +Typically, you either want to implement `LLM` (string <-> string) or `ChatLLM` (Message <-> Message). + +**πŸ‘‰ [See Examples](../examples/llms) πŸ‘ˆ** + +### Templates + +> Location within the framework `bee-agent-framework/template`. + +Prompt templating involves creating structured inputs to guide LLMs in generating desired responses. +Users can effectively steer the model's output towards particular formats or tones by designing specific templates. +This technique enhances the model's ability to produce consistent and relevant results, making it particularly useful for applications like automated content creation, customer service scripts, and interactive storytelling. +Prompt templating ensures that the LLM's responses align more closely with the intended context and purpose. + +**πŸ‘‰ [See Examples](../examples/template.ts) πŸ‘ˆ** + +### Agents + +🚧 TBD 🚧 + +**How to implement your agent runtime?** + +By implementing the agent base interface defined in `src/agents/base.ts.` + +### Memory + +> Location within the framework `bee-agent-framework/memory`. + +Memory in the context of an agent refers to the system's capability to store, recall, and utilize information from past interactions. +This enables the agent to maintain context over time, improve its responses based on previous exchanges, and provide a more personalized experience. + +The framework provides out-of-the-box following types of memories. + +| Name | Description | +| --------------------- | -------------------------------------------------------------------------------------------------------------- | +| `UnconstrainedMemory` | Unlimited size. It is suitable if your context window is huge. | +| `SlidingWindowMemory` | Keeps last `k` messages in the memory. The oldest ones are deleted. | +| `TokenMemory` | Ensures that the number of tokens of all messages is below the given threshold. The oldest are removed. | +| `SummarizeMemory` | Only a single summarization of the conversation is preserved. Summarization is updated with every new message. | +| βž• [Request](https://github.com/i-am-bee/bee-agent-framework/discussions) | | + +### Tools + +> Location within the framework `bee-agent-framework/tools`. + +Tools in the context of an agent refer to additional functionalities or capabilities integrated with the agent to perform specific tasks beyond text processing. +These tools extend the agent's abilities, allowing it to interact with external systems, access information, and execute actions. + +The framework provides out-of-the-box tools. + +| Name | Description | +| ------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| `PythonTool` | Run arbitrary Python code in the remote environment. | +| `WikipediaTool` | Search for data on Wikipedia. | +| `DuckDuckGoTool` | Search for data on DuckDuckGo. | +| `LLMTool` | Uses an LLM to process input data. | +| `DynamicTool` | Construct to create dynamic tools. | +| `ArXivTool` | Retrieves research articles published on arXiv. | +| `WebCrawlerTool` | Retrieves content of an arbitrary website. | +| `CustomTool` | Runs your own Python function in the remote environment. | +| `OpenMeteoTool` | Retrieves current, previous, or upcoming weather for a given destination. | +| βž• [Request](https://github.com/i-am-bee/bee-agent-framework/discussions) | | + +To create your own tool, you need to either implement the `BaseTool` class ([example](../examples/tools/customHttpRequest.ts)) or use `DynamicTool.` + +### Cache + +> Location within the framework `bee-agent-framework/cache`. + +> Note: Cache can be used directly with Tools. Pass the appropriate `Cache` instance to the `Tool` constructor. + +Caching is a process used to temporarily store copies of data or computations in a cache (a storage location) to facilitate faster access upon future requests. +The primary purpose of caching is to improve the efficiency and performance of systems by reducing the need to repeatedly fetch or compute the same data from a slower or more resource-intensive source. + +The framework provides out-of-the-box following cache implementations. + +| Name | Description | +|----------------------|--------------------------------------------------------------------| +| `UnconstrainedCache` | Unlimited size. | +| `FileCache` | Saves/Loads entries to/from a file. | +| `SlidingCache` | Keeps last `k` entries in the memory. The oldest ones are deleted. | +| `NullCache` | Disables caching. | +| βž• [Request](https://github.com/i-am-bee/bee-agent-framework/discussions) | | + +To create your cache implementation, you must implement the `BaseCache` class. + +### Errors + +> Location within the framework `bee-agent-framework/error`. + +> Note: We guarantee that every framework-related error is an instance of the `FrameworkError` class. + +🚧 TBD + +### Logger + +> Location within the framework `bee-agent-framework/logger`. +> To log all events in the framework set log level to 'TRACE' (root logger observes the root emitter). + +A logger is a component used for recording and tracking events, errors, and other significant actions that occur during an application's execution. +The primary purpose of a logger is to provide developers and system administrators with insights into the application's behavior, performance, and potential issues. + +Every component within the framework uses the `Logger` class either by directly creating an instance of it or because it is being passed from the creator. + +```typescript +import { Logger, LoggerLevel } from "bee-agent-framework/logger"; + +Logger.defaults.pretty = true; // (override default settings) +const root = Logger.root; // get root logger +root.level = LogerLevel.WARN; // update the logger level (default is LoggerLevel.INFO) +``` + +Some of the `Logger` defaults can be controlled via the following environmental variables. + +- `BEE_FRAMEWORK_LOG_LEVEL` +- `BEE_FRAMEWORK_LOG_PRETTY` + +> Note: The framework `Logger` class is an abstraction on top of the most popular `pino` logger. + +### Serializer + +> Location within the framework `bee-agent-framework/serializer`. + +Serialization is the process of converting complex data structures or objects into a format that can be easily stored, transmitted, and reconstructed later. + +Most parts of the framework implement the internal `Serializable` class, which exposes the following functionalities. + +- `createSnapshot` (method) +- `loadSnapshot` (method) + +- `fromSerialized` (static method) +- `fromSnapshot` (static method) + +If you want to serialize something that the framework doesn't know how to process, the following error will be thrown: `SerializerError`. +To resolve such an issue, you need to tell (register) the appropriate class to the framework via the `Serializer.register` method. + +**πŸ‘‰ [Emitter + Agent example](../examples/agents/bee_reusable.ts) πŸ‘ˆ** + +## 🚧 More content TBD 🚧 diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..eab964b --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,69 @@ +// @ts-check + +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import prettierConfig from "eslint-config-prettier"; +import unusedImports from "eslint-plugin-unused-imports"; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.strict, + ...tseslint.configs.stylistic, + { + ignores: ["**/*.js"], + languageOptions: { + parserOptions: { + project: "./tsconfig.json", + }, + }, + plugins: { + "unused-imports": unusedImports, + }, + rules: { + "no-restricted-imports": [ + "error", + { + patterns: [ + { + group: ["../", "src/"], + message: "Relative imports are not allowed.", + }, + ], + }, + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-extraneous-class": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-floating-promises": "error", + "unused-imports/no-unused-imports": "error", + "unused-imports/no-unused-vars": [ + "warn", + { + vars: "all", + varsIgnorePattern: "^_", + args: "after-used", + argsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-empty-object-type": "off", + }, + }, + { + files: ["examples/**"], + rules: { + "no-restricted-imports": "off", + "@typescript-eslint/no-unused-vars": "off", + "unused-imports/no-unused-vars": "off", + }, + }, + prettierConfig, + { + rules: { + curly: ["error", "all"], + }, + }, +); diff --git a/examples/agents/bee.ts b/examples/agents/bee.ts new file mode 100644 index 0000000..bfc83a3 --- /dev/null +++ b/examples/agents/bee.ts @@ -0,0 +1,102 @@ +import "dotenv/config.js"; +import { BeeAgent } from "@/agents/bee/agent.js"; +import { createConsoleReader } from "../helpers/io.js"; +import { FrameworkError } from "@/errors.js"; +import { TokenMemory } from "@/memory/tokenMemory.js"; +import { Logger } from "@/logger/logger.js"; +import { PythonTool } from "@/tools/python/python.js"; +import { LocalPythonStorage } from "@/tools/python/storage.js"; +import { DuckDuckGoSearchTool } from "@/tools/search/duckDuckGoSearch.js"; +import { WikipediaTool } from "@/tools/search/wikipedia.js"; +import { OpenMeteoTool } from "@/tools/weather/openMeteo.js"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { OllamaChatLLM } from "@/adapters/ollama/chat.js"; + +Logger.root.level = "silent"; // disable internal logs +const logger = new Logger({ name: "app", level: "trace" }); + +const llm = new OllamaChatLLM({ + modelId: "llama3.1", // llama3.1:70b for better performance +}); + +const codeInterpreterUrl = process.env.CODE_INTERPRETER_URL; +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const agent = new BeeAgent({ + llm, + memory: new TokenMemory({ llm }), + tools: [ + new DuckDuckGoSearchTool(), + // new WebCrawlerTool(), // HTML web page crawler + new WikipediaTool(), + new OpenMeteoTool(), // weather tool + // new ArXivTool(), // research papers + // new DynamicTool() // custom python tool + ...(codeInterpreterUrl + ? [ + new PythonTool({ + codeInterpreter: { url: codeInterpreterUrl }, + storage: new LocalPythonStorage({ + interpreterWorkingDir: `${__dirname}/tmp/code_interpreter`, + localWorkingDir: `${__dirname}/tmp/local`, + }), + }), + ] + : []), + ], +}); + +const reader = createConsoleReader(); +if (codeInterpreterUrl) { + reader.write("πŸ› οΈ System", "Please ensure that the code interpreter is running."); +} + +try { + for await (const { prompt } of reader) { + const response = await agent + .run( + { prompt }, + { + execution: { + maxRetriesPerStep: 3, + totalMaxRetries: 10, + maxIterations: 20, + }, + }, + ) + .observe((emitter) => { + // emitter.on("start", () => { + // reader.write(`Agent πŸ€– : `, "starting new iteration"); + // }); + emitter.on("error", ({ error }) => { + reader.write(`Agent πŸ€– : `, FrameworkError.ensure(error).dump()); + }); + emitter.on("retry", () => { + reader.write(`Agent πŸ€– : `, "retrying the action..."); + }); + emitter.on("update", async ({ data, update, meta }) => { + // log 'data' to see the whole state + // to log only valid runs (no errors), check if meta.success === true + reader.write(`Agent (${update.key}) πŸ€– : `, update.value); + }); + emitter.on("partialUpdate", ({ data, update, meta }) => { + // ideal for streaming (line by line) + // log 'data' to see the whole state + // to log only valid runs (no errors), check if meta.success === true + // reader.write(`Agent (partial ${update.key}) πŸ€– : `, update.value); + }); + + // To observe all events (uncomment following block) + // emitter.match("*.*", async (data: unknown, event) => { + // logger.trace(event, `Received event "${event.path}"`); + // }); + }); + + reader.write(`Agent πŸ€– : `, response.result.text); + } +} catch (error) { + logger.error(FrameworkError.ensure(error).dump()); +} finally { + process.exit(0); +} diff --git a/examples/agents/bee_reusable.ts b/examples/agents/bee_reusable.ts new file mode 100644 index 0000000..2f315d0 --- /dev/null +++ b/examples/agents/bee_reusable.ts @@ -0,0 +1,34 @@ +import "dotenv/config.js"; +import { BeeAgent } from "@/agents/bee/agent.js"; +import { DuckDuckGoSearchTool } from "@/tools/search/duckDuckGoSearch.js"; +import { UnconstrainedMemory } from "@/memory/unconstrainedMemory.js"; +import { OpenAIChatLLM } from "@/adapters/openai/chat.js"; + +// We create an agent +let agent = new BeeAgent({ + llm: new OpenAIChatLLM(), + tools: [new DuckDuckGoSearchTool()], + memory: new UnconstrainedMemory(), +}); + +// We ask the agent +let prompt = "Who is the president of USA?"; +console.info(prompt); +const response = await agent.run({ + prompt, +}); +console.info(response.result.text); + +// We can save (serialize) the agent +const json = agent.serialize(); + +// We reinitialize the agent to the exact state he was +agent = BeeAgent.fromSerialized(json); + +// We continue in our conversation +prompt = "When was he born?"; +console.info(prompt); +const response2 = await agent.run({ + prompt, +}); +console.info(response2.result.text); diff --git a/examples/agents/simple.ts b/examples/agents/simple.ts new file mode 100644 index 0000000..9566bcb --- /dev/null +++ b/examples/agents/simple.ts @@ -0,0 +1,23 @@ +import "dotenv/config.js"; +import { BeeAgent } from "@/agents/bee/agent.js"; +import { TokenMemory } from "@/memory/tokenMemory.js"; +import { DuckDuckGoSearchTool } from "@/tools/search/duckDuckGoSearch.js"; +import { OllamaChatLLM } from "@/adapters/ollama/chat.js"; +import { OpenMeteoTool } from "@/tools/weather/openMeteo.js"; + +const llm = new OllamaChatLLM(); +const agent = new BeeAgent({ + llm, + memory: new TokenMemory({ llm }), + tools: [new DuckDuckGoSearchTool(), new OpenMeteoTool()], +}); + +const response = await agent + .run({ prompt: "What's the current weather in Las Vegas?" }) + .observe((emitter) => { + emitter.on("update", async ({ data, update, meta }) => { + console.log(`Agent (${update.key}) πŸ€– : `, update.value); + }); + }); + +console.log(`Agent πŸ€– : `, response.result.text); diff --git a/examples/helpers/io.ts b/examples/helpers/io.ts new file mode 100644 index 0000000..bc26dd7 --- /dev/null +++ b/examples/helpers/io.ts @@ -0,0 +1,73 @@ +import readline from "node:readline/promises"; +import { stdin, stdout } from "node:process"; +import picocolors from "picocolors"; +import * as R from "remeda"; +import stripAnsi from "strip-ansi"; + +interface ReadFromConsoleInput { + fallback?: string; + input?: string; + allowEmpty?: boolean; +} + +export function createConsoleReader({ + fallback, + input = "User πŸ‘€ : ", + allowEmpty = false, +}: ReadFromConsoleInput = {}) { + const rl = readline.createInterface({ input: stdin, output: stdout, terminal: true }); + let isActive = true; + + return { + write(role: string, data: string) { + rl.write( + [role && R.piped(picocolors.red, picocolors.bold)(role), stripAnsi(data ?? "")] + .filter(Boolean) + .join(" ") + .concat("\n"), + ); + }, + async prompt(): Promise { + for await (const { prompt } of this) { + return prompt; + } + process.exit(0); + }, + async *[Symbol.asyncIterator]() { + if (!isActive) { + return; + } + + try { + rl.write( + `${picocolors.dim(`Interactive session has started. To escape, input 'q' and submit.\n`)}`, + ); + + for (let iteration = 1, prompt = ""; isActive; iteration++) { + prompt = await rl.question(R.piped(picocolors.cyan, picocolors.bold)(input)); + prompt = stripAnsi(prompt); + + if (prompt === "q") { + break; + } + if (!prompt.trim() || prompt === "\n") { + prompt = fallback ?? ""; + } + if (allowEmpty !== false && !prompt.trim()) { + rl.write("Error: Empty prompt is not allowed. Please try again.\n"); + iteration -= 1; + continue; + } + yield { prompt, iteration }; + } + } catch (e) { + if (e.code === "ERR_USE_AFTER_CLOSE") { + return; + } + } finally { + isActive = false; + rl.close(); + } + }, + }; +} diff --git a/examples/helpers/setup.ts b/examples/helpers/setup.ts new file mode 100644 index 0000000..3286e2c --- /dev/null +++ b/examples/helpers/setup.ts @@ -0,0 +1,4 @@ +import "dotenv/config"; + +import { Logger } from "@/logger/logger.js"; +Logger.defaults.pretty = true; diff --git a/examples/llms/chat.ts b/examples/llms/chat.ts new file mode 100644 index 0000000..79d09ca --- /dev/null +++ b/examples/llms/chat.ts @@ -0,0 +1,22 @@ +import "dotenv/config.js"; +import { createConsoleReader } from "examples/helpers/io.js"; +import { Logger } from "@/logger/logger.js"; +import { BaseMessage, Role } from "@/llms/primitives/message.js"; +import { OllamaChatLLM } from "@/adapters/ollama/chat.js"; + +Logger.root.level = "info"; // or your custom level + +const llm = new OllamaChatLLM(); + +const reader = createConsoleReader(); + +for await (const { prompt } of reader) { + const response = await llm.generate([ + BaseMessage.of({ + role: Role.USER, + text: prompt, + }), + ]); + reader.write(`LLM πŸ€– (txt) : `, response.getTextContent()); + reader.write(`LLM πŸ€– (raw) : `, JSON.stringify(response.finalResult)); +} diff --git a/examples/llms/chatCallback.ts b/examples/llms/chatCallback.ts new file mode 100644 index 0000000..8ccc9b9 --- /dev/null +++ b/examples/llms/chatCallback.ts @@ -0,0 +1,35 @@ +import "dotenv/config.js"; +import { createConsoleReader } from "examples/helpers/io.js"; +import { Logger } from "@/logger/logger.js"; +import { BaseMessage, Role } from "@/llms/primitives/message.js"; +import { OllamaChatLLM } from "@/adapters/ollama/chat.js"; + +Logger.root.level = "info"; // or your custom level + +const llm = new OllamaChatLLM(); + +const reader = createConsoleReader(); + +for await (const { prompt } of reader) { + const response = await llm + .generate( + [ + BaseMessage.of({ + role: Role.USER, + text: prompt, + }), + ], + {}, + ) + .observe((emitter) => + emitter.match("*", (data, event) => { + reader.write(`LLM πŸ€– (event: ${event.name})`, JSON.stringify(data)); + + // if you want to premature close the stream, just uncomment the following line + // callbacks.abort() + }), + ); + + reader.write(`LLM πŸ€– (txt) : `, response.getTextContent()); + reader.write(`LLM πŸ€– (raw) : `, JSON.stringify(response.finalResult)); +} diff --git a/examples/llms/chatStream.ts b/examples/llms/chatStream.ts new file mode 100644 index 0000000..8d77948 --- /dev/null +++ b/examples/llms/chatStream.ts @@ -0,0 +1,23 @@ +import "dotenv/config.js"; +import { createConsoleReader } from "examples/helpers/io.js"; +import { Logger } from "@/logger/logger.js"; +import { BaseMessage, Role } from "@/llms/primitives/message.js"; +import { OllamaChatLLM } from "@/adapters/ollama/chat.js"; + +Logger.root.level = "info"; // or your custom level + +const llm = new OllamaChatLLM(); + +const reader = createConsoleReader(); + +for await (const { prompt } of reader) { + for await (const chunk of llm.stream([ + BaseMessage.of({ + role: Role.USER, + text: prompt, + }), + ])) { + reader.write(`LLM πŸ€– (txt) : `, chunk.getTextContent()); + reader.write(`LLM πŸ€– (raw) : `, JSON.stringify(chunk.finalResult)); + } +} diff --git a/examples/llms/providers/bam.ts b/examples/llms/providers/bam.ts new file mode 100644 index 0000000..5e87dad --- /dev/null +++ b/examples/llms/providers/bam.ts @@ -0,0 +1,32 @@ +import { BaseMessage } from "@/llms/primitives/message.js"; +import { BAMLLM } from "@/adapters/bam/llm.js"; +import { BAMChatLLM } from "@/adapters/bam/chat.js"; + +{ + console.info("===RAW==="); + const llm = new BAMLLM({ + modelId: "google/flan-ul2", + }); + + console.info("Meta", await llm.meta()); + + const response = await llm.generate("Hello world!", { + stream: true, + }); + console.info(response.finalResult); +} + +{ + console.info("===CHAT==="); + const llm = BAMChatLLM.fromPreset("meta-llama/llama-3-1-70b-instruct"); + + console.info("Meta", await llm.meta()); + + const response = await llm.generate([ + BaseMessage.of({ + role: "user", + text: "Hello world!", + }), + ]); + console.info(response.messages); +} diff --git a/examples/llms/providers/langchain.ts b/examples/llms/providers/langchain.ts new file mode 100644 index 0000000..3bf4806 --- /dev/null +++ b/examples/llms/providers/langchain.ts @@ -0,0 +1,26 @@ +// NOTE: ensure you have installed following packages +// - @langchain/core +// - @langchain/cohere (or any other provider related package that you would like to use) +// List of available providers: https://js.langchain.com/v0.2/docs/integrations/chat/ + +import { BaseMessage } from "@/llms/primitives/message.js"; +import { LangChainChatLLM } from "@/adapters/langchain/llms/chat.js"; +// @ts-expect-error package not installed +import { ChatCohere } from "@langchain/cohere"; + +console.info("===CHAT==="); +const llm = new LangChainChatLLM( + new ChatCohere({ + model: "command-r-plus", + temperature: 0, + }), +); + +const response = await llm.generate([ + BaseMessage.of({ + role: "user", + text: "Hello world!", + }), +]); +console.info(response.messages); +console.info(response.getTextContent()); diff --git a/examples/llms/providers/ollama.ts b/examples/llms/providers/ollama.ts new file mode 100644 index 0000000..200b175 --- /dev/null +++ b/examples/llms/providers/ollama.ts @@ -0,0 +1,42 @@ +import { OllamaLLM } from "@/adapters/ollama/llm.js"; +import { OllamaChatLLM } from "@/adapters/ollama/chat.js"; +import { BaseMessage } from "@/llms/primitives/message.js"; + +{ + console.info("===RAW==="); + const llm = new OllamaLLM({ + modelId: "llama3.1", + parameters: { + num_predict: 10, + stop: ["post"], + }, + }); + + console.info("Meta", await llm.meta()); + + const response = await llm.generate("Hello world!", { + stream: true, + }); + console.info(response.finalResult); +} + +{ + console.info("===CHAT==="); + const llm = new OllamaChatLLM({ + modelId: "llama3.1", + parameters: { + num_predict: 10, + temperature: 0, + }, + }); + + console.info("Meta", await llm.meta()); + + const response = await llm.generate([ + BaseMessage.of({ + role: "user", + text: "Hello world!", + }), + ]); + console.info(response.finalResult); +} diff --git a/examples/llms/providers/openai.ts b/examples/llms/providers/openai.ts new file mode 100644 index 0000000..5b28ecb --- /dev/null +++ b/examples/llms/providers/openai.ts @@ -0,0 +1,20 @@ +import "dotenv/config"; +import { BaseMessage } from "@/llms/primitives/message.js"; +import { OpenAIChatLLM } from "@/adapters/openai/chat.js"; + +const llm = new OpenAIChatLLM({ + modelId: "gpt-4o", + parameters: { + max_tokens: 10, + stop: ["post"], + }, +}); + +console.info("Meta", await llm.meta()); +const response = await llm.generate([ + BaseMessage.of({ + role: "user", + text: "Hello world!", + }), +]); +console.info(response.getTextContent()); diff --git a/examples/llms/providers/watsonx.ts b/examples/llms/providers/watsonx.ts new file mode 100644 index 0000000..6971ada --- /dev/null +++ b/examples/llms/providers/watsonx.ts @@ -0,0 +1,53 @@ +import "dotenv/config"; +import { BaseMessage } from "@/llms/primitives/message.js"; +import { WatsonXChatLLM } from "@/adapters/watsonx/chat.js"; +import { WatsonXLLM } from "@/adapters/watsonx/llm.js"; +import { PromptTemplate } from "@/template.js"; + +const template = new PromptTemplate({ + variables: ["messages"], + template: `{{#messages}}{{#system}}<|begin_of_text|><|start_header_id|>system<|end_header_id|> + +{{system}}<|eot_id|>{{/system}}{{#user}}<|start_header_id|>user<|end_header_id|> + +{{user}}<|eot_id|>{{/user}}{{#assistant}}<|start_header_id|>assistant<|end_header_id|> + +{{assistant}}<|eot_id|>{{/assistant}}{{/messages}}<|start_header_id|>assistant<|end_header_id|> + +`, +}); + +const llm = new WatsonXLLM({ + modelId: "meta-llama/llama-3-70b-instruct", + projectId: process.env.WATSONX_PROJECT_ID, + apiKey: process.env.WATSONX_API_KEY, + parameters: { + decoding_method: "greedy", + max_new_tokens: 50, + }, +}); + +const chatLLM = new WatsonXChatLLM({ + llm, + config: { + messagesToPrompt(messages: BaseMessage[]) { + return template.render({ + messages: messages.map((message) => ({ + system: message.role === "system" ? [message.text] : [], + user: message.role === "user" ? [message.text] : [], + assistant: message.role === "assistant" ? [message.text] : [], + })), + }); + }, + }, +}); + +console.info("Meta", await chatLLM.meta()); + +const response = await chatLLM.generate([ + BaseMessage.of({ + role: "user", + text: "Hello world!", + }), +]); +console.info(response.messages[0]); diff --git a/examples/llms/structured.ts b/examples/llms/structured.ts new file mode 100644 index 0000000..765010e --- /dev/null +++ b/examples/llms/structured.ts @@ -0,0 +1,28 @@ +import "dotenv/config.js"; +import { z } from "zod"; +import { BaseMessage, Role } from "@/llms/primitives/message.js"; +import { OllamaChatLLM } from "@/adapters/ollama/chat.js"; + +const llm = new OllamaChatLLM(); +const response = await llm.generateStructured( + z.union([ + z.object({ + firstName: z.string().min(1), + lastName: z.string().min(1), + address: z.string(), + age: z.number().int().min(1), + hobby: z.string(), + }), + z.object({ + error: z.string(), + }), + ]), + [ + BaseMessage.of({ + role: Role.USER, + text: "Generate a profile of a citizen of Europe.", // feel free to update it + }), + ], +); +console.info(response); +process.exit(0); diff --git a/examples/llms/text.ts b/examples/llms/text.ts new file mode 100644 index 0000000..ccc33a1 --- /dev/null +++ b/examples/llms/text.ts @@ -0,0 +1,20 @@ +import "dotenv/config.js"; +import { createConsoleReader } from "examples/helpers/io.js"; +import { WatsonXLLM } from "@/adapters/watsonx/llm.js"; + +const llm = new WatsonXLLM({ + modelId: "google/flan-ul2", + projectId: process.env.WATSONX_PROJECT_ID, + apiKey: process.env.WATSONX_API_KEY, + parameters: { + decoding_method: "greedy", + max_new_tokens: 50, + }, +}); + +const reader = createConsoleReader(); + +const prompt = await reader.prompt(); +const response = await llm.generate(prompt); +reader.write(`LLM πŸ€– (text) : `, response.getTextContent()); +process.exit(0); diff --git a/examples/template.ts b/examples/template.ts new file mode 100644 index 0000000..cc77bc7 --- /dev/null +++ b/examples/template.ts @@ -0,0 +1,68 @@ +import "examples/helpers/setup.js"; +import { PromptTemplate } from "@/template.js"; +import { Logger } from "@/logger/logger.js"; + +const logger = new Logger({ name: "template" }); + +// Primitives +{ + const greetTemplate = new PromptTemplate({ + template: `Hello {{name}}`, + variables: ["name"], + }); + + const output = greetTemplate.render({ + name: "Alex", + }); + logger.info(output); // "Hello Alex!" +} + +// Arrays +{ + const template = new PromptTemplate({ + variables: ["colors"], + template: `My Favorite Colors: {{#colors}}{{.}} {{/colors}}`, + }); + const output = template.render({ + colors: ["Green", "Yellow"], + }); + logger.info(output); +} + +// Objects +{ + const template = new PromptTemplate({ + template: `Expected Duration: {{expected}}ms; Retrieved: {{#responses}}{{duration}}ms {{/responses}}`, + variables: ["expected", "responses"], + defaults: { + expected: 5, + }, + }); + const output = template.render({ + expected: PromptTemplate.defaultPlaceholder, + responses: [{ duration: 3 }, { duration: 5 }, { duration: 6 }], + }); + logger.info(output); +} + +// Forking +{ + const original = new PromptTemplate({ + template: `You are a helpful assistant called {{name}}. You objective is to {{objective}}.`, + variables: ["name", "objective"], + }); + + const modified = original.fork((oldConfig) => ({ + ...oldConfig, + template: `${oldConfig.template} Your answers must be concise.`, + defaults: { + name: "Allan", + }, + })); + + const output = modified.render({ + name: PromptTemplate.defaultPlaceholder, + objective: "fulfill the user needs", + }); + logger.info(output); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f975f70 --- /dev/null +++ b/package.json @@ -0,0 +1,153 @@ +{ + "name": "bee-agent-framework", + "version": "0.0.0", + "license": "Apache-2.0", + "description": "Bee - LLM Agent Framework", + "author": "IBM Corp.", + "contributors": [ + "Tomas Dvorak " + ], + "keywords": [ + "LLM Agent Framework", + "Bee Agent Framework", + "NodeJS Agent Framework" + ], + "packageManager": "yarn@4.1.1", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "sideEffects": false, + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./*": { + "import": { + "types": "./dist/*.d.ts", + "default": "./dist/*.js" + }, + "require": { + "types": "./dist/*.d.cts", + "default": "./dist/*.cjs" + } + } + }, + "files": [ + "dist/**/*" + ], + "scripts": { + "clean": "rimraf dist", + "build": "yarn clean && yarn ts:check && tsup", + "ts:check": "tsc --noEmit", + "start:bee": "tsx examples/agents/bee.ts", + "infra:start-all": "yarn _docker compose up -d", + "infra:start-code-interpreter": "yarn _docker compose up bee-code-interpreter", + "infra:stop-all": "yarn _docker compose down", + "infra:clean-all": "yarn _docker compose down --volumes", + "lint": "yarn eslint src examples", + "lint:fix": "yarn eslint --fix src examples", + "format": "yarn prettier --check src examples", + "format:fix": "yarn prettier --write src examples", + "test:unit": "vitest run src", + "test:unit:watch": "vitest run src", + "test:e2e": "vitest run tests", + "test:e2e:watch": "vitest watch tests", + "test:all": "vitest run", + "test:watch": "vitest watch", + "prepare": "husky", + "copyright": "./scripts/copyright.sh", + "release": "release-it", + "_ensure_env": "cp -n .env.template .env", + "_docker": "yarn _ensure_env && sh -c 'source ./.env && docker_cmd=$(which docker >/dev/null 2>&1 && printf docker || printf podman) && $docker_cmd \"$@\"' sh" + }, + "dependencies": { + "@ai-zen/node-fetch-event-source": "^2.1.4", + "@connectrpc/connect": "^1.4.0", + "@connectrpc/connect-node": "^1.4.0", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "bee-proto": "0.0.1", + "dirty-json": "0.9.2", + "duck-duck-scrape": "^2.2.5", + "fast-xml-parser": "^4.4.1", + "header-generator": "^2.1.54", + "joplin-turndown-plugin-gfm": "^1.0.12", + "mustache": "^4.2.0", + "object-hash": "^3.0.0", + "p-queue": "^8.0.1", + "p-throttle": "^6.2.0", + "pino": "^9.3.2", + "promise-based-task": "^3.0.2", + "remeda": "^2.11.0", + "serialize-error": "^11.0.3", + "string-comparison": "^1.3.0", + "string-strip-html": "^13.4.8", + "turndown": "^7.2.0", + "wikipedia": "^2.1.2", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.23.2" + }, + "peerDependencies": { + "@ibm-generative-ai/node-sdk": "~3.2.1", + "@langchain/community": "~0.2.28", + "@langchain/core": "~0.2.27", + "@langchain/langgraph": "~0.0.34", + "ollama": "^0.5.8", + "openai": "^4.56.0", + "openai-chat-tokens": "^0.2.8" + }, + "devDependencies": { + "@eslint/js": "^9.9.0", + "@ibm-generative-ai/node-sdk": "~3.2.1", + "@langchain/community": "~0.2.28", + "@langchain/core": "~0.2.27", + "@langchain/langgraph": "~0.0.34", + "@release-it/conventional-changelog": "^8.0.1", + "@rollup/plugin-commonjs": "^26.0.1", + "@swc/core": "^1.7.14", + "@types/eslint-config-prettier": "^6.11.3", + "@types/eslint__js": "^8.42.3", + "@types/mustache": "^4", + "@types/needle": "^3.3.0", + "@types/node": "^20.16.1", + "@types/object-hash": "^3.0.6", + "@types/turndown": "^5.0.5", + "dotenv": "^16.4.5", + "eslint": "^9.9.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-unused-imports": "^4.1.3", + "glob": "^11.0.0", + "husky": "^9.1.5", + "langchain": "~0.2.16", + "lint-staged": "^15.2.9", + "ollama": "^0.5.8", + "openai": "^4.56.0", + "openai-chat-tokens": "^0.2.8", + "openapi-fetch": "^0.11.1", + "openapi-typescript": "^7.3.0", + "picocolors": "^1.0.1", + "pino-pretty": "^11.2.2", + "pino-test": "^1.0.1", + "prettier": "^3.3.3", + "release-it": "^17.6.0", + "rimraf": "^6.0.1", + "strip-ansi": "^7.1.0", + "temp-dir": "^3.0.0", + "tsc-files": "^1.1.4", + "tsup": "^8.2.4", + "tsx": "^4.17.0", + "typescript": "^5.5.4", + "typescript-eslint": "^8.2.0", + "vite-tsconfig-paths": "^5.0.1", + "vitest": "^2.0.5", + "yaml": "^2.5.0" + } +} diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..ae16854 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,8 @@ +// @ts-check + +/** @type {import("prettier").Config} */ +const config = { + printWidth: 100, +}; + +export default config; diff --git a/scripts/copyright.sh b/scripts/copyright.sh new file mode 100755 index 0000000..29b5611 --- /dev/null +++ b/scripts/copyright.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Copyright 2024 IBM Corp. +# +# 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. +set -e + +# Path to the package.json file +PACKAGE_JSON_PATH="./package.json" + +# Check if the package.json file exists +if [[ ! -f "$PACKAGE_JSON_PATH" ]]; then + echo "Error: package.json file not found at $PACKAGE_JSON_PATH" + exit 1 +fi + +# Retrieve the author property using jq +AUTHOR=$(jq -r '.author' "$PACKAGE_JSON_PATH") + +# Check if the author property is not null or empty +if [[ ! -n "$AUTHOR" ]]; then + echo "Error: author property not found in package.json" +fi + +nwa add -l apache -c "$AUTHOR" src dist tests diff --git a/src/adapters/bam/__snapshots__/llm.test.ts.snap b/src/adapters/bam/__snapshots__/llm.test.ts.snap new file mode 100644 index 0000000..ec5434a --- /dev/null +++ b/src/adapters/bam/__snapshots__/llm.test.ts.snap @@ -0,0 +1,833 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`SDK LLM Output > Merging > Merges moderations 1`] = ` +{ + "generated_text": "Text 0", + "generated_token_count": 3, + "input_text": "Hello world!", + "input_token_count": 4, + "moderations": { + "hap": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.33, + "success": true, + }, + ], + "social_bias": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.77, + "success": true, + }, + ], + }, + "seed": 1230, + "stop_reason": "max_tokens", +} +`; + +exports[`SDK LLM Output > Merging > Merges moderations 2`] = ` +{ + "hap": [ + { + "flagged": true, + "position": { + "end": 777, + "start": 777, + }, + "score": 0.75, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.33, + "success": true, + }, + ], + "social_bias": [ + { + "flagged": true, + "position": { + "end": 888, + "start": 888, + }, + "score": 0.75, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.77, + "success": true, + }, + ], +} +`; + +exports[`SDK LLM Output > Merging > Multiple chunks (2) 1`] = ` +{ + "generated_text": "Text 0Text 1", + "generated_token_count": 6, + "input_text": "Hello world!", + "input_token_count": 4, + "moderations": { + "hap": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.33, + "success": true, + }, + ], + "social_bias": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.77, + "success": true, + }, + ], + }, + "seed": 1230, + "stop_reason": "max_tokens", +} +`; + +exports[`SDK LLM Output > Merging > Multiple chunks (2) 2`] = ` +{ + "hap": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.33, + "success": true, + }, + ], + "social_bias": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.77, + "success": true, + }, + ], +} +`; + +exports[`SDK LLM Output > Merging > Multiple chunks (5) 1`] = ` +{ + "generated_text": "Text 0Text 1Text 2Text 3Text 4", + "generated_token_count": 15, + "input_text": "Hello world!", + "input_token_count": 4, + "moderations": { + "hap": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.33, + "success": true, + }, + ], + "social_bias": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.77, + "success": true, + }, + ], + }, + "seed": 1230, + "stop_reason": "max_tokens", +} +`; + +exports[`SDK LLM Output > Merging > Multiple chunks (5) 2`] = ` +{ + "hap": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.33, + "success": true, + }, + ], + "social_bias": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.77, + "success": true, + }, + ], +} +`; + +exports[`SDK LLM Output > Merging > Multiple chunks (6) 1`] = ` +{ + "generated_text": "Text 0Text 1Text 2Text 3Text 4Text 5", + "generated_token_count": 18, + "input_text": "Hello world!", + "input_token_count": 4, + "moderations": { + "hap": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.33, + "success": true, + }, + ], + "social_bias": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.77, + "success": true, + }, + ], + }, + "seed": 1230, + "stop_reason": "max_tokens", +} +`; + +exports[`SDK LLM Output > Merging > Multiple chunks (6) 2`] = ` +{ + "hap": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.33, + "success": true, + }, + ], + "social_bias": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.77, + "success": true, + }, + ], +} +`; + +exports[`SDK LLM Output > Merging > Multiple chunks (10) 1`] = ` +{ + "generated_text": "Text 0Text 1Text 2Text 3Text 4Text 5Text 6Text 7Text 8Text 9", + "generated_token_count": 30, + "input_text": "Hello world!", + "input_token_count": 4, + "moderations": { + "hap": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.33, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 6, + "start": 6, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 6, + "start": 6, + }, + "score": 0.33, + "success": true, + }, + ], + "social_bias": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 6, + "start": 6, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 6, + "start": 6, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 8, + "start": 8, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 8, + "start": 8, + }, + "score": 0.77, + "success": true, + }, + ], + }, + "seed": 1230, + "stop_reason": "max_tokens", +} +`; + +exports[`SDK LLM Output > Merging > Multiple chunks (10) 2`] = ` +{ + "hap": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.33, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 6, + "start": 6, + }, + "score": 0.98, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 6, + "start": 6, + }, + "score": 0.33, + "success": true, + }, + ], + "social_bias": [ + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 0, + "start": 0, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 2, + "start": 2, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 4, + "start": 4, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 6, + "start": 6, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 6, + "start": 6, + }, + "score": 0.77, + "success": true, + }, + { + "flagged": true, + "position": { + "end": 8, + "start": 8, + }, + "score": 0.99, + "success": false, + }, + { + "flagged": true, + "position": { + "end": 8, + "start": 8, + }, + "score": 0.77, + "success": true, + }, + ], +} +`; diff --git a/src/adapters/bam/chat.ts b/src/adapters/bam/chat.ts new file mode 100644 index 0000000..6227cb3 --- /dev/null +++ b/src/adapters/bam/chat.ts @@ -0,0 +1,193 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { AsyncStream, GenerateCallbacks, LLMError } from "@/llms/base.js"; +import { isFunction, isObjectType } from "remeda"; +import { + BAMLLM, + BAMLLMGenerateOptions, + BAMLLMParameters, + BAMLLMOutput, +} from "@/adapters/bam/llm.js"; +import { ChatLLM, ChatLLMOutput } from "@/llms/chat.js"; +import { BaseMessage } from "@/llms/primitives/message.js"; +import { PromptTemplate } from "@/template.js"; +import { Cache } from "@/cache/decoratorCache.js"; +import { BAMChatLLMPreset, BAMChatLLMPresetModel } from "@/adapters/bam/chatPreset.js"; +import { Client } from "@ibm-generative-ai/node-sdk"; +import { transformAsyncIterable } from "@/internals/helpers/stream.js"; +import { shallowCopy } from "@/serializer/utils.js"; +import { Emitter } from "@/emitter/emitter.js"; +import { GetRunContext } from "@/context.js"; + +export class BAMChatLLMOutput extends ChatLLMOutput { + public readonly raw: BAMLLMOutput; + + constructor(rawOutput: BAMLLMOutput) { + super(); + this.raw = rawOutput; + } + + get finalResult() { + return this.raw.finalResult; + } + + @Cache() + get messages(): BaseMessage[] { + const text = this.raw.getTextContent(); + return [ + BaseMessage.of({ + role: "assistant", + text, + meta: this.raw.meta, + }), + ]; + } + + merge(other: BAMChatLLMOutput): void { + Cache.getInstance(this, "messages").clear(); + this.raw.merge(other.raw); + } + + getTextContent(): string { + const [message] = this.messages; + return message.text; + } + + toString(): string { + return this.getTextContent(); + } + + createSnapshot() { + return { + raw: shallowCopy(this.raw), + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} + +export interface BAMChatLLMInputConfig { + messagesToPrompt: PromptTemplate<"messages"> | ((messages: BaseMessage[]) => string); +} + +export interface BAMChatLLMInput { + llm: BAMLLM; + config: BAMChatLLMInputConfig; +} + +export class BAMChatLLM extends ChatLLM { + public readonly emitter = Emitter.root.child({ + namespace: ["bam", "chat_llm"], + creator: this, + }); + + public readonly llm: BAMLLM; + protected readonly config: BAMChatLLMInputConfig; + + constructor({ llm, config }: BAMChatLLMInput) { + super(llm.modelId, llm.executionOptions); + this.llm = llm; + this.config = config; + } + + static { + this.register(); + } + + async meta() { + return this.llm.meta(); + } + + createSnapshot() { + return { + ...super.createSnapshot(), + modelId: this.modelId, + executionOptions: shallowCopy(this.executionOptions), + llm: this.llm, + config: shallowCopy(this.config), + }; + } + + async tokenize(messages: BaseMessage[]) { + const prompt = this.messagesToPrompt(messages); + return this.llm.tokenize(prompt); + } + + protected async _generate( + messages: BaseMessage[], + options: BAMLLMGenerateOptions, + run: GetRunContext, + ): Promise { + const prompt = this.messagesToPrompt(messages); + // @ts-expect-error protected property + const rawResponse = await this.llm._generate(prompt, options, run); + return new BAMChatLLMOutput(rawResponse); + } + + protected async *_stream( + messages: BaseMessage[], + options: BAMLLMGenerateOptions, + run: GetRunContext, + ): AsyncStream { + const prompt = this.messagesToPrompt(messages); + // @ts-expect-error protected property + const response = this.llm._stream(prompt, options, run); + return yield* transformAsyncIterable(response, (output) => new BAMChatLLMOutput(output)); + } + + messagesToPrompt(messages: BaseMessage[]) { + const convertor = this.config.messagesToPrompt; + if (convertor instanceof PromptTemplate) { + return convertor.render({ messages }); + } + return convertor(messages); + } + + static fromPreset( + modelId: BAMChatLLMPresetModel, + overrides?: { + client?: Client; + parameters?: BAMLLMParameters | ((value: BAMLLMParameters) => BAMLLMParameters); + }, + ) { + const presetFactory = BAMChatLLMPreset[modelId]; + if (!presetFactory) { + throw new LLMError(`Model "${modelId}" does not exist in preset.`); + } + + const preset = presetFactory(); + let parameters = preset.base.parameters ?? {}; + if (isFunction(overrides?.parameters)) { + parameters = overrides?.parameters(parameters); + } else if (isObjectType(overrides?.parameters)) { + parameters = overrides?.parameters; + } + + return new BAMChatLLM({ + config: preset.chat, + llm: new BAMLLM({ + ...preset.base, + ...overrides, + parameters, + client: overrides?.client ?? new Client(), + modelId, + }), + }); + } +} diff --git a/src/adapters/bam/chatPreset.ts b/src/adapters/bam/chatPreset.ts new file mode 100644 index 0000000..cf97448 --- /dev/null +++ b/src/adapters/bam/chatPreset.ts @@ -0,0 +1,109 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BAMChatLLMInputConfig } from "@/adapters/bam/chat.js"; +import { BaseMessage } from "@/llms/primitives/message.js"; +import { PromptTemplate } from "@/template.js"; +import { BAMLLMInput } from "@/adapters/bam/llm.js"; +import { toBoundedFunction } from "@/serializer/utils.js"; + +interface BAMChatLLMPreset { + chat: BAMChatLLMInputConfig; + base: Omit; +} + +export const BAMChatLLMPreset = { + "meta-llama/llama-3-1-70b-instruct": (): BAMChatLLMPreset => { + return { + base: { + parameters: { + decoding_method: "greedy", + include_stop_sequence: false, + max_new_tokens: 2048, + repetition_penalty: 1.03, + stop_sequences: ["<|eot_id|>"], + }, + }, + chat: { + messagesToPrompt: toBoundedFunction( + (messages: BaseMessage[]) => { + const template = new PromptTemplate({ + variables: ["messages"], + template: `{{#messages}}{{#system}}<|begin_of_text|><|start_header_id|>system<|end_header_id|> + +{{system}}<|eot_id|>{{/system}}{{#user}}<|start_header_id|>user<|end_header_id|> + +{{user}}<|eot_id|>{{/user}}{{#assistant}}<|start_header_id|>assistant<|end_header_id|> + +{{assistant}}<|eot_id|>{{/assistant}}{{#ipython}}<|start_header_id|>ipython<|end_header_id|> + +{{ipython}}<|eot_id|>{{/ipython}}{{/messages}}<|start_header_id|>assistant<|end_header_id|> +`, + }); + return template.render({ + messages: messages.map((message) => ({ + system: message.role === "system" ? [message.text] : [], + user: message.role === "user" ? [message.text] : [], + assistant: message.role === "assistant" ? [message.text] : [], + ipython: message.role === "ipython" ? [message.text] : [], + })), + }); + }, + [PromptTemplate], + ), + }, + }; + }, + "qwen/qwen2-72b-instruct": (): BAMChatLLMPreset => { + return { + base: { + parameters: { + decoding_method: "greedy", + include_stop_sequence: false, + stop_sequences: ["<|im_end|>"], + }, + }, + chat: { + messagesToPrompt: toBoundedFunction( + (messages: BaseMessage[]) => { + const template = new PromptTemplate({ + variables: ["messages"], + template: `{{#messages}}{{#system}}<|im_start|>system +{{system}}<|im_end|> +{{ end }}{{/system}}{{#user}}<|im_start|>user +{{user}}<|im_end|> +{{ end }}{{/user}}{{#assistant}}<|im_start|>assistant +{{assistant}}<|im_end|> +{{ end }}{{/assistant}}{{/messages}}<|im_start|>assistant +`, + }); + + return template.render({ + messages: messages.map((message) => ({ + system: message.role === "system" ? [message.text] : [], + user: message.role === "user" ? [message.text] : [], + assistant: message.role === "assistant" ? [message.text] : [], + })), + }); + }, + [PromptTemplate], + ), + }, + }; + }, +} as const; + +export type BAMChatLLMPresetModel = keyof typeof BAMChatLLMPreset; diff --git a/src/adapters/bam/llm.test.ts b/src/adapters/bam/llm.test.ts new file mode 100644 index 0000000..53f13f9 --- /dev/null +++ b/src/adapters/bam/llm.test.ts @@ -0,0 +1,163 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BAMLLMOutput, BAMLLMOutputModeration, BAMLLMOutputResult } from "@/adapters/bam/llm.js"; +import { expect } from "vitest"; +import { LLMOutputError } from "@/llms/base.js"; +import { verifyDeserialization } from "@tests/e2e/utils.js"; + +describe("SDK LLM Output", () => { + const generateModeration = ( + i: number, + override: Record = {}, + ): NonNullable["hap"]>[number] => { + return { + success: true, + position: { start: i, end: i }, + score: 0.75, + flagged: true, + ...override, + }; + }; + + const generateChunks = (count: number): BAMLLMOutputResult[] => { + return Array(count) + .fill(null) + .map( + (_, i): BAMLLMOutputResult => ({ + generated_text: `Text ${i}`, + generated_token_count: 3, + moderations: undefined, + ...(i % 2 === 0 && { + moderations: { + ...(i % 3 === 0 && { + hap: [ + generateModeration(i, { score: 0.98, success: false }), + generateModeration(i, { score: 0.33 }), + ], + }), + social_bias: [ + generateModeration(i, { score: 0.99, success: false }), + generateModeration(i, { score: 0.77 }), + ], + }, + }), + stop_reason: "not_finished", + ...(i === 0 && { + input_text: "Hello world!", + input_token_count: 4, + }), + ...(i + 1 === count && { + stop_reason: "max_tokens", + }), + seed: 1230, + }), + ); + }; + + const getInstance = ( + chunks: BAMLLMOutputResult[], + moderations?: BAMLLMOutputModeration | BAMLLMOutputModeration[], + ) => { + return new BAMLLMOutput({ + results: chunks, + moderations, + meta: { + id: "X", + model_id: "model", + input_parameters: undefined, + created_at: new Date().toISOString(), + }, + }); + }; + + describe("Merging", () => { + it("Throws for no chunks", () => { + const chunks = generateChunks(0); + const instance = getInstance(chunks); + expect(() => instance.finalResult).toThrowError(LLMOutputError); + }); + + it("Single instance", () => { + const chunks = generateChunks(1); + const instance = getInstance(chunks); + expect(instance.finalResult).toStrictEqual(chunks[0]); + }); + + it.each([2, 5, 6, 10])("Multiple chunks (%i)", (chunksCount) => { + const chunks = generateChunks(chunksCount); + const instance = getInstance(chunks); + + const finalResult = instance.finalResult; + expect(finalResult).toMatchSnapshot(); + + const finalModerations = instance.finalModeration; + expect(finalModerations).toMatchSnapshot(); + }); + + it("Merges moderations", () => { + const chunks = generateChunks(1); + const moderations = { + hap: [generateModeration(777)], + social_bias: [generateModeration(888)], + }; + const instance = getInstance(chunks, moderations); + + const finalResult = instance.finalResult; + expect(finalResult).toMatchSnapshot(); + + const finalModerations = instance.finalModeration; + expect(finalModerations).toMatchSnapshot(); + }); + }); + + describe("Caching", () => { + it("Caches final result", () => { + const [firstChunk, ...chunks] = generateChunks(5); + const instance = getInstance([firstChunk, ...chunks]); + + const result = instance.finalResult; + expect(result).toBeTruthy(); + Object.defineProperty(firstChunk, "generated_text", { + get() { + throw new Error("This should not be called!"); + }, + }); + expect(instance.finalResult).toBe(result); + expect(instance.getTextContent()).toBeTruthy(); + }); + + it("Revalidates cache", () => { + const instance = getInstance(generateChunks(5)); + + const result = instance.finalResult; + expect(instance.finalResult).toBe(result); + expect(instance.finalResult).toBe(result); + instance.merge(getInstance(generateChunks(1))); + + const newResult = instance.finalResult; + expect(newResult).not.toBe(result); + expect(instance.finalResult).toBe(newResult); + }); + }); + + it("Serializes", () => { + const instance = getInstance(generateChunks(5)); + const serialized = instance.serialize(); + const deserialized = BAMLLMOutput.fromSerialized(serialized); + verifyDeserialization(instance, deserialized); + }); +}); diff --git a/src/adapters/bam/llm.ts b/src/adapters/bam/llm.ts new file mode 100644 index 0000000..42da622 --- /dev/null +++ b/src/adapters/bam/llm.ts @@ -0,0 +1,369 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { LLM, LLMInput } from "@/llms/index.js"; +import { + AsyncStream, + BaseLLMOutput, + BaseLLMTokenizeOutput, + ExecutionOptions, + GenerateCallbacks, + GenerateOptions, + LLMError, + LLMMeta, + LLMOutputError, +} from "@/llms/base.js"; +import { + Client, + TextGenerationCreateInput, + TextGenerationCreateStreamInput, + TextGenerationCreateStreamOutput, + TextGenerationCreateOutput, + HttpError, +} from "@ibm-generative-ai/node-sdk"; +import * as R from "remeda"; +import { ExcludeNonStringIndex } from "@/internals/types.js"; +import { FrameworkError, NotImplementedError } from "@/errors.js"; +import { Cache } from "@/cache/decoratorCache.js"; +import { transformAsyncIterable } from "@/internals/helpers/stream.js"; +import { shallowCopy } from "@/serializer/utils.js"; +import { safeSum } from "@/internals/helpers/number.js"; +import { customMerge, omitUndefined } from "@/internals/helpers/object.js"; +import { isEmpty, isString } from "remeda"; +import { Emitter } from "@/emitter/emitter.js"; +import { GetRunContext } from "@/context.js"; + +export type BAMLLMOutputMeta = Omit, "results">; + +export type BAMLLMOutputResult = ExcludeNonStringIndex< + TextGenerationCreateOutput["results"][number] +>; + +export type BAMLLMOutputModeration = ExcludeNonStringIndex< + TextGenerationCreateStreamOutput["moderations"] +>; + +export interface BAMLLMOutputConstructor { + meta: BAMLLMOutputMeta; + results: BAMLLMOutputResult[]; + moderations?: BAMLLMOutputModeration | BAMLLMOutputModeration[]; +} + +export class BAMLLMOutput extends BaseLLMOutput { + public readonly meta: BAMLLMOutputMeta; + public readonly results: BAMLLMOutputResult[]; + public readonly moderations: BAMLLMOutputModeration[]; + + constructor(content: BAMLLMOutputConstructor) { + super(); + this.meta = content.meta; + this.results = content.results; + this.moderations = R.isArray(content.moderations) + ? content.moderations + : [content.moderations].filter(R.isDefined); + } + + static { + this.register(); + } + + getTextContent(): string { + return this.finalResult.generated_text; + } + + get finalModeration(): Readonly { + return BAMLLMOutput._combineModerations(...this.moderations, this.finalResult.moderations); + } + + @Cache() + get finalResult(): Readonly { + if (this.results.length === 0) { + throw new LLMOutputError("No chunks to get final result from!"); + } + + return customMerge(this.results, { + generated_text: (value = "", oldValue = "") => oldValue + value, + input_token_count: safeSum, + generated_token_count: safeSum, + input_text: (value, oldValue) => value ?? oldValue, + generated_tokens: (value, oldValue) => [...(value || []), ...(oldValue || [])], + seed: (value, oldValue) => value ?? oldValue, + stop_reason: (value, oldValue) => value ?? oldValue, + stop_sequence: (value, oldValue) => value ?? oldValue, + input_tokens: (value, oldValue) => value ?? oldValue, + moderations: (value, oldValue) => + value && oldValue ? BAMLLMOutput._combineModerations(oldValue, value) : (value ?? oldValue), + }); + } + + merge(other: BAMLLMOutput): void { + Cache.getInstance(this, "finalResult").clear(); + + this.results.push(...other.results); + this.moderations.push(...other.moderations); + Object.assign(this.meta, omitUndefined(other.meta)); + } + + createSnapshot() { + return { + results: shallowCopy(this.results), + moderations: shallowCopy(this.moderations), + meta: shallowCopy(this.meta), + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } + + toString(): string { + return this.getTextContent(); + } + + protected static _combineModerations(...entries: BAMLLMOutputModeration[]) { + const newModerations: NonNullable = {}; + for (const entry of entries) { + for (const [key, records] of R.entries(entry ?? {})) { + if (R.isEmpty(records)) { + continue; + } + if (!newModerations[key]) { + newModerations[key] = []; + } + newModerations[key]!.push(...records); + } + } + return newModerations; + } +} + +export type BAMLLMParameters = NonNullable< + TextGenerationCreateInput["parameters"] & TextGenerationCreateStreamInput["parameters"] +>; + +export interface BAMLLMGenerateOptions extends GenerateOptions { + moderations?: TextGenerationCreateInput["moderations"]; +} + +export interface BAMLLMInput { + client?: Client; + modelId: string; + parameters?: BAMLLMParameters; + executionOptions?: ExecutionOptions; +} + +export class BAMLLM extends LLM { + public readonly emitter = Emitter.root.child({ + namespace: ["bam", "llm"], + creator: this, + }); + + public readonly client: Client; + public readonly parameters: Partial; + + constructor({ client, parameters, modelId, executionOptions = {} }: BAMLLMInput) { + super(modelId, executionOptions); + this.client = client ?? new Client(); + this.parameters = parameters ?? {}; + } + + static { + this.register(); + } + + async meta(): Promise { + try { + const { result } = await this.client.model.retrieve({ + id: this.modelId, + }); + + const tokenLimit = result.token_limits?.find?.((limit) => { + if (this.parameters?.beam_width !== undefined) { + return limit.token_limit !== undefined && limit.beam_width === this.parameters.beam_width; + } + return limit.token_limit !== undefined; + }); + return { + tokenLimit: tokenLimit?.token_limit ?? Infinity, + }; + } catch { + // TODO: remove once retrieval gets fixed on the API + if ( + this.modelId === "qwen/qwen2-72b-instruct" || + this.modelId === "meta-llama/llama-3-1-70b-instruct" + ) { + return { + tokenLimit: 131_072, + }; + } else if (this.modelId.includes("llama-3-70b-instruct")) { + return { + tokenLimit: 8196, + }; + } + + return { + tokenLimit: Infinity, + }; + } + } + + createSnapshot() { + return { + ...super.createSnapshot(), + client: null, + modelId: this.modelId, + parameters: shallowCopy(this.parameters), + executionOptions: shallowCopy(this.executionOptions), + }; + } + + loadSnapshot(snapshot: ReturnType): void { + super.loadSnapshot(snapshot); + Object.assign(this, snapshot, { + client: snapshot?.client ?? new Client(), // TODO: serialize? + }); + } + + protected _transformError(error: Error): Error { + if (error instanceof FrameworkError) { + throw error; + } + if (error instanceof HttpError) { + throw new LLMError("LLM has occurred an error!", [error], { + isRetryable: [408, 425, 429, 500, 503].includes(error.status_code), + }); + } + return new LLMError("LLM has occurred an error!", [error]); + } + + async tokenize(input: LLMInput): Promise { + try { + const { + results: [result], + } = await this.client.text.tokenization.create({ + input, + model_id: this.modelId, + parameters: { + return_options: { + tokens: true, + }, + }, + }); + + return { + tokensCount: result.token_count, + tokens: result.tokens, + }; + } catch (e) { + throw this._transformError(e); + } + } + + protected async _generate( + input: LLMInput, + options: BAMLLMGenerateOptions, + run: GetRunContext, + ): Promise { + try { + const response = await this.client.text.generation.create( + { + input, + moderations: options?.moderations, + model_id: this.modelId, + parameters: this._prepareParameters(options), + }, + { + signal: run.signal, + }, + ); + return this._rawResponseToOutput(response); + } catch (e) { + throw this._transformError(e); + } + } + + protected async *_stream( + input: string, + options: BAMLLMGenerateOptions, + run: GetRunContext, + ): AsyncStream { + try { + const response = await this.client.text.generation.create_stream( + { + input, + moderations: options?.moderations, + model_id: this.modelId, + parameters: this._prepareParameters(options), + }, + { + signal: run.signal, + }, + ); + yield* transformAsyncIterable( + response[Symbol.asyncIterator](), + this._rawResponseToOutput.bind(this), + ); + } catch (e) { + throw this._transformError(e); + } + } + + protected _rawResponseToOutput( + raw: TextGenerationCreateOutput | TextGenerationCreateStreamOutput, + ) { + const chunks = (raw.results ?? []) as BAMLLMOutputResult[]; + + return new BAMLLMOutput({ + results: chunks, + moderations: (raw as TextGenerationCreateStreamOutput)?.moderations, + meta: R.pickBy( + { + id: raw.id!, + model_id: raw.model_id, + created_at: raw.created_at!, + input_parameters: raw.input_parameters, + }, + R.isDefined, + ), + }); + } + + protected _prepareParameters(overrides?: GenerateOptions): typeof this.parameters { + const guided = overrides?.guided ? {} : (this.parameters.guided ?? {}); + const guidedOverride = overrides?.guided; + + if (guidedOverride?.choice) { + guided.choice = guidedOverride.choice; + } else if (guidedOverride?.grammar) { + guided.grammar = guidedOverride.grammar; + } else if (guidedOverride?.json) { + guided.json_schema = isString(guidedOverride.json) + ? JSON.parse(guidedOverride.json) + : guidedOverride.json; + } else if (guidedOverride?.regex) { + guided.regex = guidedOverride.regex; + } else if (!isEmpty(guidedOverride ?? {})) { + throw new NotImplementedError( + `Following types ${Object.keys(overrides!.guided!).join(",")}" for the constraint decoding are not supported!`, + ); + } + + return { + ...this.parameters, + guided: isEmpty(guided) ? undefined : guided, + }; + } +} diff --git a/src/adapters/langchain/llms/chat.ts b/src/adapters/langchain/llms/chat.ts new file mode 100644 index 0000000..4396fd1 --- /dev/null +++ b/src/adapters/langchain/llms/chat.ts @@ -0,0 +1,225 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { + AsyncStream, + BaseLLMTokenizeOutput, + ExecutionOptions, + GenerateCallbacks, + GenerateOptions, + LLMMeta, +} from "@/llms/base.js"; +import { shallowCopy } from "@/serializer/utils.js"; +import { load } from "@langchain/core/load"; +import { + BaseChatModel, + BaseChatModelCallOptions, +} from "@langchain/core/language_models/chat_models"; +import { ChatLLM, ChatLLMOutput } from "@/llms/chat.js"; +import { BaseMessage, Role, RoleType } from "@/llms/primitives/message.js"; +import { + BaseMessageChunk, + BaseMessage as LCBaseMessage, + ChatMessage as LCMChatMessage, + MessageContentComplex, + MessageContentText, + MessageType, +} from "@langchain/core/messages"; +import { Cache } from "@/cache/decoratorCache.js"; +import { getProp, omitUndefined } from "@/internals/helpers/object.js"; +import { Emitter } from "@/emitter/emitter.js"; +import { GetRunContext } from "@/context.js"; + +export class LangChainChatLLMOutput extends ChatLLMOutput { + constructor( + public messages: BaseMessage[], + public meta: Record = {}, + ) { + super(); + } + + static { + this.register(); + } + + merge(other: LangChainChatLLMOutput): void { + this.messages.push(...other.messages); + Object.assign(this.meta, omitUndefined(other.meta)); + } + + getTextContent(): string { + return this.messages.map((msg) => msg.text).join(""); + } + + toString(): string { + return this.getTextContent(); + } + + createSnapshot() { + return { + messages: shallowCopy(this.messages), + meta: shallowCopy(this.meta), + }; + } + + loadSnapshot(snapshot: ReturnType): void { + Object.assign(this, snapshot); + } +} + +export type LangChainChatLLMParameters = Record; +type MergedCallOptions = { lc: T } & GenerateOptions; + +export class LangChainChatLLM< + CallOptions extends BaseChatModelCallOptions, + OutputMessageType extends BaseMessageChunk, +> extends ChatLLM> { + public readonly emitter = Emitter.root.child({ + namespace: ["langchain", "chat_llm"], + creator: this, + }); + public readonly parameters: any; + + constructor( + public readonly lcLLM: BaseChatModel, + protected modelMeta?: LLMMeta, + executionOptions?: ExecutionOptions, + ) { + super(lcLLM._modelType(), executionOptions); + this.parameters = lcLLM.invocationParams(); + } + + static { + this.register(); + } + + async meta() { + if (this.modelMeta) { + return this.modelMeta; + } + + return { + tokenLimit: Infinity, + }; + } + + async tokenize(input: BaseMessage[]): Promise { + return { + tokensCount: await this.lcLLM.getNumTokens(input), + }; + } + + @Cache() + protected get mappers() { + const roleMapper = new Map([ + ["system", Role.SYSTEM], + ["assistant", Role.ASSISTANT], + ["ai", Role.ASSISTANT], + ["generic", Role.ASSISTANT], + ["function", Role.ASSISTANT], + ["tool", Role.ASSISTANT], + ["human", Role.USER], + ["tool", Role.ASSISTANT], + ]); + + return { + toLCMessage(message: BaseMessage): LCBaseMessage { + return new LCMChatMessage({ + role: message.role, + content: message.text, + response_metadata: message.meta, + }); + }, + fromLCMessage(message: LCBaseMessage | LCMChatMessage): BaseMessage { + const role: string = getProp(message, ["role"], message._getType()); + const text: string = + typeof message.content === "string" + ? message.content + : message.content + .filter( + (msg: MessageContentComplex): msg is MessageContentText => msg.type === "text", + ) + .map((msg: MessageContentText) => msg.text) + .join("\n"); + + return BaseMessage.of({ + role: roleMapper.has(role) ? roleMapper.get(role)! : Role.ASSISTANT, + text, + }); + }, + }; + } + + protected async _generate( + input: BaseMessage[], + options: MergedCallOptions, + run: GetRunContext, + ): Promise { + const lcMessages = input.map((msg) => this.mappers.toLCMessage(msg)); + const response = await this.lcLLM.invoke(lcMessages, { + ...options?.lc, + signal: run.signal, + }); + + return new LangChainChatLLMOutput( + [this.mappers.fromLCMessage(response)], + response.response_metadata, + ); + } + + protected async *_stream( + input: BaseMessage[], + options: MergedCallOptions, + run: GetRunContext, + ): AsyncStream { + const lcMessages = input.map((msg) => this.mappers.toLCMessage(msg)); + const response = this.lcLLM._streamResponseChunks(lcMessages, { + ...options?.lc, + signal: run.signal, + }); + for await (const chunk of response) { + yield new LangChainChatLLMOutput( + [this.mappers.fromLCMessage(chunk.message)], + chunk.message.response_metadata, + ); + } + } + + createSnapshot() { + return { + ...super.createSnapshot(), + modelId: this.modelId, + modelMeta: this.modelMeta, + parameters: shallowCopy(this.parameters), + executionOptions: shallowCopy(this.executionOptions), + lcLLM: JSON.stringify(this.lcLLM.toJSON()), + }; + } + + async loadSnapshot({ lcLLM, ...state }: ReturnType) { + super.loadSnapshot(state); + Object.assign(this, state, { + lcLLM: await (async () => { + if (lcLLM.includes("@ibm-generative-ai/node-sdk")) { + const { GenAIChatModel } = await import("@ibm-generative-ai/node-sdk/langchain"); + return GenAIChatModel.fromJSON(lcLLM); + } + + return await load(lcLLM); + })(), + }); + } +} diff --git a/src/adapters/langchain/llms/index.ts b/src/adapters/langchain/llms/index.ts new file mode 100644 index 0000000..6831bc6 --- /dev/null +++ b/src/adapters/langchain/llms/index.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +export * from "./llm.js"; diff --git a/src/adapters/langchain/llms/llm.test.ts b/src/adapters/langchain/llms/llm.test.ts new file mode 100644 index 0000000..cc2309b --- /dev/null +++ b/src/adapters/langchain/llms/llm.test.ts @@ -0,0 +1,41 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { verifyDeserialization } from "@tests/e2e/utils.js"; +import { LangChainLLM } from "@/adapters/langchain/llms/llm.js"; +import { GenAIModel } from "@ibm-generative-ai/node-sdk/langchain"; +import { Client } from "@ibm-generative-ai/node-sdk"; + +describe("Langchain LLM", () => { + const getInstance = () => { + return new LangChainLLM( + new GenAIModel({ + model_id: "google/flan-ul2", + client: new Client(), + parameters: { + max_new_tokens: 100, + }, + }), + ); + }; + + it("Serializes", async () => { + const instance = getInstance(); + const serialized = instance.serialize(); + const deserialized = await LangChainLLM.fromSerialized(serialized); + verifyDeserialization(instance, deserialized); + }); +}); diff --git a/src/adapters/langchain/llms/llm.ts b/src/adapters/langchain/llms/llm.ts new file mode 100644 index 0000000..51cb527 --- /dev/null +++ b/src/adapters/langchain/llms/llm.ts @@ -0,0 +1,156 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { LLM, LLMInput } from "@/llms/index.js"; +import { BaseLLM as LCBaseLLM } from "@langchain/core/language_models/llms"; +import { + AsyncStream, + BaseLLMOutput, + BaseLLMTokenizeOutput, + ExecutionOptions, + GenerateCallbacks, + InternalGenerateOptions, + LLMMeta, + StreamGenerateOptions, +} from "@/llms/base.js"; +import { load } from "@langchain/core/load"; +import { assign } from "@/internals/helpers/object.js"; +import { shallowCopy } from "@/serializer/utils.js"; +import { Emitter } from "@/emitter/emitter.js"; +import { GetRunContext } from "@/context.js"; + +export class LangChainLLMOutput extends BaseLLMOutput { + constructor( + public text: string, + public readonly meta: Record, + ) { + super(); + } + + static { + this.register(); + } + + merge(other: LangChainLLMOutput): void { + this.text += other.text; + assign(this.meta, other.meta); + } + + getTextContent(): string { + return this.text; + } + + toString(): string { + return this.getTextContent(); + } + + createSnapshot() { + return { + text: this.text, + meta: shallowCopy(this.meta), + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} + +export class LangChainLLM extends LLM { + public readonly emitter = Emitter.root.child({ + namespace: ["langchain", "llm"], + creator: this, + }); + protected readonly parameters: any; + + constructor( + public readonly lcLLM: LCBaseLLM, + private modelMeta?: LLMMeta, + executionOptions?: ExecutionOptions, + ) { + super(lcLLM._modelType(), executionOptions); + this.parameters = lcLLM.invocationParams(); + } + + static { + this.register(); + } + + async meta() { + if (this.modelMeta) { + return this.modelMeta; + } + + return { + tokenLimit: Infinity, + }; + } + + async tokenize(input: LLMInput): Promise { + return { + tokensCount: await this.lcLLM.getNumTokens(input), + }; + } + + protected async _generate( + input: LLMInput, + options: InternalGenerateOptions, + run: GetRunContext, + ): Promise { + const { generations } = await this.lcLLM.generate([input], { + signal: run.signal, + }); + return new LangChainLLMOutput(generations[0][0].text, generations[0][0].generationInfo || {}); + } + + protected async *_stream( + input: string, + options: StreamGenerateOptions, + run: GetRunContext, + ): AsyncStream { + const response = this.lcLLM._streamResponseChunks(input, { + signal: run.signal, + }); + for await (const chunk of response) { + yield new LangChainLLMOutput(chunk.text, chunk.generationInfo || {}); + } + } + + createSnapshot() { + return { + ...super.createSnapshot(), + modelId: this.modelId, + modelMeta: this.modelMeta, + parameters: shallowCopy(this.parameters), + executionOptions: shallowCopy(this.executionOptions), + lcLLM: JSON.stringify(this.lcLLM.toJSON()), + }; + } + + async loadSnapshot({ lcLLM, ...state }: ReturnType) { + super.loadSnapshot(state); + Object.assign(this, state, { + lcLLM: await (async () => { + if (lcLLM.includes("@ibm-generative-ai/node-sdk")) { + const { GenAIModel } = await import("@ibm-generative-ai/node-sdk/langchain"); + return GenAIModel.fromJSON(lcLLM); + } + + return await load(lcLLM); + })(), + }); + } +} diff --git a/src/adapters/ollama/chat.ts b/src/adapters/ollama/chat.ts new file mode 100644 index 0000000..dda554d --- /dev/null +++ b/src/adapters/ollama/chat.ts @@ -0,0 +1,215 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { + AsyncStream, + BaseLLMTokenizeOutput, + ExecutionOptions, + GenerateCallbacks, + GenerateOptions, + LLMOutputError, + StreamGenerateOptions, +} from "@/llms/base.js"; +import { shallowCopy } from "@/serializer/utils.js"; +import { ChatLLM, ChatLLMOutput } from "@/llms/chat.js"; +import { BaseMessage } from "@/llms/primitives/message.js"; +import { Emitter } from "@/emitter/emitter.js"; +import { ChatResponse, Ollama as Client, Options as Parameters } from "ollama"; +import { signalRace } from "@/internals/helpers/promise.js"; +import { GetRunContext } from "@/context.js"; +import { Cache } from "@/cache/decoratorCache.js"; +import { customMerge } from "@/internals/helpers/object.js"; +import { safeSum } from "@/internals/helpers/number.js"; +import { extractModelMeta, registerClient } from "@/adapters/ollama/shared.js"; + +export class OllamaChatLLMOutput extends ChatLLMOutput { + public readonly results: ChatResponse[]; + + constructor(response: ChatResponse) { + super(); + this.results = [response]; + } + + static { + this.register(); + } + + get messages() { + return this.results.flatMap((response) => + BaseMessage.of({ + role: response.message.role, + text: response.message.content, + }), + ); + } + + getTextContent(): string { + return this.finalResult.message.content; + } + + @Cache() + get finalResult(): Readonly { + if (this.results.length === 0) { + throw new LLMOutputError("No chunks to get final result from!"); + } + + return customMerge(this.results, { + message: (value, oldValue) => ({ + role: value.role ?? oldValue.role, + content: `${oldValue?.content ?? ""}${value?.content ?? ""}`, + images: [...(oldValue?.images ?? []), ...(value?.images ?? [])] as string[], + tool_calls: [...(oldValue?.tool_calls ?? []), ...(value?.tool_calls ?? [])], + }), + total_duration: (value, oldValue) => value ?? oldValue, + load_duration: (value, oldValue) => value ?? oldValue, + model: (value, oldValue) => value ?? oldValue, + done: (value, oldValue) => value ?? oldValue, + done_reason: (value, oldValue) => value ?? oldValue, + created_at: (value, oldValue) => value ?? oldValue, + eval_duration: (value, oldValue) => value ?? oldValue, + prompt_eval_duration: (value, oldValue) => value ?? oldValue, + prompt_eval_count: safeSum, + eval_count: safeSum, + }); + } + + merge(other: OllamaChatLLMOutput): void { + Cache.getInstance(this, "finalResult").clear(); + this.results.push(...other.results); + } + + toString(): string { + return this.getTextContent(); + } + + createSnapshot() { + return { + results: shallowCopy(this.results), + }; + } + + loadSnapshot(snapshot: ReturnType): void { + Object.assign(this, snapshot); + } +} + +interface Input { + modelId: string; + client?: Client; + parameters?: Partial; + executionOptions?: ExecutionOptions; +} + +export class OllamaChatLLM extends ChatLLM { + public readonly emitter = Emitter.root.child({ + namespace: ["ollama", "chat_llm"], + creator: this, + }); + + public readonly client: Client; + public readonly parameters: Partial; + + constructor( + { client, modelId, parameters, executionOptions = {} }: Input = { + modelId: "llama3.1", + parameters: { + temperature: 0, + }, + }, + ) { + super(modelId, executionOptions); + this.client = client ?? new Client({ fetch }); + this.parameters = parameters ?? {}; + } + + static { + this.register(); + registerClient(); + } + + async meta() { + const model = await this.client.show({ + model: this.modelId, + }); + + return extractModelMeta(model); + } + + async tokenize(input: BaseMessage[]): Promise { + const contentLength = input.reduce((acc, msg) => acc + msg.text.length, 0); + + return { + tokensCount: Math.ceil(contentLength / 4), + }; + } + + protected async _generate( + input: BaseMessage[], + options: GenerateOptions, + run: GetRunContext, + ): Promise { + const response = await signalRace( + () => + this.client.chat({ + model: this.modelId, + stream: false, + messages: input.map((msg) => ({ + role: msg.role, + content: msg.text, + })), + options: this.parameters, + format: options.guided?.json ? "json" : undefined, + }), + run.signal, + () => this.client.abort(), + ); + + return new OllamaChatLLMOutput(response); + } + + protected async *_stream( + input: BaseMessage[], + options: StreamGenerateOptions, + run: GetRunContext, + ): AsyncStream { + for await (const chunk of await this.client.chat({ + model: this.modelId, + stream: true, + messages: input.map((msg) => ({ + role: msg.role, + content: msg.text, + })), + options: this.parameters, + format: options.guided?.json ? "json" : undefined, + })) { + if (run.signal.aborted) { + break; + } + yield new OllamaChatLLMOutput(chunk); + } + run.signal.throwIfAborted(); + } + + createSnapshot() { + return { + ...super.createSnapshot(), + modelId: this.modelId, + parameters: shallowCopy(this.parameters), + executionOptions: shallowCopy(this.executionOptions), + client: this.client, + }; + } +} diff --git a/src/adapters/ollama/llm.test.ts b/src/adapters/ollama/llm.test.ts new file mode 100644 index 0000000..dbfe184 --- /dev/null +++ b/src/adapters/ollama/llm.test.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { verifyDeserialization } from "@tests/e2e/utils.js"; +import { OllamaLLM } from "@/adapters/ollama/llm.js"; +import { OllamaChatLLM } from "@/adapters/ollama/chat.js"; + +describe("Ollama LLM", () => { + const getInstance = () => { + return new OllamaLLM({ + modelId: "llama3.1", + }); + }; + + it("Serializes", async () => { + const instance = getInstance(); + const serialized = instance.serialize(); + const deserialized = OllamaLLM.fromSerialized(serialized); + verifyDeserialization(instance, deserialized); + }); +}); + +describe("Ollama ChatLLM", () => { + const getInstance = () => { + return new OllamaChatLLM({ + modelId: "llama3.1", + }); + }; + + it("Serializes", async () => { + const instance = getInstance(); + const serialized = instance.serialize(); + const deserialized = OllamaChatLLM.fromSerialized(serialized); + verifyDeserialization(instance, deserialized); + }); +}); diff --git a/src/adapters/ollama/llm.ts b/src/adapters/ollama/llm.ts new file mode 100644 index 0000000..6f5d24b --- /dev/null +++ b/src/adapters/ollama/llm.ts @@ -0,0 +1,190 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { LLM, LLMInput } from "@/llms/index.js"; +import { Emitter } from "@/emitter/emitter.js"; +import { + AsyncStream, + BaseLLMOutput, + BaseLLMTokenizeOutput, + ExecutionOptions, + GenerateCallbacks, + GenerateOptions, + LLMMeta, + LLMOutputError, + StreamGenerateOptions, +} from "@/llms/base.js"; +import { GenerateResponse, Ollama as Client, Options as Parameters } from "ollama"; +import { GetRunContext } from "@/context.js"; +import { Cache } from "@/cache/decoratorCache.js"; +import { safeSum } from "@/internals/helpers/number.js"; +import { shallowCopy } from "@/serializer/utils.js"; +import { signalRace } from "@/internals/helpers/promise.js"; +import { customMerge } from "@/internals/helpers/object.js"; +import { extractModelMeta, registerClient } from "@/adapters/ollama/shared.js"; + +interface Input { + modelId: string; + client?: Client; + parameters?: Partial; + executionOptions?: ExecutionOptions; +} + +export class OllamaLLMOutput extends BaseLLMOutput { + public readonly results: GenerateResponse[]; + + constructor(result: GenerateResponse) { + super(); + this.results = [result]; + } + + static { + this.register(); + } + + getTextContent(): string { + return this.finalResult.response; + } + + @Cache() + get finalResult(): Readonly { + if (this.results.length === 0) { + throw new LLMOutputError("No chunks to get final result from!"); + } + + return customMerge(this.results, { + response: (value = "", oldValue = "") => oldValue + value, + total_duration: (value, oldValue) => value ?? oldValue, + load_duration: (value, oldValue) => value ?? oldValue, + model: (value, oldValue) => value ?? oldValue, + done: (value, oldValue) => value ?? oldValue, + done_reason: (value, oldValue) => value ?? oldValue, + created_at: (value, oldValue) => value ?? oldValue, + eval_duration: (value, oldValue) => value ?? oldValue, + prompt_eval_duration: (value, oldValue) => value ?? oldValue, + prompt_eval_count: safeSum, + eval_count: safeSum, + context: (value, oldValue) => [...(value || []), ...(oldValue || [])], + }); + } + + merge(other: OllamaLLMOutput): void { + Cache.getInstance(this, "finalResult").clear(); + this.results.push(...other.results); + } + + createSnapshot() { + return { + results: shallowCopy(this.results), + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } + + toString(): string { + return this.getTextContent(); + } +} + +export class OllamaLLM extends LLM { + public readonly emitter = Emitter.root.child({ + namespace: ["ollama", "llm"], + creator: this, + }); + + public readonly client: Client; + public readonly parameters: Partial; + + static { + this.register(); + registerClient(); + } + + constructor({ client, modelId, parameters, executionOptions = {} }: Input) { + super(modelId, executionOptions); + this.client = client ?? new Client(); + this.parameters = parameters ?? {}; + } + + protected async _generate( + input: LLMInput, + options: GenerateOptions, + run: GetRunContext, + ): Promise { + const response = await signalRace( + () => + this.client.generate({ + model: this.modelId, + stream: false, + raw: true, + prompt: input, + options: this.parameters, + format: options.guided?.json ? "json" : undefined, + }), + run.signal, + () => this.client.abort(), + ); + + return new OllamaLLMOutput(response); + } + + protected async *_stream( + input: LLMInput, + options: StreamGenerateOptions, + run: GetRunContext, + ): AsyncStream { + for await (const chunk of await this.client.generate({ + model: this.modelId, + stream: true, + raw: true, + prompt: input, + options: this.parameters, + format: options.guided?.json ? "json" : undefined, + })) { + if (run.signal.aborted) { + break; + } + yield new OllamaLLMOutput(chunk); + } + run.signal.throwIfAborted(); + } + + async meta(): Promise { + const model = await this.client.show({ + model: this.modelId, + }); + + return extractModelMeta(model); + } + + async tokenize(input: LLMInput): Promise { + return { + tokensCount: Math.ceil(input.length / 4), + }; + } + + createSnapshot() { + return { + ...super.createSnapshot(), + modelId: this.modelId, + executionOptions: shallowCopy(this.executionOptions), + parameters: shallowCopy(this.parameters), + client: this.client, + }; + } +} diff --git a/src/adapters/ollama/shared.ts b/src/adapters/ollama/shared.ts new file mode 100644 index 0000000..c4d3113 --- /dev/null +++ b/src/adapters/ollama/shared.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { Serializer } from "@/serializer/serializer.js"; +import { Config, Ollama as Client, ShowResponse } from "ollama"; +import { getPropStrict } from "@/internals/helpers/object.js"; +import { LLMMeta } from "@/llms/base.js"; + +export function registerClient() { + Serializer.register(Client, { + toPlain: (value) => ({ + config: getPropStrict(value, "config") as Config, + fetch: getPropStrict(value, "fetch"), + }), + fromPlain: (value) => + new Client({ + fetch: value.fetch ?? value.config.fetch, + host: value.config.host, + proxy: value.config.proxy, + }), + }); +} + +export function extractModelMeta(response: ShowResponse): LLMMeta { + const tokenLimit = Object.entries(response.model_info) + .find(([k]) => k.includes("context_length") || k.includes("max_sequence_length")) + ?.at(1); + + return { + tokenLimit: tokenLimit || Infinity, + }; +} diff --git a/src/adapters/openai/chat.test.ts b/src/adapters/openai/chat.test.ts new file mode 100644 index 0000000..7d8e638 --- /dev/null +++ b/src/adapters/openai/chat.test.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { verifyDeserialization } from "@tests/e2e/utils.js"; +import { OpenAIChatLLM } from "@/adapters/openai/chat.js"; + +describe("OpenAI ChatLLM", () => { + const getInstance = () => { + return new OpenAIChatLLM({ + modelId: "gpt-4o", + }); + }; + + it("Serializes", async () => { + const instance = getInstance(); + const serialized = instance.serialize(); + const deserialized = OpenAIChatLLM.fromSerialized(serialized); + verifyDeserialization(instance, deserialized); + }); +}); diff --git a/src/adapters/openai/chat.ts b/src/adapters/openai/chat.ts new file mode 100644 index 0000000..3f4aa76 --- /dev/null +++ b/src/adapters/openai/chat.ts @@ -0,0 +1,241 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { + AsyncStream, + BaseLLMTokenizeOutput, + ExecutionOptions, + GenerateCallbacks, + GenerateOptions, + LLMMeta, + StreamGenerateOptions, +} from "@/llms/base.js"; +import { shallowCopy } from "@/serializer/utils.js"; +import { ChatLLM, ChatLLMOutput } from "@/llms/chat.js"; +import { BaseMessage, RoleType } from "@/llms/primitives/message.js"; +import { Emitter } from "@/emitter/emitter.js"; +import { ClientOptions, OpenAI as Client } from "openai"; +import { GetRunContext } from "@/context.js"; +import { promptTokensEstimate } from "openai-chat-tokens"; +import { isString } from "remeda"; +import { Serializer } from "@/serializer/serializer.js"; +import { getPropStrict } from "@/internals/helpers/object.js"; + +type Parameters = Omit; +type Response = Omit; + +export class OpenAIChatLLMOutput extends ChatLLMOutput { + public readonly responses: Response[]; + + constructor(response: Response) { + super(); + this.responses = [response]; + } + + static { + this.register(); + } + + get messages() { + return this.responses + .flatMap((response) => response.choices) + .flatMap((choice) => + BaseMessage.of({ + role: choice.delta.role as RoleType, + text: choice.delta.content!, + }), + ); + } + + getTextContent(): string { + return this.messages.map((msg) => msg.text).join("\n"); + } + + merge(other: OpenAIChatLLMOutput): void { + this.responses.push(...other.responses); + } + + toString(): string { + return this.getTextContent(); + } + + createSnapshot() { + return { + responses: shallowCopy(this.responses), + }; + } + + loadSnapshot(snapshot: ReturnType): void { + Object.assign(this, snapshot); + } +} + +interface Input { + modelId?: Client.ChatModel; + client?: Client; + parameters?: Partial; + executionOptions?: ExecutionOptions; +} + +export class OpenAIChatLLM extends ChatLLM { + public readonly emitter = Emitter.root.child({ + namespace: ["openai", "chat_llm"], + creator: this, + }); + + public readonly client: Client; + public readonly parameters: Partial; + + constructor({ client, modelId = "gpt-4o", parameters, executionOptions = {} }: Input = {}) { + super(modelId, executionOptions); + this.client = client ?? new Client(); + this.parameters = parameters ?? {}; + } + + static { + this.register(); + Serializer.register(Client, { + toPlain: (value) => ({ + options: getPropStrict(value, "_options") as ClientOptions, + }), + fromPlain: (value) => new Client(value.options), + }); + } + + async meta(): Promise { + if ( + this.modelId.includes("gpt-4o") || + this.modelId.includes("gpt-4-turbo") || + this.modelId.includes("gpt-4-0125-preview") || + this.modelId.includes("gpt-4-1106-preview") + ) { + return { tokenLimit: 128 * 1024 }; + } else if (this.modelId.includes("gpt-4")) { + return { tokenLimit: 8 * 1024 }; + } else if (this.modelId.includes("gpt-3.5-turbo")) { + return { tokenLimit: 16 * 1024 }; + } else if (this.modelId.includes("gpt-3.5")) { + return { tokenLimit: 8 * 1024 }; + } + + return { + tokenLimit: Infinity, + }; + } + + async tokenize(input: BaseMessage[]): Promise { + const tokensCount = promptTokensEstimate({ + messages: input.map( + (msg) => + ({ + role: msg.role, + content: msg.text, + }) as Client.Chat.ChatCompletionMessageParam, + ), + }); + + return { + tokensCount, + }; + } + + protected _prepareRequest( + input: BaseMessage[], + options: GenerateOptions, + ): Client.Chat.ChatCompletionCreateParams { + return { + ...this.parameters, + model: this.modelId, + stream: false, + messages: input.map( + (message) => + ({ + role: message.role, + content: message.text, + response_metadata: message.meta, + }) as Client.Chat.ChatCompletionMessageParam, + ), + ...(options?.guided?.json && { + response_format: { + type: "json_schema", + json_schema: isString(options.guided.json) + ? JSON.parse(options.guided.json) + : options.guided.json, + }, + }), + }; + } + + protected async _generate( + input: BaseMessage[], + options: GenerateOptions, + run: GetRunContext, + ): Promise { + const response = await this.client.chat.completions.create( + { + ...this._prepareRequest(input, options), + stream: false, + }, + { + signal: run.signal, + }, + ); + + return new OpenAIChatLLMOutput({ + id: response.id, + model: response.model, + created: response.created, + usage: response.usage, + service_tier: response.service_tier, + system_fingerprint: response.system_fingerprint, + choices: response.choices.map( + (choice) => + ({ + delta: choice.message, + index: 1, + logprobs: choice.logprobs, + finish_reason: choice.finish_reason, + }) as Client.Chat.ChatCompletionChunk.Choice, + ), + }); + } + + protected async *_stream( + input: BaseMessage[], + options: StreamGenerateOptions, + run: GetRunContext, + ): AsyncStream { + for await (const chunk of await this.client.chat.completions.create( + { + ...this._prepareRequest(input, options), + stream: true, + }, + { + signal: run.signal, + }, + )) { + yield new OpenAIChatLLMOutput(chunk); + } + } + + createSnapshot() { + return { + ...super.createSnapshot(), + parameters: shallowCopy(this.parameters), + client: this.client, + }; + } +} diff --git a/src/adapters/watsonx/chat.ts b/src/adapters/watsonx/chat.ts new file mode 100644 index 0000000..1fe24e7 --- /dev/null +++ b/src/adapters/watsonx/chat.ts @@ -0,0 +1,161 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { AsyncStream, GenerateCallbacks } from "@/llms/base.js"; +import { + WatsonXLLM, + WatsonXLLMGenerateOptions, + WatsonXLLMParameters, + WatsonXLLMOutput, +} from "@/adapters/watsonx/llm.js"; +import { ChatLLM, ChatLLMOutput } from "@/llms/chat.js"; +import { BaseMessage, Role } from "@/llms/primitives/message.js"; +import { PromptTemplate } from "@/template.js"; +import { Cache } from "@/cache/decoratorCache.js"; +import { transformAsyncIterable } from "@/internals/helpers/stream.js"; +import { shallowCopy } from "@/serializer/utils.js"; +import { Emitter } from "@/emitter/emitter.js"; +import { GetRunContext } from "@/context.js"; + +export class WatsonXChatLLMOutput extends ChatLLMOutput { + public readonly raw: WatsonXLLMOutput; + + constructor(rawOutput: WatsonXLLMOutput) { + super(); + this.raw = rawOutput; + } + + @Cache() + get messages(): BaseMessage[] { + const text = this.raw.getTextContent(); + return [ + BaseMessage.of({ + role: Role.ASSISTANT, + text, + meta: this.raw.meta, + }), + ]; + } + + merge(other: WatsonXChatLLMOutput): void { + Cache.getInstance(this, "messages").clear(); + this.raw.merge(other.raw); + } + + getTextContent(): string { + const [message] = this.messages; + return message.text; + } + + toString(): string { + return this.getTextContent(); + } + + createSnapshot() { + return { + raw: shallowCopy(this.raw), + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} + +export interface WatsonXChatLLMInputConfig { + messagesToPrompt: PromptTemplate<"messages"> | ((messages: BaseMessage[]) => string); +} + +export interface WatsonXChatLLMInput { + llm: WatsonXLLM; + config: WatsonXChatLLMInputConfig; +} + +export class WatsonXChatLLM extends ChatLLM { + public readonly emitter = Emitter.root.child({ + namespace: ["watsonx", "chat_llm"], + creator: this, + }); + + public readonly llm: WatsonXLLM; + protected readonly config: WatsonXChatLLMInputConfig; + public readonly parameters: WatsonXLLMParameters; + + constructor({ llm, config }: WatsonXChatLLMInput) { + super(llm.modelId, llm.executionOptions); + this.parameters = llm.parameters ?? {}; + this.llm = llm; + this.config = config; + } + + static { + this.register(); + } + + async meta() { + return this.llm.meta(); + } + + createSnapshot() { + return { + ...super.createSnapshot(), + modelId: this.modelId, + parameters: this.parameters, + executionOptions: this.executionOptions, + llm: this.llm, + config: shallowCopy(this.config), + }; + } + + loadSnapshot(data: ReturnType): void { + super.loadSnapshot(data); + } + + async tokenize(messages: BaseMessage[]) { + const prompt = this.messagesToPrompt(messages); + return this.llm.tokenize(prompt); + } + + protected async _generate( + messages: BaseMessage[], + options: WatsonXLLMGenerateOptions, + run: GetRunContext, + ): Promise { + const prompt = this.messagesToPrompt(messages); + // @ts-expect-error protected property + const rawResponse = await this.llm._generate(prompt, options, run); + return new WatsonXChatLLMOutput(rawResponse); + } + + protected async *_stream( + messages: BaseMessage[], + options: WatsonXLLMGenerateOptions, + run: GetRunContext, + ): AsyncStream { + const prompt = this.messagesToPrompt(messages); + // @ts-expect-error protected property + const response = this.llm._stream(prompt, options, run); + return yield* transformAsyncIterable(response, (output) => new WatsonXChatLLMOutput(output)); + } + + messagesToPrompt(messages: BaseMessage[]) { + const convertor = this.config.messagesToPrompt; + if (convertor instanceof PromptTemplate) { + return convertor.render({ messages }); + } + return convertor(messages); + } +} diff --git a/src/adapters/watsonx/llm.ts b/src/adapters/watsonx/llm.ts new file mode 100644 index 0000000..e6286b1 --- /dev/null +++ b/src/adapters/watsonx/llm.ts @@ -0,0 +1,413 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { LLM, LLMInput } from "@/llms/index.js"; +import { + AsyncStream, + BaseLLMOutput, + BaseLLMTokenizeOutput, + ExecutionOptions, + GenerateCallbacks, + GenerateOptions, + LLMError, + LLMFatalError, + LLMMeta, + LLMOutputError, +} from "@/llms/base.js"; +import { HttpError } from "@ibm-generative-ai/node-sdk"; +import * as R from "remeda"; +import { FrameworkError } from "@/errors.js"; +import { Cache, CacheFn } from "@/cache/decoratorCache.js"; +import { shallowCopy } from "@/serializer/utils.js"; +import { safeSum } from "@/internals/helpers/number.js"; +import { omitUndefined } from "@/internals/helpers/object.js"; +import { createURLParams, RestfulClient, RestfulClientError } from "@/internals/fetcher.js"; +import { identity } from "remeda"; +import { Emitter } from "@/emitter/emitter.js"; +import { GetRunContext } from "@/context.js"; + +export interface WatsonXLLMOutputMeta { + model_id: string; + created_at: string; +} + +export interface WatsonXLLMOutputResult { + generated_text: string; + generated_token_count: number; + input_token_count: number; + stop_reason?: string; +} + +export interface WatsonXLLMOutputConstructor { + meta: WatsonXLLMOutputMeta; + results: WatsonXLLMOutputResult[]; + system: Record[]>; +} + +export class WatsonXLLMOutput extends BaseLLMOutput { + public readonly meta: WatsonXLLMOutputMeta; + public readonly results: WatsonXLLMOutputResult[]; + + constructor(content: WatsonXLLMOutputConstructor) { + super(); + this.meta = content.meta; + this.results = content.results; + } + + static { + this.register(); + } + + getTextContent(): string { + return this.finalResult.generated_text; + } + + @Cache() + get finalResult(): Readonly { + if (this.results.length === 0) { + throw new LLMOutputError("No chunks to get final result from!"); + } + + const processors: { + [K in keyof WatsonXLLMOutputResult]: ( + value: WatsonXLLMOutputResult[K], + oldValue: WatsonXLLMOutputResult[K], + ) => WatsonXLLMOutputResult[K]; + } = { + generated_text: (value = "", oldValue = "") => oldValue + value, + input_token_count: safeSum, + generated_token_count: safeSum, + stop_reason: (value, oldValue) => value ?? oldValue, + }; + + const finalResult = {} as WatsonXLLMOutputResult; + for (const next of this.results) { + for (const [key, value] of R.entries(next)) { + const oldValue = finalResult[key]; + // @ts-expect-error weak typing due to generated types + finalResult[key] = (processors[key] ?? takeFirst)(value, oldValue); + } + } + + return finalResult; + } + + merge(other: WatsonXLLMOutput): void { + Cache.getInstance(this, "finalResult").clear(); + + this.results.push(...other.results); + Object.assign(this.meta, omitUndefined(other.meta)); + } + + createSnapshot() { + return { + results: shallowCopy(this.results), + meta: shallowCopy(this.meta), + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } + + toString(): string { + return this.getTextContent(); + } +} + +export type WatsonXLLMParameters = Record; +export type WatsonXLLMModerations = Record; + +export interface WatsonXLLMGenerateOptions extends GenerateOptions { + parameters?: WatsonXLLMParameters; + moderations?: WatsonXLLMModerations; +} + +export interface WatsonXLLMInput { + modelId: string; + projectId?: string; + spaceId?: string; + deploymentId?: string; + version?: string; + apiKey?: string; + accessToken?: string; + baseUrl?: string; + authBaseUrl?: string; + region?: string; + parameters?: WatsonXLLMParameters; + moderations?: WatsonXLLMModerations; + executionOptions?: ExecutionOptions; + transform?: WatsonXLLMTransformFn; +} + +type WatsonXLLMTransformFn = (body: Record) => Record; + +function createApiClient({ + deploymentId, + apiKey, + baseUrl, + authBaseUrl = "https://iam.cloud.ibm.com", + region = "us-south", + accessToken, + version = "2023-05-02", + projectId, + spaceId, +}: WatsonXLLMInput) { + const paths = (() => { + const pathPrefix = deploymentId ? `/ml/v1/deployments/${deploymentId}` : "/ml/v1"; + const queryParams = createURLParams({ + version, + }); + + return { + generate: `${pathPrefix}/text/generation?${queryParams}`, + generate_stream: `${pathPrefix}/text/generation_stream?${queryParams}`, + tokenization: `${pathPrefix}/text/tokenization?${queryParams}`, + models: `/ml/v1/foundation_model_specs?${queryParams}`, + deployment: deploymentId + ? `/ml/v4/deployments/${deploymentId}?${createURLParams({ version, project_id: projectId, space_id: projectId ? undefined : spaceId })}` + : "not_defined_endpoint", + }; + })(); + + const getHeaders = CacheFn.create(async () => { + const getAccessToken = async () => { + if (accessToken) { + return { ttl: Infinity, token: accessToken }; + } + + const response = await fetch(new URL("/identity/token", authBaseUrl), { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: createURLParams({ + grant_type: "urn:ibm:params:oauth:grant-type:apikey", + apikey: apiKey, + }), + }); + + if (!response.ok) { + throw new RestfulClientError("Failed to retrieve an API token.", [], { + context: response, + }); + } + + const data = await response.json(); + if (!data?.access_token) { + throw new RestfulClientError("Access Token was not found in the response."); + } + return { ttl: (data.expires_in - 60) * 1000, token: data.access_token as string }; + }; + + const response = await getAccessToken(); + getHeaders.updateTTL(response.ttl); + return new Headers({ + Authorization: `Bearer ${response.token}`, + Accept: "application/json", + "Content-Type": "application/json", + }); + }); + + return new RestfulClient({ + baseUrl: baseUrl || `https://${region}.ml.cloud.ibm.com`, + paths, + headers: getHeaders, + }); +} + +export class WatsonXLLM extends LLM { + public readonly emitter = Emitter.root.child({ + namespace: ["watsonx", "llm"], + creator: this, + }); + + protected client; + protected projectId; + protected deploymentId; + protected spaceId; + protected transform: WatsonXLLMTransformFn; + public readonly moderations; + public readonly parameters: WatsonXLLMParameters; + + constructor(input: WatsonXLLMInput) { + super(input.modelId, input.executionOptions); + this.projectId = input.projectId; + this.spaceId = input.spaceId; + this.deploymentId = input.deploymentId; + this.moderations = input.moderations; + this.transform = input.transform ?? identity(); + this.client = createApiClient(input); + this.parameters = input.parameters ?? {}; + } + + static { + this.register(); + } + + @Cache() + async meta(): Promise { + let modelId = this.modelId; + if (this.deploymentId) { + const { entity } = await this.client.fetch("deployment"); + modelId = entity.base_model_id ?? modelId; + } + + if (!modelId) { + throw new LLMFatalError(`Cannot retrieve metadata for model '${modelId ?? "undefined"}'`); + } + + const { + resources: [model], + } = await this.client.fetch("models", { + searchParams: createURLParams({ + filters: `modelid_${modelId}`, + limit: "1", + }), + }); + + return { + tokenLimit: model?.model_limits?.max_sequence_length ?? Infinity, + }; + } + + createSnapshot() { + return { + ...super.createSnapshot(), + modelId: this.modelId, + spaceId: this.spaceId, + deploymentId: this.deploymentId, + projectId: this.projectId, + parameters: shallowCopy(this.parameters), + moderations: shallowCopy(this.moderations), + executionOptions: shallowCopy(this.executionOptions), + transform: this.transform, + client: this.client, + }; + } + + loadSnapshot(snapshot: ReturnType): void { + super.loadSnapshot(snapshot); + } + + protected _transformError(error: Error): Error { + if (error instanceof FrameworkError) { + throw error; + } + if (error instanceof HttpError) { + throw new LLMError("LLM has occurred an error!", [error], { + isRetryable: [408, 425, 429, 500, 503].includes(error.status_code), + }); + } + return new LLMError("LLM has occurred an error!", [error]); + } + + async tokenize(input: LLMInput): Promise { + try { + const { result } = await this.client.fetch("tokenization", { + method: "POST", + body: JSON.stringify({ + input, + model_id: this.modelId, + parameters: { + return_tokens: true, + }, + }), + }); + + return { + tokensCount: result.token_count, + tokens: result.tokens, + }; + } catch (e) { + throw this._transformError(e); + } + } + + protected async _generate( + input: LLMInput, + options: WatsonXLLMGenerateOptions, + run: GetRunContext, + ): Promise { + try { + const response = await this.client.fetch("generate", { + method: "POST", + body: JSON.stringify( + this.transform({ + input, + ...(!this.deploymentId && { + model_id: this.modelId, + project_id: this.projectId, + space_id: this.projectId ? undefined : this.spaceId, + }), + parameters: options?.parameters ?? this.parameters, + moderations: options?.moderations ?? this.moderations, + }), + ), + signal: run.signal, + }); + return this._rawResponseToOutput(response); + } catch (e) { + throw this._transformError(e); + } + } + + protected async *_stream( + input: LLMInput, + options: WatsonXLLMGenerateOptions, + run: GetRunContext, + ): AsyncStream { + try { + const response = this.client.stream("generate_stream", { + method: "POST", + body: JSON.stringify( + this.transform({ + input, + ...(!this.deploymentId && { + model_id: this.modelId, + project_id: this.projectId, + space_id: this.projectId ? undefined : this.spaceId, + }), + parameters: options?.parameters ?? this.parameters, + moderations: options?.moderations ?? this.moderations, + }), + ), + signal: run.signal, + }); + + for await (const msg of response) { + const content = JSON.parse(msg.data); + yield this._rawResponseToOutput(content); + } + } catch (e) { + throw this._transformError(e); + } + } + + protected _rawResponseToOutput(raw: any) { + return new WatsonXLLMOutput({ + results: raw.results ?? [], + meta: R.pickBy( + { + model_id: raw.model_id, + created_at: raw.created_at!, + }, + R.isDefined, + ), + system: raw.system ?? [], + }); + } +} diff --git a/src/agents/base.ts b/src/agents/base.ts new file mode 100644 index 0000000..10fead3 --- /dev/null +++ b/src/agents/base.ts @@ -0,0 +1,78 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { FrameworkError } from "@/errors.js"; +import { AgentMeta } from "@/agents/types.js"; +import { Serializable } from "@/internals/serializable.js"; +import { GetRunContext, GetRunInstance, Run, RunContext } from "@/context.js"; +import { Emitter } from "@/emitter/emitter.js"; + +export class AgentError extends FrameworkError {} + +export interface BaseAgentRunOptions { + signal?: AbortSignal; +} + +export abstract class BaseAgent< + TInput, + TOutput, + TOptions extends BaseAgentRunOptions = BaseAgentRunOptions, +> extends Serializable { + private isRunning = false; + + public abstract readonly emitter: Emitter; + + public run( + ...[input, options]: Partial extends TOptions + ? [input: TInput, options?: TOptions] + : [input: TInput, options: TOptions] + ): Run> { + if (this.isRunning) { + throw new AgentError("Agent is already running!"); + } + + return RunContext.enter( + this, + async (context) => { + try { + // @ts-expect-error + return await this._run(input, options, context); + } catch (e) { + if (e instanceof AgentError) { + throw e; + } else { + throw new AgentError(`Error has occurred!`, [e]); + } + } finally { + this.isRunning = false; + } + }, + options?.signal, + ); + } + + protected abstract _run( + input: TInput, + options: TOptions, + run: GetRunContext, + ): Promise; + + destroy() { + this.emitter.destroy(); + } + + public abstract get meta(): AgentMeta; +} diff --git a/src/agents/bee/__snapshots__/parser.test.ts.snap b/src/agents/bee/__snapshots__/parser.test.ts.snap new file mode 100644 index 0000000..37047d2 --- /dev/null +++ b/src/agents/bee/__snapshots__/parser.test.ts.snap @@ -0,0 +1,26 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Bee Parser > Chunking > Text 1`] = ` +{ + "final_answer": "I need to search Colorado, find + + the + area that the eastern sector of the Colorado extends into, then find the elevation range of the area. + + + +Extra Content.", +} +`; + +exports[`Bee Parser > Chunking > Text 2`] = ` +{ + "final_answer": "ABC", +} +`; + +exports[`ReAct Parser > Chunking > Text 2`] = ` +{ + "final_answer": "ABC", +} +`; diff --git a/src/agents/bee/agent.ts b/src/agents/bee/agent.ts new file mode 100644 index 0000000..ad7369a --- /dev/null +++ b/src/agents/bee/agent.ts @@ -0,0 +1,176 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseAgent } from "@/agents/base.js"; +import { AnyTool } from "@/tools/base.js"; +import { BaseMemory } from "@/memory/base.js"; +import { ChatLLM, ChatLLMOutput } from "@/llms/chat.js"; +import { BaseMessage, Role } from "@/llms/primitives/message.js"; +import { AgentMeta } from "@/agents/types.js"; +import { BeeAgentTemplate, BeeAssistantPrompt } from "@/agents/bee/prompts.js"; +import * as R from "remeda"; +import { Emitter } from "@/emitter/emitter.js"; +import { + BeeAgentRunIteration, + BeeCallbacks, + BeeRunInput, + BeeRunOptions, + BeeRunOutput, +} from "@/agents/bee/types.js"; +import { GetRunContext } from "@/context.js"; +import { BeeAgentRunner } from "@/agents/bee/runner.js"; +import { BeeAgentError } from "@/agents/bee/errors.js"; +import { BeeIterationToolResult } from "@/agents/bee/parser.js"; + +export interface BeeInput { + llm: ChatLLM; + tools: AnyTool[]; + memory: BaseMemory; + promptTemplate?: BeeAgentTemplate; + meta?: AgentMeta; +} + +export class BeeAgent extends BaseAgent { + public readonly emitter = Emitter.root.child({ + namespace: ["agent", "bee"], + creator: this, + }); + + constructor(protected readonly input: BeeInput) { + super(); + + const duplicate = input.tools.find((a, i, arr) => + arr.find((b, j) => i !== j && a.name.toUpperCase() === b.name.toUpperCase()), + ); + if (duplicate) { + throw new BeeAgentError( + `Agent's tools must all have different names. Conflicting tool: ${duplicate.name}.`, + ); + } + } + + static { + this.register(); + } + + get meta(): AgentMeta { + if (this.input.meta) { + return this.input.meta; + } + + const tools = this.input.tools; + + return { + name: "Bee", + description: + "The Bee framework demonstrates its ability to auto-correct and adapt in real-time, improving the overall reliability and resilience of the system.", + ...(tools.length > 0 && { + extraDescription: [ + `Tools that I can use to accomplish given task.`, + ...tools.map((tool) => `Tool '${tool.name}': ${tool.description}.`), + ].join("\n"), + }), + }; + } + + protected async _run( + input: BeeRunInput, + options: BeeRunOptions = {}, + run: GetRunContext, + ): Promise { + const iterations: BeeAgentRunIteration[] = []; + const maxIterations = options?.execution?.maxIterations ?? Infinity; + + const runner = await BeeAgentRunner.create(run, this.input, options, input.prompt); + + let finalMessage: BaseMessage | undefined; + while (!finalMessage) { + if (iterations.length >= maxIterations) { + throw new BeeAgentError( + `Agent was not able to resolve the task in ${maxIterations} iterations.`, + [], + { isFatal: true }, + ); + } + + const iteration = await runner.llm(); + + if (iteration.state.tool_name || iteration.state.tool_caption || iteration.state.tool_input) { + const { output, success } = await runner.tool(iteration.state as BeeIterationToolResult); + + for (const key of ["partialUpdate", "update"] as const) { + await run.emitter.emit(key, { + data: { + tool_output: output, + }, + update: { key: "tool_output", value: output }, + meta: { success }, + }); + } + + await runner.memory.add( + BaseMessage.of({ + role: Role.ASSISTANT, + text: BeeAssistantPrompt.clone().render({ + toolName: [iteration.state.tool_name].filter(R.isTruthy), + toolCaption: [iteration.state.tool_caption].filter(R.isTruthy), + toolInput: [iteration.state.tool_input] + .filter(R.isTruthy) + .map((call) => JSON.stringify(call)), + thought: [iteration.state.thought].filter(R.isTruthy), + finalAnswer: [iteration.state.final_answer].filter(R.isTruthy), + toolOutput: [output], + }), + meta: { success }, + }), + ); + + iteration.state.tool_output = output; + } + if (iteration.state.final_answer) { + finalMessage = BaseMessage.of({ + role: Role.ASSISTANT, + text: iteration.state.final_answer, + }); + await run.emitter.emit("success", { + data: finalMessage, + }); + await runner.memory.add(finalMessage); + } + iterations.push(iteration); + } + + await this.input.memory.addMany([ + BaseMessage.of({ + role: Role.USER, + text: input.prompt, + }), + finalMessage, + ]); + return { result: finalMessage, iterations, memory: runner.memory }; + } + + createSnapshot() { + return { + input: this.input, + emitter: this.emitter, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} diff --git a/src/agents/bee/errors.ts b/src/agents/bee/errors.ts new file mode 100644 index 0000000..9dcadd3 --- /dev/null +++ b/src/agents/bee/errors.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { AgentError } from "@/agents/base.js"; + +export class BeeAgentError extends AgentError {} diff --git a/src/agents/bee/parser.test.ts b/src/agents/bee/parser.test.ts new file mode 100644 index 0000000..c5bc511 --- /dev/null +++ b/src/agents/bee/parser.test.ts @@ -0,0 +1,182 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BeeOutputParser, BeeOutputParserError } from "@/agents/bee/parser.js"; +import * as R from "remeda"; + +import { omitEmptyValues } from "@/internals/helpers/object.js"; +import { expect } from "vitest"; + +describe("Bee Parser", () => { + describe("Parsing", () => { + it("Basics", async () => { + const parser = new BeeOutputParser({}); + await parser.add("Final Answer: I need to find the current president of the Czech Republic."); + await parser.finalize(); + parser.validate(); + + const result = R.pipe(parser.parse(), R.pickBy(R.isTruthy)); + expect(result).toMatchInlineSnapshot(` + { + "final_answer": "I need to find the current president of the Czech Republic.", + } + `); + }); + + it("Ends up with the same result", async () => { + const partial = new Map(); + const final = new Map(); + + const parser = new BeeOutputParser(); + parser.emitter.on("update", async ({ update, type }) => { + if (type === "full") { + final.set(update.key, update.value); + } else { + partial.set(update.key, (partial.get(update.key) ?? "").concat(update.value)); + } + }); + await parser.add("Thought: I "); + await parser.add("will do it."); + await parser.finalize(); + parser.validate(); + expect(partial).toStrictEqual(final); + }); + + it("Parses chunked JSON", async () => { + const parser = new BeeOutputParser({}); + await parser.add("Tool Name:\n"); + await parser.add("Goo"); + await parser.add("gle"); + await parser.add("\n"); + await parser.add("Tool "); + await parser.add("Input:\n"); + await parser.add('{"query'); + await parser.add('": "Czech President"'); + await parser.add("}"); + await parser.finalize(); + parser.validate(); + + const result = R.pipe(parser.parse(), R.pickBy(R.isTruthy)); + expect(result).toMatchInlineSnapshot(` + { + "tool_input": { + "query": "Czech President", + }, + "tool_name": "Google", + } + `); + }); + + it("Handles newlines", async () => { + const parser = new BeeOutputParser({ + allowMultiLines: true, + preserveNewLines: false, + trimContent: true, + }); + await parser.add("Final Answer:\n\nI need to find\n\n the fastest car. \n "); + await parser.finalize(); + parser.validate(); + + const result = R.pipe(parser.parse(), R.pickBy(R.isTruthy)); + expect(result).toMatchInlineSnapshot(` + { + "final_answer": "I need to find the fastest car.", + } + `); + }); + + it("Ignores newlines before first keyword occurrence", async () => { + const parser = new BeeOutputParser(); + await parser.add(""); + await parser.add(" \n"); + await parser.add(" \n "); + await parser.add(""); + await parser.add("\n Final Answer: Hello"); + await parser.finalize(); + parser.validate(); + + const result = R.pipe(parser.parse(), R.pickBy(R.isTruthy)); + expect(result).toMatchInlineSnapshot(` + { + "final_answer": "Hello", + } + `); + }); + + it("Handles newlines with custom settings", async () => { + const parser = new BeeOutputParser({ + allowMultiLines: true, + preserveNewLines: true, + trimContent: true, + }); + await parser.add("Final Answer:\n\nI need to find\n\n the fastest car. \n "); + await parser.finalize(); + parser.validate(); + + const result = R.pipe(parser.parse(), R.pickBy(R.isTruthy)); + expect(result).toMatchInlineSnapshot(` + { + "final_answer": "I need to find + the fastest car.", + } + `); + }); + }); + + describe("Chunking", () => { + it.each([ + " F#inal #answer : #I need to# search #Colorado, find#\n#\n the\n #area th#at th#e easter#n secto#r of# the Colora#do ex#tends i#nto, then find th#e elev#ation# #range #of the area.\n\n\n\nExtra Content.", + "\nfinal answer:A#B#C###", + ])("Text", async (text) => { + const parser = new BeeOutputParser({ + allowMultiLines: true, + preserveNewLines: true, + trimContent: false, + }); + + const chunks = text.split("#"); + for (const chunk of chunks) { + await parser.add(chunk); + } + + await parser.finalize(); + parser.validate(); + + const result = R.pipe(parser.parse(), R.pickBy(R.isTruthy)); + expect(result).toMatchSnapshot(); + }); + }); + + describe("Validation", () => { + it("Throws when no data passed", () => { + const parser = new BeeOutputParser(); + expect(omitEmptyValues(parser.parse())).toStrictEqual({}); + expect(() => parser.validate()).toThrowError(BeeOutputParserError); + }); + + it.each(["Hello\nWorld", "Tool{\nxxx", "\n\n\nT"])( + "Throws on invalid data (%s)", + async (chunk) => { + const parser = new BeeOutputParser({ + allowMultiLines: false, + }); + await expect(parser.add(chunk)).rejects.toThrowError(BeeOutputParserError); + await expect(parser.finalize()).rejects.toThrowError(BeeOutputParserError); + expect(() => parser.validate()).toThrowError(BeeOutputParserError); + }, + ); + }); +}); diff --git a/src/agents/bee/parser.ts b/src/agents/bee/parser.ts new file mode 100644 index 0000000..10acd23 --- /dev/null +++ b/src/agents/bee/parser.ts @@ -0,0 +1,293 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import * as R from "remeda"; +import { FrameworkError } from "@/errors.js"; +import { Cache } from "@/cache/decoratorCache.js"; +import { halveString } from "@/internals/helpers/string.js"; +import { parseBrokenJson } from "@/internals/helpers/schema.js"; +import { Emitter } from "@/emitter/emitter.js"; +import { NonUndefined } from "@/internals/types.js"; + +export interface BeeOutputParserOptions { + allowMultiLines: boolean; + preserveNewLines: boolean; + trimContent: boolean; +} + +interface BeeIterationSerializedResult { + thought?: string; + tool_name?: string; + tool_caption?: string; + tool_input?: string; + tool_output?: string; + final_answer?: string; +} + +export interface BeeIterationResult extends Omit { + tool_input?: unknown; +} + +export type BeeIterationToolResult = NonUndefined; + +export class BeeOutputParserError extends FrameworkError {} + +const NEW_LINE_CHARACTER = "\n"; + +interface Callbacks { + update: (data: { + type: "full" | "partial"; + state: BeeIterationResult; + update: { + key: keyof BeeIterationResult; + value: string; + }; + }) => Promise; +} + +export class BeeOutputParser { + public isDone = false; + protected readonly lines: string[]; + protected lastKeyModified: keyof BeeIterationSerializedResult | null = null; + public stash: string; + public readonly emitter = new Emitter({ + creator: this, + namespace: ["agent", "bee", "parser"], + }); + + protected readonly options: BeeOutputParserOptions; + + protected readonly result: BeeIterationSerializedResult = { + thought: undefined, + tool_name: undefined, + tool_caption: undefined, + tool_input: undefined, + tool_output: undefined, + final_answer: undefined, + }; + + constructor(options?: Partial) { + this.options = { + ...this._defaultOptions, + ...options, + }; + this.lines = []; + this.stash = ""; + } + + async add(chunk: string) { + chunk = chunk ?? ""; + this.stash += chunk; + if (!chunk.includes(NEW_LINE_CHARACTER)) { + return; + } + + while (this.stash.includes(NEW_LINE_CHARACTER)) { + this.filterStash(); + const [line, stash = ""] = halveString(this.stash, NEW_LINE_CHARACTER); + this.stash = stash; + + await this._processChunk(line); + } + } + + protected filterStash() { + this.stash = this.stash.replaceAll("<|eom_id|>", ""); + this.stash = this.stash.replaceAll("<|eot_id|>", ""); + this.stash = this.stash.replaceAll("<|start_header_id|>assistant<|end_header_id|>", ""); + this.stash = this.stash.replaceAll("<|start_header_id|>", ""); + this.stash = this.stash.replaceAll("<|end_header_id|>", ""); + this.stash = this.stash.replaceAll("<|im_start|>", ""); + this.stash = this.stash.replaceAll("<|im_end|>", ""); + } + + async finalize() { + if (this.stash) { + await this._processChunk(this.stash); + this.stash = ""; + } + + if (this.isEmpty()) { + const response = this.lines.join(NEW_LINE_CHARACTER).concat(this.stash); + this.lines.length = 0; + this.stash = ""; + + await this.add(`Thought: ${response}${NEW_LINE_CHARACTER}`); + await this.add(`Final Answer: ${response}${NEW_LINE_CHARACTER}`); + } + if (this.result.thought && !this.result.final_answer && !this.result.tool_input) { + this.stash = ""; + await this.add(`Final Answer: ${this.result.thought}${NEW_LINE_CHARACTER}`); + } + + if (this.lastKeyModified) { + const parsed = this.parse(); + await this.emitter.emit("update", { + type: "full", + state: parsed, + update: { + key: this.lastKeyModified, + value: this.result[this.lastKeyModified]!, + }, + }); + } + this.lastKeyModified = null; + } + + isEmpty() { + return R.isEmpty(R.pickBy(this.result, R.isTruthy)); + } + + validate() { + if (this.isEmpty()) { + throw new BeeOutputParserError("Nothing valid has been parsed yet!", [], { + context: { + raw: this.lines.join(NEW_LINE_CHARACTER), + stash: this.stash, + }, + }); + } + + const { final_answer, tool_name, tool_input } = this.parse(); + const context = { + result: this.parse(), + stash: this.stash, + }; + if (!final_answer && !tool_input) { + if (this.result.tool_input) { + throw new BeeOutputParserError('Invalid "Tool Input" has been generated.', [], { + context: { + toolName: tool_name, + toolCaption: this.result.tool_caption, + toolInput: this.result.tool_input, + ...context, + }, + }); + } + + throw new BeeOutputParserError('Neither "Final Answer" nor "Tool Call" are present.', [], { + context, + }); + } + if (tool_input && final_answer) { + throw new BeeOutputParserError('Both "Final Answer" and "Tool Call" are present.', [], { + context, + }); + } + } + + @Cache() + protected get _defaultOptions(): BeeOutputParserOptions { + return { + allowMultiLines: true, + preserveNewLines: true, + trimContent: false, + }; + } + + protected async _processChunk(chunk: string) { + if (this.isDone) { + return; + } + + this.lines.push(chunk); + + let oldChunk = this.lastKeyModified ? this.result[this.lastKeyModified] : ""; + if (!this._extractStepPair(chunk) && this.options.allowMultiLines && this.lastKeyModified) { + const prev = this.result[this.lastKeyModified] || ""; + const newLine = this.options.preserveNewLines ? NEW_LINE_CHARACTER : ""; + chunk = `${this.lastKeyModified}:${prev}${newLine}${chunk}`; + } + + const step = this._extractStepPair(chunk); + if (!step && this.lastKeyModified === null && this.options.allowMultiLines) { + return; + } + + if (!step) { + throw new BeeOutputParserError(`No valid type has been detected in the chunk. (${chunk})}`); + } + + if (this.lastKeyModified && this.lastKeyModified !== step.type) { + this.isDone = Boolean(this.result[step.type]); + if (this.isDone) { + return; + } + + const state = this.parse(); + await this.emitter.emit("update", { + type: "full", + state, + update: { + key: this.lastKeyModified, + value: this.result[this.lastKeyModified]!, + }, + }); + oldChunk = this.result[step.type] ?? ""; + } + this.lastKeyModified = step.type; + + if (step.content) { + this.result[step.type] = step.content; + const state = this.parse(); + await this.emitter.emit("update", { + type: "partial", + state, + update: { + key: step.type, + value: step.content.replace(oldChunk ?? "", ""), + }, + }); + } + } + + parse(): BeeIterationResult { + const toolInput = parseBrokenJson(this?.result.tool_input?.trim?.(), { pair: ["{", "}"] }); + return R.pickBy( + Object.assign( + { ...this.result }, + { + tool_name: this.result.tool_name, + tool_input: toolInput ?? undefined, + }, + ), + R.isDefined, + ); + } + + protected _isValidStepType(type?: string | null): type is keyof BeeIterationSerializedResult { + return Boolean(type && type in this.result); + } + + protected _extractStepPair(line: string) { + let [, type, content] = line.match(/\s*([\w|\s]+?)\s*:\s*(.*)/ms) ?? [line, null, null]; + type = type ? type.trim().toLowerCase().replace(" ", "_") : null; + + if (!this._isValidStepType(type)) { + return null; + } + + content = content ?? ""; + if (this.options.trimContent) { + content = content.trim(); + } + + return { + type, + content, + }; + } +} diff --git a/src/agents/bee/prompts.ts b/src/agents/bee/prompts.ts new file mode 100644 index 0000000..89d44b4 --- /dev/null +++ b/src/agents/bee/prompts.ts @@ -0,0 +1,117 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { PromptTemplate } from "@/template.js"; + +export const BeeAgentSystemPrompt = new PromptTemplate({ + variables: ["instructions", "tools", "tool_names"] as const, + defaults: { + instructions: "You are a helpful assistant that uses tools to answer questions.", + }, + template: `{{instructions}} + +# Tools + +Tools must be used to retrieve factual or historical information to answer the question. +{{#tools.length}} +A tool can be used by generating the following three lines: + +Tool Name: ZblorgColorLookup +Tool Caption: Searching Zblorg #178 +Tool Input: {"id":178} + +## Available tools +{{#tools}} +Tool Name: {{name}} +Tool Description: {{description}} +Tool Input Schema: {{schema}} + +{{/tools}} +{{/tools.length}} +{{^tools.length}} +## Available tools + +No tools are available at the moment therefore you mustn't provide any factual or historical information. +If you need to, you must respond that you cannot. +{{/tools.length}} + +# Instructions + +Responses must always have the following structure: +- The user's input starts with 'Question: ' followed by the question the user asked, for example, 'Question: What is the color of Zblorg #178?' + - The question may contain square brackets with a nested sentence, like 'What is the color of [The Zblorg with the highest score of the 2023 season is Zblorg #178.]?'. Just assume that the question regards the entity described in the bracketed sentence, in this case 'Zblorg #178'. +- Line starting 'Thought: ', explaining the thought, for example 'Thought: I don't know what Zblorg is, but given that I have a ZblorgColorLookup tool, I can assume that it is something that can have a color and I should use the ZblorgColorLookup tool to find out the color of Zblorg number 178.' + - In a 'Thought', it is either determined that a Tool Call will be performed to obtain more information, or that the available information is sufficient to provide the Final Answer. + - If a tool needs to be called and is available, the following lines will be: + - Line starting 'Tool Name: ' name of the tool that you want to use. + - Line starting 'Tool Caption: ' short description of the calling action. + - Line starting 'Tool Input: ' JSON formatted input adhering to the selected tool JSON Schema. + - Line starting 'Tool Output: ', containing the tool output, for example 'Tool Output: {"success": true, "color": "green"}' + - The 'Tool Output' may or may not bring useful information. The following 'Thought' must determine whether the information is relevant and how to proceed further. + - If enough information is available to provide the Final Answer, the following line will be: + - Line starting 'Final Answer: ' followed by a response to the original question and context, for example: 'Final Answer: Zblorg #178 is green.' + - Use markdown syntax for formatting code snippets, links, JSON, tables, images, files. + - To reference an internal file, use the markdown syntax [file_name.ext](urn:file_identifier). + - The bracketed part must contain the file name, verbatim. + - The parenthesis part must contain the file URN, which can be obtained from the user or from tools. + - The agent does not, under any circumstances, reference a URN that was not provided by the user or a tool in the current conversation. + - To show an image, prepend an exclamation mark, as usual in markdown: ![file_name.ext](urn:file_identifier). + - This only applies to internal files. HTTP(S) links must be provided as is, without any modifications. +- The sequence of lines will be 'Thought' - ['Tool Name' - 'Tool Caption' - 'Tool Input' - 'Tool Output' - 'Thought'] - 'Final Answer', with the bracketed part repeating one or more times (but never repeating them in a row). Do not use empty lines between instructions. +- Sometimes, things don't go as planned. Tools may not provide useful information on the first few tries. The agent always tries a few different approaches before declaring the problem unsolvable: +- When the tool doesn't give you what you were asking for, you MUST either use another tool or a different tool input. + - When using search engines, the assistant tries different formulations of the query, possibly even in a different language. +- When executing code, the assistant fixes and retries when the execution errors out and tries a completely different approach if the code does not seem to be working. + - When the problem seems too hard for the tool, the assistant tries to split the problem into a few smaller ones. + +## Notes +- Any comparison table (including its content), file, image, link, or other asset must only be in the Final Answer. +- When the question is unclear, respond with a line starting with 'Final Answer:' followed by the information needed to solve the problem. +- When the user wants to chitchat instead, always respond politely. +- IMPORTANT: Lines 'Thought', 'Tool Name', 'Tool Caption', 'Tool Input', 'Tool Output' and 'Final Answer' must be sent within a single message. +`, +}); +export type BeeAgentTemplate = typeof BeeAgentSystemPrompt; + +export const BeeAssistantPrompt = new PromptTemplate({ + variables: ["thought", "toolName", "toolCaption", "toolInput", "toolOutput", "finalAnswer"], + optionals: ["thought", "toolName", "toolCaption", "toolInput", "toolOutput", "finalAnswer"], + template: `{{#thought}}Thought: {{.}}\n{{/thought}}{{#toolName}}Tool Name: {{.}}\n{{/toolName}}{{#toolCaption}}Tool Caption: {{.}}\n{{/toolCaption}}{{#toolInput}}Tool Input: {{.}}\n{{/toolInput}}{{#toolOutput}}Tool Output: {{.}}\n{{/toolOutput}}{{#finalAnswer}}Final Answer: {{.}}{{/finalAnswer}}`, +}); + +export const BeeUserPrompt = new PromptTemplate({ + variables: ["input"], + template: `Question: {{input}}`, +}); + +export const BeeToolErrorPrompt = new PromptTemplate({ + variables: ["reason"], + template: `The tool has failed; the error log is shown below. If the tool cannot accomplish what you want, use a different tool or explain why you can't use it. + +{{reason}}`, +}); + +export const BeeToolInputErrorPrompt = new PromptTemplate({ + variables: ["reason"], + template: `{{reason}} + +HINT: If you're convinced that the input was correct but the tool cannot process it then use a different tool or say I don't know.`, +}); + +export const BeeToolNoResultsPrompt = new PromptTemplate({ + variables: [], + template: `No results were found!`, +}); diff --git a/src/agents/bee/runner.ts b/src/agents/bee/runner.ts new file mode 100644 index 0000000..8a102ea --- /dev/null +++ b/src/agents/bee/runner.ts @@ -0,0 +1,297 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BeeAgentError } from "@/agents/bee/errors.js"; + +import { BaseMessage, Role } from "@/llms/primitives/message.js"; +import { TokenMemory } from "@/memory/tokenMemory.js"; +import { PromptTemplate } from "@/template.js"; +import { BeeAgentRunIteration, BeeCallbacks, BeeRunOptions } from "@/agents/bee/types.js"; +import { RequiredAll } from "@/internals/types.js"; +import { BaseToolRunOptions, ToolInputValidationError, ToolOutput } from "@/tools/base.js"; +import { getProp } from "@/internals/helpers/object.js"; +import { Retryable } from "@/internals/helpers/retryable.js"; +import { FrameworkError } from "@/errors.js"; +import { RunContext } from "@/context.js"; +import { BeeInput } from "@/agents/bee/agent.js"; +import { RetryCounter } from "@/internals/helpers/counter.js"; +import { + BeeAgentSystemPrompt, + BeeToolErrorPrompt, + BeeToolInputErrorPrompt, + BeeToolNoResultsPrompt, + BeeUserPrompt, +} from "@/agents/bee/prompts.js"; +import { BeeIterationToolResult, BeeOutputParser } from "@/agents/bee/parser.js"; + +export class BeeAgentRunnerFatalError extends BeeAgentError { + isFatal = true; +} + +export class BeeAgentRunner { + protected readonly failedAttemptsCounter; + + constructor( + protected readonly run: RunContext>, + protected readonly input: BeeInput, + protected readonly options: BeeRunOptions, + public readonly memory: TokenMemory, + ) { + this.failedAttemptsCounter = new RetryCounter(options?.execution?.totalMaxRetries); + } + + static async create( + run: RunContext>, + input: BeeInput, + options: BeeRunOptions, + prompt: string, + ) { + const memory = new TokenMemory({ + llm: input.llm, + capacityThreshold: 0.85, + handlers: { + removalSelector(curMessages) { + // First we remove messages from the past conversations + const prevConversationMessage = curMessages.find((msg) => + input.memory.messages.includes(msg), + ); + if (prevConversationMessage) { + return prevConversationMessage; + } + + if (curMessages.length <= 3) { + throw new BeeAgentRunnerFatalError( + "Cannot fit the current conversation into the context window!", + ); + } + + const lastMessage = + curMessages.find( + (msg) => msg.role === Role.ASSISTANT && getProp(msg, ["ctx", "success"]) === false, + ) ?? curMessages.find((msg) => msg.role === Role.ASSISTANT); + + if (!lastMessage) { + throw new BeeAgentRunnerFatalError( + "Cannot fit the current conversation into the context window!", + ); + } + return lastMessage; + }, + }, + }); + const template = input.promptTemplate ?? BeeAgentSystemPrompt; + await memory.addMany([ + BaseMessage.of({ + role: Role.SYSTEM, + text: template.render({ + tools: await Promise.all( + input.tools.map(async (tool) => ({ + name: tool.name, + description: tool.description.replaceAll("\n", ".").replace(/\.$/, "").concat("."), + schema: JSON.stringify(await tool.getInputJsonSchema()), + })), + ), + tool_names: input.tools.map((tool) => tool.name).join(","), + instructions: PromptTemplate.defaultPlaceholder, + }), + }), + ...input.memory.messages, + BaseMessage.of({ + role: Role.USER, + text: BeeUserPrompt.clone().render({ + input: prompt.trim() ? prompt : "Empty message.", + }), + }), + ]); + + return new BeeAgentRunner(run, input, options, memory); + } + + async llm(): Promise { + const emitter = this.run.emitter; + + return new Retryable({ + onRetry: () => emitter.emit("retry", undefined), + onError: async (error) => { + await emitter.emit("error", { error }); + this.failedAttemptsCounter.use(error); + }, + executor: async () => { + await emitter.emit("start", undefined); + + const outputParser = new BeeOutputParser(); + const llmOutput = await this.input.llm + .generate(this.memory.messages.slice(), { + signal: this.run.signal, + stream: true, + guided: { + regex: + /Thought:.+\n(?:Final Answer:[\S\s]+|Tool Name:.+\nTool Caption:.+\nTool Input:\{.+\}\nTool Output:)/ + .source, + }, + }) + .observe((llmEmitter) => { + outputParser.emitter.on("update", async ({ type, update, state }) => { + await emitter.emit(type === "full" ? "update" : "partialUpdate", { + data: state, + update, + meta: { success: true }, + }); + }); + + llmEmitter.on("newToken", async ({ value, callbacks }) => { + if (outputParser.isDone) { + callbacks.abort(); + return; + } + + await outputParser.add(value.getTextContent()); + if (outputParser.stash.match(/^\s*Tool Output:/i)) { + outputParser.stash = ""; + callbacks.abort(); + } + }); + }); + + await outputParser.finalize(); + outputParser.validate(); + + return { + state: outputParser.parse(), + raw: llmOutput, + }; + }, + config: { + maxRetries: this.options.execution?.maxRetriesPerStep, + signal: this.run.signal, + }, + }).get(); + } + + async tool(iteration: BeeIterationToolResult): Promise<{ output: string; success: boolean }> { + const tool = this.input.tools.find( + (tool) => tool.name.trim().toUpperCase() == iteration.tool_name?.toUpperCase(), + ); + if (!tool) { + this.failedAttemptsCounter.use(); + const availableTools = this.input.tools.map((tool) => tool.name); + return { + success: false, + output: [ + `Tool does not exist!`, + availableTools.length > 0 && + `Use one of the following tools: ${availableTools.join(",")}`, + ] + .filter(Boolean) + .join("\n"), + }; + } + const options = await (async () => { + const baseOptions: BaseToolRunOptions = { + signal: this.run.signal, + }; + const customOptions = await this.options.modifiers?.getToolRunOptions?.({ + tool, + input: iteration.tool_input, + baseOptions, + }); + return customOptions ?? baseOptions; + })(); + + return new Retryable({ + config: { + signal: this.run.signal, + maxRetries: this.options.execution?.maxRetriesPerStep, + }, + onError: async (error) => { + await this.run.emitter.emit("toolError", { + data: { + iteration, + tool, + input: iteration.tool_input, + options, + error: FrameworkError.ensure(error), + }, + }); + this.failedAttemptsCounter.use(error); + }, + executor: async () => { + await this.run.emitter.emit("toolStart", { + data: { + tool, + input: iteration.tool_input, + options, + iteration, + }, + }); + + try { + const toolOutput: ToolOutput = await tool.run(iteration.tool_input, options); + await this.run.emitter.emit("toolSuccess", { + data: { + tool, + input: iteration.tool_input, + options, + result: toolOutput, + iteration, + }, + }); + + if (toolOutput.isEmpty()) { + return { output: BeeToolNoResultsPrompt.render({}), success: true }; + } + + return { + success: true, + output: toolOutput.getTextContent(), + }; + } catch (error) { + if (error instanceof ToolInputValidationError) { + this.failedAttemptsCounter.use(error); + return { + success: false, + output: BeeToolInputErrorPrompt.render({ + reason: error.toString(), + }), + }; + } + + await this.run.emitter.emit("toolError", { + data: { + tool, + input: iteration.tool_input, + options, + error, + iteration, + }, + }); + + if (FrameworkError.isRetryable(error)) { + this.failedAttemptsCounter.use(error); + return { + success: false, + output: BeeToolErrorPrompt.render({ + reason: FrameworkError.ensure(error).explain(), + }), + }; + } + + throw error; + } + }, + }).get(); + } +} diff --git a/src/agents/bee/types.ts b/src/agents/bee/types.ts new file mode 100644 index 0000000..46b8e8c --- /dev/null +++ b/src/agents/bee/types.ts @@ -0,0 +1,102 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { ChatLLMOutput } from "@/llms/chat.js"; +import { BeeIterationResult, BeeIterationToolResult } from "@/agents/bee/parser.js"; +import { BaseMemory } from "@/memory/base.js"; +import { BaseMessage } from "@/llms/primitives/message.js"; +import { Callback } from "@/emitter/types.js"; +import { AnyTool, BaseToolRunOptions, Tool, ToolError, ToolOutput } from "@/tools/base.js"; + +export interface BeeRunInput { + prompt: string; +} + +export interface BeeRunOutput { + result: BaseMessage; + iterations: BeeAgentRunIteration[]; + memory: BaseMemory; +} + +export interface BeeAgentRunIteration { + raw: ChatLLMOutput; + state: BeeIterationResult; +} + +export interface BeeAgentExecutionConfig { + totalMaxRetries?: number; + maxRetriesPerStep?: number; + maxIterations?: number; +} + +export interface BeeRunOptions { + signal?: AbortSignal; + execution?: BeeAgentExecutionConfig; + modifiers?: { + getToolRunOptions?: (execution: { + tool: Tool; + input: unknown; + baseOptions: BaseToolRunOptions; + }) => BaseToolRunOptions | Promise; + }; +} + +export interface BeeUpdateMeta { + success: boolean; +} + +export interface BeeCallbacks { + start?: Callback; + error?: Callback<{ error: Error }>; + retry?: Callback; + success?: Callback<{ data: BaseMessage }>; + update?: Callback<{ + data: BeeIterationResult; + update: { key: keyof BeeIterationResult; value: string }; + meta: BeeUpdateMeta; + }>; + partialUpdate?: Callback<{ + data: BeeIterationResult; + update: { key: keyof BeeIterationResult; value: string }; + meta: BeeUpdateMeta; + }>; + toolStart?: Callback<{ + data: { + tool: AnyTool; + input: unknown; + options: BaseToolRunOptions; + iteration: BeeIterationToolResult; + }; + }>; + toolSuccess?: Callback<{ + data: { + tool: AnyTool; + input: unknown; + options: BaseToolRunOptions; + result: ToolOutput; + iteration: BeeIterationToolResult; + }; + }>; + toolError?: Callback<{ + data: { + tool: AnyTool; + input: unknown; + options: BaseToolRunOptions; + error: ToolError; + iteration: BeeIterationToolResult; + }; + }>; +} diff --git a/src/agents/manager.test.ts b/src/agents/manager.test.ts new file mode 100644 index 0000000..7112ef8 --- /dev/null +++ b/src/agents/manager.test.ts @@ -0,0 +1,53 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseAgentManager } from "@/agents/manager.js"; +import { expect } from "vitest"; + +describe("Agent Manager", () => { + class AgentManager extends BaseAgentManager { + protected _create(overrides: any) { + return overrides; + } + } + + it("Creates", () => { + const manager = new AgentManager(); + const agents = [{ a: "a" }, { b: "b" }, { c: "c" }]; + for (const agentInput of agents) { + const agent = manager.create(agentInput); + expect(agent).toBe(agentInput); + } + }); + + it("Destroys", () => { + const manager = new AgentManager(); + + const agents = [ + manager.create({ + destroy: vi.fn(), + }), + manager.create({ + destroy: vi.fn(), + }), + ]; + + manager.destroy(); + for (const agent of agents) { + expect(agent.destroy).toBeCalled(); + } + }); +}); diff --git a/src/agents/manager.ts b/src/agents/manager.ts new file mode 100644 index 0000000..ab1aa96 --- /dev/null +++ b/src/agents/manager.ts @@ -0,0 +1,51 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseAgent } from "@/agents/base.js"; + +export abstract class BaseAgentManager< + T extends BaseAgent, + PDefault extends readonly any[], + POverride, +> { + public readonly instances: Set>; + public readonly defaults: PDefault; + + constructor(...defaults: PDefault) { + this.defaults = defaults; + this.instances = new Set>(); + } + + protected abstract _create(overrides: POverride): T; + + create(overrides: POverride): T { + const instance = this._create(overrides); + const ref = new WeakRef(instance); + this.instances.add(ref); + return instance; + } + + destroy() { + for (const weakRef of Array.from(this.instances.values())) { + this.instances.delete(weakRef); + + const instance = weakRef.deref(); + if (instance) { + instance.destroy(); + } + } + } +} diff --git a/src/agents/types.ts b/src/agents/types.ts new file mode 100644 index 0000000..571ef4e --- /dev/null +++ b/src/agents/types.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseAgent } from "@/agents/base.js"; +import { OmitEmpty } from "@/internals/types.js"; + +export interface AgentMeta { + name: string; + description: string; + extraDescription?: string; +} + +export type AgentCallbackValue = + | { data?: never; error: Error } + | { data: unknown; error?: never } + | object; + +export type InternalAgentCallbackValue< + T extends AgentCallbackValue, + E extends NonNullable, +> = OmitEmpty & E; + +export type PublicAgentCallbackValue = + OmitEmpty; + +export type AgentCallback = (value: T) => void; + +export type GetAgentInput = T extends BaseAgent ? X : never; +export type GetAgentOutput = T extends BaseAgent ? X : never; +export type AnyAgent = BaseAgent; diff --git a/src/cache/base.ts b/src/cache/base.ts new file mode 100644 index 0000000..45d9811 --- /dev/null +++ b/src/cache/base.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { Serializable } from "@/internals/serializable.js"; + +export abstract class BaseCache extends Serializable { + public enabled = true; + + abstract size(): Promise; + abstract set(key: string, value: T): Promise; + abstract get(key: string): Promise; + abstract has(key: string): Promise; + abstract delete(key: string): Promise; + abstract clear(): Promise; +} diff --git a/src/cache/decoratorCache.test.ts b/src/cache/decoratorCache.test.ts new file mode 100644 index 0000000..82b9a36 --- /dev/null +++ b/src/cache/decoratorCache.test.ts @@ -0,0 +1,285 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { + Cache, + SingletonCacheKeyFn, + JSONCacheKeyFn, + ObjectHashKeyFn, + CacheFn, +} from "@/cache/decoratorCache.js"; +import type { AnyFn } from "@/internals/types.js"; + +describe("@Cache decorator", () => { + it("Caches method", () => { + class A { + static number = 0; + + @Cache() + getNumber() { + A.number += 1; + return A.number; + } + } + + const a = new A(); + expect(a.getNumber()).toEqual(1); + expect(a.getNumber()).toEqual(1); + }); + + it("Caches a complex datatype", () => { + class A { + static number = 0; + + @Cache({ + cacheKey: ObjectHashKeyFn, + }) + getNumber(handler: AnyFn) { + A.number += 1; + return handler; + } + } + + const a = new A(); + const x = () => 1; + const y = () => 1; + + expect(a.getNumber(x)).toEqual(x); + expect(A.number).toBe(1); + expect(a.getNumber(y)).toEqual(y); + expect(A.number).toBe(2); + expect(a.getNumber(x)).toEqual(x); + expect(A.number).toBe(2); + expect(a.getNumber(y)).toEqual(y); + expect(A.number).toBe(2); + }); + + it("Ignores args if 'EmptyCacheKeyFn' is specified.", () => { + class A { + static number = 0; + + @Cache({ + cacheKey: SingletonCacheKeyFn, + }) + // eslint-disable-next-line unused-imports/no-unused-vars + getNumber(...args: any[]) { + A.number += 1; + return A.number; + } + } + + const a = new A(); + expect(a.getNumber("a", "b")).toEqual(1); + expect(a.getNumber("b", "c")).toEqual(1); + }); + + it("Caches an accessor", () => { + class A { + static number = 0; + + @Cache() + get getNumber() { + A.number += 1; + return A.number; + } + } + + const a = new A(); + expect(a.getNumber).toEqual(1); + expect(a.getNumber).toEqual(1); + }); + + it("Controls cache behaviour", () => { + class A { + constructor(public invocations = 0) {} + + @Cache() + get getNumber() { + this.invocations += 1; + return this.invocations; + } + } + + const a = new A(); + const cache = Cache.getInstance(a, "getNumber"); + cache.disable(); + expect(a.getNumber).toEqual(1); + expect(a.getNumber).toEqual(2); + expect(a.getNumber).toEqual(3); + cache.enable(); + expect(a.getNumber).toEqual(4); + expect(a.getNumber).toEqual(4); + }); + + it("Clears cache", () => { + class A { + static number = 0; + + @Cache() + get getNumber() { + A.number += 1; + return A.number; + } + + reset() { + Cache.getInstance(this, "getNumber").clear(); + } + } + + const a = new A(); + expect(a.getNumber).toEqual(1); + expect(a.getNumber).toEqual(1); + a.reset(); + expect(a.getNumber).toEqual(2); + }); + + it("Clears cache by a key", () => { + class A { + static calls = 0; + + @Cache({ + cacheKey: JSONCacheKeyFn, + }) + getNumber(a: number) { + A.calls += a; + return A.calls; + } + + reset(values: number[]) { + Cache.getInstance(this, "getNumber").clear(values.map((value) => JSONCacheKeyFn(value))); + } + } + + const a = new A(); + expect(a.getNumber(10)).toEqual(10); + expect(a.getNumber(10)).toEqual(10); + expect(a.getNumber(20)).toEqual(30); + expect(a.getNumber(20)).toEqual(30); + a.reset([10]); + expect(a.getNumber(10)).toEqual(40); + expect(a.getNumber(20)).toEqual(30); + }); + + it("Caches a complex input", () => { + class A { + static calls = 0; + + @Cache() + calculate(a: number, b: number) { + A.calls += 1; + return a + b; + } + } + + const a = new A(); + expect(a.calculate(2, 3)).toEqual(5); + expect(a.calculate(2, 3)).toEqual(5); + expect(A.calls).toEqual(1); + }); + + it("Does not interfere cache across instances", () => { + class A { + constructor(public readonly base: number) {} + + @Cache() + add(input: number) { + return input + this.base; + } + } + + const a = new A(100); + const b = new A(200); + + expect(a.add(1)).toEqual(101); + expect(a.add(2)).toEqual(102); + + expect(b.add(1)).toEqual(201); + expect(b.add(2)).toEqual(202); + }); + + it("Preserves meta information", () => { + class A { + @Cache() + calculate() {} + } + + const a = new A(); + const b = new A(); + expect(a.calculate.name).toBe("calculate"); + expect(b.calculate.name).toBe("calculate"); + }); + + it("Static caching", () => { + class A { + static counter = 0; + + @Cache() + static add(input: number) { + A.counter += 1; + return input; + } + + static bbb(input: number) { + A.counter += 1; + return input; + } + } + + class B extends A {} + + expect(A.add(1)).toEqual(1); + expect(A.add(1)).toEqual(1); + expect(B.add(1)).toEqual(1); + expect(B.add(1)).toEqual(1); + + expect(A.counter).toBe(2); + expect(B.counter).toBe(2); + }); + + describe("CacheFn", () => { + it("Caches", () => { + const fn = vi.fn((input) => input); + + const cached = CacheFn.create(fn); + expect(cached(1)).toBe(1); + expect(cached(1)).toBe(1); + expect(cached(2)).toBe(2); + expect(cached(2)).toBe(2); + expect(cached(3)).toBe(3); + expect(cached(3)).toBe(3); + + expect(fn).toHaveBeenCalledTimes(3); + }); + + it("Updates TTL", async () => { + const sleep = async (ms: number) => await new Promise((resolve) => setTimeout(resolve, ms)); + let counter = 0; + const cached = CacheFn.create( + vi.fn(async () => { + await sleep(50); + cached.updateTTL(200); + counter += 1; + return counter; + }), + ); + await expect(cached()).resolves.toBe(1); + await expect(cached()).resolves.toBe(1); + await sleep(250); + await expect(cached()).resolves.toBe(2); + await expect(cached()).resolves.toBe(2); + }); + }); +}); diff --git a/src/cache/decoratorCache.ts b/src/cache/decoratorCache.ts new file mode 100644 index 0000000..06c0429 --- /dev/null +++ b/src/cache/decoratorCache.ts @@ -0,0 +1,302 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { AnyFn, TypedFn } from "@/internals/types.js"; +import * as R from "remeda"; +import hash from "object-hash"; +import { createHash, createRandomHash } from "@/internals/helpers/hash.js"; + +type InputFn = AnyFn; +type TargetFn = AnyFn; +type Instance = NonNullable; + +type CacheKeyFn = (...args: any[]) => string; + +export interface CacheDecoratorOptions { + enabled: boolean; + ttl?: number; + cacheKey: CacheKeyFn; + enumerable?: boolean; +} + +interface FnContext { + options: CacheDecoratorOptions; + cache: Map; +} + +interface GroupContext { + options: CacheDecoratorOptions; + instances: WeakMap; +} + +export interface CacheDecoratorInstance { + clear(keys?: string[]): void; + + isEnabled(): boolean; + + enable(): void; + + disable(): void; + + update(data: Partial): void; +} + +const state = { + container: new WeakMap(), + extractDescriptor(descriptor: PropertyDescriptor) { + // Method approach + if (descriptor.value != null) { + return { + value: descriptor.value as AnyFn, + update: (value: TargetFn) => { + descriptor.value = value; + }, + }; + } + // Accessor approach + if (descriptor.get != null) { + return { + value: descriptor.get as AnyFn, + update: (value: TargetFn) => { + descriptor.get = value; + }, + }; + } + throw new Error(`@${Cache.name} decorator must be either on a method or get accessor.`); + }, + getInstanceContext>(target: T, ctx: GroupContext) { + if (!ctx.instances.has(target)) { + ctx.instances.set(target, { + cache: new Map(), + options: ctx.options, + }); + } + return ctx.instances.get(target)!; + }, +} as const; + +const initQueue = new WeakMap(); + +export function Cache(_options?: Partial) { + const baseOptions: Required = { + enabled: true, + cacheKey: ObjectHashKeyFn, + ttl: Infinity, + enumerable: false, + ...R.pickBy(_options ?? {}, R.isDefined), + }; + + return function handler( + obj: NonNullable, + key: string | symbol, + descriptor: PropertyDescriptor, + ) { + if (Object.hasOwn(obj, "constructor")) { + const constructor = obj.constructor; + if (!initQueue.has(constructor)) { + initQueue.set(constructor, []); + } + + baseOptions.enumerable = Boolean(descriptor.get); + initQueue + .get(constructor)! + .push({ key, enumerable: _options?.enumerable ?? baseOptions.enumerable }); + } + + const target = state.extractDescriptor(descriptor); + const groupContext: GroupContext = { instances: new WeakMap(), options: baseOptions }; + + const fn = function wrapper(this: any, ...args: any[]) { + const invokeOriginal = () => target.value.apply(this, args); + const ctx = state.getInstanceContext(this, groupContext); + if (!ctx.options.enabled) { + return invokeOriginal(); + } + + const inputHash = ctx.options.cacheKey(...args); + if ( + !ctx.cache.has(inputHash) || + (ctx.cache.get(inputHash)?.expiresAt ?? Infinity) < Date.now() // is expired check + ) { + const result = invokeOriginal(); + ctx.cache.set(inputHash, { + expiresAt: Date.now() + (ctx.options.ttl ?? Infinity), + data: result, + }); + } + return ctx.cache.get(inputHash)!.data; + }; + Object.defineProperty(fn, "name", { + get: () => target.value.name ?? "anonymous", + }); + + target.update(fn); + state.container.set(fn, groupContext); + }; +} + +Cache.init = function init>(self: T) { + const task = initQueue.get(self.constructor) ?? []; + for (const { key, enumerable } of task) { + const descriptor = Object.getOwnPropertyDescriptor(self.constructor.prototype, key); + if (descriptor) { + Object.defineProperty(self, key, Object.assign(descriptor, { enumerable })); + } + } + initQueue.delete(self); +}; + +Cache.getInstance = function getInstance>( + target: T, + property: keyof T, +): CacheDecoratorInstance { + const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), property); + if (!descriptor) { + throw new TypeError(`No descriptor has been found for '${String(property)}'`); + } + const value = state.extractDescriptor(descriptor); + const ctxByInstance = state.container.get(value.value); + if (!ctxByInstance) { + throw new TypeError(`No cache instance is bounded to '${String(property)}'!`); + } + + const ctx = state.getInstanceContext(target, ctxByInstance); + return { + clear(keys?: string[]) { + if (keys) { + keys.forEach((key) => ctx.cache.delete(key)); + } else { + ctx.cache.clear(); + } + }, + update(data: CacheDecoratorOptions) { + const oldTTL = ctx.options.ttl; + const newTTL = data.ttl; + if (oldTTL !== newTTL && newTTL !== undefined) { + for (const value of ctx.cache.values()) { + if (value.expiresAt === Infinity) { + value.expiresAt = Date.now() + newTTL; + } else { + const diff = newTTL - (oldTTL ?? 0); + value.expiresAt += diff; + } + } + } + Object.assign(ctx.options, data); + }, + isEnabled() { + return ctx.options.enabled; + }, + enable() { + ctx.options.enabled = true; + }, + disable() { + ctx.options.enabled = false; + }, + }; +}; + +export const WeakRefKeyFn: CacheKeyFn = (() => { + const _lookup = new WeakMap(); + + return (...args: any[]) => { + const chunks = args.map((value) => { + if (R.isObjectType(value) || R.isFunction(value)) { + if (!_lookup.has(value)) { + _lookup.set(value, createRandomHash(6)); + } + return _lookup.get(value)!; + } + return value; + }); + return createHash(JSON.stringify(chunks)); + }; +})(); + +export const ObjectHashKeyFn: CacheKeyFn = (...args: any[]) => + hash(args, { + encoding: "base64", + replacer: (() => { + const _lookup = new WeakMap(); + return (value: unknown) => { + if (value && value instanceof AbortSignal) { + // not supported by "hash" function + if (!_lookup.has(value)) { + _lookup.set(value, createRandomHash(6)); + } + return _lookup.get(value)!; + } + return value; + }; + })(), + unorderedArrays: false, + unorderedObjects: false, + unorderedSets: false, + }); +export const JSONCacheKeyFn: CacheKeyFn = (...args: any[]) => JSON.stringify(args); +// eslint-disable-next-line unused-imports/no-unused-vars +export const SingletonCacheKeyFn: CacheKeyFn = (...args: any[]) => ""; + +export class CacheFn

extends Function { + readonly name = CacheFn.name; + + static create( + fn: (...args: A) => B, + options?: Partial, + ) { + const instance = new CacheFn(fn, options); + return instance as TypedFn & CacheFn; + } + + constructor( + protected readonly fn: (...args: P) => R, + protected readonly options?: Partial, + ) { + super(); + + Cache.getInstance(this, "get").update(options ?? {}); + return new Proxy(this, { + apply(this: CacheFn, target, _, args: any[]) { + return target.get(...(args as unknown as P)); + }, + get(target, prop: string | symbol, receiver: any) { + const value = target[prop as keyof typeof target]; + if (value instanceof Function) { + return function (this: CacheFn, ...args: P) { + return value.apply(this === receiver ? target : this, args); + }; + } + return value; + }, + }) as unknown as CacheFn & typeof fn; + } + + updateTTL(ttl: number) { + Cache.getInstance(this, "get").update({ ttl }); + } + + createSnapshot() { + return { + fn: this.fn, + options: this.options, + }; + } + + @Cache() + get(...args: P) { + return this.fn(...args); + } +} diff --git a/src/cache/fileCache.ts b/src/cache/fileCache.ts new file mode 100644 index 0000000..0fcf3a4 --- /dev/null +++ b/src/cache/fileCache.ts @@ -0,0 +1,123 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseCache } from "@/cache/base.js"; +import fs from "node:fs"; +import { SlidingCache } from "@/cache/slidingCache.js"; +import { Cache } from "@/cache/decoratorCache.js"; +import { Serializer } from "@/serializer/serializer.js"; +import { SerializableClass } from "@/internals/serializable.js"; + +interface Input { + fullPath: string; +} + +export class FileCache extends BaseCache { + constructor(protected readonly input: Input) { + super(); + } + + static { + this.register(); + } + + static async fromProvider(provider: BaseCache, input: Input) { + await fs.promises.writeFile(input.fullPath, provider.serialize()); + return new FileCache(input); + } + + @Cache() + protected async getProvider(): Promise> { + const exists = await fs.promises + .stat(this.input.fullPath) + .then((r) => r.isFile()) + .catch(() => false); + + if (exists) { + const serialized = await fs.promises.readFile(this.input.fullPath, "utf8"); + const { target, snapshot } = await Serializer.deserialize(serialized); + const Target = Serializer.getFactory(target).ref as SerializableClass>; + const instance = Target.fromSnapshot(snapshot); + if (!(instance instanceof BaseCache)) { + throw new TypeError("Provided file does not serialize any instance of BaseCache class."); + } + return instance; + } else { + return new SlidingCache({ + size: Infinity, + ttl: Infinity, + }); + } + } + + async reload() { + // @ts-expect-error protected property + Cache.getInstance(this, "getProvider").clear(); + void (await this.getProvider()); + } + + protected async save() { + const provider = await this.getProvider(); + return await fs.promises.writeFile(this.input.fullPath, provider.serialize()); + } + + async size() { + const provider = await this.getProvider(); + return provider.size(); + } + + async set(key: string, value: T) { + const provider = await this.getProvider(); + await provider.set(key, value); + void provider.get(key).finally(() => { + void this.save(); + }); + } + + async get(key: string) { + const provider = await this.getProvider(); + return await provider.get(key); + } + + async has(key: string) { + const provider = await this.getProvider(); + return await provider.has(key); + } + + async delete(key: string) { + const provider = await this.getProvider(); + const result = await provider.delete(key); + await this.save(); + return result; + } + + async clear() { + const provider = await this.getProvider(); + await provider.clear(); + await this.save(); + } + + async createSnapshot() { + return { + input: { fullPath: this.input.fullPath }, + provider: await this.getProvider(), + }; + } + + loadSnapshot(snapshot: Awaited>): void { + Object.assign(this, snapshot); + } +} diff --git a/src/cache/nullCache.test.ts b/src/cache/nullCache.test.ts new file mode 100644 index 0000000..ad39bff --- /dev/null +++ b/src/cache/nullCache.test.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { NullCache } from "@/cache/nullCache.js"; +import { verifyDeserialization } from "@tests/e2e/utils.js"; + +describe("NullCache", () => { + it("Handles basic operations", async () => { + const instance = new NullCache(); + await instance.set("1", 1); + await expect(instance.has("1")).resolves.toBe(false); + await expect(instance.get("1")).resolves.toBe(undefined); + await instance.delete("1"); + await expect(instance.size()).resolves.toBe(0); + await instance.clear(); + }); + + it("Serializes", async () => { + const instance = new NullCache(); + await instance.set("1", 1); + const serialized = instance.serialize(); + const deserialized = NullCache.fromSerialized(serialized); + verifyDeserialization(instance, deserialized); + }); +}); diff --git a/src/cache/nullCache.ts b/src/cache/nullCache.ts new file mode 100644 index 0000000..1bf7dfe --- /dev/null +++ b/src/cache/nullCache.ts @@ -0,0 +1,51 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseCache } from "@/cache/base.js"; + +export class NullCache extends BaseCache { + enabled = false; + + async set(_key: string, _value: T) {} + + async get(_key: string) { + return undefined; + } + + async has(_key: string) { + return false; + } + + async delete(_key: string) { + return true; + } + + async clear() {} + + async size() { + return 0; + } + + createSnapshot() { + return { + enabled: this.enabled, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} diff --git a/src/cache/slidingCache.test.ts b/src/cache/slidingCache.test.ts new file mode 100644 index 0000000..f288a3f --- /dev/null +++ b/src/cache/slidingCache.test.ts @@ -0,0 +1,73 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { verifyDeserialization } from "@tests/e2e/utils.js"; +import { SlidingCache } from "@/cache/slidingCache.js"; +import { beforeEach } from "vitest"; + +describe("SlidingCache", () => { + beforeEach(() => { + vitest.useFakeTimers(); + }); + + it("Handles basic operations", async () => { + const instance = new SlidingCache({ + ttl: 5000, + size: 10, + }); + await instance.set("1", 1); + await instance.set("2", 2); + await expect(instance.has("1")).resolves.toBe(true); + await expect(instance.get("1")).resolves.toBe(1); + await expect(instance.size()).resolves.toBe(2); + await instance.delete("1"); + await expect(instance.size()).resolves.toBe(1); + await instance.clear(); + await expect(instance.size()).resolves.toBe(0); + }); + + it("Removes old entries", async () => { + const instance = new SlidingCache({ + ttl: 2500, + size: 10, + }); + + await instance.set("1", 1); + await vitest.advanceTimersByTimeAsync(1500); + await expect(instance.size()).resolves.toBe(1); + await expect(instance.has("1")).resolves.toBe(true); + await expect(instance.get("1")).resolves.toBe(1); + + await vitest.advanceTimersByTimeAsync(2000); + await expect(instance.has("1")).resolves.toBe(false); + await expect(instance.get("1")).resolves.toBe(undefined); + + await instance.clear(); + }); + + it("Serializes", async () => { + const instance = new SlidingCache({ + ttl: 5000, + size: 10, + }); + await instance.set("1", 1); + await instance.set("2", 2); + await instance.set("3", 3); + const serialized = instance.serialize(); + const deserialized = SlidingCache.fromSerialized(serialized); + verifyDeserialization(instance, deserialized); + }); +}); diff --git a/src/cache/slidingCache.ts b/src/cache/slidingCache.ts new file mode 100644 index 0000000..49c5cf7 --- /dev/null +++ b/src/cache/slidingCache.ts @@ -0,0 +1,101 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { SlidingTaskMap, Task } from "promise-based-task"; +import { BaseCache } from "@/cache/base.js"; +import { Serializable } from "@/internals/serializable.js"; + +export interface SlidingCacheInput { + size: number; + ttl?: number; +} + +class SlidingCacheEntry extends Serializable { + constructor(protected readonly value: T) { + super(); + } + + static { + this.register(); + } + + destructor() { + if (this.value instanceof Task) { + this.value.destructor(); + } + } + + unwrap(): T { + return this.value; + } + + createSnapshot() { + return { value: this.value }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} + +export class SlidingCache extends BaseCache { + protected readonly provider; + + constructor(input: SlidingCacheInput) { + super(); + this.provider = new SlidingTaskMap>(input.size, input.ttl); + } + + static { + this.register(); + } + + async get(key: string) { + const value = this.provider.get(key); + return value?.unwrap?.(); + } + + async has(key: string) { + return this.provider.has(key); + } + + async clear() { + this.provider.clear(); + } + + async delete(key: string) { + return this.provider.delete(key); + } + + async set(key: string, value: T) { + this.provider.set(key, new SlidingCacheEntry(value)); + } + + async size() { + return this.provider.size; + } + + createSnapshot() { + return { + enabled: this.enabled, + provider: this.provider, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} diff --git a/src/cache/unconstrainedCache.test.ts b/src/cache/unconstrainedCache.test.ts new file mode 100644 index 0000000..09fd899 --- /dev/null +++ b/src/cache/unconstrainedCache.test.ts @@ -0,0 +1,46 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { verifyDeserialization } from "@tests/e2e/utils.js"; +import { beforeEach } from "vitest"; +import { UnconstrainedCache } from "@/cache/unconstrainedCache.js"; + +describe("UnconstrainedCache", () => { + beforeEach(() => { + vitest.useFakeTimers(); + }); + + it("Handles basic operations", async () => { + const instance = new UnconstrainedCache(); + await instance.set("1", 1); + await instance.set("2", 2); + await expect(instance.has("1")).resolves.toBe(true); + await expect(instance.get("1")).resolves.toBe(1); + await expect(instance.size()).resolves.toBe(2); + await instance.delete("1"); + await expect(instance.size()).resolves.toBe(1); + await instance.clear(); + await expect(instance.size()).resolves.toBe(0); + }); + + it("Serializes", async () => { + const instance = new UnconstrainedCache(); + await instance.set("1", 1); + const serialized = instance.serialize(); + const deserialized = UnconstrainedCache.fromSerialized(serialized); + verifyDeserialization(instance, deserialized); + }); +}); diff --git a/src/cache/unconstrainedCache.ts b/src/cache/unconstrainedCache.ts new file mode 100644 index 0000000..19589d3 --- /dev/null +++ b/src/cache/unconstrainedCache.ts @@ -0,0 +1,56 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseCache } from "@/cache/base.js"; + +export class UnconstrainedCache extends BaseCache { + protected readonly provider = new Map(); + + async get(key: string) { + return this.provider.get(key); + } + + async has(key: string) { + return this.provider.has(key); + } + + async clear() { + this.provider.clear(); + } + + async delete(key: string) { + return this.provider.delete(key); + } + + async set(key: string, value: T) { + this.provider.set(key, value); + } + + async size() { + return this.provider.size; + } + + createSnapshot() { + return { + enabled: this.enabled, + provider: this.provider, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..0e693c8 --- /dev/null +++ b/src/context.ts @@ -0,0 +1,175 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { AsyncLocalStorage } from "node:async_hooks"; +import { Emitter } from "@/emitter/emitter.js"; +import { createRandomHash } from "@/internals/helpers/hash.js"; +import { omit } from "remeda"; +import { Callback } from "@/emitter/types.js"; +import { createNonOverridableObject } from "@/internals/helpers/object.js"; +import { registerSignals } from "@/internals/helpers/cancellation.js"; +import { Serializable } from "@/internals/serializable.js"; +import { LazyPromise } from "@/internals/helpers/promise.js"; +import { FrameworkError } from "@/errors.js"; + +export interface RunInstance { + emitter: Emitter; +} + +export interface RunContextCallbacks { + start: Callback; + success: Callback; + error: Callback; + finish: Callback; +} + +export type GetRunContext = T extends RunInstance ? RunContext

: never; +export type GetRunInstance = T extends RunInstance ? P : never; + +export class Run extends LazyPromise { + constructor( + handler: () => Promise, + protected readonly runContext: RunContext, + ) { + super(handler); + } + + readonly [Symbol.toStringTag] = "Promise"; + + observe(fn: (emitter: Emitter) => void) { + fn(this.runContext.emitter as any); + return this; + } + + context(value: object) { + Object.assign(this.runContext.context, value); + Object.assign(this.runContext.emitter.context, value); + return this; + } + + middleware(fn: (context: RunContext) => void) { + fn(this.runContext); + return this; + } +} + +export class RunContext extends Serializable { + static #storage = new AsyncLocalStorage>(); + + protected readonly controller: AbortController; + public readonly runId: string; + public readonly groupId: string; + public readonly parentId?: string; + public readonly emitter; + public readonly context: object; + + get signal() { + return this.controller.signal; + } + + abort() { + this.controller.abort(); + } + + constructor( + protected readonly instance: RunInstance, + parent?: RunContext, + signal?: AbortSignal, + ) { + super(); + this.runId = createRandomHash(5); + this.parentId = parent?.runId; + this.groupId = parent?.groupId ?? createRandomHash(); + this.context = createNonOverridableObject( + omit((parent?.context ?? {}) as any, ["id", "parentId"]), + ); + + this.controller = new AbortController(); + registerSignals(this.controller, [signal, parent?.signal]); + + this.emitter = instance.emitter.child({ + context: this.context, + trace: { + id: this.groupId, + runId: this.runId, + parentRunId: parent?.runId, + }, + }); + if (parent) { + this.emitter.pipe(parent.emitter); + } + } + + destroy() { + this.emitter.destroy(); + this.controller.abort(new FrameworkError("Context destroyed.")); + } + + static enter( + self: RunInstance, + fn: (context: RunContext) => Promise, + signal?: AbortSignal, + ) { + const parent = RunContext.#storage.getStore(); + const runContext = new RunContext(self, parent, signal); + + return new Run(async () => { + const emitter = runContext.emitter.child({ + namespace: ["run"], + creator: runContext, + context: { internal: true }, + }); + + try { + await emitter.emit("start", null); + const result = await Promise.race([ + RunContext.#storage.run(runContext, fn, runContext), + new Promise((_, reject) => + runContext.signal.addEventListener("abort", () => reject(runContext.signal.reason)), + ), + ]); + await emitter.emit("success", result); + return result; + } catch (_e) { + const e = FrameworkError.ensure(_e); + await emitter.emit("error", e); + throw e; + } finally { + await emitter.emit("finish", null); + runContext.destroy(); + } + }, runContext); + } + + static { + this.register(); + } + + createSnapshot() { + return { + controller: this.controller, + runId: this.runId, + groupId: this.groupId, + parentId: this.parentId, + emitter: this.emitter, + context: this.context, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} diff --git a/src/emitter/__snapshots__/emitter.test.ts.snap b/src/emitter/__snapshots__/emitter.test.ts.snap new file mode 100644 index 0000000..9d138dd --- /dev/null +++ b/src/emitter/__snapshots__/emitter.test.ts.snap @@ -0,0 +1,36 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Emitter > Complex workflow > Emits events in-order 1`] = ` +[ + [ + "start", + { + "a": 1, + }, + ], + [ + "success", + "a", + ], + [ + "success", + "b", + ], + [ + "start", + { + "a": 2, + }, + ], + [ + "start", + { + "a": 3, + }, + ], + [ + "success", + "c", + ], +] +`; diff --git a/src/emitter/emitter.test.ts b/src/emitter/emitter.test.ts new file mode 100644 index 0000000..75016b0 --- /dev/null +++ b/src/emitter/emitter.test.ts @@ -0,0 +1,114 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { Emitter } from "@/emitter/emitter.js"; +import { describe, expect } from "vitest"; +import { EventMeta } from "@/emitter/types.js"; + +describe("Emitter", () => { + it("Emits", async () => { + const emitter = new Emitter({ + namespace: ["app"], + creator: this, + }); + const fn = vitest.fn(); + emitter.on("onStart", fn); + await emitter.emit("onStart", "1"); + expect(fn).toBeCalledWith( + "1", + expect.objectContaining({ + name: "onStart", + path: "app.onStart", + creator: this, + source: emitter, + createdAt: expect.any(Date), + context: expect.any(Object), + }), + ); + }); + + describe("Complex workflow", () => { + const recorder: [keyof MyEvents, unknown][] = []; + + interface MyEvents { + start: (data: { a: number }) => void; + success: (data: string) => void; + } + + const emitter = new Emitter({ + namespace: ["agent"], + }); + + it("Emits events in-order", async () => { + emitter.on("start", (data) => recorder.push(["start", data])); + emitter.on("success", (data) => recorder.push(["success", data])); + await emitter.emit("start", { a: 1 }); + await emitter.emit("success", "a"); + await emitter.emit("success", "b"); + await emitter.emit("start", { a: 2 }); + await emitter.emit("start", { a: 3 }); + await emitter.emit("success", "c"); + + expect(recorder).toMatchSnapshot(); + }); + + it("Resets", async () => { + recorder.length = 0; + emitter.reset(); + await emitter.emit("success", "c"); + expect(recorder).toHaveLength(0); + }); + }); + + it("Accepts callbacks", async () => { + const emitter = new Emitter<{ + start: (value: number) => void; + }>(); + const recorder: string[] = []; + emitter.registerCallbacks({ + start: (_, event) => recorder.push(event.path), + }); + await emitter.emit("start", 10); + expect(recorder).toHaveLength(1); + }); + + it("Handles nesting", async () => { + const recorder: { source: string; meta: EventMeta }[] = []; + + const root = Emitter.root; + root.match("*", (data, meta) => { + recorder.push({ source: "root.*", meta }); + }); + //root.on("agent.onStart", (_, meta) => + // recorder.push({ name: meta.name, path: meta.path, source: "root" }), + //); + //root.on("agent.runner.onStart", (_, meta) => recorder.push({ source: "root.runner", meta })); + + const agent = root.child({ + namespace: ["agent"], + }); + agent.on("onStart", (_, meta) => recorder.push({ source: "agent", meta })); + agent.on("*", (_, meta) => recorder.push({ source: "agent.*", meta })); + await agent.emit("onStart", null); + + // agent.runner.onStart + const runner = agent.child({ + namespace: ["runner"], + }); + // agent.runner, agent.runner.onStart + await runner.emit("onStart", null); + }); +}); diff --git a/src/emitter/emitter.ts b/src/emitter/emitter.ts new file mode 100644 index 0000000..1496ed7 --- /dev/null +++ b/src/emitter/emitter.ts @@ -0,0 +1,238 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { + Callback, + CleanupFn, + EmitterOptions, + EventMeta, + EventTrace, + Matcher, + StringKey, +} from "@/emitter/types.js"; +export type { EventMeta, EventTrace }; +import { Cache } from "@/cache/decoratorCache.js"; +import { createFullPath, isPath, assertValidName, assertValidNamespace } from "@/emitter/utils.js"; +import { EmitterError } from "@/emitter/errors.js"; +import { createRandomHash } from "@/internals/helpers/hash.js"; +import { shallowCopy, toBoundedFunction } from "@/serializer/utils.js"; +import { RequiredAll } from "@/internals/types.js"; +import { Serializable } from "@/internals/serializable.js"; +import { pick } from "remeda"; + +export interface EmitterInput { + groupId?: string; + namespace?: string[]; + creator?: object; + context?: E & object; + trace?: EventTrace; +} +export interface EmitterChildInput { + groupId?: string; + namespace?: string[]; + creator?: object; + context?: E & object; + trace?: EventTrace; +} + +interface Listener { + readonly match: (event: EventMeta) => boolean; + readonly raw: Matcher; + readonly callback: Callback; + readonly options?: EmitterOptions; +} + +export class Emitter>> extends Serializable { + protected listeners = new Set(); + protected readonly groupId?: string; + public readonly namespace: string[]; + public readonly creator?: object; + public readonly context: object; + protected readonly trace?: EventTrace; + + constructor(input: EmitterInput = {}) { + super(); + this.groupId = input?.groupId; + this.namespace = input?.namespace ?? []; + this.creator = input.creator ?? Object.prototype; + this.context = input?.context ?? {}; + this.trace = input.trace; + assertValidNamespace(this.namespace); + } + + static { + this.register(); + } + + @Cache() + static get root() { + return new Emitter>>({ creator: Object.create(null) }); + } + + child(input: EmitterChildInput = {}): Emitter { + const child = new Emitter({ + trace: input.trace ?? this.trace, + groupId: input?.groupId ?? this.groupId, + context: { ...this.context, ...input?.context }, + creator: input?.creator ?? this.creator, + namespace: input?.namespace + ? [...this.namespace, ...input.namespace] + : this.namespace.slice(), + }); + + child.pipe(this); + return child; + } + + pipe(target: Emitter): CleanupFn { + return this.on( + // @ts-expect-error + "*.*", + toBoundedFunction( + // @ts-expect-error + (...args) => target.invoke(...args), + [{ value: target, name: "target" }], + ), + { + isBlocking: true, + once: false, + persistent: true, + }, + ); + } + + destroy() { + this.listeners.clear(); + } + + reset() { + for (const listener of this.listeners) { + if (!listener.options?.persistent) { + this.listeners.delete(listener); + } + } + } + + registerCallbacks>>( + callbacks: Partial< + Record extends Callback ? Callback : never> + >, + options?: Partial>, + ): CleanupFn { + const listeners: CleanupFn[] = []; + Object.entries(callbacks).forEach(([key, value]) => { + if (value) { + // @ts-expect-error + listeners.push(this.on(key, value, options?.[key])); + } + }); + return () => listeners.forEach((cleanup) => cleanup()); + } + + on>>( + event: K, + callback: NonNullable extends Callback ? T[K] : never, + options?: EmitterOptions, + ): CleanupFn { + return this.match(event as Matcher, callback as Callback, options); + } + + match(matcher: Matcher, callback: Callback, options?: EmitterOptions): CleanupFn { + const listener: Listener = { + options, + callback, + raw: matcher, + match: (() => { + if (matcher === "*") { + return (event) => event.path === createFullPath(this.namespace, event.name); + } else if (matcher === "*.*") { + return () => true; + } else if (matcher instanceof RegExp) { + return (event) => matcher.test(event.path); + } else if (typeof matcher === "function") { + return matcher; + } else if (typeof matcher === "string") { + return isPath(matcher) + ? (event) => event.path === matcher + : (event) => + event.name === matcher && event.path === createFullPath(this.namespace, event.name); + } else { + throw new EmitterError("Invalid matcher provided!"); + } + })(), + }; + this.listeners.add(listener); + + return () => this.listeners.delete(listener); + } + + async emit>>( + name: K, + value: NonNullable extends Callback ? X : unknown, + ): Promise { + assertValidName(name); + + const event = this.createEvent(name); + return await this.invoke(value, event); + } + + protected async invoke(data: unknown, event: EventMeta) { + const executions: unknown[] = []; + for (const listener of this.listeners.values()) { + if (!listener.match(event)) { + continue; + } + + if (listener.options?.once) { + this.listeners.delete(listener); + } + + const run = async () => listener.callback(data, event); + executions.push(listener.options?.isBlocking ? await run() : run()); + } + await Promise.all(executions); + } + + protected createEvent(name: string): EventMeta { + return { + id: createRandomHash(), + groupId: this.groupId, + name, + path: createFullPath(this.namespace, name), + createdAt: new Date(), + source: this, + creator: this.creator!, + context: Object.assign({}, this.context, {}), // TODO: use createInStone + trace: shallowCopy(this.trace), // TODO + }; + } + + createSnapshot() { + return { + groupId: this.groupId, + namespace: shallowCopy(this.namespace), + creator: this.creator, + context: this.context, + trace: this.trace, + listeners: Array.from(this.listeners).map(pick(["raw", "options", "callback"])), + }; + } + + loadSnapshot({ listeners, ...snapshot }: ReturnType): void { + Object.assign(this, snapshot, { listeners: new Set() }); + listeners.forEach(({ raw, callback, options }) => this.match(raw, callback, options)); + } +} diff --git a/src/emitter/errors.ts b/src/emitter/errors.ts new file mode 100644 index 0000000..57c2484 --- /dev/null +++ b/src/emitter/errors.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { FrameworkError } from "@/errors.js"; + +export class EmitterError extends FrameworkError {} diff --git a/src/emitter/types.ts b/src/emitter/types.ts new file mode 100644 index 0000000..473ddd9 --- /dev/null +++ b/src/emitter/types.ts @@ -0,0 +1,56 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { AnyVoid } from "@/internals/types.js"; +import { Emitter } from "@/emitter/emitter.js"; + +export type Matcher = "*" | "*.*" | RegExp | ((event: EventMeta) => boolean); +//export type Callback = ((value: T) => AnyVoid) | ((value: T, event: EventMeta) => AnyVoid); +export type InferCallbackValue = NonNullable extends Callback ? P : never; +export type Callback = (value: T, event: EventMeta) => AnyVoid; +export type CleanupFn = () => void; +export type StringKey = Extract; +export interface EmitterOptions { + isBlocking?: boolean; + once?: boolean; + persistent?: boolean; +} +export interface EventTrace { + id: string; + runId: string; + parentRunId?: string; +} +export interface EventMeta { + id: string; + groupId?: string; + name: string; + path: string; + createdAt: Date; + source: Emitter; + creator: object; + context: C; + trace?: EventTrace; +} + +// CREATED +// Agent onStart({..., duration: 0, id: 1, parentId: null }) +// LLM onStart({..., duration: 0, id: 2, parentId: 1, groupId: xxx }) +// LLM onNewToken({..., duration: 10, id: 3, parentId: 1, groupId: xxx }) +// LLM onNewToken({..., duration:5, id: 4, parentId: 1, groupId: xxx}) +// LLM onNewToken({..., duration: 7, id: 5, parentId: 1, groupId: xxx}) +// LLM onNewToken({..., duration:20, id: 6, parentId: 1, groupId: xxx}) +// LLM onEnd({..., duration:10+5+7+20, groupId: xxx}) +// DESTROYED diff --git a/src/emitter/utils.ts b/src/emitter/utils.ts new file mode 100644 index 0000000..b7765a4 --- /dev/null +++ b/src/emitter/utils.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { EmitterError } from "@/emitter/errors.js"; + +export function assertValidNamespace(path: string[]) { + if (!path || !Array.isArray(path)) { + throw new EmitterError("Event path cannot be empty!"); + } + path.forEach((part) => assertValidName(part)); +} + +export function assertValidName(name: string) { + if (!name || !/(^[a-zA-Z_]+$)/.test(name)) { + console.info({ name }); + throw new EmitterError("Name must contain only letters (optionally underscores)."); + } +} + +export function createFullPath(path: string[], name: string) { + return name ? path.concat(name).join(".") : path.join("."); +} + +export function isPath(name: string) { + return name.includes("."); +} diff --git a/src/errors.test.ts b/src/errors.test.ts new file mode 100644 index 0000000..77df79d --- /dev/null +++ b/src/errors.test.ts @@ -0,0 +1,95 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { FrameworkError } from "@/errors.js"; + +describe("FrameworkError", () => { + it("Correctly sets all properties", () => { + const errMessage = "TOP LEVEL"; + + const error = new FrameworkError(errMessage); + expect(error.name).toBe("FrameworkError"); + expect(error.message).toBe(errMessage); + expect(error.cause).toBe(undefined); + expect(error.errors).toHaveLength(0); + }); + + it("Sets correct name when inheriting", () => { + class CustomFrameworkError extends FrameworkError {} + expect(CustomFrameworkError.name).toBe("CustomFrameworkError"); + expect(new CustomFrameworkError().name).toBe("CustomFrameworkError"); + + class CustomCustomFrameworkError extends CustomFrameworkError {} + expect(CustomCustomFrameworkError.name).toBe("CustomCustomFrameworkError"); + expect(new CustomCustomFrameworkError().name).toBe("CustomCustomFrameworkError"); + }); + + it("Correctly traverse all errors", () => { + const errors: Error[] = [ + new Error("1", { cause: new Error("1A") }), + new Error("2"), + new FrameworkError("3", [ + new Error("4"), + new FrameworkError("5"), + new FrameworkError("6", [new Error("7"), new Error("8")]), + new TypeError("9"), + ]), + new Error("10"), + ]; + + const error = new FrameworkError("Error!", errors); + expect(Array.from(error.traverseErrors()).map((err) => err.message)).toMatchInlineSnapshot(` + [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + ] + `); + }); + + it("Correctly explain/format the error as a whole", () => { + const error = new FrameworkError("Something went wrong!", [ + new Error("1", { cause: new Error("Something went wrong.") }), + new Error("2"), + new FrameworkError("3", [ + new Error("4"), + new FrameworkError("5"), + new FrameworkError("6", [new Error("7"), new Error("8")]), + new TypeError("9"), + ]), + ]); + expect(error.explain()).toMatchInlineSnapshot(` + "FrameworkError: Something went wrong! + Error: 1 + Cause: Error: Something went wrong. + Error: 2 + FrameworkError: 3 + Error: 4 + FrameworkError: 5 + FrameworkError: 6 + Error: 7 + Error: 8 + TypeError: 9" + `); + }); +}); diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..c0ea30d --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,144 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import util from "util"; +import * as R from "remeda"; +import type { ObjectLike } from "@/internals/types.js"; + +export interface FrameworkErrorOptions { + isFatal?: boolean; + isRetryable?: boolean; + context?: ObjectLike; +} + +export class FrameworkError extends AggregateError { + isFatal = false; + isRetryable = true; + context: Record; + + constructor( + message = "Framework error has occurred.", + errors?: Error[], + options?: FrameworkErrorOptions, + ) { + super(errors || [], message); + this.name = this.constructor.name; + Error.captureStackTrace(this, AggregateError); + + if (R.isBoolean(options?.isFatal)) { + this.isFatal = options?.isFatal; + } + if (R.isBoolean(options?.isRetryable)) { + this.isRetryable = options.isRetryable; + } + this.context = options?.context ?? {}; + } + + hasFatalError() { + if (this.isFatal) { + return true; + } + for (const err of this.traverseErrors()) { + if (err instanceof FrameworkError && err.isFatal) { + return true; + } + } + return false; + } + + *traverseErrors(): Generator { + for (const error of this.errors) { + yield error; + if (error instanceof FrameworkError) { + yield* error.traverseErrors(); + } + } + } + + getCause(): Error { + const errors = Array.from(this.traverseErrors()); + return errors.at(-1) ?? this; + } + + explain(): string { + const output = []; + for (const [index, error] of [this, ...this.traverseErrors()].entries()) { + const offset = ` `.repeat(2 * index); + output.push(`${offset}${error.toString()}`); + if (error.cause) { + output.push(`${offset}Cause: ${error.cause.toString()}`); + } + } + return output.join("\n"); + } + + dump() { + return util.inspect(this, { + compact: false, + depth: Infinity, + }); + } + + static ensure(error: Error): FrameworkError { + return error instanceof FrameworkError + ? error + : new FrameworkError("Framework error has occurred.", [error]); + } + + static isInstanceOf(error: unknown): error is FrameworkError { + return error instanceof FrameworkError; + } + + static isAbortError(error: unknown) { + return error instanceof AbortError || (error instanceof Error && error?.name === "AbortError"); + } + + static isRetryable(error: unknown) { + if (error instanceof FrameworkError) { + return error.isRetryable; + } + return !FrameworkError.isAbortError(error); + } +} + +export class NotImplementedError extends FrameworkError { + constructor(message = "Not implemented!", errors?: Error[]) { + super(message, errors, { + isFatal: true, + isRetryable: false, + }); + } +} + +export class ValueError extends FrameworkError { + constructor( + message = "Provided value is not supported!", + errors: Error[] = [], + options?: FrameworkErrorOptions, + ) { + super(message, errors, options); + } +} + +export class AbortError extends FrameworkError { + constructor( + message = "Operation has been aborted!", + errors: Error[] = [], + options?: FrameworkErrorOptions, + ) { + super(message, errors, options); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f646a56 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +export * from "./logger/logger.js"; +export * from "./template.js"; +export * from "./errors.js"; +export * from "./version.js"; +export * from "./serializer/serializer.js"; diff --git a/src/internals/env.ts b/src/internals/env.ts new file mode 100644 index 0000000..594cae5 --- /dev/null +++ b/src/internals/env.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { z } from "zod"; +import { FrameworkError } from "@/errors.js"; +import { getProp } from "@/internals/helpers/object.js"; + +export function getEnv(key: string) { + return getProp(process.env, [key], undefined); +} + +export function parseEnv(key: string, schema: z.ZodType, defaultValue?: T): T { + const value = getEnv(key) ?? defaultValue; + const result = schema.safeParse(value); + if (!result.success) { + if (value === undefined) { + throw new FrameworkError(`Required variable '${key}' is not set!`); + } + + throw new FrameworkError(`Failed to parse ENV variable (${key})!`, [result.error]); + } + return result.data; +} + +export function hasEnv(key: string) { + return getProp(process.env, [key]) !== undefined; +} diff --git a/src/internals/fetcher.ts b/src/internals/fetcher.ts new file mode 100644 index 0000000..29327b6 --- /dev/null +++ b/src/internals/fetcher.ts @@ -0,0 +1,144 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { FrameworkError } from "@/errors.js"; +import { Serializable } from "@/internals/serializable.js"; +import { + EventSourceMessage, + EventStreamContentType, + fetchEventSource, +} from "@ai-zen/node-fetch-event-source"; +import { FetchEventSourceInit } from "@ai-zen/node-fetch-event-source/lib/cjs/fetch.js"; +import { emitterToGenerator } from "@/internals/helpers/promise.js"; + +export class RestfulClientError extends FrameworkError {} + +type URLParamType = string | number | boolean | null | undefined; +export function createURLParams(data: Record) { + const urlTokenParams = new URLSearchParams(); + for (const [key, value] of Object.entries(data)) { + if (value === undefined) { + continue; + } + + if (Array.isArray(value)) { + value.forEach((v) => { + if (v !== undefined) { + urlTokenParams.append(key, String(v)); + } + }); + } else { + urlTokenParams.set(key, String(value)); + } + } + return urlTokenParams; +} + +export class RestfulClient> extends Serializable { + constructor( + protected input: { + baseUrl: string; + headers: () => Promise; + paths: K; + }, + ) { + super(); + } + + async *stream( + path: keyof K, + init: FetchEventSourceInit, + ): AsyncGenerator { + const { paths, baseUrl, headers } = this.input; + + const target = new URL(paths[path] ?? path, baseUrl); + return yield* emitterToGenerator(async ({ emit }) => + fetchEventSource(target.toString(), { + method: "POST", + headers: await headers().then((raw) => Object.fromEntries(raw.entries())), + async onopen(response) { + if (response.ok && response.headers.get("content-type") === EventStreamContentType) { + return; + } + throw new RestfulClientError("Failed to stream!", [], { + context: { + url: response.url, + err: await response.text(), + response, + }, + isRetryable: response.status >= 400 && response.status < 500 && response.status !== 429, + }); + }, + onmessage(msg) { + if (msg?.event === "error") { + throw new RestfulClientError(`Error during streaming has occurred.`, [], { + context: msg, + }); + } + emit(msg); + }, + onclose() {}, + onerror(err) { + throw new RestfulClientError(`Error during streaming has occurred.`, [err]); + }, + ...init, + fetch, + }), + ); + } + + async fetch(path: keyof K, init?: RequestInit & { searchParams?: URLSearchParams }) { + const { paths, baseUrl, headers: getHeaders } = this.input; + + const target = new URL(paths[path] ?? path, baseUrl); + if (init?.searchParams) { + for (const [key, value] of init.searchParams) { + target.searchParams.set(key, value); + } + } + + const headers = await getHeaders().then((raw) => + Object.assign(Object.fromEntries(raw.entries()), init?.headers), + ); + const response = await fetch(target.toString(), { + ...init, + headers, + }); + + if (!response.ok) { + throw new RestfulClientError("Fetch has failed", [], { + context: { + url: response.url, + error: await response.text(), + response, + }, + isRetryable: [408, 503].includes(response.status ?? 500), + }); + } + + return response.json(); + } + + createSnapshot() { + return { + input: this.input, + }; + } + + loadSnapshot(snapshot: ReturnType): void { + Object.assign(this, snapshot); + } +} diff --git a/src/internals/helpers/__snapshots__/retryable.test.ts.snap b/src/internals/helpers/__snapshots__/retryable.test.ts.snap new file mode 100644 index 0000000..7f6871b --- /dev/null +++ b/src/internals/helpers/__snapshots__/retryable.test.ts.snap @@ -0,0 +1,164 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Retryable > Grouping > Strategy (SETTLE_ALL) 1`] = ` +[ + [ + 0, + "START", + ], + [ + 1, + "START", + ], + [ + 2, + "START", + ], + [ + 3, + "START", + ], + [ + 4, + "START", + ], + [ + 1, + "END", + ], + [ + 1, + "ERROR", + ], + [ + 3, + "END", + ], + [ + 3, + "ERROR", + ], + [ + 0, + "END", + ], + [ + 0, + "FATAL_ERROR", + ], + [ + 2, + "SUCCESS", + ], + [ + 2, + "END", + ], + [ + 4, + "SUCCESS", + ], + [ + 4, + "END", + ], +] +`; + +exports[`Retryable > Grouping > Strategy (SETTLE_ROUND) 1`] = ` +[ + [ + 0, + "START", + ], + [ + 1, + "START", + ], + [ + 2, + "START", + ], + [ + 3, + "START", + ], + [ + 4, + "START", + ], + [ + 1, + "END", + ], + [ + 1, + "ERROR", + ], + [ + 3, + "END", + ], + [ + 3, + "ERROR", + ], + [ + 0, + "END", + ], + [ + 0, + "FATAL_ERROR", + ], + [ + 2, + "SUCCESS", + ], + [ + 2, + "END", + ], + [ + 4, + "SUCCESS", + ], + [ + 4, + "END", + ], +] +`; + +exports[`Retryable > Grouping > Strategy (THROW_IMMEDIATELY) 1`] = ` +[ + [ + 0, + "START", + ], + [ + 1, + "START", + ], + [ + 2, + "START", + ], + [ + 3, + "START", + ], + [ + 4, + "START", + ], + [ + 1, + "END", + ], + [ + 1, + "ERROR", + ], +] +`; diff --git a/src/internals/helpers/array.ts b/src/internals/helpers/array.ts new file mode 100644 index 0000000..71cd07a --- /dev/null +++ b/src/internals/helpers/array.ts @@ -0,0 +1,30 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +export function removeFromArray(arr: T[], target: T): boolean { + const index = arr.findIndex((value) => value === target); + if (index === -1) { + return false; + } + + arr.splice(index, 1); + return true; +} + +export function castArray(arr: T) { + const result = Array.isArray(arr) ? arr : [arr]; + return result as T extends unknown[] ? T : [T]; +} diff --git a/src/internals/helpers/cancellation.ts b/src/internals/helpers/cancellation.ts new file mode 100644 index 0000000..4a279cb --- /dev/null +++ b/src/internals/helpers/cancellation.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +export function createAbortController(...signals: (AbortSignal | undefined)[]) { + const controller = new AbortController(); + registerSignals(controller, signals); + return controller; +} + +export function registerSignals(controller: AbortController, signals: (AbortSignal | undefined)[]) { + signals.forEach((signal) => { + if (signal?.aborted) { + controller.abort(signal.reason); + } + + signal?.addEventListener?.( + "abort", + () => { + controller.abort(signal?.reason); + }, + { + once: true, + signal: controller.signal, + }, + ); + }); +} diff --git a/src/internals/helpers/counter.ts b/src/internals/helpers/counter.ts new file mode 100644 index 0000000..4f1d700 --- /dev/null +++ b/src/internals/helpers/counter.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { FrameworkError } from "@/errors.js"; +import { Serializable } from "@/internals/serializable.js"; + +export class RetryCounter extends Serializable { + public remaining: number; + protected readonly maxRetries: number; + protected lastError?: Error; + + constructor(maxRetries = 0) { + super(); + this.maxRetries = maxRetries; + this.remaining = maxRetries; + } + + use(error?: Error) { + this.lastError = error ?? this.lastError; + + if (this.remaining <= 0) { + throw new FrameworkError( + `Maximal amount of global retries '${this.maxRetries}' has been reached!`, + this.lastError ? [this.lastError] : undefined, + { isFatal: true }, + ); + } + this.remaining--; + } + + createSnapshot() { + return { + remaining: this.remaining, + maxRetries: this.maxRetries, + lastError: this.lastError, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} diff --git a/src/internals/helpers/general.ts b/src/internals/helpers/general.ts new file mode 100644 index 0000000..41a0044 --- /dev/null +++ b/src/internals/helpers/general.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { setTimeout } from "node:timers/promises"; +import { ZodType } from "zod"; + +export async function* sleep(ms: number, signal?: AbortSignal) { + const start = Date.now(); + const ctx = { iteration: 0, elapsed: 0 }; + while (true) { + await setTimeout(ms, { + signal, + }); + ctx.elapsed = Date.now() - start; + ctx.iteration++; + yield ctx; + } +} + +export function validate(value: unknown, schema: ZodType): asserts value is T { + schema.parse(value); +} diff --git a/src/internals/helpers/guards.ts b/src/internals/helpers/guards.ts new file mode 100644 index 0000000..16f4320 --- /dev/null +++ b/src/internals/helpers/guards.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import * as R from "remeda"; + +export type Primitive = string | number | symbol | bigint | boolean | null | undefined; +export function isPrimitive(value: unknown): value is Primitive { + return ( + R.isString(value) || + R.isNumber(value) || + R.isBoolean(value) || + R.isNullish(value) || + R.isSymbol(value) + ); +} diff --git a/src/internals/helpers/hash.ts b/src/internals/helpers/hash.ts new file mode 100644 index 0000000..2b30faa --- /dev/null +++ b/src/internals/helpers/hash.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { createHash as _createHash, randomBytes } from "node:crypto"; + +export function createHash(input: string, length = 4) { + return _createHash("shake256", { + outputLength: length, + }) + .update(input) + .digest("hex"); +} + +export function createRandomHash(length = 4) { + return createHash(randomBytes(20).toString("base64"), length); +} diff --git a/src/internals/helpers/map.ts b/src/internals/helpers/map.ts new file mode 100644 index 0000000..2c70cd9 --- /dev/null +++ b/src/internals/helpers/map.ts @@ -0,0 +1,23 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +export function findUniqueKey(baseKey: string, map: Map) { + let key = baseKey; + for (let i = 1; map.has(key); i++) { + key = baseKey.concat(String(i)); + } + return key; +} diff --git a/src/internals/helpers/number.ts b/src/internals/helpers/number.ts new file mode 100644 index 0000000..f142271 --- /dev/null +++ b/src/internals/helpers/number.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import * as R from "remeda"; + +export function safeSum(...numbers: (number | undefined | null)[]): number { + return R.sum(numbers?.filter(R.isNonNullish) || [0]); +} diff --git a/src/internals/helpers/object.ts b/src/internals/helpers/object.ts new file mode 100644 index 0000000..71339b5 --- /dev/null +++ b/src/internals/helpers/object.ts @@ -0,0 +1,147 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import * as R from "remeda"; +import { FrameworkError } from "@/errors.js"; + +export type Override> = { + [K in keyof T2]: K extends keyof T ? T2[K] : never; +}; + +export function assignFactory, T2 extends Partial>(target: T) { + return (source: NonNullable>) => { + Object.assign(target, source); + }; +} + +export const assign = , T2 extends Partial>( + a: T, + b: NonNullable>, +) => assignFactory(a)(b); +export const omitEmptyValues = R.pickBy(R.isTruthy); +export const omitUndefined = R.pickBy(R.isDefined); + +export function hasProp(target: T | undefined, key: keyof T): key is keyof T { + return Boolean(target) && Object.prototype.hasOwnProperty.call(target, key); +} + +export function hasProps(keys: (keyof T)[]) { + return (target: T | undefined) => keys.every((key) => hasProp(target, key)); +} + +export function setProp(target: unknown, paths: readonly string[], value: unknown) { + for (const [idx, key] of paths.entries()) { + if (!R.isPlainObject(target)) { + throw new TypeError("Only plain objects are supported!"); + } + + const isLast = idx === paths.length - 1; + const newValue = isLast ? value : (target[key] ?? {}); + Object.assign(target, { [key]: newValue }); + target = target[key]; + } +} + +export function getPropStrict(target: NonNullable, path: string): any { + if (!target || !(path in target)) { + throw new TypeError(`Target does not contain key "${path}"`); + } + return target[path as keyof typeof target]; +} + +export function getProp( + target: unknown, + paths: readonly (string | symbol)[], + defaultValue: any = undefined, +) { + let value: any = target; + if (!value) { + return undefined; + } + + for (const key of paths) { + if (!hasProp(value, key)) { + return defaultValue; + } + value = value[key]; + } + return value; +} + +export function deleteProps(target: T, keys: readonly K[]) { + keys.forEach((key) => { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete target[key]; + }); +} + +export function updateObject( + old: T, + update: L, +): asserts old is T & L { + for (const [key, val] of Object.entries(update)) { + const existing = old[key as keyof T]; + if (existing !== undefined && existing !== null) { + throw new FrameworkError( + `Cannot update object. Key '${key}' already exists and is defined.`, + [], + { + context: { + existing, + update: val, + }, + }, + ); + } + old[key as keyof T] = val; + } +} + +export function createNonOverridableObject(target: T) { + return new Proxy(target, { + set(target: any, k: keyof any, v) { + const existingValue = target[k]; + if (existingValue === undefined || existingValue === null) { + target[k] = v; + return true; + } + + console.error(`Property '${String(k)}' already exists. Cannot set value.`); + return false; + }, + }); +} + +export function customMerge>( + results: T[], + processors: { + [K in keyof T]: (value: T[K], oldValue: T[K]) => T[K]; + }, +) { + if (results.length === 0) { + throw new TypeError("Cannot merge content of an empty array!"); + } + + const finalResult = {} as T; + for (const next of results) { + for (const [key, value] of R.entries(next)) { + const oldValue = finalResult[key as keyof T]; + // @ts-expect-error weak typing due to generated types + finalResult[key] = (processors[key] ?? R.takeFirstBy(1))(value, oldValue); + } + } + return finalResult; +} diff --git a/src/internals/helpers/promise.ts b/src/internals/helpers/promise.ts new file mode 100644 index 0000000..6d2412c --- /dev/null +++ b/src/internals/helpers/promise.ts @@ -0,0 +1,155 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { Task, TaskState } from "promise-based-task"; +import { isFunction, isPromise } from "remeda"; +import { getProp } from "@/internals/helpers/object.js"; + +export function isPromiseLike(value: unknown): value is PromiseLike { + return isPromise(value) || isFunction(getProp(value, ["then"])); +} + +export async function arrayFromAsync( + generator: AsyncGenerator | AsyncIterableIterator | AsyncIterable, + limit?: number, +) { + limit = limit ?? Infinity; + const results: A[] = []; + for await (const chunk of generator) { + if (results.length >= limit) { + break; + } + results.push(chunk); + } + return results; +} + +export type EmitterToGeneratorFn = (data: { emit: (data: T) => void }) => Promise; +export async function* emitterToGenerator(fn: EmitterToGeneratorFn) { + const EmptyTag = Symbol("EMPTY"); + + let task = new Task(); + const emit = (data: T) => { + void task.resolve(data); + task = new Task(); + }; + + const promise = fn({ + emit, + }) + .catch((err) => { + task.reject(err); + throw err; + }) + .finally(() => { + task.resolve(EmptyTag); + }); + + while (task && task.state === TaskState.PENDING) { + const result = await task; + if (result !== EmptyTag) { + yield result; + } + } + + return await promise; +} + +export async function asyncProperties>( + obj: T, +): Promise<{ [K in keyof T]: T[K] extends Promise ? P : T[K] }> { + return Object.fromEntries( + await Promise.all(Object.entries(obj).map(async ([key, value]) => [key, await value])), + ); +} + +interface SafeExecuteOptions { + handler: () => T; + onSuccess?: (result: T) => void; + onError?: (err: Error) => void; +} + +export function safeExecute({ handler, onError, onSuccess }: SafeExecuteOptions): void { + try { + const result = handler(); + if (isPromiseLike(result)) { + result.then( + (result) => onSuccess?.(result), + (err) => onError?.(err), + ); + } else { + onSuccess?.(result); + } + } catch (e) { + onError?.(e); + } +} + +export function asyncExecute(handler: () => R): Promise { + return new Promise((onSuccess, onError) => { + safeExecute({ + handler, + onSuccess, + onError, + }); + }); +} + +export class LazyPromise implements Promise { + constructor(protected readonly handler: () => Promise) {} + + readonly [Symbol.toStringTag] = "Promise"; + + then( + onfulfilled?: ((value: R) => PromiseLike | TResult1) | undefined | null, + onrejected?: ((reason: any) => PromiseLike | TResult2) | undefined | null, + ): Promise { + return this.handler().then(onfulfilled).catch(onrejected); + } + + catch( + onrejected?: ((reason: any) => PromiseLike | TResult) | undefined | null, + ): Promise { + return this.handler().then(undefined).catch(onrejected); + } + + finally(onfinally?: (() => void) | undefined | null): Promise { + return this.handler().finally(onfinally); + } +} + +export async function signalRace( + fn: () => Promise, + signal?: AbortSignal, + onAbort?: () => void, +): Promise { + return new Promise((resolve, reject) => { + if (signal?.aborted) { + reject(signal.reason); + return; + } + + const signalFn = () => { + onAbort?.(); + reject(signal?.reason); + }; + signal?.addEventListener?.("abort", signalFn); + fn() + .then(resolve) + .catch(reject) + .finally(() => signal?.removeEventListener?.("abort", signalFn)); + }); +} diff --git a/src/internals/helpers/prototype.ts b/src/internals/helpers/prototype.ts new file mode 100644 index 0000000..aeafcea --- /dev/null +++ b/src/internals/helpers/prototype.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { ClassConstructor } from "@/internals/types.js"; +import * as R from "remeda"; + +export function* traversePrototypeChain(value: unknown, excluded?: Set) { + let node = value; + while (node !== null && node !== undefined) { + node = Object.getPrototypeOf(node); + if (!excluded?.has?.(node)) { + yield node; + } + } +} + +export function isDirectInstanceOf( + object: unknown, + constructor: ClassConstructor, +): object is A { + return R.isTruthy(object) && Object.getPrototypeOf(object) === constructor.prototype; +} diff --git a/src/internals/helpers/retry.ts b/src/internals/helpers/retry.ts new file mode 100644 index 0000000..01b7bcb --- /dev/null +++ b/src/internals/helpers/retry.ts @@ -0,0 +1,72 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { setTimeout } from "node:timers/promises"; +import { signalRace } from "@/internals/helpers/promise.js"; +import { FrameworkError } from "@/errors.js"; + +interface Meta { + attempt: number; + remaining: number; +} + +interface Options { + signal?: AbortSignal; + factor?: number; + retries?: number; + shouldRetry?: (e: Error, meta: Meta) => boolean | Promise; + onFailedAttempt?: (e: Error, meta: Meta) => void | Promise; +} + +export async function pRetry( + fn: (attempt: number) => Promise, + options?: Options, +): Promise { + const handler = async (attempt: number, remaining: number) => { + try { + const factor = options?.factor ?? 2; + if (attempt > 1) { + const ms = Math.round(Math.pow(factor, attempt - 1)) * 1000; + await setTimeout(ms, undefined, { + signal: options?.signal, + }); + } + + return await fn(attempt); + } catch (e) { + const meta: Meta = { + attempt, + remaining, + }; + + if (FrameworkError.isAbortError(e)) { + throw e.cause || e; + } + + await options?.onFailedAttempt?.(e, meta); + if (remaining <= 0) { + throw e; + } + + if ((await options?.shouldRetry?.(e, meta)) === false) { + throw e; + } + return await handler(attempt + 1, remaining - 1); + } + }; + + return await signalRace(() => handler(1, options?.retries ?? 0), options?.signal); +} diff --git a/src/internals/helpers/retryable.test.ts b/src/internals/helpers/retryable.test.ts new file mode 100644 index 0000000..76a73fe --- /dev/null +++ b/src/internals/helpers/retryable.test.ts @@ -0,0 +1,178 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { setTimeout as pSetTimeout } from "node:timers/promises"; +import { Retryable, RunStrategy } from "@/internals/helpers/retryable.js"; +import { expect } from "vitest"; + +describe("Retryable", () => { + describe("Execution", () => { + it("Executes only once", async () => { + const executor = vi.fn().mockReturnValue(1); + + const instance = new Retryable({ + executor, + config: { + maxRetries: 100, + }, + }); + + expect(executor).not.toBeCalled(); + const results = await Promise.all([ + instance.get(), + instance.get(), + instance.get(), + instance.get(), + ]); + expect(results.every((result) => result === 1)).toBe(true); + await expect(instance.get()).resolves.toBe(1); + expect(executor).toBeCalledTimes(1); + }); + + it("Re-Executes on error", async () => { + const executor = vi.fn(); + const instance = new Retryable({ + executor, + config: { + maxRetries: 0, + }, + }); + + executor.mockImplementationOnce(() => { + throw new Error("something went wrong"); + }); + await expect(instance.get()).rejects.toThrowError("something went wrong"); + await expect(instance.get()).rejects.toThrowError("something went wrong"); + + instance.reset(); + + executor.mockResolvedValueOnce(1); + await expect(instance.get()).resolves.toBe(1); + + expect(executor).toBeCalledTimes(2); + }); + + it("Re-Executes on reset", async () => { + const executor = vi.fn(); + const instance = new Retryable({ + executor, + config: { + maxRetries: 100, + }, + }); + + executor.mockResolvedValueOnce(1); + await expect(instance.get()).resolves.toBe(1); + instance.reset(); + executor.mockResolvedValueOnce(2); + await expect(instance.get()).resolves.toBe(2); + expect(executor).toBeCalledTimes(2); + }); + }); + + describe("Aborting", () => { + it.each([-1, 0, 50, 100, 150])("Aborts after specific amount of time", async (delay) => { + vi.useRealTimers(); + + const startFn = vi.fn(); + const endFn = vi.fn(); + + const controller = new AbortController(); + + const instance = new Retryable({ + executor: async ({ attempt }) => { + startFn(); + await pSetTimeout(200); + if (attempt === 1) { + throw new Error("Last error"); + } + endFn(); + }, + config: { + signal: controller.signal, + maxRetries: 100, + }, + }); + + if (delay < 0) { + controller.abort(new Error("Stop!")); + } + setTimeout(() => controller.abort(new Error("Stop!")), delay); + await expect(instance.get()).rejects.toThrowError("Stop!"); + expect(endFn).not.toBeCalled(); + if (delay >= 0) { + expect(startFn).toBeCalled(); + } else { + expect(startFn).not.toBeCalled(); + } + }); + }); + + describe("Grouping", () => { + class FatalError extends Error {} + class BaseError extends Error {} + + const createContext = () => { + const log: [number, "START" | "FATAL_ERROR" | "ERROR" | "RETRY" | "SUCCESS" | "END"][] = + [] as const; + const inputs = Array(5) + .fill(null) + .map( + (_, idx) => + new Retryable({ + executor: async (meta) => { + log.push([idx, "START"]); + try { + if (idx === 0) { + await pSetTimeout(500); + throw new FatalError(); + } + if (idx % 2 === 1 && meta.attempt % 2 === 1) { + await pSetTimeout(100); + throw new BaseError("Dummy error"); + } + await pSetTimeout(300 + idx * 100); + log.push([idx, "SUCCESS"]); + return idx; + } finally { + log.push([idx, "END"]); + } + }, + onRetry: () => { + log.push([idx, "RETRY"]); + }, + onError: (e) => { + if (e instanceof FatalError) { + log.push([idx, "FATAL_ERROR"]); + throw e; + } else { + log.push([idx, "ERROR"]); + } + }, + }), + ); + + return { log, inputs }; + }; + + it.each(Object.values(RunStrategy))("Strategy (%s)", async (strategy) => { + const { log, inputs } = createContext(); + await expect(Retryable.runGroup(strategy, inputs)).rejects.toThrowError(BaseError); + expect(log.length).toBeGreaterThan(0); + expect(log).toMatchSnapshot(); + }); + }); +}); diff --git a/src/internals/helpers/retryable.ts b/src/internals/helpers/retryable.ts new file mode 100644 index 0000000..6d2c771 --- /dev/null +++ b/src/internals/helpers/retryable.ts @@ -0,0 +1,218 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import * as R from "remeda"; +import { Task, TaskState } from "promise-based-task"; +import { FrameworkError } from "@/errors.js"; +import { EnumValue } from "@/internals/types.js"; +import { asyncProperties } from "@/internals/helpers/promise.js"; +import { createRandomHash } from "@/internals/helpers/hash.js"; +import { pRetry } from "@/internals/helpers/retry.js"; + +export interface RetryableConfig { + maxRetries: number; + factor?: number; + signal?: AbortSignal; +} + +export const RunStrategy = { + /** + * Once a single Retryable throws, other retry ables get cancelled immediately. + */ + THROW_IMMEDIATELY: "THROW_IMMEDIATELY", + + /** + * Once a single Retryable throws, wait for other to completes, but prevent further retries. + */ + SETTLE_ROUND: "SETTLE_ROUND", + + /** + * Once a single Retryable throws, other Retryables remains to continue. Error is thrown by the end. + */ + SETTLE_ALL: "SETTLE_ALL", +} as const; + +export interface RetryableRunConfig { + groupSignal: AbortSignal; +} + +export interface RetryableContext { + executionId: string; + attempt: number; + signal?: AbortSignal; +} + +export type RetryableHandler = (ctx: RetryableContext) => Promise; +export type ResetHandler = () => void; +export type ErrorHandler = (error: Error, ctx: RetryableContext) => void | Promise; +export type RetryHandler = (ctx: RetryableContext, lastError: Error) => void | Promise; + +export class Retryable { + readonly #id: string; + #value: Task | null; + #config: RetryableConfig; + #handlers; + + constructor(ctx: { + executor: RetryableHandler; + onReset?: ResetHandler; + onError?: ErrorHandler; + onRetry?: RetryHandler; + config?: Partial; + }) { + this.#value = null; + this.#id = createRandomHash(); + this.#handlers = { + executor: ctx.executor, + onReset: ctx.onReset, + onError: ctx.onError, + onRetry: ctx.onRetry, + } as const; + this.#config = { + ...ctx.config, + maxRetries: Math.max(ctx.config?.maxRetries || 0, 0), + }; + } + + static async runGroup( + strategy: EnumValue, + inputs: Retryable[], + ): Promise { + if (strategy === RunStrategy.THROW_IMMEDIATELY) { + return await Promise.all(inputs.map((input) => input.get())); + } + + const controller = new AbortController(); + const results = await Promise.allSettled( + inputs.map((input) => + input + .get(strategy === RunStrategy.SETTLE_ALL ? { groupSignal: controller.signal } : undefined) + .catch((err) => { + controller.abort(err); + throw err; + }), + ), + ); + controller.signal.throwIfAborted(); + return results.map((result) => (result as PromiseFulfilledResult).value!); + } + + static async *runSequence(inputs: readonly Retryable[]): AsyncGenerator { + for (const input of inputs) { + yield await input.get(); + } + } + + static async collect(inputs: T & Record>) { + // Solve everything + await Promise.all(R.values(inputs).map((input) => input.get())); + + // Obtain latest values + return await asyncProperties( + R.mapValues(inputs, (value) => (value as Retryable).get()) as { + [K in keyof T]: Promise ? Q : never>; + }, + ); + } + + #getContext(attempt: number): RetryableContext { + const ctx: RetryableContext = { + attempt, + executionId: this.#id, + signal: this.#config.signal, + }; + Object.defineProperty(ctx, "signal", { + enumerable: false, + }); + return ctx; + } + + get isResolved(): boolean { + return this.#value?.state === TaskState.RESOLVED; + } + + get isRejected(): boolean { + return this.#value?.state === TaskState.REJECTED; + } + + protected _run(config?: RetryableRunConfig) { + const task = new Task(); + + const assertAborted = () => { + this.#config.signal?.throwIfAborted?.(); + config?.groupSignal?.throwIfAborted?.(); + }; + + let lastError: Error | null = null; + pRetry( + async (attempt) => { + assertAborted(); + + const ctx = this.#getContext(attempt); + if (attempt > 1) { + await this.#handlers.onRetry?.(ctx, lastError!); + } + return await this.#handlers.executor(ctx); + }, + { + retries: this.#config.maxRetries, + factor: this.#config.factor, + signal: this.#config.signal, + shouldRetry: (e) => { + if (!FrameworkError.isRetryable(e)) { + return false; + } + return !config?.groupSignal?.aborted && !this.#config.signal?.aborted; + }, + onFailedAttempt: async (e, meta) => { + lastError = e; + await this.#handlers.onError?.(e, this.#getContext(meta.attempt)); + if (!FrameworkError.isRetryable(e)) { + throw e; + } + assertAborted(); + }, + }, + ) + .then((x) => task.resolve(x)) + .catch((x) => task.reject(x)); + + return task; + } + + async get(config?: RetryableRunConfig): Promise { + if (this.isResolved) { + return this.#value!.resolvedValue()!; + } + if (this.isRejected) { + throw this.#value?.rejectedValue(); + } + + if (this.#value?.state === TaskState.PENDING && !config) { + return this.#value; + } + + this.#value?.catch?.(() => {}); + this.#value = this._run(config); + return this.#value; + } + + reset() { + this.#value?.catch?.(() => {}); + this.#value = null; + this.#handlers.onReset?.(); + } +} diff --git a/src/internals/helpers/schema.ts b/src/internals/helpers/schema.ts new file mode 100644 index 0000000..e158c9a --- /dev/null +++ b/src/internals/helpers/schema.ts @@ -0,0 +1,87 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { TypeOf, ZodType, ZodEffects, ZodTypeAny, AnyZodObject } from "zod"; +import { zodToJsonSchema } from "zod-to-json-schema"; +import { Ajv, SchemaObject, ValidateFunction, Options as AjvOptions } from "ajv"; +import addFormats from "ajv-formats"; +import { findFirstPair } from "@/internals/helpers/string.js"; +// @ts-expect-error missing types +import djson from "dirty-json"; +import { FrameworkErrorOptions, ValueError } from "@/errors.js"; + +export type AnyToolSchemaLike = AnyZodObject | SchemaObject; +export type AnySchemaLike = ZodTypeAny | SchemaObject; +export type FromSchemaLike = T extends ZodTypeAny ? TypeOf : unknown; + +export function validateSchema( + schema: T | ZodEffects, + errorOptions?: FrameworkErrorOptions, +): asserts schema is T { + if (schema && schema instanceof ZodEffects) { + throw new ValueError( + "zod effects (refine, superRefine, transform, ...) cannot be converted to JSONSchema!", + [], + errorOptions, + ); + } +} + +export function toJsonSchema(schema: T): SchemaObject { + validateSchema(schema); + if (schema instanceof ZodType) { + return zodToJsonSchema(schema); + } + return schema; +} + +export function createSchemaValidator( + schema: T, + options?: AjvOptions, +): ValidateFunction> { + const jsonSchema = toJsonSchema(schema); + + const ajv = new Ajv( + options ?? { + coerceTypes: true, + }, + ); + addFormats.default(ajv); + return ajv.compile>(jsonSchema); +} + +interface ParseBrokenJsonOptions { + pair?: [string, string] | null; +} +export function parseBrokenJson(input: string | undefined, options?: ParseBrokenJsonOptions) { + input = (input ?? "")?.trim(); + + try { + try { + return JSON.parse(input); + } catch { + const pair = options?.pair; + if (pair) { + const [start, end] = findFirstPair(input, pair) ?? [0, input.length - 1]; + return djson.parse(input.substring(start, end + 1)); + } else { + return djson.parse(input); + } + } + } catch { + return null; + } +} diff --git a/src/internals/helpers/stream.ts b/src/internals/helpers/stream.ts new file mode 100644 index 0000000..a42a061 --- /dev/null +++ b/src/internals/helpers/stream.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +export async function* transformAsyncIterable( + generator: AsyncGenerator | AsyncIterableIterator, + transformer: (old: A) => A2, +): AsyncGenerator { + let next: IteratorResult; + while (!(next = await generator.next()).done) { + yield transformer(next.value); + } + return next.value; +} diff --git a/src/internals/helpers/string.test.ts b/src/internals/helpers/string.test.ts new file mode 100644 index 0000000..aadc3fe --- /dev/null +++ b/src/internals/helpers/string.test.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { expect } from "vitest"; +import { splitString } from "./string.js"; + +describe("String Utilities", () => { + it("splitString", () => { + const text = + "00 01 02 03 04 05 06 07 08 09 " + + "10 11 12 13 14 15 16 17 18 19 " + + "20 21 22 23 24 25 26 27 28 29 " + + "30 31 32 33 34 35 36 37 38 39 "; + const chunks = [...splitString(text, { size: 30, overlap: 15 })]; + expect(chunks).toEqual([ + "00 01 02 03 04 05 06 07 08 09 ", + "05 06 07 08 09 10 11 12 13 14 ", + "10 11 12 13 14 15 16 17 18 19 ", + "15 16 17 18 19 20 21 22 23 24 ", + "20 21 22 23 24 25 26 27 28 29 ", + "25 26 27 28 29 30 31 32 33 34 ", + "30 31 32 33 34 35 36 37 38 39 ", + "35 36 37 38 39 ", + ]); + }); +}); diff --git a/src/internals/helpers/string.ts b/src/internals/helpers/string.ts new file mode 100644 index 0000000..4141ae3 --- /dev/null +++ b/src/internals/helpers/string.ts @@ -0,0 +1,100 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { ValueOf } from "@/internals/types.js"; +import * as R from "remeda"; + +export function* splitString( + text: T, + options: { size: number; overlap: number }, +) { + if (options.overlap >= options.size) { + throw new Error("overlap must be less than size"); + } + for (let i = 0; i < text.length; i += options.size - options.overlap) { + yield text.slice(i, i + options.size); + } +} + +export const Comparator = { + EQ: [0] as const, + GT: [1] as const, + GTE: [0, 1] as const, + LT: [-1] as const, + LTE: [-1, 0] as const, +} as const; + +export function compareVersion( + a: string, + comparator: ValueOf, + b: string, +): boolean { + const diff = a.replace("v", "").trim().localeCompare(b.replace("v", "").trim(), undefined, { + numeric: true, + sensitivity: "base", + }); + const diffNormalized = Math.min(1, Math.max(-1, diff)); + return R.isIncludedIn(diffNormalized, comparator); +} + +export function isJsonLikeString(value: string | undefined): value is string { + if (!value) { + return false; + } + return value.startsWith("{") && value.endsWith("}"); +} + +export function halveString( + value: string, + seq: string, + includeSeq = false, +): [string] | [string, string] { + if (seq === "") { + return [value]; + } + + const index = value.indexOf(seq); + if (index === -1) { + return [value]; + } else { + return [value.slice(0, index), value.slice(index + (includeSeq ? 0 : seq.length))]; + } +} + +export function findFirstPair(text: string, pair: [string, string]): [number, number] | null { + const [opening, closing] = pair; + + let balance = 0; + let startIndex = -1; + + for (let index = 0; index < text.length; index++) { + if (text[index] === opening) { + if (balance === 0) { + startIndex = index; + } + balance++; + } else if (text[index] === closing) { + if (balance > 0) { + balance--; + if (balance === 0) { + return [startIndex, index]; + } + } + } + } + + return null; +} diff --git a/src/internals/helpers/weakRef.ts b/src/internals/helpers/weakRef.ts new file mode 100644 index 0000000..e6d0fce --- /dev/null +++ b/src/internals/helpers/weakRef.ts @@ -0,0 +1,71 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +export function isWeakKey(value: unknown): value is WeakKey { + return value != null && typeof value === "object"; +} + +export class SafeWeakSet extends WeakSet { + has(value: unknown) { + if (isWeakKey(value)) { + return super.has(value); + } + return false; + } + + add(value: unknown) { + if (isWeakKey(value)) { + super.add(value); + } + return this; + } + + delete(value: unknown) { + if (isWeakKey(value)) { + return super.delete(value); + } + return false; + } +} + +export class SafeWeakMap extends WeakMap { + has(value: unknown) { + if (isWeakKey(value)) { + return super.has(value); + } + return false; + } + + get(key: unknown) { + if (isWeakKey(key)) { + return super.get(key); + } + } + + set(key: unknown, value: T) { + if (isWeakKey(key)) { + super.set(key, value); + } + return this; + } + + delete(value: unknown) { + if (isWeakKey(value)) { + return super.delete(value); + } + return false; + } +} diff --git a/src/internals/serializable.ts b/src/internals/serializable.ts new file mode 100644 index 0000000..ba5540c --- /dev/null +++ b/src/internals/serializable.ts @@ -0,0 +1,103 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { Serializer } from "@/serializer/serializer.js"; +import { ClassConstructor, PromiseOrPlain } from "@/internals/types.js"; +import * as R from "remeda"; +import { extractClassName } from "@/serializer/utils.js"; +import { SerializerError } from "@/serializer/error.js"; +import { Cache } from "@/cache/decoratorCache.js"; + +export type SerializableClass = ClassConstructor> & + Pick, "fromSnapshot" | "fromSerialized">; + +interface SerializableStructure { + target: string; + snapshot: T; +} + +export abstract class Serializable { + abstract createSnapshot(): T; + abstract loadSnapshot(snapshot: T): void; + + constructor() { + Object.getPrototypeOf(this).constructor.register(); + Cache.init(this); + } + + protected static register(this: SerializableClass) { + Serializer.registerSerializable(this); + } + + clone(this: T): T { + const snapshot = this.createSnapshot(); + + const target = Object.create(this.constructor.prototype); + target.loadSnapshot(snapshot); + return target; + } + + serialize(): string { + const snapshot = this.createSnapshot(); + return Serializer.serialize>({ + target: extractClassName(this), + snapshot, + }); + } + + protected deserialize(value: string): T { + const { __root } = Serializer.deserializeWithMeta>(value); + if (!__root.target) { + console.warn( + `Serializable class must be serialized via "serialize" method and not via Serializer class. This may lead to incorrect deserialization.`, + ); + return __root as T; + } + + const current = extractClassName(this); + if (current !== __root.target) { + throw new SerializerError( + `Snapshot has been created for class '${__root.target}' but you want to use it for class '${current}'.`, + ); + } + return __root.snapshot; + } + + static fromSnapshot( + this: new (...args: any[]) => T extends Serializable

? T : never, + state: P, + ): T { + const target = Object.create(this.prototype); + target.loadSnapshot(state); + Cache.init(target); + return target; + } + + static fromSerialized( + this: abstract new (...args: any[]) => T, + serialized: string, + ): PromiseOrPlain { + const target = Object.create(this.prototype) as T; + const state = target.deserialize(serialized); + const load = target.loadSnapshot(state); + Cache.init(target); + + return (R.isPromise(load) ? load.then(() => target) : target) as PromiseOrPlain< + T, + T["loadSnapshot"] + >; + } +} diff --git a/src/internals/types.ts b/src/internals/types.ts new file mode 100644 index 0000000..6370795 --- /dev/null +++ b/src/internals/types.ts @@ -0,0 +1,112 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import type { Primitive } from "@/internals/helpers/guards.js"; + +type ExcludeArrayLike = object & Exclude; +export type ObjectLike = Record; +export type PlainObject = Exclude< + ExcludeArrayLike>, + any[] +>; + +export type ExcludeNonStringIndex = { + [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]; +}; + +export type AnyFn = (...args: any[]) => any; + +export type Enum = Readonly<{ + [K in keyof T]: K; +}>; +export type EnumFromUnion = Readonly<{ + [K in T as Uppercase]: K; +}>; +export type EnumValue = T extends Enum ? P[keyof P] : never; +export type EnumLowerCaseValue = Readonly<{ + [K in keyof T]: K extends string ? Lowercase : never; +}>[keyof T]; +export type EmptyObject = Record; + +export type OmitType = { [K in keyof T as T[K] extends O ? never : K]: T[K] }; + +export type Class = new (...args: any[]) => T; + +type TupleUnion = { + [S in U]: Exclude extends never ? [...R, S] : TupleUnion, [...R, S]>; +}[U]; +export type ArrayKeys = TupleUnion>; +export type Common = { + [P in keyof A & keyof B]: A[P] | B[P]; +}; +export type ValueOf> = T[keyof T]; + +export type Head = T extends [infer A, ...any] ? A : never; +export type Tail = T extends [any, ...infer B] ? B : never; +export type RequiredExcept = Omit, K> & Pick, K>; +export type OptionalExcept = Pick, K> & Omit, K>; +export type NonUndefined = Pick, K> & Omit; + +export type RequiredAll = { + [P in keyof T]-?: NonNullable; +}; + +export type OmitEmpty = OmitType; +export type NonEmptyArray = [T, ...T[]]; +export type Unwrap = T extends (infer X)[] ? X : T; +export type Constructable = new (...args: any[]) => T; +export type AbstractConstructable = abstract new (...args: any[]) => any; +export type AnyConstructable = Constructable; + +export type Constructor any> = T extends new ( + ...args: infer A +) => infer R + ? new (...args: A) => R + : never; + +export interface ClassConstructor { + name: string; + new (...args: any[]): T; +} +export type AbstractClassConstructor = abstract new (...args: any[]) => T; +export interface NamedFunction { + name: string; + (...args: any[]): T; +} + +export type PromiseOrPlain = + ReturnType extends Promise ? Promise : T; + +export type NoPromise = T extends Promise ? never : T; + +export type TypedFn

= (...args: P) => R; + +export function narrowTo(value: unknown, fn: boolean | ((value: T) => boolean)): value is T { + if (typeof fn === "function") { + return fn(value as T); + } + return fn; +} + +type Without = { [P in Exclude]?: never }; +export type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U; +export type OneOf = T extends [infer Only] + ? Only + : T extends [infer A, infer B, ...infer Rest] + ? OneOf<[XOR, ...Rest]> + : never; + +export type AnyVoid = Promise | unknown; diff --git a/src/llms/base.test.ts b/src/llms/base.test.ts new file mode 100644 index 0000000..9ada4d7 --- /dev/null +++ b/src/llms/base.test.ts @@ -0,0 +1,106 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { + BaseLLMTokenizeOutput, + StreamGenerateOptions, + AsyncStream, + BaseLLMOutput, + GenerateOptions, + BaseLLM, + GenerateCallbacks, +} from "./base.js"; +import { Emitter } from "@/emitter/emitter.js"; + +describe("BaseLLM", () => { + class DummyOutput extends BaseLLMOutput { + constructor(public content: string) { + super(); + } + + merge(other: DummyOutput) { + this.content += other.content; + } + + getTextContent(): string { + return this.content; + } + + toString(): string { + return this.getTextContent(); + } + + createSnapshot(): unknown { + return { content: this.content }; + } + + loadSnapshot(snapshot: unknown) { + Object.assign(this, snapshot); + } + } + + class DummyLLM extends BaseLLM { + public readonly emitter = Emitter.root.child({ + namespace: ["dummy", "llm"], + creator: this, + }); + + async meta() { + return { tokenLimit: 4096 }; + } + + // eslint-disable-next-line unused-imports/no-unused-vars + tokenize(input: string): Promise { + throw new Error("Method not implemented."); + } + + // eslint-disable-next-line unused-imports/no-unused-vars + protected _generate(input: string, options?: GenerateOptions): Promise { + throw new Error("Method not implemented."); + } + protected async *_stream( + input: string, + options?: StreamGenerateOptions, + ): AsyncStream { + for (const chunk of input.split(",")) { + options?.signal?.throwIfAborted(); + await new Promise((resolve) => setTimeout(resolve, 100)); + yield new DummyOutput(chunk); + } + } + } + + it("Stops generating", async () => { + const model = new DummyLLM("my-model"); + + const chunks: string[] = []; + await model + .generate("1,2,3,4,5", { + stream: true, + }) + .observe((emitter) => + emitter.registerCallbacks({ + newToken: ({ value, callbacks: { abort } }) => { + chunks.push(value.getTextContent()); + if (value.getTextContent() === "3") { + abort(); + } + }, + }), + ); + expect(chunks.join(",")).toBe("1,2,3"); + }); +}); diff --git a/src/llms/base.ts b/src/llms/base.ts new file mode 100644 index 0000000..6c9ecf9 --- /dev/null +++ b/src/llms/base.ts @@ -0,0 +1,249 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { FrameworkError } from "@/errors.js"; +import { Serializable } from "@/internals/serializable.js"; +import { createAbortController } from "@/internals/helpers/cancellation.js"; +import { OneOf } from "@/internals/types.js"; +import { Emitter } from "@/emitter/emitter.js"; +import { GetRunContext, RunContext } from "@/context.js"; +import { Callback } from "@/emitter/types.js"; +import { shallowCopy } from "@/serializer/utils.js"; +import { pRetry } from "@/internals/helpers/retry.js"; + +export interface GenerateCallbacks { + newToken?: Callback<{ value: BaseLLMOutput; callbacks: { abort: () => void } }>; + success?: Callback<{ value: BaseLLMOutput }>; + start?: Callback<{ input: any; options: unknown }>; + error?: Callback<{ input: any; error: FrameworkError; options: unknown }>; + finish?: Callback; +} + +export type GuidedOptions = OneOf< + [ + { + json?: string | Record; + }, + { + regex?: string; + }, + { + choice?: string[]; + }, + { + grammar?: string; + }, + { + decoding_backend?: string; + }, + { + whitespace_pattern?: string; + }, + ] +>; + +export interface GenerateOptions { + stream?: boolean; + signal?: AbortSignal; + guided?: GuidedOptions; +} + +export interface InternalGenerateOptions { + signal?: AbortSignal; +} + +export interface StreamGenerateOptions { + signal?: AbortSignal; + guided?: GuidedOptions; +} + +export type AsyncStream = AsyncGenerator; + +export class LLMError extends FrameworkError {} +export class LLMFatalError extends LLMError { + constructor(message: string, errors?: Error[]) { + super(message, errors, { + isRetryable: false, + isFatal: true, + }); + } +} +export class LLMOutputError extends LLMFatalError {} + +export interface BaseLLMTokenizeOutput { + tokensCount: number; + tokens?: string[]; +} + +export abstract class BaseLLMOutput extends Serializable { + mergeImmutable(this: T, other: T): T { + const newInstance = this.clone() as T; + newInstance.merge(other); + return newInstance; + } + + abstract merge(other: BaseLLMOutput): void; + + abstract getTextContent(): string; + + abstract toString(): string; +} + +export interface ExecutionOptions { + maxRetries?: number; +} + +export interface LLMMeta { + tokenLimit: number; +} + +export abstract class BaseLLM< + TInput, + TOutput extends BaseLLMOutput, + TGenerateOptions extends GenerateOptions = GenerateOptions, +> extends Serializable { + public abstract readonly emitter: Emitter; + + constructor( + public readonly modelId: string, + public readonly executionOptions: ExecutionOptions = {}, + ) { + super(); + } + + abstract meta(): Promise; + + abstract tokenize(input: TInput): Promise; + + generate(input: TInput, options?: TGenerateOptions) { + return RunContext.enter( + this, + async (run) => { + try { + await run.emitter.emit("start", { input, options }); + + if (options?.stream) { + const chunks: TOutput[] = []; + const controller = createAbortController(options?.signal); + + const tokenEmitter = run.emitter.child({ groupId: "tokens" }); + for await (const chunk of this._stream( + input, + { + ...options, + signal: controller.signal, + }, + // @ts-expect-error wrong types + run, + )) { + chunks.push(chunk); + await tokenEmitter.emit("newToken", { + value: chunk, + callbacks: { abort: () => controller.abort() }, + }); + if (controller.signal.aborted) { + break; + } + } + + const result = this._mergeChunks(chunks); + await run.emitter.emit("success", { value: result }); + return result; + } + + // @ts-expect-error types + const result = await pRetry(() => this._generate(input, options ?? {}, run), { + retries: this.executionOptions.maxRetries || 0, + ...options, + signal: run.signal, + }); + await run.emitter.emit("success", { value: result }); + return result; + } catch (error) { + await run.emitter.emit("error", { input, error, options }); + if (error instanceof LLMError) { + throw error; + } else { + throw new LLMError(`LLM has occurred an error.`, [error]); + } + } finally { + await run.emitter.emit("finish", null); + } + }, + options?.signal, + ); + } + + async *stream(input: TInput, options?: StreamGenerateOptions): AsyncStream { + return yield* await RunContext.enter( + this, + async (run) => { + // @ts-expect-error wrong types + return this._stream(input, options, run); + }, + options?.signal, + ); + } + + protected abstract _generate( + input: TInput, + options: TGenerateOptions, + run: GetRunContext, + ): Promise; + + protected abstract _stream( + input: TInput, + options: StreamGenerateOptions, + run: GetRunContext, + ): AsyncStream; + + protected _mergeChunks(chunks: TOutput[]): TOutput { + if (chunks.length === 0) { + throw new LLMOutputError("Cannot merge empty chunks!"); + } + return chunks.reduce((prev, cur) => prev.mergeImmutable(cur)); + } + + static cast>( + this: new (...args: any[]) => T, + value: unknown, + ): asserts value is T {} + + static castInput( + this: new (...args: any[]) => BaseLLM, + value: unknown, + ): asserts value is A {} + + static castOutput>( + this: new (...args: any[]) => T, + value: BaseLLMOutput, + ): asserts value is InferLLMOutput {} + + createSnapshot() { + return { + modelId: this.modelId, + executionOptions: shallowCopy(this.executionOptions), + emitter: this.emitter, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} + +export type AnyLLM = BaseLLM; +export type InferLLMOutput = T extends BaseLLM ? A : never; diff --git a/src/llms/chat.ts b/src/llms/chat.ts new file mode 100644 index 0000000..c8cdfc4 --- /dev/null +++ b/src/llms/chat.ts @@ -0,0 +1,110 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseLLM, BaseLLMOutput, GenerateOptions, LLMError } from "@/llms/base.js"; +import { BaseMessage, Role } from "@/llms/primitives/message.js"; +import { + AnySchemaLike, + createSchemaValidator, + FromSchemaLike, + parseBrokenJson, + toJsonSchema, +} from "@/internals/helpers/schema.js"; +import { Retryable } from "@/internals/helpers/retryable.js"; +import { GeneratedStructuredErrorTemplate, GeneratedStructuredTemplate } from "@/llms/prompts.js"; + +export abstract class ChatLLMOutput extends BaseLLMOutput { + abstract get messages(): readonly BaseMessage[]; +} + +export interface GenerateSchemaInput { + template?: typeof GeneratedStructuredTemplate; + errorTemplate?: typeof GeneratedStructuredErrorTemplate; + maxRetries?: number; + options?: T; +} + +export abstract class ChatLLM< + TOutput extends ChatLLMOutput, + TGenerateOptions extends GenerateOptions = GenerateOptions, +> extends BaseLLM { + async generateStructured( + schema: T, + input: BaseMessage[], + { + template = GeneratedStructuredTemplate, + errorTemplate = GeneratedStructuredErrorTemplate, + maxRetries = 3, + options, + }: GenerateSchemaInput = {}, + ): Promise> { + const jsonSchema = toJsonSchema(schema); + const validator = createSchemaValidator(jsonSchema); + + const finalOptions = { ...options } as TGenerateOptions; + if (!options?.guided) { + finalOptions.guided = { json: jsonSchema }; + } + + const messages: BaseMessage[] = [ + BaseMessage.of({ + role: Role.SYSTEM, + text: template.render({ + schema: JSON.stringify(jsonSchema, null, 2), + }), + }), + ...input, + ]; + + return new Retryable({ + executor: async () => { + const rawResponse = await this.generate(messages, finalOptions); + const textResponse = rawResponse.getTextContent(); + const jsonResponse: any = parseBrokenJson(textResponse); + + const success = validator(jsonResponse); + if (!success) { + const context = { + expected: JSON.stringify(jsonSchema), + received: jsonResponse ? JSON.stringify(jsonResponse) : textResponse, + errors: JSON.stringify(validator.errors ?? []), + }; + + messages.push( + BaseMessage.of({ + role: Role.USER, + text: errorTemplate.render(context), + }), + ); + throw new LLMError( + "Failed to generate a structured response adhering to the provided schema.", + [], + { + isFatal: false, + isRetryable: true, + context, + }, + ); + } + return jsonResponse as FromSchemaLike; + }, + config: { + signal: options?.signal, + maxRetries, + }, + }).get(); + } +} diff --git a/src/llms/index.ts b/src/llms/index.ts new file mode 100644 index 0000000..6831bc6 --- /dev/null +++ b/src/llms/index.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +export * from "./llm.js"; diff --git a/src/llms/llm.ts b/src/llms/llm.ts new file mode 100644 index 0000000..45ddb71 --- /dev/null +++ b/src/llms/llm.ts @@ -0,0 +1,24 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseLLM, BaseLLMOutput, GenerateOptions } from "./base.js"; + +export type LLMInput = string; + +export abstract class LLM< + TOutput extends BaseLLMOutput, + TGenerateOptions extends GenerateOptions = GenerateOptions, +> extends BaseLLM {} diff --git a/src/llms/primitives/message.ts b/src/llms/primitives/message.ts new file mode 100644 index 0000000..3ea9ac5 --- /dev/null +++ b/src/llms/primitives/message.ts @@ -0,0 +1,63 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { EnumLowerCaseValue } from "@/internals/types.js"; +import { Serializable } from "@/internals/serializable.js"; +import { shallowCopy } from "@/serializer/utils.js"; + +export const Role = { + ASSISTANT: "assistant", + SYSTEM: "system", + USER: "user", +} as const; + +export type RoleType = EnumLowerCaseValue | string; + +export interface BaseMessageInput { + role: RoleType; + text: string; + meta?: Record; +} + +export class BaseMessage extends Serializable { + constructor( + public readonly role: RoleType, + public readonly text: string, + public readonly meta?: Record, + ) { + super(); + } + + static { + this.register(); + } + + static of({ role, text, meta }: BaseMessageInput) { + return new BaseMessage(role, text, meta); + } + + createSnapshot() { + return { + role: this.role, + text: this.text, + meta: shallowCopy(this.meta), + }; + } + + loadSnapshot(state: ReturnType) { + return Object.assign(this, state); + } +} diff --git a/src/llms/prompts.ts b/src/llms/prompts.ts new file mode 100644 index 0000000..ca8fc06 --- /dev/null +++ b/src/llms/prompts.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { PromptTemplate } from "@/template.js"; + +export const GeneratedStructuredTemplate = new PromptTemplate({ + variables: ["schema"], + template: `You are a helpful assistant that generates only valid JSON adhering to the following JSON Schema. + +\`\`\` +{{schema}} +\`\`\` + +IMPORTANT: Every message must be a parsable JSON string without additional output. +`, +}); + +export const GeneratedStructuredErrorTemplate = new PromptTemplate({ + variables: ["errors", "expected", "received"], + template: `Generated response does not match the expected schema! +Validation Errors: "{{errors}}"`, +}); diff --git a/src/logger/logger.test.ts b/src/logger/logger.test.ts new file mode 100644 index 0000000..3f55984 --- /dev/null +++ b/src/logger/logger.test.ts @@ -0,0 +1,101 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { Logger } from "@/logger/logger.js"; +import { verifyDeserialization } from "@tests/e2e/utils.js"; +import pinoTest from "pino-test"; +import * as R from "remeda"; + +describe("Logger", () => { + const createInstance = () => { + const stream = pinoTest.sink(); + const logger = new Logger( + {}, + Logger.createRaw( + { + level: "info", + transport: undefined, + timestamp: true, + formatters: { + bindings: R.identity(), + }, + }, + stream, + ), + ); + + return { + stream, + logger, + }; + }; + + it("Logs", async () => { + const { logger, stream } = createInstance(); + + logger.info("Hello world!"); + await pinoTest.once(stream, { + level: "INFO", + message: "Hello world!", + }); + }); + + it("Forks", async () => { + const { logger: root, stream } = createInstance(); + + root.info("Root"); + const child = root.child({ + name: "A", + }); + child.info("A"); + + const subchild = child.child({ + name: "B", + }); + subchild.info("B"); + + await pinoTest.consecutive(stream, [ + { + level: "INFO", + message: "Root", + }, + { + level: "INFO", + message: "A", + name: "A", + }, + { + level: "INFO", + message: "B", + name: "A.B", + }, + ]); + }); + + it("Serializes", () => { + const instance = new Logger({ + name: "Root", + bindings: { + id: 123, + }, + }); + instance.level = "fatal"; + + const serialized = instance.serialize(); + const deserialized = Logger.fromSerialized(serialized); + verifyDeserialization(instance, deserialized); + }); +}); diff --git a/src/logger/logger.ts b/src/logger/logger.ts new file mode 100644 index 0000000..840bb8e --- /dev/null +++ b/src/logger/logger.ts @@ -0,0 +1,217 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { pino as pinoLogger, ChildLoggerOptions, LoggerOptions, DestinationStream } from "pino"; +import { FrameworkError } from "@/errors.js"; +import { Serializable } from "@/internals/serializable.js"; +import { Cache } from "@/cache/decoratorCache.js"; +import { EnumFromUnion, ValueOf } from "@/internals/types.js"; +import { parseEnv } from "@/internals/env.js"; +import { z } from "zod"; +import { isTruthy } from "remeda"; +import { PrettyOptions } from "pino-pretty"; +import { shallowCopy } from "@/serializer/utils.js"; +import { stdout } from "node:process"; +import * as url from "node:url"; +import path from "node:path"; +import { Emitter } from "@/emitter/emitter.js"; + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); + +export interface LoggerBindings extends Record {} + +export class LoggerError extends FrameworkError {} + +export const LoggerLevel: EnumFromUnion = { + DEBUG: "debug", + ERROR: "error", + FATAL: "fatal", + INFO: "info", + TRACE: "trace", + WARN: "warn", + SILENT: "silent", +} as const; +export type LoggerLevelType = ValueOf; + +export interface LoggerInput { + name?: string; + bindings?: LoggerBindings; + level?: LoggerLevelType; + raw?: ChildLoggerOptions; +} + +export class Logger extends Serializable implements pinoLogger.BaseLogger { + protected raw!: pinoLogger.Logger; + + info!: pinoLogger.LogFn; + warn!: pinoLogger.LogFn; + fatal!: pinoLogger.LogFn; + error!: pinoLogger.LogFn; + debug!: pinoLogger.LogFn; + trace!: pinoLogger.LogFn; + silent!: pinoLogger.LogFn; + + static { + this.register(); + } + + get level(): LoggerLevelType { + return this.raw.level as LoggerLevelType; + } + + set level(value: LoggerLevelType) { + this.raw.level = value; + } + + constructor( + public readonly input: LoggerInput, + raw?: pinoLogger.Logger, + ) { + super(); + this.raw = raw!; + this.init(); + } + + static of(input: LoggerInput) { + return new Logger(input); + } + + private init() { + const parent = this.raw || Logger.root.raw; + const instance = parent.child( + { + ...this.input.bindings, + name: this.input.name ?? this.input.bindings?.name, + }, + { + ...this.input.raw, + level: this.input.level ?? this.input.raw?.level, + }, + ); + + this.raw = instance; + this.info = instance.info.bind(instance); + this.warn = instance.warn.bind(instance); + this.fatal = instance.fatal.bind(instance); + this.error = instance.error.bind(instance); + this.debug = instance.debug.bind(instance); + this.trace = instance.trace.bind(instance); + this.silent = instance.silent.bind(instance); + } + + @Cache() + static get root() { + const logger = new Logger(Logger.defaults, Logger.createRaw(Logger.defaults)); + Emitter.root.on("*.*", (event, data) => { + logger.trace({ + event, + data, + }); + }); + return logger; + } + + @Cache() + static get defaults(): Omit & { pretty: boolean } { + return { + name: undefined, + pretty: Boolean(parseEnv("BEE_FRAMEWORK_LOG_PRETTY", z.coerce.boolean().default(false))), + bindings: {}, + level: parseEnv( + "BEE_FRAMEWORK_LOG_LEVEL", + z.nativeEnum(LoggerLevel).default(LoggerLevel.INFO), + ), + }; + } + + child(input?: LoggerInput) { + const name = [this.input.name, input?.name].filter(isTruthy).join("."); + + return new Logger( + { + ...this.input, + level: this.level, + ...input, + name, + bindings: { + name, + }, + }, + this.raw, + ); + } + + createSnapshot() { + return { + input: shallowCopy(this.input), + level: this.raw.level, + }; + } + + loadSnapshot({ level, ...extra }: ReturnType) { + Object.assign(this, extra); + this.init(); + this.raw.level = level; + } + + public static createRaw(options?: LoggerOptions, stream?: DestinationStream | undefined) { + const defaults = Logger.defaults; + const isPretty = defaults.pretty; + + const targetStream = stream ?? (isPretty ? pinoLogger.destination(stdout) : undefined); + + return pinoLogger( + { + ...(isPretty && { + transport: { + target: path.join(__dirname, "pretty.js"), + options: { + messageKey: "message", + nestedKey: undefined, + errorKey: "error", + colorize: true, + sync: true, + singleLine: parseEnv( + "BEE_FRAMEWORK_LOG_SINGLE_LINE", + z.coerce.boolean().default(true), + ), + } as PrettyOptions, + }, + }), + messageKey: "message", + nestedKey: defaults.pretty ? undefined : "payload", + errorKey: "error", + timestamp: true, + name: defaults.name, + level: defaults.level, + ...options, + formatters: { + bindings: ({ pid: _, hostname: __, ...others }) => { + return others; + }, + log: (record) => { + return record; + }, + level: (label) => { + return { level: label.toUpperCase() }; + }, + ...options?.formatters, + }, + }, + targetStream, + ); + } +} diff --git a/src/logger/pretty.js b/src/logger/pretty.js new file mode 100644 index 0000000..f6b5b7e --- /dev/null +++ b/src/logger/pretty.js @@ -0,0 +1,63 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import pinoPretty from "pino-pretty"; +import picocolors from "picocolors"; + +const compose = + (...fns) => + (value) => + fns.reduce((res, f) => f(res), value); + +export default (opts) => { + return pinoPretty({ + colorize: true, + colorizeObjects: true, + singleLine: true, + hideObject: false, + sync: true, + levelFirst: true, + ...opts, + translateTime: "HH:MM:ss", + customPrettifiers: { + level: (() => { + const levels = { + TRACE: { letters: "TRC", icon: "πŸ”Ž", formatter: picocolors.gray }, + DEBUG: { letters: "DBG", icon: "πŸͺ²", formatter: picocolors.yellow }, + INFO: { letters: "INF", icon: "ℹ️", formatter: picocolors.green }, + WARN: { letters: "WRN", icon: "⚠️", formatter: picocolors.yellow }, + ERROR: { letters: "ERR", icon: "πŸ”₯", formatter: picocolors.red }, + FATAL: { + letters: "FTL", + icon: "πŸ’£", + formatter: compose(picocolors.black, picocolors.bgRed), + }, + }; + const fallback = { letters: "???", icon: "πŸ€·β€", formatter: picocolors.gray }; + + return (logLevel) => { + const target = levels[logLevel] || fallback; + return `${target.formatter(target.letters)} ${target.icon} `; + }; + })(), + time: (timestamp) => picocolors.dim(timestamp), + caller: (caller, key, log, { colors }) => `${colors.greenBright(caller)}`, + }, + messageFormat: (log, messageKey) => { + return `${log[messageKey]}`; + }, + }); +}; diff --git a/src/memory/base.ts b/src/memory/base.ts new file mode 100644 index 0000000..db5f706 --- /dev/null +++ b/src/memory/base.ts @@ -0,0 +1,79 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseMessage } from "@/llms/primitives/message.js"; +import { FrameworkError } from "@/errors.js"; +import { Serializable } from "@/internals/serializable.js"; + +export class MemoryError extends FrameworkError {} +export class MemoryFatalError extends MemoryError { + constructor(message: string, errors?: Error[]) { + super(message, errors, { + isFatal: true, + isRetryable: false, + }); + } +} + +export abstract class BaseMemory extends Serializable { + abstract get messages(): readonly BaseMessage[]; + abstract add(message: BaseMessage): Promise; + abstract reset(): void; + + async addMany(messages: Iterable | AsyncIterable) { + for await (const msg of messages) { + await this.add(msg); + } + } + + isEmpty() { + return this.messages.length === 0; + } + + asReadOnly() { + return new ReadOnlyMemory(this); + } + + abstract loadSnapshot(state: TState): void; + abstract createSnapshot(): TState; +} + +export class ReadOnlyMemory extends BaseMemory<{ source: T }> { + constructor(public readonly source: T) { + super(); + } + + static { + this.register(); + } + + // eslint-disable-next-line unused-imports/no-unused-vars + async add(message: BaseMessage) {} + + get messages(): readonly BaseMessage[] { + return this.source.messages; + } + + reset(): void {} + + createSnapshot() { + return { source: this.source }; + } + + loadSnapshot(state: { source: T }) { + Object.assign(this, state); + } +} diff --git a/src/memory/slidingWindowMemory.ts b/src/memory/slidingWindowMemory.ts new file mode 100644 index 0000000..44c0741 --- /dev/null +++ b/src/memory/slidingWindowMemory.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseMessage } from "@/llms/primitives/message.js"; +import { BaseMemory } from "@/memory/base.js"; +import { shallowCopy } from "@/serializer/utils.js"; + +export interface SlidingWindowMemoryInput { + size: number; +} + +export class SlidingWindowMemory extends BaseMemory { + public readonly messages: BaseMessage[]; + + constructor(public config: SlidingWindowMemoryInput) { + super(); + this.messages = []; + } + + static { + this.register(); + } + + async add(message: BaseMessage) { + if (this.messages.length + 1 > this.config.size) { + this.messages.shift(); + } + this.messages.push(message); + } + + reset() { + this.messages.length = 0; + } + + createSnapshot() { + return { config: shallowCopy(this.config), messages: shallowCopy(this.messages) }; + } + + loadSnapshot(state: ReturnType) { + Object.assign(this, state); + } +} diff --git a/src/memory/summarizeMemory.ts b/src/memory/summarizeMemory.ts new file mode 100644 index 0000000..aae39ab --- /dev/null +++ b/src/memory/summarizeMemory.ts @@ -0,0 +1,109 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseMessage } from "@/llms/primitives/message.js"; +import { BaseMemory } from "@/memory/base.js"; +import { PromptTemplate } from "@/template.js"; +import { LLM } from "@/llms/index.js"; +import { BaseLLMOutput } from "@/llms/base.js"; +import { shallowCopy } from "@/serializer/utils.js"; + +export interface SummarizeMemoryInput { + llm: LLM; + template: typeof SummarizeMemoryTemplate; +} + +export const SummarizeMemoryTemplate = new PromptTemplate({ + variables: ["summary", "new_lines"], + template: `Progressively summarize the lines of conversation provided, adding onto the previous summary returning a new summary. + +EXAMPLE +Current summary: +The human asks what the AI thinks of artificial intelligence. The AI thinks artificial intelligence is a force for good. + +New lines of conversation: +Human: Why do you think artificial intelligence is a force for good? +AI: Because artificial intelligence will help humans reach their full potential. + +New summary: +The human asks what the AI thinks of artificial intelligence. The AI thinks artificial intelligence is a force for good because it will help humans reach their full potential. +END OF EXAMPLE + +Current summary: +{{summary}} + +New lines of conversation: +{{new_lines}} + +New summary:`, +}); + +export class SummarizeMemory extends BaseMemory { + protected summary = ""; + protected template; + protected llm; + + constructor(config: SummarizeMemoryInput) { + super(); + this.template = config.template; + this.llm = config.llm; + } + + static { + this.register(); + } + + get messages(): BaseMessage[] { + const currentSummary = this.summary; + if (!currentSummary) { + return []; + } + + return [ + BaseMessage.of({ + role: "system", + text: currentSummary, + }), + ]; + } + + async add(message: BaseMessage) { + const prompt = this.template.render({ + summary: this.summary, + new_lines: `${message.role}: ${message.text}`, + }); + + const response = await this.llm.generate(prompt); + this.summary = response.getTextContent(); + } + + reset() { + this.summary = ""; + } + + createSnapshot() { + return { + summary: this.summary, + template: this.template, + llm: this.llm, + messages: shallowCopy(this.messages), + }; + } + + loadSnapshot(state: ReturnType) { + Object.assign(this, state); + } +} diff --git a/src/memory/tokenMemory.test.ts b/src/memory/tokenMemory.test.ts new file mode 100644 index 0000000..fc242df --- /dev/null +++ b/src/memory/tokenMemory.test.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { TokenMemory } from "@/memory/tokenMemory.js"; +import { BAMLLM } from "@/adapters/bam/llm.js"; +import { Client } from "@ibm-generative-ai/node-sdk"; +import { BAMChatLLM } from "@/adapters/bam/chat.js"; +import { BaseMessage, Role } from "@/llms/primitives/message.js"; +import * as R from "remeda"; +import { verifyDeserialization } from "@tests/e2e/utils.js"; + +describe("Token Memory", () => { + const getInstance = () => { + const llm = new BAMChatLLM({ + llm: new BAMLLM({ + client: new Client(), + modelId: "google/flan-ul2", + }), + config: { + messagesToPrompt: (messages) => + messages.map((msg) => `${msg.role}: ${msg.text}`).join("\n\n"), + }, + }); + + vi.spyOn(llm, "tokenize").mockImplementation(async (messages: BaseMessage[]) => ({ + tokensCount: R.sum(messages.map((msg) => [msg.role, msg.text].join("").length)), + })); + + return new TokenMemory({ + llm, + maxTokens: 1000, + }); + }; + + it("Serializes", async () => { + const instance = getInstance(); + await instance.add( + BaseMessage.of({ + text: "I am a Batman!", + role: Role.USER, + }), + ); + const serialized = instance.serialize(); + const deserialized = TokenMemory.fromSerialized(serialized); + verifyDeserialization(instance, deserialized); + }); +}); diff --git a/src/memory/tokenMemory.ts b/src/memory/tokenMemory.ts new file mode 100644 index 0000000..e394d44 --- /dev/null +++ b/src/memory/tokenMemory.ts @@ -0,0 +1,128 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseMessage } from "@/llms/primitives/message.js"; +import { BaseMemory, MemoryFatalError } from "@/memory/base.js"; +import { ChatLLM, ChatLLMOutput } from "@/llms/chat.js"; +import * as R from "remeda"; +import { shallowCopy } from "@/serializer/utils.js"; +import { removeFromArray } from "@/internals/helpers/array.js"; + +export interface Handlers { + removalSelector: (messages: BaseMessage[]) => BaseMessage; +} + +export interface TokenMemoryInput { + llm: ChatLLM; + maxTokens?: number; + capacityThreshold?: number; + handlers?: Handlers; +} + +export class TokenMemory extends BaseMemory { + public readonly messages: BaseMessage[] = []; + + protected llm: ChatLLM; + protected threshold = 1; + protected maxTokens: number | null = null; + protected tokensUsed = 0; + protected tokensByMessage = new WeakMap(); + public readonly handlers: Handlers; + + constructor(config: TokenMemoryInput) { + super(); + this.llm = config.llm; + this.maxTokens = config.maxTokens ?? null; + this.threshold = config.capacityThreshold ?? 0.75; + this.handlers = { + ...config?.handlers, + removalSelector: config.handlers?.removalSelector || ((messages) => messages[0]), + }; + if (!R.clamp({ min: 0, max: 1 })(this.threshold)) { + throw new TypeError('"capacityThreshold" must be a number in range (0, 1>'); + } + } + + static { + this.register(); + } + + async add(message: BaseMessage) { + if (this.maxTokens === null) { + const meta = await this.llm.meta(); + this.maxTokens = Math.ceil((meta.tokenLimit ?? Infinity) * this.threshold); + } + + const meta = await this.llm.tokenize([message]); + if (meta.tokensCount > this.maxTokens) { + throw new MemoryFatalError( + `Retrieved message (${meta.tokensCount} tokens) cannot fit inside current memory (${this.maxTokens} tokens)`, + ); + } + + while (this.tokensUsed > this.maxTokens - meta.tokensCount) { + const messageToDelete = this.handlers.removalSelector(this.messages); + const exists = removeFromArray(this.messages, messageToDelete); + + if (!messageToDelete || !exists) { + throw new MemoryFatalError('The "removalSelector" handler must return a valid message!'); + } + + const tokensCount = this.tokensByMessage.get(messageToDelete) ?? 0; + this.tokensUsed -= tokensCount; + } + + this.tokensUsed += meta.tokensCount; + this.tokensByMessage.set(message, meta.tokensCount); + this.messages.push(message); + } + + reset() { + for (const msg of this.messages) { + this.tokensByMessage.delete(msg); + } + this.messages.length = 0; + this.tokensUsed = 0; + } + + stats() { + return { + tokensUsed: this.tokensUsed, + maxTokens: this.maxTokens, + messagesCount: this.messages.length, + }; + } + + createSnapshot() { + return { + tokensUsed: this.tokensUsed, + llm: this.llm, + maxTokens: this.maxTokens, + threshold: this.threshold, + messages: shallowCopy(this.messages), + handlers: this.handlers, + tokensByMessage: this.messages + .map((message) => [message, this.tokensByMessage.get(message)]) + .filter(([_, value]) => value !== undefined) as [BaseMessage, number][], + }; + } + + loadSnapshot({ tokensByMessage, ...state }: ReturnType) { + Object.assign(this, state, { + tokensByMessage: new WeakMap(tokensByMessage), + }); + } +} diff --git a/src/memory/unconstrainedMemory.ts b/src/memory/unconstrainedMemory.ts new file mode 100644 index 0000000..c8e399b --- /dev/null +++ b/src/memory/unconstrainedMemory.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseMessage } from "@/llms/primitives/message.js"; +import { BaseMemory } from "@/memory/base.js"; +import { shallowCopy } from "@/serializer/utils.js"; + +export class UnconstrainedMemory extends BaseMemory { + public messages: BaseMessage[] = []; + + static { + this.register(); + } + + async add(message: BaseMessage) { + this.messages.push(message); + } + + reset() { + this.messages.length = 0; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } + + createSnapshot() { + return { + messages: shallowCopy(this.messages), + }; + } +} diff --git a/src/serializer/error.ts b/src/serializer/error.ts new file mode 100644 index 0000000..65f2f0a --- /dev/null +++ b/src/serializer/error.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { FrameworkError } from "@/errors.js"; + +export class SerializerError extends FrameworkError { + constructor(message: string, errors?: Error[]) { + super(message, errors, { + isFatal: true, + isRetryable: false, + }); + } +} diff --git a/src/serializer/serializer.test.ts b/src/serializer/serializer.test.ts new file mode 100644 index 0000000..a544258 --- /dev/null +++ b/src/serializer/serializer.test.ts @@ -0,0 +1,275 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { Serializer } from "@/serializer/serializer.js"; +import * as R from "remeda"; +import { FrameworkError } from "@/errors.js"; +import { AgentError } from "@/agents/base.js"; +import { BaseMessage } from "@/llms/primitives/message.js"; +import { beforeEach, expect } from "vitest"; +import { verifyDeserialization } from "@tests/e2e/utils.js"; +import { BAMLLM } from "@/adapters/bam/llm.js"; +import { BAMChatLLM } from "@/adapters/bam/chat.js"; +import { SerializerError } from "@/serializer/error.js"; + +describe("Serializer", () => { + it.each([ + 1, + true, + false, + null, + undefined, + BigInt(100), + "", + {}, + { a: "a", b: true }, + [], + [null, 1, null], + [1, 2, 3], + [1, "2", true, false], + Infinity, + -Infinity, + new RegExp(/.+/), + new Error("ohh no!"), + new FrameworkError(":(", [], { + isFatal: true, + isRetryable: true, + }), + new AgentError("Agent went wrong."), + /.+/g, + () => 1, + (...args: string[]) => { + return args; + }, + function (arg: string) { + return arg; + }, + function echo(arg: string) { + return arg; + }, + async function echoCurry(arg: string) { + return async () => arg; + }, + function* () {}, + function* xxx() {}, + new Date(), + new Set([1, "1", null]), + { + a: { b: { c: { d: { e: { f: { g: { e: { h: { value: 1, name: "X" } } } } } } } } }, + }, + ])("Handles basics '%s'", (value) => { + const json = Serializer.serialize(value); + const deserialized = Serializer.deserialize(json); + + if (R.isFunction(value)) { + expect(String(deserialized)).toStrictEqual(String(value)); + } else { + expect(deserialized).toEqual(value); + } + }); + + it("Handles various function definitions", () => { + const inputs = { + value: 10, + a(input: unknown) { + return [input, this?.value]; + }, + a1: function aX(input: unknown) { + return [input, this?.value]; + }, + a2: function (input: unknown) { + return [input, this?.value]; + }, + b: (input: unknown) => { + return [input]; + }, + b1: async (input: unknown) => { + return [input]; + }, + b2: async function bX(input: unknown) { + return [input, this?.value]; + }, + b3: async function (input: unknown) { + return [input, this?.value]; + }, + async b4(input: unknown) { + return [input, this?.value]; + }, + *c(input: unknown) { + yield [input, this?.value]; + }, + c1: function* cX(input: unknown) { + yield [input, this?.value]; + }, + c2: function* (input: unknown) { + yield [input, this?.value]; + }, + d: (input: unknown) => { + return input; + }, + d2: async (input: unknown) => { + return input; + }, + x(input: string): string { + return (() => input)(); + }, + y: (a: string) => (b: string) => a + b, + } as const; + + const serialized = Serializer.serialize(inputs); + const deserialized: typeof inputs = Serializer.deserialize(serialized); + expect(deserialized).toMatchInlineSnapshot(` + { + "a": [Function], + "a1": [Function], + "a2": [Function], + "b": [Function], + "b1": [Function], + "b2": [Function], + "b3": [Function], + "b4": [Function], + "c": [Function], + "c1": [Function], + "c2": [Function], + "d": [Function], + "d2": [Function], + "value": 10, + "x": [Function], + "y": [Function], + } + `); + + for (const [key, fn] of R.entries(inputs)) { + const target: typeof fn = deserialized[key]; + verifyDeserialization(fn, target); + + if (R.isFunction(fn) && R.isFunction(target)) { + const l = fn("A"); + const r = target("A"); + + if (R.isFunction(l) && R.isFunction(r)) { + expect(l("B")).toEqual(r("B")); + } else { + expect(fn("A")).toEqual(target("A")); + } + } else { + expect(fn).toBe(target); + } + } + }); + + it("Handles circular dependencies", () => { + const a = { name: "A" }; + const b = { name: "B" }; + Object.assign(a, { b }); + Object.assign(b, { a }); + + const input = { a, b }; + const json = Serializer.serialize(input); + const deserialized = Serializer.deserialize(json); + expect(deserialized).toStrictEqual(input); + }); + + it("Preserves references", () => { + const a = { name: "A" }; + const b = { name: "B", a }; + const c = { name: "C", a, b }; + const d = { name: "C", a, b, c }; + + const input = { a, b, c, d }; + const json = Serializer.serialize(input); + const deserialized = Serializer.deserialize(json); + + expect([b.a, c.a, d.a].every((v) => v === a)).toBeTruthy(); + expect([c.b, d.b].every((v) => v === b)).toBeTruthy(); + expect([d.c].every((v) => v === c)).toBeTruthy(); + expect(deserialized).toStrictEqual(input); + }); + + it("Handles self referencing", () => { + const a = { name: "A" }; + Object.assign(a, { a, b: a, c: a }); + + const input = { a }; + const json = Serializer.serialize(input); + const deserialized = Serializer.deserialize(json); + + expect(deserialized).toStrictEqual(input); + expect(deserialized.a === deserialized.a.a).toBeTruthy(); + expect(deserialized.a === deserialized.a.b).toBeTruthy(); + expect(deserialized.a === deserialized.a.c).toBeTruthy(); + }); + + describe("Loading", () => { + const json = JSON.stringify({ + __version: "0.0.0", + __root: { + message: { + __value: { + role: "system", + text: "a", + meta: { __value: {}, __serializer: true, __class: "Object", __ref: "2" }, + }, + __serializer: true, + __class: "BaseMessage", + __ref: "1", + }, + }, + }); + + beforeEach(() => { + Serializer.deregister(BaseMessage); + }); + + it("Automatically registers serializable classes", () => { + expect(Serializer.hasFactory("BaseMessage")).toBe(false); + const message = BaseMessage.of({ role: "system", text: "a", meta: {} }); + const input = { message }; + const json = Serializer.serialize(input); + const deserialized = Serializer.deserialize(json); + expect(deserialized).toStrictEqual(input); + }); + + it("Allows to re-register same class", () => { + expect(Serializer.hasFactory("BaseMessage")).toBe(false); + Serializer.registerSerializable(BaseMessage); + expect(Serializer.hasFactory("BaseMessage")).toBe(true); + Serializer.registerSerializable(BaseMessage); + }); + + it("Throws when required class is not present", () => { + expect(() => Serializer.deserialize(json)).toThrowError(SerializerError); + }); + + it("Parses when class is registered", () => { + Serializer.registerSerializable(BaseMessage); + expect(() => Serializer.deserialize(json)).not.toThrowError(); + }); + + it("Parses when class is passed as external parameter.", () => { + expect(() => Serializer.deserialize(json, [BaseMessage])).not.toThrowError(); + }); + + it("Handles nested functions", () => { + const { data } = JSON.parse( + `{"kind":"token","data":"{\\"__version\\":\\"0.0.35\\",\\"__root\\":{\\"__serializer\\":true,\\"__class\\":\\"Object\\",\\"__ref\\":\\"18\\",\\"__value\\":{\\"tokensUsed\\":{\\"__serializer\\":true,\\"__class\\":\\"Number\\",\\"__ref\\":\\"19\\",\\"__value\\":\\"66\\"},\\"config\\":{\\"__serializer\\":true,\\"__class\\":\\"Object\\",\\"__ref\\":\\"9\\",\\"__value\\":{\\"maxTokens\\":{\\"__serializer\\":true,\\"__class\\":\\"Number\\",\\"__ref\\":\\"10\\",\\"__value\\":\\"7000\\"},\\"llm\\":{\\"__serializer\\":true,\\"__class\\":\\"BAMChatLLM\\",\\"__ref\\":\\"3\\",\\"__value\\":{\\"llm\\":{\\"__serializer\\":true,\\"__class\\":\\"BAMLLM\\",\\"__ref\\":\\"4\\",\\"__value\\":{\\"modelId\\":\\"qwen/qwen2-72b-instruct\\",\\"parameters\\":{\\"__serializer\\":true,\\"__class\\":\\"Object\\",\\"__ref\\":\\"5\\",\\"__value\\":{}},\\"executionOptions\\":{\\"__serializer\\":true,\\"__class\\":\\"Object\\",\\"__ref\\":\\"6\\",\\"__value\\":{}}}},\\"config\\":{\\"__serializer\\":true,\\"__class\\":\\"Object\\",\\"__ref\\":\\"7\\",\\"__value\\":{\\"messagesToPrompt\\":{\\"__serializer\\":true,\\"__class\\":\\"Function\\",\\"__ref\\":\\"8\\",\\"__value\\":{\\"name\\":\\"messagesToPrompt\\",\\"fn\\":\\"messagesToPrompt(messages){return llamaTemplate.render({messages:messages.map(message=>({system:message.role===\\\\\\"system\\\\\\"?[message.text]:[],user:message.role===\\\\\\"user\\\\\\"?[message.text]:[],assistant:message.role===\\\\\\"assistant\\\\\\"?[message.text]:[]}))})}\\"}}}}}}}},\\"messages\\":{\\"__serializer\\":true,\\"__class\\":\\"Array\\",\\"__ref\\":\\"11\\",\\"__value\\":[{\\"__serializer\\":true,\\"__class\\":\\"BaseMessage\\",\\"__ref\\":\\"12\\",\\"__value\\":{\\"role\\":\\"user\\",\\"text\\":\\"My name is Tom, what is my name?\\",\\"meta\\":{\\"__serializer\\":true,\\"__class\\":\\"Undefined\\",\\"__ref\\":\\"13\\"}}},{\\"__serializer\\":true,\\"__class\\":\\"BaseMessage\\",\\"__ref\\":\\"14\\",\\"__value\\":{\\"role\\":\\"assistant\\",\\"text\\":\\"Your name is Tom.\\",\\"meta\\":{\\"__serializer\\":true,\\"__class\\":\\"Undefined\\",\\"__ref\\":\\"15\\"}}},{\\"__serializer\\":true,\\"__class\\":\\"BaseMessage\\",\\"__ref\\":\\"16\\",\\"__value\\":{\\"role\\":\\"user\\",\\"text\\":\\"My name is Tom, what is my name?\\",\\"meta\\":{\\"__serializer\\":true,\\"__class\\":\\"Undefined\\",\\"__ref\\":\\"17\\"}}},{\\"__serializer\\":true,\\"__class\\":\\"BaseMessage\\",\\"__ref\\":\\"14\\",\\"__value\\":\\"__ref\\"}]}}}}"}`, + ); + expect(() => + Serializer.deserialize(data, [BAMLLM, BAMChatLLM, BaseMessage]), + ).not.toThrowError(); + }); + }); +}); diff --git a/src/serializer/serializer.ts b/src/serializer/serializer.ts new file mode 100644 index 0000000..67bd9aa --- /dev/null +++ b/src/serializer/serializer.ts @@ -0,0 +1,561 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import * as R from "remeda"; +import { Serializable, SerializableClass } from "@/internals/serializable.js"; +import { AnyConstructable, ClassConstructor, NamedFunction } from "@/internals/types.js"; +import { SafeWeakMap, SafeWeakSet } from "@/internals/helpers/weakRef.js"; +import { deserializeError, serializeError } from "serialize-error"; +import { Version } from "@/version.js"; +import { + extractClassName, + getFunctionBinds, + isRootNode, + isSerializationRequired, + isSerializerNode, + primitiveToSerializableClass, + RefPlaceholder, + RootNode, + SerializerNode, + SerializerRefIdentifier, + traverseObject, + traverseWithUpdate, +} from "@/serializer/utils.js"; +import { SlidingTaskMap, Task, TaskState } from "promise-based-task"; +import { getProp, setProp } from "@/internals/helpers/object.js"; +import { halveString } from "@/internals/helpers/string.js"; +import { traversePrototypeChain } from "@/internals/helpers/prototype.js"; +import { CacheFn } from "@/cache/decoratorCache.js"; +import { SerializerError } from "@/serializer/error.js"; +import { ZodType } from "zod"; +import { toJsonSchema } from "@/internals/helpers/schema.js"; +import { createAbortController } from "@/internals/helpers/cancellation.js"; + +export interface SerializeFactory { + ref: ClassConstructor | NamedFunction; + createEmpty?: () => A; + updateInstance?: (instance: A, update: A) => void; + toPlain: (value: A) => B; + fromPlain: (value: B) => A; +} + +export class Serializer { + private static factories = new Map>(); + + static registerSerializable( + ref: SerializableClass, + processors?: Partial, A>, "ref">>, + ) { + return Serializer.register(ref, { + toPlain: (value) => value.createSnapshot(), + fromPlain: (value) => ref.fromSnapshot(value), + createEmpty: () => Object.create(ref.prototype), + updateInstance: (instance, update) => instance.loadSnapshot(update), + ...R.pickBy(processors ?? {}, R.isDefined), + }); + } + + static deregister(ref: ClassConstructor | NamedFunction) { + const className = extractClassName(ref); + Serializer.factories.delete(className); + } + + static register( + ref: ClassConstructor | NamedFunction, + processors: Omit, "ref">, + ): void { + const className = extractClassName(ref); + const currentFactory = Serializer.factories.get(className); + if (currentFactory && currentFactory.ref !== ref) { + throw new SerializerError(`Factory for class "${className}" already exists!`); + } + + Serializer.factories.set(className, { + ref, + ...processors, + }); + } + + static getFactory(clsName: string): SerializeFactory { + const factory = Serializer.factories.get(clsName); + if (!factory) { + throw new SerializerError(`Class "${clsName}" was not found!`); + } + return factory; + } + + static findFactory(value: unknown) { + let targetClass = extractClassName(value); + if (!Serializer.hasFactory(targetClass) && R.isFunction(value)) { + targetClass = Function.name; + } + + if (Serializer.hasFactory(targetClass)) { + const factory = Serializer.getFactory(targetClass); + return { targetClass, factory }; + } + + const excluded = new Set([null, Object.prototype]); + for (const proto of traversePrototypeChain(value, excluded)) { + const targetClass = extractClassName(proto); + + const factory = this.factories.get(targetClass); + if (factory) { + return { targetClass, factory }; + } + } + + throw new SerializerError(`Class "${targetClass}" was not found!`); + } + + static hasFactory(clsName: string) { + return Serializer.factories.has(clsName); + } + + static serialize(rawData: T): string { + const output = Serializer._createOutputBuilder(); + const getRefId = (() => { + let id = 0; + return () => { + id += 1; + return id; + }; + })(); + + const seen = new SafeWeakMap(); + + const assertValidSnapshot = (() => { + const allowedTypes = new Set([ + "Number", + "String", + "Object", + "Array", + "Undefined", + "Null", + "Boolean", + ]); + return (snapshot: unknown, factory: SerializeFactory) => { + const className = extractClassName(snapshot); + + if (!allowedTypes.has(className)) { + throw new SerializerError( + `Cannot serialize '${className}' as a top level property. "toPlain" function in "${extractClassName(factory.ref)}" should return an ${Array.from(allowedTypes.values()).join(",")}.`, + ); + } + }; + })(); + + const toSerializable = (rawValue: unknown): SerializerNode | unknown => { + if (seen.has(rawValue)) { + return seen.get(rawValue); + } + + if (rawValue instanceof Serializable && !this.hasFactory(extractClassName(rawValue))) { + const Class = rawValue.constructor as SerializableClass; + Serializer.registerSerializable(Class); + } + + const { targetClass, factory } = Serializer.findFactory(rawValue); + if (!isSerializationRequired(factory.ref)) { + return rawValue; + } + + const snapshot = factory.toPlain(rawValue); + assertValidSnapshot(snapshot, factory); + + const result: SerializerNode = { + __serializer: true, + __class: targetClass, + __ref: getRefId().toString(), + __value: snapshot, + }; + seen.set(rawValue, result); + + for (const node of traverseWithUpdate(snapshot)) { + const newValue = toSerializable(node.value); + if (newValue !== node.value) { + node.update(newValue); + } + } + return result; + }; + + const root: RootNode = { __version: Version, __root: rawData }; + traverseObject(root, ({ value, path }) => { + const content = toSerializable(value); + output.update(path, content); + }); + return output.toJSON(); + } + + /** @internal */ + static deserializeWithMeta( + raw: string, + extraClasses?: SerializableClass[], + ): RootNode { + extraClasses?.forEach((ref) => Serializer.registerSerializable(ref)); + + const output = Serializer._createOutputBuilder>(); + const instances = new Map(); + + const toDeserialize = (contentRaw: unknown) => { + if (isSerializerNode(contentRaw)) { + const clsName = String(contentRaw.__class); + const factory = Serializer.getFactory(clsName); + const rawData = contentRaw.__value; + + if (rawData === SerializerRefIdentifier) { + if (!instances.has(contentRaw.__ref!)) { + throw new SerializerError(`Missing reference "${contentRaw.__ref}"!`); + } + + const data = instances.get(contentRaw.__ref!)!; + if (data instanceof RefPlaceholder) { + return data.value; + } + return data; + } + + const traverseNested = () => { + for (const node of traverseWithUpdate(rawData)) { + const newValue = toDeserialize(node.value); + if (newValue !== node.value) { + node.update(newValue); + } + } + }; + + // Handle circular dependencies + const placeholder = new RefPlaceholder(contentRaw, factory); + instances.set(contentRaw.__ref!, placeholder); + traverseNested(); + instances.set(contentRaw.__ref!, placeholder.final); + return placeholder.final; + } + return contentRaw; + }; + + const root = JSON.parse(raw); + if (!isRootNode(root)) { + throw new SerializerError("Provided data cannot be deserialized due to malformed format!"); + } + + traverseObject( + root, + ({ value: contentRaw, path }) => { + output.update(path, toDeserialize(contentRaw)); + }, + (_obj) => isSerializerNode(_obj), + ); + return output.get(); + } + + static deserialize(raw: string, extraClasses?: SerializableClass[]): T { + const response = Serializer.deserializeWithMeta(raw, extraClasses); + return response.__root; + } + + protected static _createOutputBuilder() { + return { + _container: {} as T, + get() { + return this._container; + }, + update(path: readonly string[], value: any) { + setProp(this._container, path, value); + }, + toJSON() { + const seen = new SafeWeakSet(); + + return JSON.stringify(this._container, (key, value) => { + if (seen.has(value) && isSerializerNode(value)) { + const updated: SerializerNode = { + ...value, + __value: SerializerRefIdentifier, + }; + return updated; + } + + seen.add(value); + return value; + }); + }, + }; + } +} + +Serializer.register(Task, { + toPlain: (task) => ({ value: task.resolvedValue(), state: task.state }), + fromPlain: ({ state, value }) => { + const task = new Task(); + if (state === TaskState.RESOLVED) { + task.resolve(value); + } else if (state === TaskState.REJECTED) { + task.reject(value); + } else { + task.reject(new SerializerError("Task cannot be solved due to serialization.")); + } + task.resolve(value); + return task; + }, + createEmpty: () => new Task(), + updateInstance: (instance, value) => { + instance.resolve(value); + }, +}); +Serializer.register(SlidingTaskMap, { + toPlain: (value) => ({ + config: { + windowSize: value.windowSize, + ttl: value.ttl, + }, + entries: Array.from(value.entries()).filter(([_, task]) => { + if (task instanceof Task) { + return task.state === TaskState.RESOLVED; + } + return true; + }), + }), + fromPlain: ({ entries, config }) => { + const instance = new SlidingTaskMap(config.windowSize, config.ttl); + for (const [key, value] of entries) { + instance.set(key, value); + } + return instance; + }, + createEmpty: () => new SlidingTaskMap(1, 1000), + updateInstance: (instance, newInstance) => { + Object.assign(instance, { windowSize: newInstance.windowSize, ttl: newInstance.ttl }); + newInstance.forEach((value, key) => instance.set(key, value)); + }, +}); +Serializer.register(Map, { + toPlain: (value) => Array.from(value.entries()), + fromPlain: (value) => new Map(value), + createEmpty: () => new Map(), + updateInstance: (instance, update) => { + update.forEach(([key, value]) => instance.set(key, value)); + }, +}); +Serializer.register(Set, { + toPlain: (value) => Array.from(value.values()), + fromPlain: (value) => new Set(value), + createEmpty: () => new Set(), + updateInstance: (instance, update) => { + update.forEach((value) => instance.add(value)); + }, +}); +Serializer.register(Array, { + toPlain: (value) => value.slice(), + fromPlain: (value) => value.slice(), + createEmpty: () => [], + updateInstance: (value, update) => { + value.push(...update); + }, +}); +Serializer.register(Object, { + toPlain: (value) => Object.assign({}, value), + fromPlain: (value) => Object.assign({}, value), + createEmpty: () => ({}), + updateInstance: (value, update) => Object.assign(value, update), +}); +Serializer.register(Number, { + toPlain: (value) => Number(value).toString(), + fromPlain: (value) => Number(value), +}); +Serializer.register(String, { + toPlain: (value) => String(value), + fromPlain: (value) => String(value), +}); +Serializer.register(Boolean, { + toPlain: (value) => Boolean(value), + fromPlain: (value) => Boolean(value), +}); +Serializer.register(BigInt, { + toPlain: (value) => String(value), + fromPlain: (value) => BigInt(value), +}); +Serializer.register(Symbol, { + toPlain: (value) => value.description, + fromPlain: (value) => Symbol(value), +}); +Serializer.register(Date, { + toPlain: (value) => value.toISOString(), + fromPlain: (value) => new Date(value), +}); +Serializer.register(CacheFn, { + toPlain: (value) => value.createSnapshot(), + fromPlain: (value) => CacheFn.create(value.fn, value.options), +}); +Serializer.register(Function, { + toPlain: (value) => { + const isNative = getProp(global, [value.name]) === value; + + function isConstructor(obj: any): obj is AnyConstructable { + return obj && !!obj.prototype && !!obj.prototype.constructor.name; + } + + return { + name: value.name, + binds: getFunctionBinds(value).map((bound) => ({ + name: isConstructor(bound) ? Serializer.findFactory(bound).targetClass : bound.name, + value: "value" in bound ? bound.value : null, + })), + fn: isNative ? "" : String(value), + isNative, + }; + }, + fromPlain: (value) => { + if (value.isNative) { + return getProp(global, [value.name])!; + } + + const toParsableForm = (): string => { + if (value.fn.match(/^\s*function.*?\(/)) { + return value.fn; + } + + const arrowStart = value.fn.indexOf("=>"); + const bracketStart = value.fn.indexOf("("); + + const [fnPrefix, fnContent = ""] = + bracketStart === -1 || bracketStart > arrowStart + ? halveString(value.fn, "{", true) + : halveString(value.fn, "(", true); + + const reservedSymbols = new Set(["async", "*"]); + + const nonReservedSymbols = fnPrefix + .trim() + .split(" ") + .map((x) => x.trim()) + .filter(Boolean) + .every((content) => reservedSymbols.has(content)); + + if (nonReservedSymbols) { + return value.fn; + } + + const name = value.name && fnPrefix.includes(value.name) ? value.name : ""; + let parameters = ""; + if (!fnContent.startsWith("(")) { + if (fnPrefix.includes("(")) { + parameters = (fnPrefix.match(/\((.+)\)/) ?? [null, ""])[1]; + } else { + parameters = fnPrefix.replace("=>", "").replace("async", "").replace("*", ""); + } + } + + return [ + fnPrefix.includes("async") && "async ", + "function", + fnPrefix.includes("*") && "*", + name && ` ${name}`, + parameters && `(${parameters})`, + fnContent, + ] + .filter(Boolean) + .join(""); + }; + + const binds = value?.binds ?? []; + const fn = Function( + ...binds.map((b) => b.name), + `return ${toParsableForm()}`, + )(...binds.map((b) => (b.value ? b.value : Serializer.getFactory(b.name)!.ref))); + + if (value.name) { + Object.defineProperty( + fn, + "name", + Object.assign({}, Object.getOwnPropertyDescriptor(Object.getPrototypeOf(fn), "name"), { + value: value.name, + }), + ); + } + Object.defineProperty( + fn, + "toString", + Object.assign({}, Object.getOwnPropertyDescriptor(Object.getPrototypeOf(fn), "name"), { + value: () => value.fn, + }), + ); + return fn; + }, +}); + +Serializer.register(Error, { + toPlain: (value) => serializeError(value), + fromPlain: (value) => deserializeError(value), +}); +Serializer.register(RegExp, { + toPlain: (value) => ({ source: value.source, flags: value.flags }), + fromPlain: (value) => new RegExp(value.source, value.flags), +}); +Serializer.register(WeakSet, { + toPlain: () => {}, + fromPlain: () => new WeakSet(), + createEmpty: () => new WeakSet(), + updateInstance: () => {}, +}); +Serializer.register(WeakMap, { + toPlain: () => {}, + fromPlain: () => new WeakMap(), + createEmpty: () => new WeakMap(), + updateInstance: () => {}, +}); +Serializer.register(WeakRef, { + toPlain: () => {}, + fromPlain: () => new WeakRef({}), +}); +Serializer.register(primitiveToSerializableClass(undefined), { + toPlain: () => undefined, + fromPlain: () => undefined, +}); +Serializer.register(primitiveToSerializableClass(null), { + toPlain: () => null, + fromPlain: () => null, +}); +// @ts-expect-error +Serializer.register(ZodType, { + toPlain: (value) => toJsonSchema(value), + fromPlain: () => { + throw new Error("JSONSchema cannot be converted to zod!"); + }, +}); +Serializer.register(Buffer, { + toPlain: (value: Buffer) => value.toString("base64"), + fromPlain: (data) => Buffer.from(data, "base64"), +}); +Serializer.register(AbortSignal, { + toPlain: (value: AbortSignal) => ({ + aborted: value.aborted, + reason: value.reason, + }), + fromPlain: (data) => { + const controller = createAbortController(); + if (data.aborted) { + controller.abort(data.reason); + } + return controller.signal; + }, +}); +Serializer.register(AbortController, { + toPlain: (value: AbortController) => ({ + signal: value.signal, + }), + fromPlain: (data) => createAbortController(data.signal), +}); diff --git a/src/serializer/utils.ts b/src/serializer/utils.ts new file mode 100644 index 0000000..e240b37 --- /dev/null +++ b/src/serializer/utils.ts @@ -0,0 +1,224 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import * as R from "remeda"; +import { SafeWeakSet } from "@/internals/helpers/weakRef.js"; +import { AnyConstructable, AnyFn, ClassConstructor, NamedFunction } from "@/internals/types.js"; +import { SerializeFactory } from "@/serializer/serializer.js"; +import { Cache } from "@/cache/decoratorCache.js"; +import { getProp, hasProp } from "@/internals/helpers/object.js"; +import { isDirectInstanceOf } from "@/internals/helpers/prototype.js"; +import { SerializerError } from "@/serializer/error.js"; + +export const SerializerRefIdentifier = "__ref"; +export class RefPlaceholder { + private static EmptyPlaceholder = Symbol(); + private partialResult: T = RefPlaceholder.EmptyPlaceholder as T; + + constructor( + protected readonly node: SerializerNode, + protected readonly factory: SerializeFactory, + ) {} + + get value() { + if (this.partialResult !== RefPlaceholder.EmptyPlaceholder) { + return this.partialResult; + } + + const { createEmpty, updateInstance } = this.factory; + if (!createEmpty || !updateInstance) { + throw new SerializerError("Circular dependency has been detected!"); + } + + this.partialResult = createEmpty(); + return this.partialResult; + } + + @Cache() + get final() { + const finalInstance = this.factory.fromPlain(this.node.__value); + if (this.partialResult === RefPlaceholder.EmptyPlaceholder) { + return finalInstance; + } + + this.factory.updateInstance!(this.partialResult, finalInstance); + return this.partialResult; + } +} + +export interface SerializerNode { + __class: string; + __ref: string; + __value: unknown; + __serializer: true; +} +export function isSerializerNode(data: unknown): data is SerializerNode { + return R.isPlainObject(data) && hasProp(data as unknown as SerializerNode, "__serializer"); +} + +export interface RootNode { + __version: string; + __root: T; +} + +export function isRootNode(data: unknown): data is RootNode { + return ( + R.isPlainObject(data) && + hasProp(data as unknown as RootNode, "__root") && + hasProp(data as unknown as RootNode, "__version") + ); +} + +export function extractClassName(value: unknown): string { + if (R.isObjectType(value) && "constructor" in value) { + return value.constructor.name; + } + if (R.isFunction(value)) { + return value.name || value.constructor?.name || Function.name; + } + return extractClassName(primitiveToSerializableClass(value)); +} + +const ClassByValueType = { + string: String, + number: Number, + bigint: BigInt, + boolean: Boolean, + symbol: Symbol, + undefined: class Undefined {}, + null: class Null {}, + object: Object, + function: Function, +} as const; + +export function primitiveToSerializableClass(value: unknown) { + const type = value === null ? "null" : typeof value; + return ClassByValueType[type]; +} + +type TraverseObjectFn = (el: { key: string; path: readonly string[]; value: any }) => void; +export function traverseObject( + obj: unknown, + handler: TraverseObjectFn, + stopFn?: (_obj: unknown) => boolean, +) { + const seen = new SafeWeakSet(); + + const traverse = (_obj: unknown, path: readonly string[] = []) => { + if (!R.isPlainObject(_obj) || seen.has(_obj)) { + return; + } + seen.add(_obj); + + if (stopFn?.(_obj)) { + return; + } + + for (const [key, value] of Object.entries(_obj)) { + traverse(value, path.concat(key)); + + handler({ + key, + value, + path: path.concat(key), + }); + } + }; + + return traverse(obj, []); +} + +export function isSerializationRequired(factory: ClassConstructor | NamedFunction) { + const primitive: unknown[] = [ClassByValueType.string, ClassByValueType.boolean]; + return !primitive.includes(factory); +} + +export function* traverseWithUpdate( + _obj: unknown, +): Generator<{ value: T; update: (value: T) => void }> { + if (Array.isArray(_obj)) { + for (const [idx, value] of _obj.entries()) { + yield { + value, + update: (newValue) => { + _obj[idx] = newValue; + }, + }; + } + } else if (R.isPlainObject(_obj)) { + for (const [key, value] of R.entries(_obj)) { + yield { + value: value as T, + update: (newValue) => { + _obj[key] = newValue; + }, + }; + } + } else if (_obj instanceof Map) { + for (const [key, value] of _obj.entries()) { + yield { + value, + update: (newValue) => { + _obj.set(key, newValue); + }, + }; + } + } else if (_obj instanceof Set) { + for (const value of _obj.values()) { + yield { + value, + update: (newValue) => { + _obj.delete(value); + _obj.add(newValue); + }, + }; + } + } +} + +export function shallowCopy(value: T): T { + if (R.isPlainObject(value)) { + return Object.assign({}, value); + } else if (R.isArray(value)) { + return value.slice() as T; + } else if (isDirectInstanceOf(value, Map)) { + return new Map(value.entries()) as T; + } else if (isDirectInstanceOf(value, Set)) { + return new Set(value.values()) as T; + } else if (isDirectInstanceOf(value, Date)) { + return new Date(value) as T; + } + return value; +} + +type Bounded = + | { + name: string; + value: any; + } + | AnyConstructable; + +export function toBoundedFunction(fn: T, binds: [Bounded, ...Bounded[]]) { + Object.assign(fn, { [toBoundedFunction.symbol]: binds }); + return fn; +} +toBoundedFunction.symbol = Symbol("bounded"); + +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +export function getFunctionBinds(fn: T) { + const target = getProp(fn, [toBoundedFunction.symbol], []); + return target as Bounded[]; +} diff --git a/src/template.test.ts b/src/template.test.ts new file mode 100644 index 0000000..1c7853c --- /dev/null +++ b/src/template.test.ts @@ -0,0 +1,191 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { PromptTemplateError, PromptTemplate, ValidationPromptTemplateError } from "@/template.js"; + +describe("Prompt Template", () => { + describe("Rendering", () => { + it.each([1, "1", true, false, "Hey!"])("Handles primitives (%s)", (value) => { + const template = new PromptTemplate({ + variables: ["value"], + template: `Input: {{value}}!`, + }); + + expect(template.render({ value })).toStrictEqual(`Input: ${value}!`); + }); + + it.each([[[]], [[1, 2, 3]], [["a", "b", "c"]]])("Handles arrays (%s)", (values) => { + const template = new PromptTemplate({ + variables: ["values"], + template: `Input: {{#values}}{{.}}{{/values}}`, + }); + + expect(template.render({ values })).toStrictEqual(`Input: ${values.join("")}`); + }); + + it.each([[[{ name: "Tomas" }, { name: "Lukas" }, { name: "Alex" }]], [[]], [[{}]]])( + "Handles plain objects (%s)", + (values) => { + const template = new PromptTemplate({ + variables: ["values"], + template: `{{#values}}Name: {{name}}\n{{/values}}`, + }); + + expect(template.render({ values })).toMatch( + values.map((value: any) => `Name: ${value?.name ?? ""}\n`).join(""), + ); + }, + ); + + it("Handles multiple occurrences of same variable", () => { + const template = new PromptTemplate({ + variables: ["firstName", "lastName"], + template: `First Name: {{firstName}}. Last Name {{lastName}}. Full Name: {{firstName}} {{lastName}}.`, + }); + expect(template.render({ firstName: "A", lastName: "B" })).toMatchInlineSnapshot( + `"First Name: A. Last Name B. Full Name: A B."`, + ); + }); + + it("Uses the value from defaults", () => { + const template = new PromptTemplate({ + variables: ["name"], + template: `{{name}}`, + defaults: { + name: "Alex", + }, + }); + expect( + template.render({ + name: PromptTemplate.defaultPlaceholder, + }), + ).toStrictEqual("Alex"); + }); + }); + + describe("Validation", () => { + it.each([null, undefined])("Validates required variables (%s)", (value) => { + const template = new PromptTemplate({ + variables: ["value"], + template: `Input: {{value}}!`, + }); + + expect(() => template.render({ value })).toThrowError(PromptTemplateError); + }); + + it.each([null, undefined])("Validates only required variables (%s)", (value) => { + const template = new PromptTemplate({ + variables: ["value"], + optionals: ["value"], + template: `Input: {{value}}!`, + }); + + expect(template.render({ value })).toBeTruthy(); + }); + + it.each([ + { + variables: ["name"], + template: `Name: {{name}}; Age: {{age}}`, + }, + { + variables: ["name"], + optionals: ["age"], + template: `Name: {{name}}; Age: {{age}}`, + }, + { + variables: [], + template: `{{#values}}{{.}}{{/values}}`, + }, + ])("Throws when variable is not explicitly mentioned", ({ variables, optionals, template }) => { + expect( + () => + new PromptTemplate({ + variables, + optionals, + template, + }), + ).toThrowError(PromptTemplateError); + }); + + it.each([ + [ + { + template: `{{#value}}{{.}}{{/value}}`, + values: [true, false, {}, new Map(), new Set(), "", 1, false], + }, + ], + [ + { + template: `{{value}}`, + values: [{}, [], new Map(), new Set()], + }, + ], + [ + { + template: `{{value}}`, + values: [() => ({})], + }, + ], + ])("Throws wrong data-type is passed (%s)", ({ template: templateRaw, values }) => { + const template = new PromptTemplate({ + variables: ["value"], + template: templateRaw, + }); + + for (const value of values) { + expect(() => template.render({ value })).toThrowError(ValidationPromptTemplateError); + } + }); + }); + + describe("Customization", () => { + const createTemplate = () => { + return new PromptTemplate({ + template: `Hello <>!`, + variables: ["name"], + optionals: [], + customTags: ["<<", ">>"], + escape: false, + defaults: {}, + }); + }; + + it("Clones", () => { + const template = createTemplate(); + const cloned = template.clone(); + expect(cloned).toEqual(template); + }); + + it("Forks", () => { + const template = new PromptTemplate({ + template: `Hello <>!`, + variables: ["name"], + optionals: [], + customTags: ["<<", ">>"], + escape: false, + defaults: {}, + }); + const forked = template.fork((config) => ({ + ...config, + template: "Hello {{name}}!", + customTags: ["{{", "}}"], + })); + + expect(template.render({ name: "Tomas" })).toEqual(forked.render({ name: "Tomas" })); + }); + }); +}); diff --git a/src/template.ts b/src/template.ts new file mode 100644 index 0000000..3c6ede5 --- /dev/null +++ b/src/template.ts @@ -0,0 +1,203 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { FrameworkError } from "@/errors.js"; +import { ObjectLike } from "@/internals/types.js"; +import * as R from "remeda"; +import Mustache, { TemplateSpans } from "mustache"; +import { Serializable } from "@/internals/serializable.js"; +import { hasProp } from "@/internals/helpers/object.js"; +import { shallowCopy } from "@/serializer/utils.js"; + +export type PromptTemplateRenderFn = () => any; +export type PromptTemplateValue = any | PromptTemplateRenderFn; +export type PromptTemplateRenderInput = Record; + +export interface PromptTemplateInput { + template: string; + customTags?: [string, string]; + escape?: boolean; + variables: readonly K[]; + optionals?: readonly Partial[]; + defaults?: Partial>; +} + +export class PromptTemplateError extends FrameworkError { + template: PromptTemplate; + + constructor(message: string, template: PromptTemplate, context?: ObjectLike) { + super(message, [], { + context, + }); + this.template = template; + } +} + +export class ValidationPromptTemplateError extends PromptTemplateError {} + +type VariableType = "primitive" | "array" | "inverse"; +const typeToVariableType: Record = { + "#": "array", + "^": "inverse", +}; + +interface ParsedTemplateSpans { + variables: Map; + raw: TemplateSpans; +} + +export class PromptTemplate extends Serializable { + protected config: Required>; + protected parsed: ParsedTemplateSpans; + + constructor(config: PromptTemplateInput) { + super(); + this.config = { + ...config, + escape: Boolean(config.escape), + customTags: config.customTags ?? ["{{", "}}"], + optionals: config.optionals ?? [], + defaults: config.defaults ?? {}, + }; + this.parsed = this._parse(); + } + + static { + this.register(); + } + + static readonly defaultPlaceholder = Symbol("default"); + + protected _parse(): ParsedTemplateSpans { + const _raw = Mustache.parse(this.config.template, this.config.customTags); + const result: ParsedTemplateSpans = { + raw: _raw, + variables: new Map(), + }; + + const definedVariables = new Set(this.config.variables); + const seenVariables = new Set(); + + // eslint-disable-next-line prefer-const + for (let [type, name] of _raw) { + if (type === "text") { + continue; + } + if (name.includes(".")) { + name = name.substring(0, name.indexOf(".")); + } + if (!definedVariables.has(name) && !seenVariables.has(name)) { + throw new PromptTemplateError( + `Variable "${name}" was found in template but not in initial definition!`, + this, + ); + } + seenVariables.add(name); + definedVariables.delete(name); + + result.variables.set(name as K, { + type: typeToVariableType[type] ?? "primitive", + }); + } + + return result; + } + + fork(customizer?: (config: Required>) => PromptTemplateInput) { + const config = R.clone(this.config); + return new PromptTemplate(customizer?.(config) ?? config); + } + + render(inputs: PromptTemplateRenderInput): string { + const allowedKeys = Array.from(this.parsed.variables.keys()); + const rawView: Record = R.merge( + R.pick(this.config.defaults ?? {}, allowedKeys), + R.pick(inputs, allowedKeys), + ); + + const view: Record = {}; + for (const [key, { type }] of this.parsed.variables) { + const isOptional = this.config.optionals.includes(key); + + let value = rawView[key]; + if (value === PromptTemplate.defaultPlaceholder) { + if (!hasProp(this.config.defaults, key)) { + throw new PromptTemplateError(`No default value exists for variable "${key}"`, this); + } + value = this.config.defaults?.[key]; + } + + if (!isOptional && R.isNullish(value)) { + throw new PromptTemplateError(`Empty value has been passed for variable "${key}".`, this, { + value, + inputs, + }); + } + + if (!R.isFunction(value)) { + if (type === "array" && !R.isArray(value)) { + throw new ValidationPromptTemplateError( + `Invalid value provided for variable "${key}" (expected array)`, + this, + ); + } else if (type === "primitive" && R.isObjectType(value)) { + throw new ValidationPromptTemplateError( + `Invalid value provided for variable "${key}" (expected literal)`, + this, + ); + } + } + + view[key] = value; + } + + const output = Mustache.render( + this.config.template, + view, + {}, + { + tags: this.config.customTags, + ...(!this.config.escape && { + escape: R.identity(), + }), + }, + ); + + if (output.match(/\[object (.+)]/)) { + throw new ValidationPromptTemplateError( + "Rendered template contains incorrectly serialized value!", + this, + { + output, + }, + ); + } + + return output; + } + + loadSnapshot(data: ReturnType) { + this.config = data.config; + this.parsed = this._parse(); + } + + createSnapshot() { + return { + config: shallowCopy(this.config), + parsed: this.parsed, + }; + } +} diff --git a/src/tools/arxiv.ts b/src/tools/arxiv.ts new file mode 100644 index 0000000..8198d0b --- /dev/null +++ b/src/tools/arxiv.ts @@ -0,0 +1,265 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { + BaseToolOptions, + BaseToolRunOptions, + JSONToolOutput, + Tool, + ToolError, + ToolInput, + ToolInputValidationError, +} from "@/tools/base.js"; +import { z } from "zod"; +import { createURLParams } from "@/internals/fetcher.js"; +import { Cache } from "@/cache/decoratorCache.js"; +import { XMLParser } from "fast-xml-parser"; +import { getProp } from "@/internals/helpers/object.js"; +import { isDefined, isEmpty, pickBy } from "remeda"; +import { castArray } from "@/internals/helpers/array.js"; +import { ValueOf } from "@/internals/types.js"; +import { AnyToolSchemaLike } from "@/internals/helpers/schema.js"; + +type ToolOptions = BaseToolOptions; +type ToolRunOptions = BaseToolRunOptions; + +export const SortType = { + RELEVANCE: "relevance", + LAST_UPDATED_DATE: "lastUpdatedDate", + SUBMITTED_DATE: "submittedDate", +} as const; + +export const SortOrder = { + ASCENDING: "ascending", + DESCENDING: "descending", +} as const; + +export const FilterType = { + ALL: "all", + TITLE: "title", + AUTHOR: "author", + ABSTRACT: "abstract", + COMMENT: "comment", + JOURNAL_REFERENCE: "journal_reference", + SUBJECT_CATEGORY: "subject_category", + REPORT_NUMBER: "report_number", +} as const; + +const FilterTypeMapping: Record, string> = { + all: "all", + title: "ti", + author: "au", + abstract: "abs", + comment: "co", + journal_reference: "jr", + subject_category: "cat", + report_number: "rn", +}; + +export const Separators = { + AND: "+AND+", + OR: "+OR+", + ANDNOT: "+ANDNOT+", +} as const; + +export interface ArXivResponse { + totalResults: number; + startIndex: number; + itemsPerPage: number; + entries: { + id: string; + title: string; + summary: string; + published: string; + updated: string; + authors: { name: string; affiliation: string[] }[]; + doi: string; + comment: string; + journalReference: string; + primaryCategory: string; + categories: string[]; + links: string[]; + }[]; +} + +export class ArXivToolOutput extends JSONToolOutput { + isEmpty(): boolean { + return !this.result || this.result.totalResults === 0 || this.result.entries.length === 0; + } +} + +const extractId = (value: string) => + value.replace("https://arxiv.org/abs/", "").replace("https://arxiv.org/pdf/", ""); + +export class ArXivTool extends Tool { + name = "ArXiv"; + description = `Retrieves research articles published on arXiv including related metadata.`; + + @Cache() + inputSchema() { + const entrySchema = z.object({ + field: z.nativeEnum(FilterType).default(FilterType.ALL), + value: z.string().min(1), + }); + + return z.object({ + id_list: z.array(z.string().min(1)).nullish(), + filters: z + .array( + z.object({ + include: z + .array(entrySchema) + .nonempty() + .describe(`Entries are combined as a logical 'AND'`), + exclude: z + .array(entrySchema) + .optional() + .describe( + `Entries are combined with those mentioned in 'include' as a logical 'ANDNOT`, + ), + }), + ) + .describe( + `Entries are combined as a logical 'OR'. Note: filtering by date is not supported.`, + ) + .nullish() + .default([]), + start: z.number().int().min(0).default(0), + maxResults: z.number().int().min(1).max(100).default(5), + sort: z + .object({ + type: z.nativeEnum(SortType).default(SortType.RELEVANCE), + order: z.nativeEnum(SortOrder).default(SortOrder.DESCENDING), + }) + .default({ + type: SortType.RELEVANCE, + order: SortOrder.DESCENDING, + }), + }); + } + + static { + this.register(); + } + + protected validateInput( + schema: AnyToolSchemaLike, + rawInput: unknown, + ): asserts rawInput is ToolInput { + super.validateInput(schema, rawInput); + if (isEmpty(rawInput.id_list ?? []) && isEmpty(rawInput.filters ?? [])) { + throw new ToolInputValidationError( + `Property 'filters' must be provided and non empty if the 'id_list' property is not provided!`, + ); + } + } + + protected _prepareParams(input: ToolInput) { + return createURLParams({ + start: input.start, + max_results: input.maxResults, + sortBy: input.sort.type, + sortOrder: input.sort.order, + id_list: isEmpty(input.id_list ?? []) ? undefined : input.id_list?.map(extractId), + search_query: input.filters + ?.map(({ include, exclude = [] }) => + [ + include + .map((tag) => `${FilterTypeMapping[tag.field]}:${tag.value}`) + .join(Separators.AND), + exclude + .map((tag) => `${FilterTypeMapping[tag.field]}:${tag.value}`) + .join(Separators.ANDNOT), + ] + .filter(Boolean) + .join(Separators.ANDNOT), + ) + .filter(Boolean) + .join(Separators.OR), + }); + } + + protected async _run(input: ToolInput, options?: BaseToolRunOptions) { + const params = this._prepareParams(input); + const url = `https://export.arxiv.org/api/query?${decodeURIComponent(params.toString())}`; + const response = await fetch(url, { + signal: options?.signal, + }); + const data = await this._parseResponse(response); + return new ArXivToolOutput(data); + } + + protected async _parseResponse(response: Response): Promise { + const parser = new XMLParser({ + allowBooleanAttributes: true, + alwaysCreateTextNode: false, + attributeNamePrefix: "@_", + attributesGroupName: false, + cdataPropName: "#cdata", + ignoreAttributes: true, + numberParseOptions: { hex: false, leadingZeros: true }, + parseAttributeValue: false, + parseTagValue: true, + preserveOrder: false, + removeNSPrefix: true, + textNodeName: "#text", + trimValues: true, + }); + + const text = await response.text(); + const parsedData = parser.parse(text); + + if (!response.ok) { + throw new ToolError("Request to ArXiv API has failed!", [ + new Error(JSON.stringify(getProp(parsedData, ["entry"], parsedData), null, 2)), + ]); + } + + let entries: Record[] = getProp(parsedData, ["feed", "entry"], []); + entries = castArray(entries); + + return { + totalResults: Math.max(getProp(parsedData, ["feed", "totalResults"], 0), entries.length), + startIndex: getProp(parsedData, ["feed", "startIndex"], 0), + itemsPerPage: getProp(parsedData, ["feed", "itemsPerPage"], 0), + entries: entries.map((entry) => + pickBy( + { + id: extractId(entry.id), + url: entry.id, + title: entry.title, + summary: entry.summary, + published: entry.published, + updated: entry.updated, + authors: castArray(entry.author) + .filter(Boolean) + .map((author: any) => ({ + name: author.name, + affiliation: castArray(author.affiliation ?? []), + })), + doi: entry.doi, + comment: entry.comment, + journalReference: entry.journal_ref, + primaryCategory: entry.primary_category, + categories: castArray(entry.category).filter(Boolean), + links: castArray(entry.link).filter(Boolean), + }, + isDefined, + ), + ), + }; + } +} diff --git a/src/tools/base.test.ts b/src/tools/base.test.ts new file mode 100644 index 0000000..fff20ec --- /dev/null +++ b/src/tools/base.test.ts @@ -0,0 +1,516 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { + BaseToolOptions, + BaseToolRunOptions, + DynamicTool, + JSONToolOutput, + StringToolOutput, + Tool, + ToolError, + ToolInput, +} from "@/tools/base.js"; +import { AnyFn } from "@/internals/types.js"; +import { setTimeout } from "node:timers/promises"; +import { beforeEach, expect, vi } from "vitest"; +import { z } from "zod"; + +import { SlidingCache } from "@/cache/slidingCache.js"; +import { Serializer } from "@/serializer/serializer.js"; +import { verifyDeserialization } from "@tests/e2e/utils.js"; + +describe("Base Tool", () => { + beforeEach(() => { + vi.clearAllTimers(); + }); + + class FatalError extends Error {} + + const createDummyTool = (options: BaseToolOptions, fn: T) => { + class DummyTool extends Tool { + name = "DummyTool"; + description = "DummyTool description"; + + inputSchema() { + return z.object({ query: z.string() }); + } + + protected async _run( + { query }: ToolInput, + options?: BaseToolRunOptions, + ): Promise { + const result = await fn(query, options); + return new StringToolOutput(result); + } + } + + const instance = new DummyTool(options); + Serializer.deregister(DummyTool); + return vi.mocked(instance) as typeof instance; + }; + + interface SettingsTestCase { + query: string; + output: string; + runOptions?: BaseToolRunOptions; + } + it.each([ + { + query: "Hello!", + output: "World!", + runOptions: undefined, + }, + ] as SettingsTestCase[])( + "Implementation is correctly called (%o)", + async ({ query, output, runOptions }) => { + const handler = vi.fn(); + const tool = createDummyTool({}, handler); + + handler.mockResolvedValue(output); + await expect(tool.run({ query }, runOptions)).resolves.toBeTruthy(); + expect(handler).toBeCalledTimes(1); + expect(handler).toBeCalledWith(query, runOptions); + }, + ); + + interface Retries { + options: { + fatalErrors: BaseToolOptions["fatalErrors"]; + retryOptions: BaseToolOptions["retryOptions"]; + }; + errors?: Error[]; + expected: { + totalCalls: number; + }; + } + it.each([ + { + options: { + fatalErrors: [], + retryOptions: { + maxRetries: 5, + factor: 0, + }, + }, + expected: { + totalCalls: 6, + }, + }, + { + options: { + fatalErrors: [FatalError], + retryOptions: { + maxRetries: 100, + factor: 0, + }, + }, + errors: [new Error("Test"), new FatalError("Fatal!")], + expected: { + totalCalls: 2, + }, + }, + { + options: { + fatalErrors: [], + retryOptions: { + maxRetries: 0, + factor: 0, + }, + }, + expected: { + totalCalls: 1, + }, + }, + { + options: { + fatalErrors: [Error], + retryOptions: { + maxRetries: 5, + factor: 0, + }, + }, + errors: [new Error("Test")], + expected: { + totalCalls: 1, + }, + }, + ] as Retries[])("Correctly handles retries (%i)", async ({ errors, options, expected }) => { + const handler = vi.fn( + (() => { + if (errors) { + return () => { + const err = errors.shift(); + expect(err).toBeDefined(); + throw err; + }; + } else { + return () => { + throw new Error("Error!"); + }; + } + })(), + ); + + const tool = createDummyTool(options, handler); + await expect(tool.run({ query: "Hello!" })).rejects.toThrowError( + new ToolError('Tool "DummyTool" has occurred an error!', errors), + ); + expect(handler).toBeCalledTimes(expected.totalCalls); + }); + + it("Aborts on signal", async () => { + vi.useRealTimers(); + + const handler = vi.fn(); + handler.mockImplementationOnce(async () => { + await setTimeout(50); + throw new Error("First error!"); + }); + handler.mockImplementationOnce(async () => { + await setTimeout(200); + expect(true).toBe(false); // should never be called! + }); + + const tool = createDummyTool( + { + retryOptions: { + maxRetries: 5, + factor: 0, + }, + }, + handler, + ); + + const controller = new AbortController(); + const abortError = new Error("Action has been cancelled!"); + void setTimeout(150).then(() => controller.abort(abortError)); + + try { + const task = tool.run({ query: "Hello!" }, { signal: controller.signal }); + await task; + expect(true).toBe(false); + } catch (e) { + expect(e).toBeInstanceOf(ToolError); + expect((e as ToolError).errors[0]).toBe(abortError); + } + expect(handler).toBeCalledTimes(2); + }); + + interface CacheTestInput { + options: BaseToolOptions; + inputs: { + query: string; + options: BaseToolOptions; + cached: boolean; + sleep?: number; + }[]; + } + it.each([ + { + options: { cache: new SlidingCache({ size: 10 }) }, + inputs: [ + { query: "IBM Research", options: {}, cached: false }, + { query: "IBM Research", options: {}, cached: true }, + { query: "IBM Research", options: {}, cached: true }, + { query: "IBM Software", options: {}, cached: false }, + { + query: "Who was Alan Turing?", + options: {}, + cached: false, + }, + ], + }, + { + options: { cache: new SlidingCache({ size: 2 }) }, + inputs: [ + { query: "A", options: {}, cached: false }, + { query: "B", options: {}, cached: false }, + { query: "A", options: {}, cached: true }, + { query: "B", options: {}, cached: true }, + { query: "C", options: {}, cached: false }, + { query: "B", options: {}, cached: true }, + { query: "C", options: {}, cached: true }, + { query: "A", options: {}, cached: false }, + ], + }, + { + options: { cache: new SlidingCache({ size: 10 }) }, + inputs: [ + { query: "A", cached: false }, + { query: "A", cached: true }, + null, // clear cache + { query: "A", cached: false }, + { query: "A", cached: true }, + ], + }, + { + options: { cache: new SlidingCache({ size: 10, ttl: 1000 }) }, + inputs: [ + { query: "A", cached: false }, + { query: "B", cached: false }, + { query: "A", cached: true }, + { query: "B", cached: true }, + { sleep: 1500 }, + { query: "A", cached: false }, + { query: "B", cached: false }, + { query: "A", cached: true }, + { query: "B", cached: true }, + ], + }, + ] as CacheTestInput[])("Retrieves data from cache (%o)", async ({ options, inputs }) => { + vi.useFakeTimers(); + const handler = vi.fn((query) => query); + const tool = createDummyTool(options, handler); + + for (const input of inputs) { + if (input === null) { + await tool.clearCache(); + continue; + } + if (input.sleep) { + vi.advanceTimersByTime(input.sleep); + continue; + } + + await tool.run({ query: input.query }, input.options); + if (input.cached) { + expect(handler).not.toHaveBeenCalledOnce(); + } else { + expect(handler).toHaveBeenCalledOnce(); + } + handler.mockReset(); + } + }); + + describe("Tool schema", () => { + class ComplexTool extends Tool { + name = "ComplexTool"; + description = "ComplexTool description"; + + inputSchema() { + return z.object({ foo: z.string(), bar: z.string() }); + } + + protected async _run(arg: ToolInput): Promise { + return new StringToolOutput(JSON.stringify(arg)); + } + } + + it("Succeeds on valid input", async () => { + const tool = new ComplexTool(); + + const result = await tool.run({ foo: "foo", bar: "bar" }); + + expect(result.getTextContent()).toMatchInlineSnapshot(`"{"foo":"foo","bar":"bar"}"`); + }); + + it("Fails on invalid input", async () => { + const tool = new ComplexTool(); + + await expect(tool.run("fail" as any)).rejects.toThrowErrorMatchingInlineSnapshot(` + [ToolInputValidationError: The received tool input does not match the expected schema. + Input Schema: "{"type":"object","properties":{"foo":{"type":"string"},"bar":{"type":"string"}},"required":["foo","bar"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}" + Validation Errors: [{"instancePath":"","schemaPath":"#/type","keyword":"type","params":{"type":"object"},"message":"must be object"}]] + `); + }); + }); + + describe("DynamicTool", () => { + it("Calls provided handler", async () => { + const handler = vi.fn((query) => query); + const tool = new DynamicTool({ + name: "custom-tool", + description: "Custom tool", + inputSchema: z.string(), + async handler(input) { + handler(input); + return new StringToolOutput(input); + }, + }); + await tool.run("test"); + + expect(handler).toBeCalledWith("test"); + }); + + const toolDef = { + name: "get_weather", + description: "Determine weather in my location", + parameters: { + type: "object", + properties: { + location: { + type: "string", + description: "The city and state e.g. San Francisco, CA", + }, + unit: { + type: "string", + enum: ["c", "f"], + }, + }, + required: ["location"], + }, + example: { + location: "Prague", + unit: "c", + }, + }; + + it("Passes handler result as the invoke fn result", async () => { + const tool = new DynamicTool({ + name: toolDef.name, + description: toolDef.description, + inputSchema: toolDef.parameters, + async handler(input) { + const call = { + type: "function", + function: { + name: this.name, + arguments: input, + }, + }; + return new StringToolOutput(JSON.stringify({ tool_call: call })); + }, + }); + + const result = await tool.run(toolDef.example); + + expect(result.getTextContent()).toMatchInlineSnapshot( + `"{"tool_call":{"type":"function","function":{"name":"get_weather","arguments":{"location":"Prague","unit":"c"}}}}"`, + ); + }); + + it("Correctly transforms/parses zod schema and JsonSchema", async () => { + const tool = new DynamicTool({ + name: "DummyTool", + description: "DummyTool", + inputSchema: z.object({ + a: z.string().min(1).max(100).describe("a property"), + b: z.number().int().min(10).max(20).optional(), + c: z.number().finite(), + d: z.coerce.boolean().default(true), + e: z.string().date(), + }), + handler: async (input) => new JSONToolOutput(input), + }); + expect(await tool.getInputJsonSchema()).toMatchInlineSnapshot(` + { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "a": { + "description": "a property", + "maxLength": 100, + "minLength": 1, + "type": "string", + }, + "b": { + "maximum": 20, + "minimum": 10, + "type": "integer", + }, + "c": { + "type": "number", + }, + "d": { + "default": true, + "type": "boolean", + }, + "e": { + "format": "date", + "type": "string", + }, + }, + "required": [ + "a", + "c", + "e", + ], + "type": "object", + } + `); + + const input = { + a: "A", + b: 10, + c: 15, + d: true, + e: "2025-01-01", + }; + const a = await tool.run(input).then((result) => result.result); + expect(a).toStrictEqual((await tool.inputSchema()).parse(input)); + }); + + it("Emits events", async () => { + const tool = createDummyTool({}, vi.fn().mockResolvedValue("Hey!")); + const callbacks: any[] = []; + tool.emitter.match("*.*", (data, event) => { + callbacks.push({ event: event.path, data: JSON.stringify(data) }); + }); + await tool.run({ query: "Hello!" }); + expect(callbacks).toMatchInlineSnapshot(` + [ + { + "data": "null", + "event": "tool.run.start", + }, + { + "data": "{"input":{"query":"Hello!"}}", + "event": "tool.start", + }, + { + "data": "{"output":{"result":"Hey!"},"input":{"query":"Hello!"}}", + "event": "tool.success", + }, + { + "data": "null", + "event": "tool.finish", + }, + { + "data": "{"result":"Hey!"}", + "event": "tool.run.success", + }, + { + "data": "null", + "event": "tool.run.finish", + }, + ] + `); + }); + + it("Serializes", () => { + const tool = new DynamicTool({ + name: toolDef.name, + description: toolDef.description, + inputSchema: toolDef.parameters, + async handler(input) { + const call = { + type: "function", + function: { + name: this.name, + arguments: input, + }, + }; + return new StringToolOutput(JSON.stringify({ tool_call: call })); + }, + }); + + const serialized = tool.serialize(); + const deserialized = DynamicTool.fromSerialized(serialized); + verifyDeserialization(tool, deserialized); + }); + }); +}); diff --git a/src/tools/base.ts b/src/tools/base.ts new file mode 100644 index 0000000..863dc01 --- /dev/null +++ b/src/tools/base.ts @@ -0,0 +1,428 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { FrameworkError } from "@/errors.js"; +import * as R from "remeda"; +import { Retryable, RetryableConfig } from "@/internals/helpers/retryable.js"; +import { Serializable } from "@/internals/serializable.js"; +import { Task } from "promise-based-task"; +import { Cache, ObjectHashKeyFn } from "@/cache/decoratorCache.js"; +import { BaseCache } from "@/cache/base.js"; +import { NullCache } from "@/cache/nullCache.js"; +import type { ErrorObject, ValidateFunction } from "ajv"; +import { + AnyToolSchemaLike, + createSchemaValidator, + FromSchemaLike, + toJsonSchema, + validateSchema, +} from "@/internals/helpers/schema.js"; +import { validate } from "@/internals/helpers/general.js"; +import { z, ZodSchema } from "zod"; +import { Emitter } from "@/emitter/emitter.js"; +import { Callback } from "@/emitter/types.js"; +import { GetRunContext, RunContext } from "@/context.js"; +import { shallowCopy } from "@/serializer/utils.js"; + +export class ToolError extends FrameworkError {} + +export class ToolInputValidationError extends ToolError { + validationErrors: ErrorObject[]; + + constructor(message: string, validationErrors: ErrorObject[] = []) { + super(message, []); + this.validationErrors = validationErrors; + } +} + +export interface RetryOptions { + maxRetries?: number; + factor?: number; +} + +export interface BaseToolOptions { + retryOptions?: RetryOptions; + fatalErrors?: ErrorConstructor[]; + cache?: BaseCache> | false; +} + +export interface BaseToolRunOptions { + retryOptions?: RetryOptions; + signal?: AbortSignal; +} + +export abstract class ToolOutput extends Serializable { + abstract getTextContent(): string; + abstract isEmpty(): boolean; + + toString() { + return this.getTextContent(); + } +} + +export class StringToolOutput extends ToolOutput { + constructor( + public readonly result = "", + public readonly ctx?: Record, + ) { + super(); + this.result = result ?? ""; + } + + static { + this.register(); + } + + isEmpty() { + return !this.result; + } + + @Cache() + getTextContent(): string { + return this.result.toString(); + } + + createSnapshot() { + return { + result: this.result, + ctx: this.ctx, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} + +export class JSONToolOutput extends ToolOutput { + constructor( + public readonly result: T, + public readonly ctx?: Record, + ) { + super(); + } + + static { + this.register(); + } + + isEmpty() { + return !this.result || R.isEmpty(this.result); + } + + @Cache() + getTextContent(): string { + return JSON.stringify(this.result); + } + + createSnapshot() { + return { + result: this.result, + ctx: this.ctx, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} + +export interface ToolSnapshot { + name: string; + description: string; + options: TOptions; + cache: BaseCache>; + emitter: Emitter; +} + +export type ToolInput = FromSchemaLike>>; + +type ToolConstructorParameters = + Partial extends TOptions ? [options?: TOptions] : [options: TOptions]; + +export interface ToolCallbacks { + start: Callback<{ input: TInput; options: unknown }>; + success: Callback<{ output: TOutput; input: TInput; options: unknown }>; + error: Callback<{ input: TInput; error: ToolError | ToolInputValidationError; options: unknown }>; + retry: Callback<{ error: ToolError | ToolInputValidationError; input: TInput; options: unknown }>; + finish: Callback; +} + +export abstract class Tool< + TOutput extends ToolOutput = ToolOutput, + TOptions extends BaseToolOptions = BaseToolOptions, + TRunOptions extends BaseToolRunOptions = BaseToolRunOptions, +> extends Serializable { + abstract name: string; + abstract description: string; + + public readonly cache: BaseCache>; + protected readonly options: TOptions; + + public readonly emitter = new Emitter, TOutput>>({ + namespace: ["tool"], + creator: this, + }); + + abstract inputSchema(): Promise | AnyToolSchemaLike; + + constructor(...args: ToolConstructorParameters) { + super(); + + const [options] = args; + this.options = options ?? ({} as TOptions); + this.cache = options?.cache ? options.cache : new NullCache(); + } + + protected toError(e: Error, context: any) { + if (e instanceof ToolError) { + return e; + } else { + return new ToolError(`Tool "${this.name}" has occurred an error!`, [e], { + context, + }); + } + } + + run(input: ToolInput, options?: TRunOptions): Promise { + return RunContext.enter(this, async (run) => { + const meta = { input, options }; + let errorPropagated = false; + + try { + await this.assertInput(input); + + const output = await new Retryable({ + executor: async () => { + errorPropagated = false; + await run.emitter.emit("start", { ...meta }); + return this.cache.enabled + ? // @ts-expect-error wrong types + await this._runCached(input, options, run) + : // @ts-expect-error wrong types + await this._run(input, options, run); + }, + onError: async (error) => { + errorPropagated = true; + await run.emitter.emit("error", { + error: this.toError(error, meta), + ...meta, + }); + if (this.options.fatalErrors?.some((cls) => error instanceof cls)) { + throw error; + } + }, + onRetry: async (_, error) => { + await run.emitter.emit("retry", { ...meta, error: this.toError(error, meta) }); + }, + config: { + ...this._createRetryOptions(options?.retryOptions), + signal: options?.signal, + }, + }).get(); + + await run.emitter.emit("success", { output, ...meta }); + return output; + } catch (e) { + const error = this.toError(e, meta); + if (!errorPropagated) { + await run.emitter.emit("error", { + error, + options, + input, + }); + } + throw error; + } finally { + await this.emitter.emit("finish", null); + } + }); + } + + protected async _runCached( + input: ToolInput, + options: TRunOptions | undefined, + run: GetRunContext, + ): Promise { + const key = ObjectHashKeyFn({ + input, + options: R.omit(options ?? ({} as TRunOptions), ["signal", "retryOptions"]), + }); + + const cacheEntry = await this.cache.get(key); + if (cacheEntry !== undefined) { + return cacheEntry!; + } + + const task = new Task(); + await this.cache.set(key, task); + this._run(input, options, run) + .then((req) => task.resolve(req)) + .catch(async (err) => { + void task.reject(err); + await this.cache.delete(key); + }); + return task; + } + + public async clearCache() { + await this.cache.clear(); + } + + protected abstract _run( + arg: ToolInput, + options: TRunOptions | undefined, + run: GetRunContext, + ): Promise; + + async getInputJsonSchema() { + return toJsonSchema(await this.inputSchema()); + } + + static isTool(value: unknown): value is Tool { + return value instanceof Tool && "name" in value && "description" in value; + } + + private _createRetryOptions(...overrides: (RetryOptions | undefined)[]): RetryableConfig { + const defaultOptions: Required = { + maxRetries: 0, + factor: 1, + }; + + return R.pipe( + [defaultOptions, this.options.retryOptions, ...overrides], + R.filter(R.isTruthy), + R.map((input: RetryOptions) => { + const options: RetryableConfig = { + maxRetries: input.maxRetries ?? defaultOptions.maxRetries, + factor: input.factor ?? defaultOptions.maxRetries, + }; + return R.pickBy(options, R.isDefined); + }), + R.mergeAll, + ) as RetryableConfig; + } + + protected async assertInput(input: unknown) { + const schema = await this.inputSchema(); + if (schema) { + validateSchema(schema, { + context: { + tool: this.constructor.name, + hint: `To do post-validation override the '${this.validateInput.name}' method.`, + schema, + isFatal: true, + isRetryable: false, + }, + }); + } + + this.validateInput(schema, input); + } + + protected validateInput( + schema: AnyToolSchemaLike, + rawInput: unknown, + ): asserts rawInput is ToolInput { + const validator = createSchemaValidator(schema) as ValidateFunction>; + const success = validator(rawInput); + if (!success) { + throw new ToolInputValidationError( + [ + `The received tool input does not match the expected schema.`, + `Input Schema: "${JSON.stringify(toJsonSchema(schema))}"`, + `Validation Errors: ${JSON.stringify(validator.errors)}`, + ].join("\n"), + // ts doesn't infer that when success is false `validator.errors` is defined + validator.errors!, + ); + } + } + + createSnapshot(): ToolSnapshot { + return { + name: this.name, + description: this.description, + cache: this.cache, + options: shallowCopy(this.options), + emitter: this.emitter, + }; + } + + loadSnapshot(snapshot: ToolSnapshot): void { + Object.assign(this, snapshot); + } +} + +export type AnyTool = Tool; + +export class DynamicTool< + TOutput extends ToolOutput, + TInputSchema extends AnyToolSchemaLike, + TOptions extends BaseToolOptions = BaseToolOptions, + TRunOptions extends BaseToolRunOptions = BaseToolRunOptions, + TInput = FromSchemaLike, +> extends Tool { + static { + this.register(); + } + + declare name: string; + declare description: string; + private readonly _inputSchema: AnyToolSchemaLike; + private readonly handler; + + inputSchema() { + return this._inputSchema; + } + + constructor(fields: { + name: string; + description: string; + inputSchema: TInputSchema; + handler: (input: TInput, options?: TRunOptions) => Promise; + options?: TOptions; + }) { + validate( + fields, + z.object({ + name: z.string().min(1), + description: z.string().min(1), + inputSchema: z.union([z.instanceof(ZodSchema), z.object({}).passthrough()]), + handler: z.function(), + options: z.object({}).passthrough().optional(), + }), + ); + super(...([fields.options] as ToolConstructorParameters)); + this.name = fields.name; + this.description = fields.description; + this._inputSchema = fields.inputSchema; + this.handler = fields.handler; + } + + protected _run(arg: TInput, options?: TRunOptions): Promise { + return this.handler(arg, options); + } + + createSnapshot() { + return { ...super.createSnapshot(), handler: this.handler, _inputSchema: this._inputSchema }; + } + + loadSnapshot({ handler, ...snapshot }: ReturnType) { + super.loadSnapshot(snapshot); + Object.assign(this, { handler }); + } +} diff --git a/src/tools/custom.test.ts b/src/tools/custom.test.ts new file mode 100644 index 0000000..5757c81 --- /dev/null +++ b/src/tools/custom.test.ts @@ -0,0 +1,177 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { describe, it, expect, vi } from "vitest"; +import { CustomTool } from "./custom.js"; +import { StringToolOutput } from "./base.js"; + +const mocks = vi.hoisted(() => { + return { + parseCustomTool: vi.fn(), + executeCustomTool: vi.fn(), + }; +}); + +vi.mock("@connectrpc/connect", () => ({ + createPromiseClient: vi.fn().mockReturnValue({ + parseCustomTool: mocks.parseCustomTool, + executeCustomTool: mocks.executeCustomTool, + }), +})); + +describe("CustomTool", () => { + it("should instantiate correctly", async () => { + mocks.parseCustomTool.mockResolvedValue({ + response: { + case: "success", + value: { + toolName: "test", + toolDescription: "A test tool", + toolInputSchemaJson: `{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "a": { "type": "integer" }, + "b": { "type": "string" } + } + }`, + }, + }, + }); + + const customTool = await CustomTool.fromSourceCode("http://localhost", "source code"); + + expect(customTool.name).toBe("test"); + expect(customTool.description).toBe("A test tool"); + expect(await customTool.inputSchema()).toEqual({ + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + a: { type: "integer" }, + b: { type: "string" }, + }, + }); + }); + + it("should throw InvalidCustomToolError on parse error", async () => { + mocks.parseCustomTool.mockResolvedValue({ + response: { + case: "error", + value: { + errorMessages: ["Error parsing tool"], + }, + }, + }); + + await expect(CustomTool.fromSourceCode("http://localhost", "source code")).rejects.toThrow( + "Error parsing tool", + ); + }); + + it("should run the custom tool", async () => { + mocks.parseCustomTool.mockResolvedValue({ + response: { + case: "success", + value: { + toolName: "test", + toolDescription: "A test tool", + toolInputSchemaJson: `{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "a": { "type": "integer" }, + "b": { "type": "string" } + } + }`, + }, + }, + }); + + const customTool = await CustomTool.fromSourceCode( + "http://localhost", + "source code", + "executor-id", + ); + + mocks.executeCustomTool.mockResolvedValue({ + response: { + case: "success", + value: { + toolOutputJson: '{"something": "42"}', + }, + }, + }); + + const result = await customTool.run( + { + a: 42, + b: "test", + }, + { + signal: new AbortController().signal, + }, + ); + expect(result).toBeInstanceOf(StringToolOutput); + expect(result.getTextContent()).toEqual('{"something": "42"}'); + }); + + it("should throw CustomToolExecutionError on execution error", async () => { + mocks.parseCustomTool.mockResolvedValue({ + response: { + case: "success", + value: { + toolName: "test", + toolDescription: "A test tool", + toolInputSchemaJson: `{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "a": { "type": "integer" }, + "b": { "type": "string" } + } + }`, + }, + }, + }); + + const customTool = await CustomTool.fromSourceCode( + "http://localhost", + "source code", + "executor-id", + ); + + mocks.executeCustomTool.mockResolvedValue({ + response: { + case: "error", + value: { + stderr: "Error executing tool", + }, + }, + }); + + await expect( + customTool.run( + { + a: 42, + b: "test", + }, + { + signal: new AbortController().signal, + }, + ), + ).rejects.toThrow('Tool "test" has occurred an error!'); + }); +}); diff --git a/src/tools/custom.ts b/src/tools/custom.ts new file mode 100644 index 0000000..ab75a24 --- /dev/null +++ b/src/tools/custom.ts @@ -0,0 +1,118 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseToolOptions, BaseToolRunOptions, StringToolOutput, Tool } from "@/tools/base.js"; +import { createGrpcTransport } from "@connectrpc/connect-node"; +import { PromiseClient, createPromiseClient } from "@connectrpc/connect"; +import { FrameworkError } from "@/errors.js"; +import { z } from "zod"; +import { validate } from "@/internals/helpers/general.js"; +import { CodeInterpreterService } from "bee-proto/code_interpreter/v1/code_interpreter_service_connect"; + +export class CustomToolCreateError extends FrameworkError {} +export class CustomToolExecuteError extends FrameworkError {} + +const toolOptionsSchema = z + .object({ + codeInterpreterUrl: z.string().url(), + sourceCode: z.string().min(1), + name: z.string().min(1), + description: z.string().min(1), + inputSchema: z.any(), + executorId: z.string().nullable().optional(), + }) + .passthrough(); + +export type CustomToolOptions = z.output & BaseToolOptions; + +function createCodeInterpreterClient(url: string) { + return createPromiseClient( + CodeInterpreterService, + createGrpcTransport({ baseUrl: url, httpVersion: "2" }), + ); +} + +export class CustomTool extends Tool { + public get name() { + return this.options.name; + } + public get description() { + return this.options.description; + } + public inputSchema() { + return this.options.inputSchema; + } + + protected client: PromiseClient; + + static { + this.register(); + } + + public constructor( + options: CustomToolOptions, + client?: PromiseClient, + ) { + validate(options, toolOptionsSchema); + super(options); + this.client = client || createCodeInterpreterClient(options.codeInterpreterUrl); + } + + protected async _run(input: any, options: BaseToolRunOptions) { + const { response } = await this.client.executeCustomTool( + { + executorId: this.options.executorId || "default", + toolSourceCode: this.options.sourceCode, + toolInputJson: JSON.stringify(input), + }, + { signal: options.signal }, + ); + + if (response.case === "error") { + throw new CustomToolExecuteError(response.value.stderr); + } + + return new StringToolOutput(response.value!.toolOutputJson); + } + + loadSnapshot(snapshot: ReturnType): void { + super.loadSnapshot(snapshot); + this.client = createCodeInterpreterClient(this.options.codeInterpreterUrl); + } + + static async fromSourceCode(codeInterpreterUrl: string, sourceCode: string, executorId?: string) { + const client = createCodeInterpreterClient(codeInterpreterUrl); + const response = await client.parseCustomTool({ toolSourceCode: sourceCode }); + + if (response.response.case === "error") { + throw new CustomToolCreateError(response.response.value.errorMessages.join("\n")); + } + + const { toolName, toolDescription, toolInputSchemaJson } = response.response.value!; + + return new CustomTool( + { + codeInterpreterUrl, + sourceCode, + name: toolName, + description: toolDescription, + inputSchema: JSON.parse(toolInputSchemaJson), + executorId, + }, + client, + ); + } +} diff --git a/src/tools/llm.ts b/src/tools/llm.ts new file mode 100644 index 0000000..19c854b --- /dev/null +++ b/src/tools/llm.ts @@ -0,0 +1,58 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseToolOptions, StringToolOutput, Tool, ToolInput } from "@/tools/base.js"; +import { AnyLLM, GenerateOptions } from "@/llms/base.js"; +import { z } from "zod"; + +export type LLMToolInput = string; + +export type LLMToolOptions = { + llm: AnyLLM; +} & BaseToolOptions & + (T extends LLMToolInput + ? { + transform?: (input: string) => T; + } + : { + transform: (input: string) => T; + }); + +export interface LLMToolRunOptions extends GenerateOptions, BaseToolOptions {} + +export class LLMTool extends Tool, LLMToolRunOptions> { + name = "LLM"; + description = + "Give a prompt to an LLM assistant. Useful to extract and re-format information, and answer intermediate questions."; + + inputSchema() { + return z.object({ input: z.string() }); + } + + static { + this.register(); + } + + protected async _run( + { input }: ToolInput, + options?: LLMToolRunOptions, + ): Promise { + const { llm, transform } = this.options; + const llmInput = transform ? transform(input) : (input as T); + const response = await llm.generate(llmInput, options); + return new StringToolOutput(response.getTextContent(), response); + } +} diff --git a/src/tools/python/output.ts b/src/tools/python/output.ts new file mode 100644 index 0000000..d03e5db --- /dev/null +++ b/src/tools/python/output.ts @@ -0,0 +1,68 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { ToolOutput } from "@/tools/base.js"; +import { PythonFile } from "@/tools/python/storage.js"; + +export class PythonToolOutput extends ToolOutput { + constructor( + public readonly stdout: string, + public readonly stderr: string, + public readonly exitCode?: number, + public readonly outputFiles: PythonFile[] = [], + ) { + super(); + } + + static { + this.register(); + } + + isEmpty() { + return false; + } + + getTextContent() { + const fileList = this.outputFiles + .map((file) => `- [${file.filename}](urn:${file.hash})`) + .join("\n"); + return `The code exited with code ${this.exitCode}. +stdout: +\`\`\` +${this.stdout} +\`\`\` + +stderr: +\`\`\` +${this.stderr} +\`\`\` + +${fileList ? "Files that were created or modified:\n" + fileList : "No files were created or modified."}`; + } + + createSnapshot() { + return { + stdout: this.stdout, + stderr: this.stderr, + exitCode: this.exitCode, + outputFiles: this.outputFiles, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} diff --git a/src/tools/python/python.ts b/src/tools/python/python.ts new file mode 100644 index 0000000..c42d8fd --- /dev/null +++ b/src/tools/python/python.ts @@ -0,0 +1,186 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseToolOptions, BaseToolRunOptions, Tool, ToolInput } from "@/tools/base.js"; +import { createGrpcTransport } from "@connectrpc/connect-node"; +import { PromiseClient, createPromiseClient } from "@connectrpc/connect"; +import { CodeInterpreterService } from "bee-proto/code_interpreter/v1/code_interpreter_service_connect"; +import { z } from "zod"; +import { BaseLLMOutput } from "@/llms/base.js"; +import { LLM } from "@/llms/index.js"; +import { PromptTemplate } from "@/template.js"; +import { mapToObj } from "remeda"; +import { PythonFile, PythonStorage } from "@/tools/python/storage.js"; +import { PythonToolOutput } from "@/tools/python/output.js"; +import { ValidationError } from "ajv"; +import { ConnectionOptions } from "node:tls"; + +export interface PythonToolOptions extends BaseToolOptions { + codeInterpreter: { + url: string; + connectionOptions?: ConnectionOptions; + }; + executorId?: string; + preprocess?: { llm: LLM; promptTemplate: PromptTemplate<"input"> }; + storage: PythonStorage; +} + +export class PythonTool extends Tool { + name = "Python"; + description = [ + "Run Python code and return the console output. Use for tasks that involve calculations, computations, or data manipulation.", + "If files are created inside Python code, the user won't see them right away -- tool output will provide hashes of the created files, and the assistant must reference them using a Markdown link or image, by using the provided URN. ", + "To work with some files, you must specify them in the input schema; otherwise, the file will not be accessible.", + "It is necessary to always print() results. Available libraries include numpy, pandas, scipy, matplotlib (but you have to save the plot as a file, showing does not work), but you can import any library and it will be installed for you.", + "The execution state is not preserved -- each invocation is a new environment. Do not use this tool multiple times in a row, always write the full code you want to run in a single invocation.", + ].join(""); + + public readonly storage: PythonStorage; + + async inputSchema() { + const files = await this.storage.list(); + + return z.object({ + code: z.string().min(1).describe("full source code file in Python that will be executed"), + inputFiles: z + .object( + mapToObj(files, (value) => [ + value.hash, + z.literal(value.filename).describe("filename of a file"), + ]), + ) + .partial() + .optional() + .describe( + [ + "To access an existing file, you must specify it; otherwise, the file will not be accessible. IMPORTANT: If the file is not provided in the input, it will not be accessible.", + "The key is the final segment of a file URN, and the value is the filename. ", + files.length > 0 + ? `Example: {"${files[0].hash}":"${files[0].filename}"} -- the files will be available to the Python code in the working directory.` + : `Example: {"e6979b7bec732b89a736fd19436ec295f6f64092c0c6c0c86a2a7f27c73519d6":"file.txt"} -- the files will be available to the Python code in the working directory.`, + ].join(""), + ), + }); + } + + protected readonly client: PromiseClient; + protected readonly preprocess; + + public constructor(options: PythonToolOptions) { + super(options); + if (!options.codeInterpreter.url) { + throw new ValidationError([ + { + message: "Property must be a valid URL!", + data: options, + propertyName: "codeInterpreter.url", + }, + ]); + } + this.client = this._createClient(); + this.preprocess = options.preprocess; + this.storage = options.storage; + } + + static { + this.register(); + } + + protected _createClient(): PromiseClient { + return createPromiseClient( + CodeInterpreterService, + createGrpcTransport({ + baseUrl: this.options.codeInterpreter.url, + httpVersion: "2", + nodeOptions: this.options.codeInterpreter.connectionOptions, + }), + ); + } + + protected async _run(input: ToolInput, options?: BaseToolRunOptions) { + const inputFiles: PythonFile[] = Object.entries(input.inputFiles ?? {}) + .filter(([k, v]) => k && v) + .map(([hash, filename]) => ({ + hash, + filename: filename!, + })); + + await this.storage.upload(inputFiles); + + // replace relative paths in "files" with absolute paths by prepending "/workspace" + const filesInput = Object.fromEntries( + inputFiles + .filter((file) => file.filename) + .map((file) => [`/workspace/${file.filename}`, file.hash]), + ); + + const getSourceCode = async () => { + if (this.preprocess) { + const { llm, promptTemplate } = this.preprocess; + const response = await llm.generate(promptTemplate.render({ input: input.code }), { + signal: options?.signal, + stream: false, + }); + return response.getTextContent().trim(); + } + return input.code; + }; + + const result = await this.client.execute( + { + sourceCode: await getSourceCode(), + executorId: this.options.executorId ?? "default", + files: filesInput, + }, + { signal: options?.signal }, + ); + + // replace absolute paths in "files" with relative paths by removing "/workspace/" + // skip files that are not in "/workspace" + // skip entries that are also entries in filesInput + const prefix = "/workspace/"; + const filesOutput: PythonFile[] = Object.entries(result.files) + .map(([k, v]): PythonFile => ({ filename: k, hash: v })) + .filter( + (file) => + file.filename.startsWith(prefix) && + !( + file.filename.slice(prefix.length) in filesInput && + filesInput[file.filename.slice(prefix.length)] === file.hash + ), + ) + .map((file) => ({ + hash: file.hash, + filename: file.filename.slice(prefix.length), + })); + + await this.storage.download(filesOutput); + return new PythonToolOutput(result.stdout, result.stderr, result.exitCode, filesOutput); + } + + createSnapshot() { + return { + ...super.createSnapshot(), + storage: this.storage, + preprocess: this.preprocess, + }; + } + + loadSnapshot(snapshot: ReturnType): void { + super.loadSnapshot(snapshot); + Object.assign(this, { client: this._createClient() }); + } +} diff --git a/src/tools/python/storage.ts b/src/tools/python/storage.ts new file mode 100644 index 0000000..063374e --- /dev/null +++ b/src/tools/python/storage.ts @@ -0,0 +1,146 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { PathLike } from "fs"; +import * as crypto from "node:crypto"; +import { pipeline } from "node:stream/promises"; +import fs, { createReadStream } from "node:fs"; +import { copyFile } from "node:fs/promises"; +import path from "node:path"; +import { Cache } from "@/cache/decoratorCache.js"; +import { Serializable } from "@/internals/serializable.js"; +import { shallowCopy } from "@/serializer/utils.js"; + +export interface PythonFile { + hash: string; + filename: string; +} + +export abstract class PythonStorage extends Serializable { + /** + * List all files that code interpreter can use. + */ + abstract list(): Promise; + + /** + * Prepare subset of available files to code interpreter. + */ + abstract upload(files: PythonFile[]): Promise; + + /** + * Process updated/modified/deleted files from code interpreter response. + */ + abstract download(files: PythonFile[]): Promise; +} + +export class TemporaryStorage extends PythonStorage { + protected files: PythonFile[] = []; + + async list() { + return this.files.slice(); + } + + async upload() {} + + async download(files: PythonFile[]) { + this.files = [ + ...this.files.filter((file) => files.every((f) => f.filename !== file.filename)), + ...files, + ]; + } + + createSnapshot() { + return { files: this.files.slice() }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} + +export class LocalPythonStorage extends PythonStorage { + constructor( + protected readonly input: { + localWorkingDir: PathLike; + interpreterWorkingDir: PathLike; + }, + ) { + super(); + } + + @Cache() + protected async init() { + await fs.promises.mkdir(this.input.localWorkingDir, { recursive: true }); + await fs.promises.mkdir(this.input.interpreterWorkingDir, { recursive: true }); + } + + async list(): Promise { + await this.init(); + + const files = await fs.promises.readdir(this.input.localWorkingDir, { + withFileTypes: true, + recursive: false, + }); + return Promise.all( + files + .filter((file) => file.isFile()) + .map(async (file) => ({ + filename: file.name, + hash: await this.computeHash(path.join(this.input.localWorkingDir.toString(), file.name)), + })), + ); + } + + async upload(files: PythonFile[]): Promise { + await this.init(); + + await Promise.all( + files.map((file) => + copyFile( + path.join(this.input.localWorkingDir.toString(), file.filename), + path.join(this.input.interpreterWorkingDir.toString(), file.hash), + ), + ), + ); + } + + async download(files: PythonFile[]) { + await this.init(); + + await Promise.all( + files.map((file) => + copyFile( + path.join(this.input.interpreterWorkingDir.toString(), file.hash), + path.join(this.input.localWorkingDir.toString(), file.filename), + ), + ), + ); + } + + protected async computeHash(file: PathLike) { + const hash = crypto.createHash("sha256"); + await pipeline(createReadStream(file), hash); + return hash.digest("hex"); + } + + createSnapshot() { + return { input: shallowCopy(this.input) }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} diff --git a/src/tools/search/base.ts b/src/tools/search/base.ts new file mode 100644 index 0000000..0f12875 --- /dev/null +++ b/src/tools/search/base.ts @@ -0,0 +1,50 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseToolOptions, BaseToolRunOptions, ToolOutput } from "@/tools/base.js"; +import { Cache } from "@/cache/decoratorCache.js"; +import * as R from "remeda"; + +export interface SearchToolOptions extends BaseToolOptions {} + +export interface SearchToolRunOptions extends BaseToolRunOptions {} + +export interface SearchToolResult { + title: string; + description: string; + url: string; +} + +export abstract class SearchToolOutput< + TSearchToolResult extends SearchToolResult = SearchToolResult, +> extends ToolOutput { + constructor(public readonly results: TSearchToolResult[]) { + super(); + } + + get sources() { + return R.unique(this.results.map((result) => result.url)); + } + + isEmpty() { + return this.results.length === 0; + } + + @Cache() + getTextContent(): string { + return this.results.map((result) => JSON.stringify(result, null, 2)).join("\n\n"); + } +} diff --git a/src/tools/search/duckDuckGoSearch.test.ts b/src/tools/search/duckDuckGoSearch.test.ts new file mode 100644 index 0000000..4f29a02 --- /dev/null +++ b/src/tools/search/duckDuckGoSearch.test.ts @@ -0,0 +1,121 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { + DuckDuckGoSearchTool, + DuckDuckGoSearchToolOptions, + DuckDuckGoSearchToolOutput, +} from "@/tools/search/duckDuckGoSearch.js"; +import { beforeEach, expect, vi } from "vitest"; + +import * as ddg from "duck-duck-scrape"; +import { Tool } from "@/tools/base.js"; +import { Task } from "promise-based-task"; + +import { SlidingCache } from "@/cache/slidingCache.js"; +import { verifyDeserialization } from "@tests/e2e/utils.js"; +vi.mock("duck-duck-scrape"); + +describe("DuckDuckGoSearch Tool", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.clearAllTimers(); + }); + + const generateResults = (count = 3): ddg.SearchResults => { + return { + noResults: count === 0, + vqd: "", + results: Array(count) + .fill(null) + .map((_, i) => ({ + title: `Result ${i + 1}`, + url: `https://example.com/#${i + 1}`, + description: `

Response ${i + 1}

`, + rawDescription: `

Response ${i + 1}

`, + hostname: "hostname", + icon: "", + bang: undefined, + })), + }; + }; + + it("Is valid tool", async () => { + const tool = new DuckDuckGoSearchTool(); + expect(tool).instanceOf(Tool); + expect(Tool.isTool(tool)).toBe(true); + expect(tool.name).toBeDefined(); + expect(tool.description).toBeDefined(); + }); + + interface RetrieveDataInput { + query: string; + options: DuckDuckGoSearchToolOptions; + } + it.each([ + { query: "LLM", options: { maxResultsPerPage: 1 } }, + { query: "IBM Research" }, + { query: "NLP", options: { maxResultsPerPage: 3 } }, + ] as RetrieveDataInput[])("Retrieves data (%o)", async (input) => { + const globalMaxResults = 10; + const maxResultsPerPage = (input as any).options?.maxResultsPerPage ?? globalMaxResults; + + const tool = new DuckDuckGoSearchTool({ + maxResultsPerPage: globalMaxResults, + cache: false, + throttle: false, + }); + + vi.mocked(ddg.search).mockResolvedValue(generateResults(maxResultsPerPage)); + + const response = await tool.run({ query: input.query }, input.options); + expect(response.results.length).toEqual(maxResultsPerPage); + expect(ddg.search).toBeCalledWith( + input.query, + expect.objectContaining(input.options?.search ?? {}), + expect.any(Object), + ); + }); + + it("Serializes", async () => { + const tool = new DuckDuckGoSearchTool({ + throttle: false, + cache: new SlidingCache({ + size: 10, + ttl: 1000, + }), + maxResultsPerPage: 1, + }); + + await tool.cache!.set( + "A", + Task.resolve( + new DuckDuckGoSearchToolOutput([ + { + title: "A", + url: "http://example.com", + description: "A", + }, + ]), + ), + ); + await tool.cache!.set("B", Task.resolve(new DuckDuckGoSearchToolOutput([]))); + const serialized = tool.serialize(); + const deserialized = DuckDuckGoSearchTool.fromSerialized(serialized); + expect(await tool.cache.get("A")).toStrictEqual(await deserialized.cache.get("A")); + verifyDeserialization(tool, deserialized); + }); +}); diff --git a/src/tools/search/duckDuckGoSearch.ts b/src/tools/search/duckDuckGoSearch.ts new file mode 100644 index 0000000..67d496c --- /dev/null +++ b/src/tools/search/duckDuckGoSearch.ts @@ -0,0 +1,155 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { SearchOptions, search as rawDDGSearch, SafeSearchType } from "duck-duck-scrape"; +import { stripHtml } from "string-strip-html"; +import pThrottle, { Options as ThrottleOptions } from "p-throttle"; +import { + SearchToolOptions, + SearchToolOutput, + SearchToolResult, + SearchToolRunOptions, +} from "./base.js"; +import { Tool, ToolInput } from "@/tools/base.js"; +import { HeaderGenerator } from "header-generator"; +import type { NeedleOptions } from "needle"; +import { z } from "zod"; +import { Cache } from "@/cache/decoratorCache.js"; + +export interface DuckDuckGoSearchToolOptions extends SearchToolOptions { + search?: SearchOptions; + throttle?: ThrottleOptions | false; + httpClientOptions?: NeedleOptions; + maxResultsPerPage: number; +} + +export interface DuckDuckGoSearchToolRunOptions extends SearchToolRunOptions { + search?: SearchOptions; + httpClientOptions?: NeedleOptions; +} + +export interface DuckDuckGoSearchToolResult extends SearchToolResult {} + +export class DuckDuckGoSearchToolOutput extends SearchToolOutput { + constructor(public readonly results: DuckDuckGoSearchToolResult[]) { + super(results); + } + + static { + this.register(); + } + + createSnapshot() { + return { + results: this.results, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} + +export class DuckDuckGoSearchTool extends Tool< + DuckDuckGoSearchToolOutput, + DuckDuckGoSearchToolOptions, + DuckDuckGoSearchToolRunOptions +> { + name = "DuckDuckGo"; + description = + "Search a query on DuckDuckGo. Useful for when you need to answer questions about current events. Output is a list of relevant websites with a concrete page description."; + + protected readonly client: typeof rawDDGSearch; + + @Cache() + inputSchema() { + return z.object({ + query: z.string({ description: `Search query` }).min(1).max(128), + page: z + .number() + .int() + .min(1) + .max(10) + .describe( + `Search result page (each page contains maximally ${this.options.maxResultsPerPage} results)`, + ) + .default(1) + .optional(), + }); + } + + public constructor(options: DuckDuckGoSearchToolOptions = { maxResultsPerPage: 15 }) { + super(options); + + this.client = this._createClient(); + } + + static { + this.register(); + } + + protected _createClient() { + const { throttle } = this.options; + + return throttle === false + ? rawDDGSearch + : pThrottle({ + ...throttle, + limit: throttle?.limit ?? 1, + interval: throttle?.interval ?? 3000, + })(rawDDGSearch); + } + + protected async _run( + { query: input, page = 1 }: ToolInput, + options?: DuckDuckGoSearchToolRunOptions, + ) { + const headers = new HeaderGenerator().getHeaders(); + + const { results } = await this.client( + input, + { + offset: this.options.maxResultsPerPage * (page - 1), + safeSearch: SafeSearchType.MODERATE, + ...this.options.search, + ...options?.search, + }, + { + headers, + user_agent: headers["user-agent"], + ...this.options?.httpClientOptions, + ...options?.httpClientOptions, + }, + ); + + if (results.length > this.options.maxResultsPerPage) { + results.length = this.options.maxResultsPerPage; + } + + return new DuckDuckGoSearchToolOutput( + results.map((result) => ({ + title: stripHtml(result.title).result, + description: stripHtml(result.description).result, + url: result.url, + })), + ); + } + + loadSnapshot(snapshot: ReturnType): void { + super.loadSnapshot(snapshot); + Object.assign(this, { client: this._createClient() }); + } +} diff --git a/src/tools/search/wikipedia.test.ts b/src/tools/search/wikipedia.test.ts new file mode 100644 index 0000000..270b904 --- /dev/null +++ b/src/tools/search/wikipedia.test.ts @@ -0,0 +1,57 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +vitest.mock("wikipedia", () => { + return { + default: { + default: { + setLang(lang: string) { + return lang; + }, + async search(input: string) { + return { + results: [{ title: input }], + suggestion: [], + }; + }, + async page(title: string) { + return { + title, + content: async () => "Content", + infobox: async () => ({ text: "Infobox" }), + }; + }, + }, + }, + }; +}); + +import { verifyDeserialization } from "@tests/e2e/utils.js"; +import { WikipediaTool } from "@/tools/search/wikipedia.js"; + +describe("Wikipedia", () => { + it("Serializes", async () => { + const instance = new WikipediaTool({ + extraction: { + fields: ["infobox"], + }, + }); + await instance.run({ query: "Prague" }); + const serialized = instance.serialize(); + const deserialized = WikipediaTool.fromSerialized(serialized); + verifyDeserialization(instance, deserialized); + }); +}); diff --git a/src/tools/search/wikipedia.ts b/src/tools/search/wikipedia.ts new file mode 100644 index 0000000..4348a71 --- /dev/null +++ b/src/tools/search/wikipedia.ts @@ -0,0 +1,314 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import wiki from "wikipedia"; +import { Cache } from "@/cache/decoratorCache.js"; +import stringComparison from "string-comparison"; +import * as R from "remeda"; +import type { Page, pageFunctions, searchOptions } from "wikipedia"; +import { ArrayKeys, Common } from "@/internals/types.js"; +import { + SearchToolOptions, + SearchToolOutput, + SearchToolResult, + SearchToolRunOptions, +} from "./base.js"; +import { asyncProperties } from "@/internals/helpers/promise.js"; +import { z } from "zod"; +import { Tool, ToolInput } from "@/tools/base.js"; +import Turndown from "turndown"; +// @ts-expect-error missing types +import turndownPlugin from "joplin-turndown-plugin-gfm"; + +wiki.default.setLang("en"); + +export interface SearchOptions extends searchOptions {} + +export interface FilterOptions { + excludeOthersOnExactMatch?: boolean; + minPageNameSimilarity?: number; +} + +export type PageFunctions = pageFunctions | "markdown"; + +export interface ExtractionOptions { + fields?: PageFunctions[]; +} + +export interface OutputOptions { + maxSerializedLength?: number; + maxDescriptionLength?: number; +} + +export interface WikipediaToolOptions extends SearchToolOptions { + filters?: FilterOptions; + search?: SearchOptions; + extraction?: ExtractionOptions; + output?: OutputOptions; +} + +export interface WikipediaToolRunOptions extends SearchToolRunOptions { + filters?: FilterOptions; + search?: SearchOptions; + extraction?: ExtractionOptions; + output?: OutputOptions; +} + +export interface WikipediaToolResult extends SearchToolResult { + fields: Record; +} + +export class WikipediaToolOutput extends SearchToolOutput { + constructor( + public readonly results: WikipediaToolResult[], + protected readonly maxSerializedLength: number, + ) { + super(results); + } + + static { + this.register(); + } + + @Cache() + getTextContent(): string { + const target = this.results.length === 1 ? this.results[0] : this.results; + const response = JSON.stringify(target); + + return this.maxSerializedLength < Infinity + ? response.substring(0, this.maxSerializedLength) + : response; + } + + createSnapshot() { + return { + results: this.results, + maxSerializedLength: this.maxSerializedLength, + }; + } + + loadSnapshot(snapshot: ReturnType) { + Object.assign(this, snapshot); + } +} + +export class WikipediaTool extends Tool< + WikipediaToolOutput, + WikipediaToolOptions, + WikipediaToolRunOptions +> { + name = "Wikipedia"; + description = + "Search a query on Wikipedia. Useful when you need to get information about famous people, places, companies, historical events, or other subjects."; + + inputSchema() { + return z.object({ + query: z + .string({ description: `Name of the wikipedia page, for example 'New York'` }) + .min(1) + .max(128), + }); + } + + public constructor(public readonly config: WikipediaToolOptions = {}) { + super(config); + } + + static { + this.register(); + } + + @Cache() + protected get _mappers(): Record< + PageFunctions, + (page: Page, runOptions: WikipediaToolRunOptions) => Promise + > { + return { + categories: (page) => page.categories(), + content: (page) => page.content(), + html: (page) => page.html(), + markdown: async (page) => { + const html = await page.html().then((result) => { + const url = new URL(page.fullurl); + const base = `${url.protocol}//${[url.hostname, url.port].filter(Boolean).join(":")}`; + return ( + result + // Missing a protocol + .replace(/( { + switch (node.tagName.toLowerCase()) { + case "link": + case "style": + return true; + default: + return ( + [ + "toc", + "reflist", + "mw-references-wrap", + "navbox", + "navbox-styles", + "mw-editsection", + "sistersitebox", + "navbox-inner", + "refbegin", + "notpageimage", + "mw-file-element", + ].some((cls) => node.className.includes(cls)) || + ["navigation"].some((role) => node.role === role) + ); + } + }) + .turndown(html); + }, + images: (page) => page.images(), + intro: (page) => page.intro(), + infobox: (page) => page.infobox(), + links: (page) => page.links(), + coordinates: (page) => page.coordinates(), + langLinks: (page) => page.langLinks(), + references: (page) => page.references(), + related: (page) => page.related(), + summary: (page) => page.summary(), + tables: (page) => page.tables(), + }; + } + + @Cache() + protected get _defaultRunOptions(): WikipediaToolRunOptions { + return { + extraction: { + fields: ["markdown"], + }, + filters: { + minPageNameSimilarity: 0.5, + excludeOthersOnExactMatch: true, + }, + search: { + limit: 3, + suggestion: true, + }, + output: { + maxSerializedLength: 25_000, + maxDescriptionLength: 250, + }, + }; + } + + protected _createRunOptions(overrides?: WikipediaToolRunOptions): WikipediaToolRunOptions { + const baseKeys: ArrayKeys> = [ + "filters", + "search", + "extraction", + "retryOptions", + "output", + ]; + + return R.pipe( + { ...this._defaultRunOptions }, + R.mergeDeep(R.pick(this.options ?? {}, baseKeys)), + R.mergeDeep({ ...overrides }), + ); + } + + protected async _run( + { query: input }: ToolInput, + _options?: WikipediaToolRunOptions, + ): Promise { + const runOptions = this._createRunOptions(_options); + + const { results: searchRawResults, suggestion } = await wiki.default.search(input, { + suggestion: Boolean(_options?.search?.suggestion), + ...runOptions.search, + }); + + if (searchRawResults.length === 0 && suggestion && runOptions.search?.suggestion) { + return await this._run({ query: suggestion }, _options); + } + + const bestCandidates = stringComparison.jaccardIndex + .sortMatch( + input, + searchRawResults.map((result) => result.title), + ) + .map((result) => ({ + pageId: searchRawResults[result.index].pageid, + score: result.rating, + })) + .filter((result) => result.score >= (runOptions.filters?.minPageNameSimilarity ?? 0)) + .sort((a, b) => b.score - a.score); + + if (bestCandidates.at(0)?.score === 1 && runOptions.filters?.excludeOthersOnExactMatch) { + bestCandidates.length = 1; + } + + if (bestCandidates.length === 0) { + bestCandidates.push(...searchRawResults); + } + + const results = await Promise.all( + bestCandidates.map(async ({ pageId }) => { + const page = await wiki.default.page(pageId, { + redirect: true, + preload: false, + fields: (runOptions?.extraction?.fields ?? []).filter( + (field): field is pageFunctions => field !== "markdown", + ), + }); + + return asyncProperties({ + title: page.title, + description: ((): Promise => { + const length = runOptions?.output?.maxDescriptionLength ?? 0; + return length <= 0 + ? Promise.resolve("") + : page.content().then((content) => content.substring(0, length)); + })(), + url: page.fullurl, + fields: asyncProperties( + R.mapToObj(runOptions?.extraction?.fields || [], (key) => [ + key, + this._mappers[key](page, runOptions).catch(() => null), + ]), + ), + }); + }), + ); + return new WikipediaToolOutput(results, runOptions.output?.maxSerializedLength ?? Infinity); + } + + createSnapshot() { + return { + ...super.createSnapshot(), + config: this.config, + }; + } + + loadSnapshot(snapshot: ReturnType) { + super.loadSnapshot(snapshot); + } +} diff --git a/src/tools/similarity.test.ts b/src/tools/similarity.test.ts new file mode 100644 index 0000000..9abe557 --- /dev/null +++ b/src/tools/similarity.test.ts @@ -0,0 +1,75 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { beforeEach, expect, vi } from "vitest"; + +import { Tool } from "@/tools/base.js"; +import { SimilarityTool, SimilarityToolOptions } from "./similarity.js"; +vi.mock("duck-duck-scrape"); + +describe("WebSearch Tool", () => { + beforeEach(() => { + vi.clearAllTimers(); + }); + + const similarityProvider: SimilarityToolOptions["provider"] = async ({ documents }) => + documents.map((_, idx) => ({ score: idx })); + + it("Is valid tool", async () => { + const tool = new SimilarityTool({ provider: similarityProvider }); + expect(tool).instanceOf(Tool); + expect(Tool.isTool(tool)).toBe(true); + expect(tool.name).toBeDefined(); + expect(tool.description).toBeDefined(); + }); + + it("Uses provider", async () => { + const providerMock = vi.fn(similarityProvider); + const tool = new SimilarityTool({ provider: providerMock }); + + await tool.run({ + query: "foo", + documents: [{ text: "foo" }, { text: "bar" }], + }); + + expect(providerMock).toBeCalled(); + }); + + it("Returns top-k results", async () => { + const providerMock = vi.fn(similarityProvider); + + const maxResultsGlobal = 3; + const maxResultsRun = 2; + + const tool = new SimilarityTool({ provider: providerMock, maxResults: maxResultsGlobal }); + + const output1 = await tool.run({ + query: "foo", + documents: [{ text: "foo" }, { text: "bar" }, { text: "foo" }, { text: "bar" }], + }); + + expect(output1.result.length).toBe(maxResultsGlobal); + + const output2 = await tool.run( + { + query: "foo", + documents: [{ text: "foo" }, { text: "bar" }, { text: "foo" }, { text: "bar" }], + }, + { maxResults: maxResultsRun }, + ); + expect(output2.result.length).toBe(maxResultsRun); + }); +}); diff --git a/src/tools/similarity.ts b/src/tools/similarity.ts new file mode 100644 index 0000000..7966c40 --- /dev/null +++ b/src/tools/similarity.ts @@ -0,0 +1,97 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseToolOptions, BaseToolRunOptions, JSONToolOutput, Tool, ToolInput } from "./base.js"; +import { string, z } from "zod"; +import * as R from "remeda"; + +const documentSchema = z.object({ text: string() }).passthrough(); + +type Document = z.infer; + +export interface SimilarityToolOptions extends BaseToolOptions { + provider: ( + input: { query: string; documents: Document[] }, + options?: TProviderOptions, + ) => Promise<{ score: number }[]>; + maxResults?: number; +} + +export interface SimilarityToolRunOptions extends BaseToolRunOptions { + provider?: TProviderOptions; + maxResults?: number; +} + +export interface SimilarityToolResult { + document: Document; + index: number; + score: number; +} + +export class SimilarityToolOutput extends JSONToolOutput {} + +export class SimilarityTool extends Tool< + SimilarityToolOutput, + SimilarityToolOptions, + SimilarityToolRunOptions +> { + name = "Similarity"; + description = "Extract relevant information from documents."; + + inputSchema() { + return z.object({ query: z.string(), documents: z.array(documentSchema) }); + } + + static { + this.register(); + } + + protected async _run( + input: ToolInput, + options?: SimilarityToolRunOptions, + ) { + const { query, documents } = input; + + const results = await this.options.provider( + { + query, + documents, + }, + options?.provider, + ); + + const resultsWithDocumentIndices = results.map(({ score }, idx) => ({ + documentIndex: idx, + score, + })); + const sortedResultsWithDocumentIndices = R.sortBy(resultsWithDocumentIndices, [ + ({ score }) => score, + "desc", + ]); + const filteredResultsWithDocumentIndices = sortedResultsWithDocumentIndices.slice( + 0, + options?.maxResults ?? this.options.maxResults, + ); + + return new SimilarityToolOutput( + filteredResultsWithDocumentIndices.map(({ documentIndex, score }) => ({ + document: documents[documentIndex], + index: documentIndex, + score, + })), + ); + } +} diff --git a/src/tools/weather/openMeteo.ts b/src/tools/weather/openMeteo.ts new file mode 100644 index 0000000..36efe9d --- /dev/null +++ b/src/tools/weather/openMeteo.ts @@ -0,0 +1,173 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { + BaseToolOptions, + BaseToolRunOptions, + JSONToolOutput, + Tool, + ToolError, + ToolInput, +} from "@/tools/base.js"; +import { z } from "zod"; +import { createURLParams } from "@/internals/fetcher.js"; +import { isNullish, pick, pickBy } from "remeda"; +import { Cache } from "@/cache/decoratorCache.js"; + +type ToolOptions = { apiKey?: string } & BaseToolOptions; +type ToolRunOptions = BaseToolRunOptions; + +interface Location { + latitude: number; + longitude: number; +} + +interface LocationSearch { + name: string; + language?: string; +} + +interface Response { + latitude: number; + longitude: number; + generationtime_ms: number; + utc_offset_seconds: number; + timezone: string; + timezone_abbreviation: string; + elevation: number; + current_units: Record; + current: Record; + hourly_units: Record; + hourly: Record; + daily_units: Record; + daily: Record; +} + +export class OpenMeteoTool extends Tool, ToolOptions, ToolRunOptions> { + name = "OpenMeteo"; + description = `Retrieves current, previous, or upcoming weather for a given destination.`; + + inputSchema() { + return z + .object({ + location: z + .object({ + latitude: z.number(), + longitude: z.number(), + }) + .strip() + .or( + z + .object({ + name: z.string().min(1), + language: z.string().default("English"), + }) + .strip(), + ), + elevation: z.number().nullish(), + timezone: z.string(), + start_date: z + .string() + .date() + .describe("Date Format: YYYY-MM-DD (omit the field for the current date)") + .nullish(), + end_date: z + .string() + .date() + .describe("Date Format: YYYY-MM-DD (omit the field for the current date)") + .nullish(), + forecast_days: z.number().int().min(0).max(16).default(7), + past_days: z.number().int().min(0).max(92).default(0), + temperature_unit: z.enum(["celsius", "fahrenheit"]).default("celsius"), + }) + .strip(); + } + + static { + this.register(); + } + + protected async _run({ location, ...input }: ToolInput, options?: BaseToolRunOptions) { + const { apiKey } = this.options; + + const extractLocation = async (): Promise => { + if ("name" in location) { + const response = await this._geocode( + { + name: location.name, + language: location.language, + }, + options?.signal, + ); + return pick(response, ["latitude", "longitude"]); + } + return location; + }; + + const params = createURLParams({ + ...pickBy(input, (v) => !isNullish(v) && v !== ""), + ...(await extractLocation()), + current: ["temperature_2m", "rain", "apparent_temperature"], + daily: ["apparent_temperature_max", "apparent_temperature_min", "sunrise", "sunset"], + hourly: ["temperature_2m", "relative_humidity_2m", "apparent_temperature"], + }); + const response = await fetch(`https://api.open-meteo.com/v1/forecast?${params}`, { + headers: { + ...(apiKey && { + Authorization: `Bearer ${apiKey}`, + }), + }, + signal: options?.signal, + }); + + if (!response.ok) { + throw new ToolError("Request to OpenMeteo API has failed!", [ + new Error(await response.text()), + ]); + } + + const data: Response = await response.json(); + return new JSONToolOutput(data); + } + + @Cache() + protected async _geocode(location: LocationSearch, signal?: AbortSignal) { + const { apiKey } = this.options; + + const params = createURLParams({ + name: location.name, + language: location.language, + format: "json", + count: 1, + }); + const response = await fetch(`https://geocoding-api.open-meteo.com/v1/search?${params}`, { + headers: { + ...(apiKey && { + Authorization: `Bearer ${apiKey}`, + }), + }, + signal, + }); + if (!response.ok) { + throw new ToolError(`Failed to GeoCode provided location (${location.name}).`, [ + new Error(await response.text()), + ]); + } + + const { results } = await response.json(); + return results[0]; + } +} diff --git a/src/tools/web/webCrawler.ts b/src/tools/web/webCrawler.ts new file mode 100644 index 0000000..36682db --- /dev/null +++ b/src/tools/web/webCrawler.ts @@ -0,0 +1,124 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { + BaseToolOptions, + BaseToolRunOptions, + JSONToolOutput, + Tool, + ToolInput, +} from "@/tools/base.js"; +import { z } from "zod"; +import { Cache } from "@/cache/decoratorCache.js"; +import { stripHtml } from "string-strip-html"; + +interface CrawlerOutput { + url: string; + statusCode: number; + statusText: string; + contentType: string; + content: string; +} + +export class WebCrawlerToolOutput extends JSONToolOutput { + @Cache() + getTextContent(): string { + return [ + `URL: ${this.result.url}`, + `STATUS: ${this.result.statusCode} (${this.result.statusText})`, + `CONTENT-TYPE: ${this.result.contentType}`, + `CONTENT: ${this.result.content}`, + ].join("\n"); + } +} + +export type HttpClient = (url: string, options?: RequestInit) => Promise; +interface HttpClientResponse { + status: number; + statusText: string; + headers: Headers; + text(): Promise; +} + +type Parser = (response: HttpClientResponse) => Promise; +interface WebsiteCrawlerToolOptions extends BaseToolOptions { + client?: HttpClient; + parser?: Parser; + request?: RequestInit; +} + +async function defaultParser(response: HttpClientResponse) { + const text = await response.text(); + if (text) { + const contentType = response.headers.get("content-type") ?? ""; + if (contentType.includes("text/html")) { + return stripHtml(text).result; + } + } + return text || "No Content"; +} + +export class WebCrawlerTool extends Tool { + name = "WebCrawler"; + description = `Retrieves content of an arbitrary website.`; + inputSchema() { + return z.object({ + url: z.string().url().describe("Website URL"), + }); + } + + protected client: HttpClient; + protected parser: Parser; + + constructor({ client, parser, ...options }: WebsiteCrawlerToolOptions = {}) { + super(options); + this.client = client ?? fetch; + this.parser = parser ?? defaultParser; + } + + protected async _run({ url }: ToolInput, options?: BaseToolRunOptions) { + const response = await this.client(url, { + redirect: "follow", + ...this.options.request, + signal: options?.signal, + }); + + const content = await this.parser(response); + return new WebCrawlerToolOutput({ + url, + statusCode: response.status, + statusText: response.statusText, + contentType: response.headers.get("content-type") ?? "unknown", + content, + }); + } + + createSnapshot() { + return { + ...super.createSnapshot(), + client: this.client, + parser: this.parser, + }; + } + + loadSnapshot({ client, parser, ...snapshot }: ReturnType) { + super.loadSnapshot(snapshot); + Object.assign(this, { + client: client ?? fetch, + parser: parser ?? defaultParser, + }); + } +} diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 0000000..dca8d9c --- /dev/null +++ b/src/version.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +declare const __LIBRARY_VERSION: string; + +let Version = "0.0.0"; +try { + Version = __LIBRARY_VERSION; +} catch { + /* empty */ +} + +export { Version }; diff --git a/tests/e2e/adapters/langchain/chat.test.ts b/tests/e2e/adapters/langchain/chat.test.ts new file mode 100644 index 0000000..66793bc --- /dev/null +++ b/tests/e2e/adapters/langchain/chat.test.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BaseMessage, Role } from "@/llms/primitives/message.js"; +import { ChatOpenAI } from "@langchain/openai"; +import { LangChainChatLLM } from "@/adapters/langchain/llms/chat.js"; + +describe("Adapter LangChain Chat LLM", () => { + const createChatLLM = () => { + const model = new ChatOpenAI({ + temperature: 0, + apiKey: process.env.OPENAI_API_KEY, + }); + return new LangChainChatLLM(model); + }; + + it("Generates", async () => { + const conversation = [ + BaseMessage.of({ + role: Role.SYSTEM, + text: `You are a helpful and respectful and honest assistant. Your answer should be short and concise.`, + }), + ]; + const llm = createChatLLM(); + + for (const { question, answer } of [ + { + question: `What is the coldest continent? Response must be a single word.`, + answer: "Antarctica", + }, + { + question: + "What is the most common typical animal that lives there? Response must be a single word.", + answer: "Penguin", + }, + ]) { + conversation.push( + BaseMessage.of({ + role: Role.USER, + text: question, + }), + ); + const response = await llm.generate(conversation); + expect(response.messages.length).toBeGreaterThan(0); + expect(response.getTextContent()).toBe(answer); + conversation.push( + BaseMessage.of({ + role: Role.ASSISTANT, + text: response.getTextContent(), + }), + ); + } + }); +}); diff --git a/tests/e2e/adapters/sdk/chat.test.ts b/tests/e2e/adapters/sdk/chat.test.ts new file mode 100644 index 0000000..1a5b20b --- /dev/null +++ b/tests/e2e/adapters/sdk/chat.test.ts @@ -0,0 +1,61 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { BAMChatLLM } from "@/adapters/bam/chat.js"; +import { BaseMessage } from "@/llms/primitives/message.js"; +import { expect } from "vitest"; +import { verifyDeserialization } from "@tests/e2e/utils.js"; + +describe("Adapter SDK Chat LLM", () => { + const createChatLLM = () => { + return BAMChatLLM.fromPreset("meta-llama/llama-3-1-70b-instruct"); + }; + + it("Generates", async () => { + const conversation = [ + BaseMessage.of({ + role: "system", + text: `You are a helpful and respectful and honest assistant. Answer should be a single word.`, + }), + ]; + const llm = createChatLLM(); + + for (const { question, answer } of [ + { question: `What is the coldest continent?`, answer: "Antarctica" }, + { question: "What is the most common typical animal that lives there?", answer: "Penguin" }, + ]) { + conversation.push( + BaseMessage.of({ + role: "user", + text: question, + }), + ); + const response = await llm.generate(conversation); + + const newMessages = response.messages; + expect(newMessages).toHaveLength(1); + expect(newMessages[0].text).toContain(answer); + conversation.push(...newMessages); + } + }); + + it("Serializes", () => { + const llm = createChatLLM(); + const serialized = llm.serialize(); + const deserialized = BAMChatLLM.fromSerialized(serialized); + verifyDeserialization(llm, deserialized); + }); +}); diff --git a/tests/e2e/adapters/watsonx/chat.test.ts b/tests/e2e/adapters/watsonx/chat.test.ts new file mode 100644 index 0000000..bf31a5a --- /dev/null +++ b/tests/e2e/adapters/watsonx/chat.test.ts @@ -0,0 +1,101 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { PromptTemplate } from "@/template.js"; +import { BaseMessage } from "@/llms/primitives/message.js"; +import { expect } from "vitest"; +import { verifyDeserialization } from "@tests/e2e/utils.js"; +import { WatsonXChatLLM } from "@/adapters/watsonx/chat.js"; +import { WatsonXLLM } from "@/adapters/watsonx/llm.js"; + +const apiKey = process.env.WATSONX_API_KEY!; +const projectId = process.env.WATSONX_PROJECT_ID!; + +describe.runIf(Boolean(apiKey && projectId))("WatsonX Chat LLM", () => { + const createChatLLM = () => { + const template = new PromptTemplate({ + variables: ["messages"], + template: `{{#messages}}{{#system}}<|begin_of_text|><|start_header_id|>system<|end_header_id|> + +{{system}}<|eot_id|>{{/system}}{{#user}}<|start_header_id|>user<|end_header_id|> + +{{user}}<|eot_id|>{{/user}}{{#assistant}}<|start_header_id|>assistant<|end_header_id|> + +{{assistant}}<|eot_id|>{{/assistant}}{{/messages}}<|start_header_id|>assistant<|end_header_id|> + +`, + }); + + return new WatsonXChatLLM({ + llm: new WatsonXLLM({ + modelId: "meta-llama/llama-3-70b-instruct", + projectId, + apiKey, + parameters: { + decoding_method: "greedy", + min_new_tokens: 5, + max_new_tokens: 50, + }, + }), + config: { + messagesToPrompt(messages: BaseMessage[]) { + return template.render({ + messages: messages.map((message) => ({ + system: message.role === "system" ? [message.text] : [], + user: message.role === "user" ? [message.text] : [], + assistant: message.role === "assistant" ? [message.text] : [], + })), + }); + }, + }, + }); + }; + + it("Generates", async () => { + const conversation = [ + BaseMessage.of({ + role: "system", + text: `You are a helpful and respectful and honest assistant. Your answer should be short and concise.`, + }), + ]; + const llm = createChatLLM(); + + for (const { question, answer } of [ + { question: `What is the coldest continent?`, answer: "arctica" }, + { question: "What is the most common typical animal that lives there?", answer: "penguin" }, + ]) { + conversation.push( + BaseMessage.of({ + role: "user", + text: question, + }), + ); + const response = await llm.generate(conversation); + + const newMessages = response.messages; + expect(newMessages).toHaveLength(1); + expect(newMessages[0].text.toLowerCase()).toContain(answer.toLowerCase()); + conversation.push(...newMessages); + } + }); + + it("Serializes", () => { + const llm = createChatLLM(); + const serialized = llm.serialize(); + const deserialized = WatsonXChatLLM.fromSerialized(serialized); + verifyDeserialization(llm, deserialized); + }); +}); diff --git a/tests/e2e/adapters/watsonx/llm.test.ts b/tests/e2e/adapters/watsonx/llm.test.ts new file mode 100644 index 0000000..a6dfdf9 --- /dev/null +++ b/tests/e2e/adapters/watsonx/llm.test.ts @@ -0,0 +1,71 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { WatsonXLLM, WatsonXLLMOutput } from "@/adapters/watsonx/llm.js"; +import { verifyDeserialization } from "@tests/e2e/utils.js"; + +const apiKey = process.env.WATSONX_API_KEY!; +const projectId = process.env.WATSONX_PROJECT_ID!; +const spaceId = process.env.WATSONX_SPACE_ID; +const deploymentId = process.env.WATSONX_DEPLOYMENT_ID; + +describe.runIf(Boolean(apiKey && projectId))("WatsonX SDK LLM", () => { + const createLLM = () => { + return new WatsonXLLM({ + apiKey, + projectId, + modelId: "google/flan-ul2", + spaceId, + deploymentId, + ...(deploymentId && { + transform: ({ input, ...body }) => ({ + ...body, + parameters: { + ...body?.parameters, + prompt_variables: { ...body?.parameters?.prompt_variables, name: input }, + }, + }), + }), + }); + }; + + it("Meta", async () => { + const llm = createLLM(); + const response = await llm.meta(); + expect(response.tokenLimit).toBeGreaterThan(0); + }); + + it("Generates", async () => { + const llm = createLLM(); + const response = await llm.generate("Hello world!"); + expect(response).toBeInstanceOf(WatsonXLLMOutput); + }); + + it("Streams", async () => { + const llm = createLLM(); + for await (const chunk of llm.stream("Hello world!")) { + expect(chunk).toBeInstanceOf(WatsonXLLMOutput); + expect(chunk.finalResult).toBeTruthy(); + } + }); + + it("Serializes", () => { + const llm = createLLM(); + const serialized = llm.serialize(); + const deserialized = WatsonXLLM.fromSerialized(serialized); + verifyDeserialization(llm, deserialized); + }); +}); diff --git a/tests/e2e/agents/bee.test.ts b/tests/e2e/agents/bee.test.ts new file mode 100644 index 0000000..a23b1a2 --- /dev/null +++ b/tests/e2e/agents/bee.test.ts @@ -0,0 +1,137 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { DuckDuckGoSearchTool } from "@/tools/search/duckDuckGoSearch.js"; +import { FrameworkError } from "@/errors.js"; +import { beforeEach, expect, vi } from "vitest"; +import { Logger } from "@/logger/logger.js"; +import { BeeAgent } from "@/agents/bee/agent.js"; +import { BAMChatLLM } from "@/adapters/bam/chat.js"; +import { UnconstrainedMemory } from "@/memory/unconstrainedMemory.js"; +import { BaseMessage } from "@/llms/primitives/message.js"; +import { createCallbackRegister } from "@tests/e2e/utils.js"; +import { omitEmptyValues } from "@/internals/helpers/object.js"; + +describe("Bee Agent", () => { + const createChatLLM = () => { + return BAMChatLLM.fromPreset("meta-llama/llama-3-1-70b-instruct"); + }; + + const createAgent = () => { + return new BeeAgent({ + llm: createChatLLM(), + memory: new UnconstrainedMemory(), + tools: [ + new DuckDuckGoSearchTool({ + maxResultsPerPage: 10, + throttle: { + interval: 5000, + limit: 1, + strict: true, + }, + httpClientOptions: { + proxy: process.env.PROXY, + }, + }), + ], + }); + }; + + beforeEach(() => { + vi.useRealTimers(); + }); + + it("Aborts", async () => { + const abortController = new AbortController(); + const myError = new Error("Stop!"); + setTimeout(() => { + abortController.abort(myError); + }, 500); + + const agent = createAgent(); + try { + await agent.run( + { + prompt: 'What"s the biggest building on the world?"', + }, + { + signal: abortController.signal, + execution: { + maxIterations: 10, + totalMaxRetries: 0, + maxRetriesPerStep: 0, + }, + }, + ); + } catch (e) { + expect(e).toBeInstanceOf(FrameworkError); + expect(e.getCause()).toBe(myError); + } + }); + + it("Runs", async () => { + const callbacks = createCallbackRegister(); + const userLogger = Logger.of({ name: "user" }); + const agent = createAgent(); + + let response: Awaited>; + try { + response = await agent + .run( + { prompt: "Who is the president of Czech Republic?" }, + { + execution: { + maxIterations: 3, + totalMaxRetries: 5, + }, + }, + ) + .observe((emitter) => { + emitter.registerCallbacks({ + success: callbacks.create("success", { + check: ({ data }) => { + expect(data).toBeInstanceOf(BaseMessage); + expect(Object.keys(omitEmptyValues(data)).length).toBeGreaterThan(0); + }, + }), + retry: callbacks.create("retry", { required: false }), + error: callbacks.create("error", { required: false }), + start: callbacks.create("start"), + update: callbacks.create("update"), + toolStart: callbacks.create("toolStart"), + toolSuccess: callbacks.create("toolSuccess"), + toolError: callbacks.create("toolError", { required: false }), + }); + }); + } catch (e) { + expect(e).toBeInstanceOf(FrameworkError); + throw e; + } finally { + agent.destroy(); + } + + userLogger.info("==================END====================="); + expect(response.result).toBeDefined(); + expect(response.result.text).toMatch(/petr pavel/i); + + callbacks.verify(({ fn }) => { + for (const [_, event, ...extra] of fn.mock.calls) { + expect(event).toBeDefined(); + expect(extra).toHaveLength(0); + } + }); + }); +}); diff --git a/tests/e2e/utils.ts b/tests/e2e/utils.ts new file mode 100644 index 0000000..cb145fb --- /dev/null +++ b/tests/e2e/utils.ts @@ -0,0 +1,134 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import { Mock } from "vitest"; +import * as R from "remeda"; +import { Serializer } from "@/serializer/serializer.js"; +import { Client } from "@ibm-generative-ai/node-sdk"; +import { ClassConstructor } from "@/internals/types.js"; +import { Logger } from "@/logger/logger.js"; +import { pino } from "pino"; +import { BaseLLM as LCBaseLLM } from "@langchain/core/language_models/llms"; +import { ZodType } from "zod"; +import { Callback } from "@/emitter/types.js"; +import { RunContext } from "@/context.js"; +import { Emitter } from "@/emitter/emitter.js"; +import { toJsonSchema } from "@/internals/helpers/schema.js"; +import { OpenAI } from "openai"; + +interface CallbackOptions { + required?: boolean; + check?: Callback; +} + +export function createCallbackRegister() { + return { + _container: new Map }>(), + create(name: string, options: CallbackOptions = {}) { + const fn = vi.fn(); + if (options.check) { + fn.mockImplementation(options?.check); + } + if (this._container.has(name)) { + throw new Error(`Function '${name}' already registered!`); + } + this._container.set(name, { + fn, + options: { required: true, ...R.pickBy(R.isDefined)(options) }, + }); + return fn as NonNullable; + }, + [Symbol.iterator]() { + return this._container.entries(); + }, + verify(handler?: (ctx: { name: string; fn: Mock; options: CallbackOptions }) => void) { + for (const [name, { fn, options }] of this._container.entries()) { + if (options.required) { + expect(fn).toHaveBeenCalled(); + } + handler?.({ name, fn, options }); + } + }, + }; +} + +export function verifyDeserialization(ref: unknown, deserialized: unknown, parent?: any) { + if (R.isPromise(ref) || R.isPromise(deserialized)) { + throw new TypeError('Value passed to "verifyDeserialization" is promise (forgotten await)!'); + } + + if (R.isFunction(ref) && R.isFunction(deserialized)) { + expect(deserialized.toString()).toStrictEqual(ref.toString()); + return; + } + + if (R.isObjectType(ref) && R.isObjectType(deserialized)) { + const getNonIgnoredKeys = (instance: any) => + new Set( + Object.entries(instance) + .filter(([_, value]) => !verifyDeserialization.isIgnored(value, instance)) + .map(([key, _]) => key) + .sort(), + ); + + const refKeys = getNonIgnoredKeys(ref); + const keysB = getNonIgnoredKeys(deserialized); + expect(keysB).toStrictEqual(refKeys); + + for (const key of refKeys.values()) { + let value: any = ref[key as keyof typeof ref]; + let target: any = deserialized[key as keyof typeof deserialized]; + + if (value instanceof ZodType) { + value = toJsonSchema(value); + } + if (target instanceof ZodType) { + target = toJsonSchema(target); + } + + Serializer.findFactory(target); + verifyDeserialization(value, target, parent); + } + } else { + expect(deserialized).toStrictEqual(ref); + } +} +verifyDeserialization.ignoredClasses = [ + Logger, + Client, + LCBaseLLM, + RunContext, + Emitter, +] as ClassConstructor[]; +verifyDeserialization.isIgnored = (value: unknown, parent?: any) => { + const ignored = verifyDeserialization.ignoredClasses; + + // Pino check + if (R.isObjectType(value) && Object.values(pino.symbols).some((symbol) => symbol in value)) { + return true; + } + + if (parent && parent instanceof OpenAI) { + try { + Serializer.findFactory(value); + return false; + } catch { + return true; + } + } + + return ignored.some((Class) => value instanceof Class); +}; diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..3b44f14 --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import dotenv from "dotenv"; +dotenv.config(); +dotenv.config({ + path: ".env.test", + override: true, +}); +dotenv.config({ + path: ".env.test.local", + override: true, +}); diff --git a/tests/utils/file.ts b/tests/utils/file.ts new file mode 100644 index 0000000..d9b342a --- /dev/null +++ b/tests/utils/file.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2024 IBM Corp. + * + * 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. + */ + +import * as os from "node:os"; +import path from "node:path"; +import fs from "node:fs"; +import { createRandomHash } from "@/internals/helpers/hash.js"; + +export async function getTempFile(name: string, data: string) { + const fullPath = path.join(os.tmpdir(), `${createRandomHash(4)}_${name}`); + await fs.promises.writeFile(fullPath, data); + + return { + fullPath, + async [Symbol.asyncDispose]() { + await fs.promises.unlink(fullPath); + }, + }; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ac40f78 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "rootDir": ".", + "moduleResolution": "NodeNext", + "allowSyntheticDefaultImports": true, + "isolatedModules": true, + "useDefineForClassFields": true, + "incremental": false, + "moduleDetection": "auto", + "baseUrl": ".", + "types": ["node", "vitest/globals"], + "declarationMap": false, + "declaration": false, + "resolveJsonModule": true, + "sourceMap": false, + "allowJs": true, + "outDir": "./dist", + "removeComments": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "strict": true, + "skipLibCheck": true, + "paths": { + "@/*": ["./src/*"], + "@tests/*": ["./tests/*"] + }, + "useUnknownInCatchVariables": false + } +} diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..c3673e4 --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,100 @@ +import { defineConfig } from "tsup"; +import packageJson from "./package.json"; +import swc, { JscConfig } from "@swc/core"; +import path from "node:path"; + +import tsConfig from "./tsconfig.json"; +import { JscTarget } from "@swc/types"; + +export default defineConfig({ + entry: ["src/**/*.{ts,js}", "!src/**/*.test.ts"], + tsconfig: "./tsconfig.json", + sourcemap: true, + dts: true, + format: ["esm", "cjs"], + plugins: [ + { + name: "fix-cjs-imports", + renderChunk(code) { + if (this.format === "cjs") { + const regexCjs = /require\((?['"])(?\.[^'"]+)\.js['"]\)/g; + const regexEsm = /from(?[\s]*)(?['"])(?\.[^'"]+)\.js['"]/g; + return { + code: code + .replace(regexCjs, "require($$.cjs$)") + .replace(regexEsm, "from$$$.cjs$"), + }; + } + }, + }, + { + name: "override-swc", + esbuildOptions: (options) => { + const plugin = options.plugins?.find((p) => p.name === "swc"); + if (plugin) { + // Original Source: https://github.com/egoist/tsup/blob/49c11c3073ce977a01c84e7848fc070d5de0a652/src/esbuild/swc.ts#L14-L67 + // Reason: tsup does not provide a way to modify 'jsc' config + plugin.setup = (build) => { + // Force esbuild to keep class names as well + build.initialOptions.keepNames = true; + + build.onLoad({ filter: /\.[jt]sx?$/ }, async (args: any) => { + const isTs = /\.tsx?$/.test(args.path); + + const jsc: JscConfig = { + parser: { + syntax: isTs ? "typescript" : "ecmascript", + decorators: true, + }, + transform: { + legacyDecorator: true, + decoratorMetadata: true, + }, + baseUrl: path.resolve(__dirname, tsConfig.compilerOptions.baseUrl || "."), + paths: tsConfig.compilerOptions.paths, + keepClassNames: true, + preserveAllComments: true, + target: (tsConfig.compilerOptions.target || "es2022").toLowerCase() as JscTarget, + }; + + const result = await swc.transformFile(args.path, { + jsc, + sourceMaps: true, + configFile: false, + swcrc: false, + }); + + let code = result.code; + if (result.map) { + const map: { sources: string[] } = JSON.parse(result.map); + // Make sure sources are relative path + map.sources = map.sources.map((source) => { + return path.isAbsolute(source) + ? path.relative(path.dirname(args.path), source) + : source; + }); + code += `//# sourceMappingURL=data:application/json;base64,${Buffer.from( + JSON.stringify(map), + ).toString("base64")}`; + } + return { + contents: code, + }; + }); + }; + } + }, + }, + ], + treeshake: true, + shims: true, + skipNodeModulesBundle: true, + legacyOutput: false, + bundle: false, + splitting: false, + silent: false, + clean: true, + define: { + __LIBRARY_VERSION: JSON.stringify(packageJson.version), + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..50e3fdc --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "vitest/config"; +import tsConfigPaths from "vite-tsconfig-paths"; +import packageJson from "./package.json"; + +export default defineConfig({ + test: { + globals: true, + passWithNoTests: true, + testTimeout: 120 * 1000, + setupFiles: ["./tests/setup.ts"], + deps: { + interopDefault: false, + }, + }, + define: { + __LIBRARY_VERSION: JSON.stringify(packageJson.version), + }, + plugins: [tsConfigPaths()], +}); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..fb75c97 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8778 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@ai-zen/node-fetch-event-source@npm:^2.1.2, @ai-zen/node-fetch-event-source@npm:^2.1.4": + version: 2.1.4 + resolution: "@ai-zen/node-fetch-event-source@npm:2.1.4" + dependencies: + cross-fetch: "npm:^4.0.0" + checksum: 10c0/9d2753f308bf6258c85d52328ef87e45dd4d2888f2bae7dca8b64b183d9d8f674a5f41aa57909d48f00402a0999aa7908dfa7da0f4b51ca22e82c333cd358ef5 + languageName: node + linkType: hard + +"@ampproject/remapping@npm:^2.3.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.21.4, @babel/code-frame@npm:^7.22.13": + version: 7.24.7 + resolution: "@babel/code-frame@npm:7.24.7" + dependencies: + "@babel/highlight": "npm:^7.24.7" + picocolors: "npm:^1.0.0" + checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-validator-identifier@npm:7.24.7" + checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651 + languageName: node + linkType: hard + +"@babel/highlight@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/highlight@npm:7.24.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.24.7" + chalk: "npm:^2.4.2" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a + languageName: node + linkType: hard + +"@bufbuild/protobuf@npm:^1.10.0": + version: 1.10.0 + resolution: "@bufbuild/protobuf@npm:1.10.0" + checksum: 10c0/5487b9c2e63846d0e3bde4d025cc77ae44a22166a5d6c184df0da5581e1ab6d66dd115af0ccad814576dcd011bb1b93989fb0ac1eb4ae452979bb8b186693ba0 + languageName: node + linkType: hard + +"@connectrpc/connect-node@npm:^1.4.0": + version: 1.4.0 + resolution: "@connectrpc/connect-node@npm:1.4.0" + dependencies: + undici: "npm:^5.28.3" + peerDependencies: + "@bufbuild/protobuf": ^1.4.2 + "@connectrpc/connect": 1.4.0 + checksum: 10c0/8699e3cb8d5521c8a10841169a9bf549728de2651fef66b450a0b1a2c59057a0a7abd08fab21ccdfc0e5f45394112ad072db754001741b97ccd4376c10105b24 + languageName: node + linkType: hard + +"@connectrpc/connect@npm:^1.4.0": + version: 1.4.0 + resolution: "@connectrpc/connect@npm:1.4.0" + peerDependencies: + "@bufbuild/protobuf": ^1.4.2 + checksum: 10c0/6006e4aef6908a786ffbda6f7c9c92fe0906e031e891c7ce28de9d1b8e3feb9115d924ec6fc38131a5b9936b537594b0a828711e3e0544f760c78e010eda1ea1 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/aix-ppc64@npm:0.23.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-arm64@npm:0.23.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-arm@npm:0.23.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-x64@npm:0.23.1" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/darwin-arm64@npm:0.23.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/darwin-x64@npm:0.23.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/freebsd-arm64@npm:0.23.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/freebsd-x64@npm:0.23.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-arm64@npm:0.23.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-arm@npm:0.23.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-ia32@npm:0.23.1" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-loong64@npm:0.23.1" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-mips64el@npm:0.23.1" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-ppc64@npm:0.23.1" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-riscv64@npm:0.23.1" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-s390x@npm:0.23.1" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-x64@npm:0.23.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/netbsd-x64@npm:0.23.1" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/openbsd-arm64@npm:0.23.1" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/openbsd-x64@npm:0.23.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/sunos-x64@npm:0.23.1" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-arm64@npm:0.23.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-ia32@npm:0.23.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-x64@npm:0.23.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": + version: 4.4.0 + resolution: "@eslint-community/eslint-utils@npm:4.4.0" + dependencies: + eslint-visitor-keys: "npm:^3.3.0" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/7e559c4ce59cd3a06b1b5a517b593912e680a7f981ae7affab0d01d709e99cd5647019be8fafa38c350305bc32f1f7d42c7073edde2ab536c745e365f37b607e + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.11.0": + version: 4.11.0 + resolution: "@eslint-community/regexpp@npm:4.11.0" + checksum: 10c0/0f6328869b2741e2794da4ad80beac55cba7de2d3b44f796a60955b0586212ec75e6b0253291fd4aad2100ad471d1480d8895f2b54f1605439ba4c875e05e523 + languageName: node + linkType: hard + +"@eslint/config-array@npm:^0.17.1": + version: 0.17.1 + resolution: "@eslint/config-array@npm:0.17.1" + dependencies: + "@eslint/object-schema": "npm:^2.1.4" + debug: "npm:^4.3.1" + minimatch: "npm:^3.1.2" + checksum: 10c0/b986a0a96f2b42467578968ce3d4ae3b9284e587f8490f2dcdc44ff1b8d30580c62b221da6e58d07b09e156c3050e2dc38267f9370521d9cafc099c4e30154ef + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^3.1.0": + version: 3.1.0 + resolution: "@eslint/eslintrc@npm:3.1.0" + dependencies: + ajv: "npm:^6.12.4" + debug: "npm:^4.3.2" + espree: "npm:^10.0.1" + globals: "npm:^14.0.0" + ignore: "npm:^5.2.0" + import-fresh: "npm:^3.2.1" + js-yaml: "npm:^4.1.0" + minimatch: "npm:^3.1.2" + strip-json-comments: "npm:^3.1.1" + checksum: 10c0/5b7332ed781edcfc98caa8dedbbb843abfb9bda2e86538529c843473f580e40c69eb894410eddc6702f487e9ee8f8cfa8df83213d43a8fdb549f23ce06699167 + languageName: node + linkType: hard + +"@eslint/js@npm:9.9.0, @eslint/js@npm:^9.9.0": + version: 9.9.0 + resolution: "@eslint/js@npm:9.9.0" + checksum: 10c0/6ec9f1f0d576132444d6a5c66a8a08b0be9444e3ebb563fa6a6bebcf5299df3da7e454dc04c0fa601bb811197f00764b3a04430d8458cdb8e3a4677993d23f30 + languageName: node + linkType: hard + +"@eslint/object-schema@npm:^2.1.4": + version: 2.1.4 + resolution: "@eslint/object-schema@npm:2.1.4" + checksum: 10c0/e9885532ea70e483fb007bf1275968b05bb15ebaa506d98560c41a41220d33d342e19023d5f2939fed6eb59676c1bda5c847c284b4b55fce521d282004da4dda + languageName: node + linkType: hard + +"@fastify/busboy@npm:^2.0.0": + version: 2.1.1 + resolution: "@fastify/busboy@npm:2.1.1" + checksum: 10c0/6f8027a8cba7f8f7b736718b013f5a38c0476eea67034c94a0d3c375e2b114366ad4419e6a6fa7ffc2ef9c6d3e0435d76dd584a7a1cbac23962fda7650b579e3 + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/retry@npm:^0.3.0": + version: 0.3.0 + resolution: "@humanwhocodes/retry@npm:0.3.0" + checksum: 10c0/7111ec4e098b1a428459b4e3be5a5d2a13b02905f805a2468f4fa628d072f0de2da26a27d04f65ea2846f73ba51f4204661709f05bfccff645e3cedef8781bb6 + languageName: node + linkType: hard + +"@hutson/parse-repository-url@npm:^5.0.0": + version: 5.0.0 + resolution: "@hutson/parse-repository-url@npm:5.0.0" + checksum: 10c0/068c5c9e38fecc10e3aa6f6eee5818db6f3f29a70d01fec64e9ec0ee985e8995a0cf79ec5f7c80530f1fb27d99668ee2f38d8929b712b82d5100ebd2c9153e85 + languageName: node + linkType: hard + +"@iarna/toml@npm:2.2.5": + version: 2.2.5 + resolution: "@iarna/toml@npm:2.2.5" + checksum: 10c0/d095381ad4554aca233b7cf5a91f243ef619e5e15efd3157bc640feac320545450d14b394aebbf6f02a2047437ced778ae598d5879a995441ab7b6c0b2c2f201 + languageName: node + linkType: hard + +"@ibm-generative-ai/node-sdk@npm:~3.2.1": + version: 3.2.1 + resolution: "@ibm-generative-ai/node-sdk@npm:3.2.1" + dependencies: + "@ai-zen/node-fetch-event-source": "npm:^2.1.2" + fetch-retry: "npm:^5.0.6" + http-status-codes: "npm:^2.3.0" + openapi-fetch: "npm:^0.8.2" + p-queue-compat: "npm:^1.0.225" + yaml: "npm:^2.3.3" + peerDependencies: + "@langchain/core": ">=0.1.0" + checksum: 10c0/4963f0b3ee27a881544b760358c34680db9d0edd833dcb2224967295367ec1086b553c7dabbecb674f25826ff8ed3b58430996a3d29f9565edbd50559b628888 + languageName: node + linkType: hard + +"@inquirer/figures@npm:^1.0.3": + version: 1.0.5 + resolution: "@inquirer/figures@npm:1.0.5" + checksum: 10c0/ec9ba23db42cb33fa18eb919abf2a18e750e739e64c1883ce4a98345cd5711c60cac12d1faf56a859f52d387deb221c8d3dfe60344ee07955a9a262f8b821fe3 + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.2, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 + languageName: node + linkType: hard + +"@langchain/community@npm:~0.2.28": + version: 0.2.28 + resolution: "@langchain/community@npm:0.2.28" + dependencies: + "@langchain/core": "npm:>=0.2.21 <0.3.0" + "@langchain/openai": "npm:>=0.2.0 <0.3.0" + binary-extensions: "npm:^2.2.0" + expr-eval: "npm:^2.0.2" + flat: "npm:^5.0.2" + js-yaml: "npm:^4.1.0" + langchain: "npm:~0.2.3" + langsmith: "npm:~0.1.30" + uuid: "npm:^10.0.0" + zod: "npm:^3.22.3" + zod-to-json-schema: "npm:^3.22.5" + peerDependencies: + "@aws-crypto/sha256-js": ^5.0.0 + "@aws-sdk/client-bedrock-agent-runtime": ^3.583.0 + "@aws-sdk/client-bedrock-runtime": ^3.422.0 + "@aws-sdk/client-dynamodb": ^3.310.0 + "@aws-sdk/client-kendra": ^3.352.0 + "@aws-sdk/client-lambda": ^3.310.0 + "@aws-sdk/client-s3": ^3.310.0 + "@aws-sdk/client-sagemaker-runtime": ^3.310.0 + "@aws-sdk/client-sfn": ^3.310.0 + "@aws-sdk/credential-provider-node": ^3.388.0 + "@azure/search-documents": ^12.0.0 + "@azure/storage-blob": ^12.15.0 + "@browserbasehq/sdk": "*" + "@clickhouse/client": ^0.2.5 + "@cloudflare/ai": "*" + "@datastax/astra-db-ts": ^1.0.0 + "@elastic/elasticsearch": ^8.4.0 + "@getmetal/metal-sdk": "*" + "@getzep/zep-cloud": ^1.0.6 + "@getzep/zep-js": ^0.9.0 + "@gomomento/sdk": ^1.51.1 + "@gomomento/sdk-core": ^1.51.1 + "@google-ai/generativelanguage": "*" + "@google-cloud/storage": ^6.10.1 || ^7.7.0 + "@gradientai/nodejs-sdk": ^1.2.0 + "@huggingface/inference": ^2.6.4 + "@langchain/langgraph": ~0.0.26 + "@layerup/layerup-security": ^1.5.12 + "@mendable/firecrawl-js": ^0.0.13 + "@mlc-ai/web-llm": 0.2.46 + "@mozilla/readability": "*" + "@neondatabase/serverless": "*" + "@notionhq/client": ^2.2.10 + "@opensearch-project/opensearch": "*" + "@pinecone-database/pinecone": "*" + "@planetscale/database": ^1.8.0 + "@premai/prem-sdk": ^0.3.25 + "@qdrant/js-client-rest": ^1.8.2 + "@raycast/api": ^1.55.2 + "@rockset/client": ^0.9.1 + "@smithy/eventstream-codec": ^2.0.5 + "@smithy/protocol-http": ^3.0.6 + "@smithy/signature-v4": ^2.0.10 + "@smithy/util-utf8": ^2.0.0 + "@spider-cloud/spider-client": ^0.0.21 + "@supabase/supabase-js": ^2.45.0 + "@tensorflow-models/universal-sentence-encoder": "*" + "@tensorflow/tfjs-converter": "*" + "@tensorflow/tfjs-core": "*" + "@upstash/ratelimit": ^1.1.3 + "@upstash/redis": ^1.20.6 + "@upstash/vector": ^1.1.1 + "@vercel/kv": ^0.2.3 + "@vercel/postgres": ^0.5.0 + "@writerai/writer-sdk": ^0.40.2 + "@xata.io/client": ^0.28.0 + "@xenova/transformers": ^2.17.2 + "@zilliz/milvus2-sdk-node": ">=2.3.5" + apify-client: ^2.7.1 + assemblyai: ^4.6.0 + better-sqlite3: ">=9.4.0 <12.0.0" + cassandra-driver: ^4.7.2 + cborg: ^4.1.1 + cheerio: ^1.0.0-rc.12 + chromadb: "*" + closevector-common: 0.1.3 + closevector-node: 0.1.6 + closevector-web: 0.1.6 + cohere-ai: "*" + convex: ^1.3.1 + couchbase: ^4.3.0 + crypto-js: ^4.2.0 + d3-dsv: ^2.0.0 + discord.js: ^14.14.1 + dria: ^0.0.3 + duck-duck-scrape: ^2.2.5 + epub2: ^3.0.1 + faiss-node: ^0.5.1 + firebase-admin: ^11.9.0 || ^12.0.0 + google-auth-library: "*" + googleapis: "*" + hnswlib-node: ^3.0.0 + html-to-text: ^9.0.5 + ignore: ^5.2.0 + interface-datastore: ^8.2.11 + ioredis: ^5.3.2 + it-all: ^3.0.4 + jsdom: "*" + jsonwebtoken: ^9.0.2 + llmonitor: ^0.5.9 + lodash: ^4.17.21 + lunary: ^0.7.10 + mammoth: ^1.6.0 + mongodb: ">=5.2.0" + mysql2: ^3.9.8 + neo4j-driver: "*" + node-llama-cpp: "*" + notion-to-md: ^3.1.0 + officeparser: ^4.0.4 + pdf-parse: 1.1.1 + pg: ^8.11.0 + pg-copy-streams: ^6.0.5 + pickleparser: ^0.2.1 + playwright: ^1.32.1 + portkey-ai: ^0.1.11 + puppeteer: "*" + redis: "*" + replicate: ^0.29.4 + sonix-speech-recognition: ^2.1.1 + srt-parser-2: ^1.2.3 + typeorm: ^0.3.20 + typesense: ^1.5.3 + usearch: ^1.1.1 + vectordb: ^0.1.4 + voy-search: 0.6.2 + weaviate-ts-client: "*" + web-auth-library: ^1.0.3 + ws: ^8.14.2 + youtube-transcript: ^1.0.6 + youtubei.js: ^9.1.0 + peerDependenciesMeta: + "@aws-crypto/sha256-js": + optional: true + "@aws-sdk/client-bedrock-agent-runtime": + optional: true + "@aws-sdk/client-bedrock-runtime": + optional: true + "@aws-sdk/client-dynamodb": + optional: true + "@aws-sdk/client-kendra": + optional: true + "@aws-sdk/client-lambda": + optional: true + "@aws-sdk/client-s3": + optional: true + "@aws-sdk/client-sagemaker-runtime": + optional: true + "@aws-sdk/client-sfn": + optional: true + "@aws-sdk/credential-provider-node": + optional: true + "@azure/search-documents": + optional: true + "@azure/storage-blob": + optional: true + "@browserbasehq/sdk": + optional: true + "@clickhouse/client": + optional: true + "@cloudflare/ai": + optional: true + "@datastax/astra-db-ts": + optional: true + "@elastic/elasticsearch": + optional: true + "@getmetal/metal-sdk": + optional: true + "@getzep/zep-cloud": + optional: true + "@getzep/zep-js": + optional: true + "@gomomento/sdk": + optional: true + "@gomomento/sdk-core": + optional: true + "@google-ai/generativelanguage": + optional: true + "@google-cloud/storage": + optional: true + "@gradientai/nodejs-sdk": + optional: true + "@huggingface/inference": + optional: true + "@langchain/langgraph": + optional: true + "@layerup/layerup-security": + optional: true + "@mendable/firecrawl-js": + optional: true + "@mlc-ai/web-llm": + optional: true + "@mozilla/readability": + optional: true + "@neondatabase/serverless": + optional: true + "@notionhq/client": + optional: true + "@opensearch-project/opensearch": + optional: true + "@pinecone-database/pinecone": + optional: true + "@planetscale/database": + optional: true + "@premai/prem-sdk": + optional: true + "@qdrant/js-client-rest": + optional: true + "@raycast/api": + optional: true + "@rockset/client": + optional: true + "@smithy/eventstream-codec": + optional: true + "@smithy/protocol-http": + optional: true + "@smithy/signature-v4": + optional: true + "@smithy/util-utf8": + optional: true + "@spider-cloud/spider-client": + optional: true + "@supabase/supabase-js": + optional: true + "@tensorflow-models/universal-sentence-encoder": + optional: true + "@tensorflow/tfjs-converter": + optional: true + "@tensorflow/tfjs-core": + optional: true + "@upstash/ratelimit": + optional: true + "@upstash/redis": + optional: true + "@upstash/vector": + optional: true + "@vercel/kv": + optional: true + "@vercel/postgres": + optional: true + "@writerai/writer-sdk": + optional: true + "@xata.io/client": + optional: true + "@xenova/transformers": + optional: true + "@zilliz/milvus2-sdk-node": + optional: true + apify-client: + optional: true + assemblyai: + optional: true + better-sqlite3: + optional: true + cassandra-driver: + optional: true + cborg: + optional: true + cheerio: + optional: true + chromadb: + optional: true + closevector-common: + optional: true + closevector-node: + optional: true + closevector-web: + optional: true + cohere-ai: + optional: true + convex: + optional: true + couchbase: + optional: true + crypto-js: + optional: true + d3-dsv: + optional: true + discord.js: + optional: true + dria: + optional: true + duck-duck-scrape: + optional: true + epub2: + optional: true + faiss-node: + optional: true + firebase-admin: + optional: true + google-auth-library: + optional: true + googleapis: + optional: true + hnswlib-node: + optional: true + html-to-text: + optional: true + ignore: + optional: true + interface-datastore: + optional: true + ioredis: + optional: true + it-all: + optional: true + jsdom: + optional: true + jsonwebtoken: + optional: true + llmonitor: + optional: true + lodash: + optional: true + lunary: + optional: true + mammoth: + optional: true + mongodb: + optional: true + mysql2: + optional: true + neo4j-driver: + optional: true + node-llama-cpp: + optional: true + notion-to-md: + optional: true + officeparser: + optional: true + pdf-parse: + optional: true + pg: + optional: true + pg-copy-streams: + optional: true + pickleparser: + optional: true + playwright: + optional: true + portkey-ai: + optional: true + puppeteer: + optional: true + redis: + optional: true + replicate: + optional: true + sonix-speech-recognition: + optional: true + srt-parser-2: + optional: true + typeorm: + optional: true + typesense: + optional: true + usearch: + optional: true + vectordb: + optional: true + voy-search: + optional: true + weaviate-ts-client: + optional: true + web-auth-library: + optional: true + ws: + optional: true + youtube-transcript: + optional: true + youtubei.js: + optional: true + checksum: 10c0/ff3ff37a70f62a8b65ac9e089f891970e333f1d497cda202431edbef1b203c7eaab2e498f5ee04b340f228ecc2fe604d2e070b98d5a8ff92f3ebf84a28807617 + languageName: node + linkType: hard + +"@langchain/core@npm:>0.2.0 <0.3.0, @langchain/core@npm:>=0.2.20 <0.3.0, @langchain/core@npm:>=0.2.21 <0.3.0, @langchain/core@npm:>=0.2.26 <0.3.0, @langchain/core@npm:~0.2.27": + version: 0.2.27 + resolution: "@langchain/core@npm:0.2.27" + dependencies: + ansi-styles: "npm:^5.0.0" + camelcase: "npm:6" + decamelize: "npm:1.2.0" + js-tiktoken: "npm:^1.0.12" + langsmith: "npm:~0.1.39" + mustache: "npm:^4.2.0" + p-queue: "npm:^6.6.2" + p-retry: "npm:4" + uuid: "npm:^10.0.0" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.22.3" + checksum: 10c0/2206ae7bce2ec3abfa4535460db8b835fa63b28f422d193e322a9270493c5dd0662fbdeeeb06733d7736badf90df8e45c6ceca212e2fd4c79d24202e90950ad6 + languageName: node + linkType: hard + +"@langchain/langgraph@npm:~0.0.34": + version: 0.0.34 + resolution: "@langchain/langgraph@npm:0.0.34" + dependencies: + "@langchain/core": "npm:>=0.2.20 <0.3.0" + uuid: "npm:^10.0.0" + zod: "npm:^3.23.8" + peerDependencies: + better-sqlite3: ^9.5.0 + peerDependenciesMeta: + better-sqlite3: + optional: true + checksum: 10c0/1bc24e2f8fab94d9d3e77c9b21938fafcc346e3239ecd00f8f632e3c388164210783e0526035fc1239bb9238253857706246062eb90c40686551448d942bedb5 + languageName: node + linkType: hard + +"@langchain/openai@npm:>=0.1.0 <0.3.0, @langchain/openai@npm:>=0.2.0 <0.3.0": + version: 0.2.7 + resolution: "@langchain/openai@npm:0.2.7" + dependencies: + "@langchain/core": "npm:>=0.2.26 <0.3.0" + js-tiktoken: "npm:^1.0.12" + openai: "npm:^4.55.0" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.22.3" + checksum: 10c0/3a7a4bd0feda22c58704651ed8ac5ac1a0fcc7fdb0509989254c9eb950e01ed852a94fdd1c301bb4a89d750335e20afe0237899bfce5df55e7d450cef8c63de3 + languageName: node + linkType: hard + +"@langchain/textsplitters@npm:~0.0.0": + version: 0.0.3 + resolution: "@langchain/textsplitters@npm:0.0.3" + dependencies: + "@langchain/core": "npm:>0.2.0 <0.3.0" + js-tiktoken: "npm:^1.0.12" + checksum: 10c0/3297b48f636a8a6acbd65f1465624741e6d557512ea8020a208cc6b2aa6e8d752cd08511a92ef980a06ed95858b7750a1126a4e6acfbb75fd4733e050651f405 + languageName: node + linkType: hard + +"@mixmark-io/domino@npm:^2.2.0": + version: 2.2.0 + resolution: "@mixmark-io/domino@npm:2.2.0" + checksum: 10c0/aa468a15f9217d425220fe6a4b3f9416cbe8e566ee14efc191c6d5cc04fe39338b16a90bbac190f28d44e69465db5f2cf95f479c621ce38060ca6b2a3d346e9d + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^2.0.0": + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae + languageName: node + linkType: hard + +"@npmcli/fs@npm:^3.1.0": + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99 + languageName: node + linkType: hard + +"@octokit/auth-token@npm:^4.0.0": + version: 4.0.0 + resolution: "@octokit/auth-token@npm:4.0.0" + checksum: 10c0/57acaa6c394c5abab2f74e8e1dcf4e7a16b236f713c77a54b8f08e2d14114de94b37946259e33ec2aab0566b26f724c2b71d2602352b59e541a9854897618f3c + languageName: node + linkType: hard + +"@octokit/core@npm:^5.0.2": + version: 5.2.0 + resolution: "@octokit/core@npm:5.2.0" + dependencies: + "@octokit/auth-token": "npm:^4.0.0" + "@octokit/graphql": "npm:^7.1.0" + "@octokit/request": "npm:^8.3.1" + "@octokit/request-error": "npm:^5.1.0" + "@octokit/types": "npm:^13.0.0" + before-after-hook: "npm:^2.2.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10c0/9dc5cf55b335da382f340ef74c8009c06a1f7157b0530d3ff6cacf179887811352dcd405448e37849d73f17b28970b7817995be2260ce902dad52b91905542f0 + languageName: node + linkType: hard + +"@octokit/endpoint@npm:^9.0.1": + version: 9.0.5 + resolution: "@octokit/endpoint@npm:9.0.5" + dependencies: + "@octokit/types": "npm:^13.1.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10c0/e9bbb2111abe691c146075abb1b6f724a9b77fa8bfefdaaa82b8ebad6c8790e949f2367bb0b79800fef93ad72807513333e83e8ffba389bc85215535f63534d9 + languageName: node + linkType: hard + +"@octokit/graphql@npm:^7.1.0": + version: 7.1.0 + resolution: "@octokit/graphql@npm:7.1.0" + dependencies: + "@octokit/request": "npm:^8.3.0" + "@octokit/types": "npm:^13.0.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10c0/6d50a013d151f416fc837644e394e8b8872da7b17b181da119842ca569b0971e4dfacda55af6c329b51614e436945415dd5bd75eb3652055fdb754bbcd20d9d1 + languageName: node + linkType: hard + +"@octokit/openapi-types@npm:^22.2.0": + version: 22.2.0 + resolution: "@octokit/openapi-types@npm:22.2.0" + checksum: 10c0/a45bfc735611e836df0729f5922bbd5811d401052b972d1e3bc1278a2d2403e00f4552ce9d1f2793f77f167d212da559c5cb9f1b02c935114ad6d898779546ee + languageName: node + linkType: hard + +"@octokit/plugin-paginate-rest@npm:11.3.1": + version: 11.3.1 + resolution: "@octokit/plugin-paginate-rest@npm:11.3.1" + dependencies: + "@octokit/types": "npm:^13.5.0" + peerDependencies: + "@octokit/core": 5 + checksum: 10c0/72107ff7e459c49d1f13bbe44ac07b073497692eba28cb5ac6dbfa41e0ebc059ad7bccfa3dd45d3165348adcc2ede8ac159f8a9b637389b8e335af16aaa01469 + languageName: node + linkType: hard + +"@octokit/plugin-request-log@npm:^4.0.0": + version: 4.0.1 + resolution: "@octokit/plugin-request-log@npm:4.0.1" + peerDependencies: + "@octokit/core": 5 + checksum: 10c0/6f556f86258c5fbff9b1821075dc91137b7499f2ad0fd12391f0876064a6daa88abe1748336b2d483516505771d358aa15cb4bcdabc348a79e3d951fe9726798 + languageName: node + linkType: hard + +"@octokit/plugin-rest-endpoint-methods@npm:13.2.2": + version: 13.2.2 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.2.2" + dependencies: + "@octokit/types": "npm:^13.5.0" + peerDependencies: + "@octokit/core": ^5 + checksum: 10c0/0f2b14b7a185b49908bcc01bcae9849aae2da46c88f500c143d230caa3cd35540839b916e88a4642c60a5499d33e7a37faf1aa42c5bab270cefc10f5d6202893 + languageName: node + linkType: hard + +"@octokit/request-error@npm:^5.1.0": + version: 5.1.0 + resolution: "@octokit/request-error@npm:5.1.0" + dependencies: + "@octokit/types": "npm:^13.1.0" + deprecation: "npm:^2.0.0" + once: "npm:^1.4.0" + checksum: 10c0/61e688abce17dd020ea1e343470b9758f294bfe5432c5cb24bdb5b9b10f90ecec1ecaaa13b48df9288409e0da14252f6579a20f609af155bd61dc778718b7738 + languageName: node + linkType: hard + +"@octokit/request@npm:^8.3.0, @octokit/request@npm:^8.3.1": + version: 8.4.0 + resolution: "@octokit/request@npm:8.4.0" + dependencies: + "@octokit/endpoint": "npm:^9.0.1" + "@octokit/request-error": "npm:^5.1.0" + "@octokit/types": "npm:^13.1.0" + universal-user-agent: "npm:^6.0.0" + checksum: 10c0/b857782ac2ff5387e9cc502759de73ea642c498c97d06ad2ecd8a395e4b9532d9f3bc3fc460e0d3d0e8f0d43c917a90c493e43766d37782b3979d3afffbf1b4b + languageName: node + linkType: hard + +"@octokit/rest@npm:20.1.1": + version: 20.1.1 + resolution: "@octokit/rest@npm:20.1.1" + dependencies: + "@octokit/core": "npm:^5.0.2" + "@octokit/plugin-paginate-rest": "npm:11.3.1" + "@octokit/plugin-request-log": "npm:^4.0.0" + "@octokit/plugin-rest-endpoint-methods": "npm:13.2.2" + checksum: 10c0/9b62e0372381b548806edbd9e32059ebaec315ddf90e9c3df7e0f2bfab2fc938ca5c3b939035e082e245315b2359947f52f853027a8ca2510fddb79ff5cc9e8a + languageName: node + linkType: hard + +"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.1.0, @octokit/types@npm:^13.5.0": + version: 13.5.0 + resolution: "@octokit/types@npm:13.5.0" + dependencies: + "@octokit/openapi-types": "npm:^22.2.0" + checksum: 10c0/355ebc6776ce23feace1b1be0927cdda758790fda83068109c4f27b354dcd43d0447d4dc24e5eafdb596465469ea1baed23f3fd63adfec508cc375ccd1dcb0a3 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + +"@pnpm/config.env-replace@npm:^1.1.0": + version: 1.1.0 + resolution: "@pnpm/config.env-replace@npm:1.1.0" + checksum: 10c0/4cfc4a5c49ab3d0c6a1f196cfd4146374768b0243d441c7de8fa7bd28eaab6290f514b98490472cc65dbd080d34369447b3e9302585e1d5c099befd7c8b5e55f + languageName: node + linkType: hard + +"@pnpm/network.ca-file@npm:^1.0.1": + version: 1.0.2 + resolution: "@pnpm/network.ca-file@npm:1.0.2" + dependencies: + graceful-fs: "npm:4.2.10" + checksum: 10c0/95f6e0e38d047aca3283550719155ce7304ac00d98911e4ab026daedaf640a63bd83e3d13e17c623fa41ac72f3801382ba21260bcce431c14fbbc06430ecb776 + languageName: node + linkType: hard + +"@pnpm/npm-conf@npm:^2.1.0": + version: 2.3.1 + resolution: "@pnpm/npm-conf@npm:2.3.1" + dependencies: + "@pnpm/config.env-replace": "npm:^1.1.0" + "@pnpm/network.ca-file": "npm:^1.0.1" + config-chain: "npm:^1.1.11" + checksum: 10c0/778a3a34ff7d6000a2594d2a9821f873f737bc56367865718b2cf0ba5d366e49689efe7975148316d7afd8e6f1dcef7d736fbb6ea7ef55caadd1dc93a36bb302 + languageName: node + linkType: hard + +"@redocly/ajv@npm:^8.11.0": + version: 8.11.0 + resolution: "@redocly/ajv@npm:8.11.0" + dependencies: + fast-deep-equal: "npm:^3.1.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + uri-js: "npm:^4.2.2" + checksum: 10c0/7ee77a8304b54189e903c30248282f81638e2bb7dcf8b7cb80e078966999c4de4eb6dabe6ac8406b5c8fe7a1443540e2bd0009d975ac285d38c2426157bc774d + languageName: node + linkType: hard + +"@redocly/config@npm:^0.9.0": + version: 0.9.0 + resolution: "@redocly/config@npm:0.9.0" + checksum: 10c0/74c8efe421f28bf658c5f2372460ba695adefa9808c48fc0c0a9e0efbb8a612d32303d71088a24b9ca11ed441f8ed7a939d1048b82d458445d9938c9c476f293 + languageName: node + linkType: hard + +"@redocly/openapi-core@npm:^1.16.0": + version: 1.20.0 + resolution: "@redocly/openapi-core@npm:1.20.0" + dependencies: + "@redocly/ajv": "npm:^8.11.0" + "@redocly/config": "npm:^0.9.0" + colorette: "npm:^1.2.0" + https-proxy-agent: "npm:^7.0.4" + js-levenshtein: "npm:^1.1.6" + js-yaml: "npm:^4.1.0" + lodash.isequal: "npm:^4.5.0" + minimatch: "npm:^5.0.1" + node-fetch: "npm:^2.6.1" + pluralize: "npm:^8.0.0" + yaml-ast-parser: "npm:0.0.43" + checksum: 10c0/0a49716c9f776dc2b6d1b1aaa1172ce79835a076cf8fc14698f2a0835c7e9b9b8eca48b27e0dc9f1551ed30f626a8e6a6ec8422b04bfb25f4d5593563276e347 + languageName: node + linkType: hard + +"@release-it/conventional-changelog@npm:^8.0.1": + version: 8.0.1 + resolution: "@release-it/conventional-changelog@npm:8.0.1" + dependencies: + concat-stream: "npm:^2.0.0" + conventional-changelog: "npm:^5.1.0" + conventional-recommended-bump: "npm:^9.0.0" + semver: "npm:^7.5.4" + peerDependencies: + release-it: ^17.0.0 + checksum: 10c0/b6057038b91c665129e37af1d297dad68abbe2095cc10e9d507be6dcc1432bc6b7545c1bbdf6eab4c35eeafca223c5a43a567ba1cdfdd774aa62bb6220c5ebe6 + languageName: node + linkType: hard + +"@rollup/plugin-commonjs@npm:^26.0.1": + version: 26.0.1 + resolution: "@rollup/plugin-commonjs@npm:26.0.1" + dependencies: + "@rollup/pluginutils": "npm:^5.0.1" + commondir: "npm:^1.0.1" + estree-walker: "npm:^2.0.2" + glob: "npm:^10.4.1" + is-reference: "npm:1.2.1" + magic-string: "npm:^0.30.3" + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 10c0/483290d327bdb4147584c37d73e47df2c717735f1902cd2f66ebc83c7b40ae10e5a8d5e626f24b76ad4ac489eab4a8c13869410aad663810848b0abc89a630cf + languageName: node + linkType: hard + +"@rollup/pluginutils@npm:^5.0.1": + version: 5.1.0 + resolution: "@rollup/pluginutils@npm:5.1.0" + dependencies: + "@types/estree": "npm:^1.0.0" + estree-walker: "npm:^2.0.2" + picomatch: "npm:^2.3.1" + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 10c0/c7bed15711f942d6fdd3470fef4105b73991f99a478605e13d41888963330a6f9e32be37e6ddb13f012bc7673ff5e54f06f59fd47109436c1c513986a8a7612d + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.21.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-android-arm64@npm:4.21.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.21.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.21.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.21.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.21.0" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.21.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.21.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.21.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.21.0" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.21.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.21.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.21.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.21.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.21.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.21.0": + version: 4.21.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.21.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@sindresorhus/is@npm:^4.2.0": + version: 4.6.0 + resolution: "@sindresorhus/is@npm:4.6.0" + checksum: 10c0/33b6fb1d0834ec8dd7689ddc0e2781c2bfd8b9c4e4bacbcb14111e0ae00621f2c264b8a7d36541799d74888b5dccdf422a891a5cb5a709ace26325eedc81e22e + languageName: node + linkType: hard + +"@sindresorhus/is@npm:^5.2.0": + version: 5.6.0 + resolution: "@sindresorhus/is@npm:5.6.0" + checksum: 10c0/66727344d0c92edde5760b5fd1f8092b717f2298a162a5f7f29e4953e001479927402d9d387e245fb9dc7d3b37c72e335e93ed5875edfc5203c53be8ecba1b52 + languageName: node + linkType: hard + +"@sindresorhus/merge-streams@npm:^2.1.0": + version: 2.3.0 + resolution: "@sindresorhus/merge-streams@npm:2.3.0" + checksum: 10c0/69ee906f3125fb2c6bb6ec5cdd84e8827d93b49b3892bce8b62267116cc7e197b5cccf20c160a1d32c26014ecd14470a72a5e3ee37a58f1d6dadc0db1ccf3894 + languageName: node + linkType: hard + +"@swc/core-darwin-arm64@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-darwin-arm64@npm:1.7.14" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-darwin-x64@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-darwin-x64@npm:1.7.14" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@swc/core-linux-arm-gnueabihf@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.7.14" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@swc/core-linux-arm64-gnu@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-linux-arm64-gnu@npm:1.7.14" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-arm64-musl@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-linux-arm64-musl@npm:1.7.14" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-linux-x64-gnu@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-linux-x64-gnu@npm:1.7.14" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-x64-musl@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-linux-x64-musl@npm:1.7.14" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-win32-arm64-msvc@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-win32-arm64-msvc@npm:1.7.14" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-win32-ia32-msvc@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-win32-ia32-msvc@npm:1.7.14" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@swc/core-win32-x64-msvc@npm:1.7.14": + version: 1.7.14 + resolution: "@swc/core-win32-x64-msvc@npm:1.7.14" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@swc/core@npm:^1.7.14": + version: 1.7.14 + resolution: "@swc/core@npm:1.7.14" + dependencies: + "@swc/core-darwin-arm64": "npm:1.7.14" + "@swc/core-darwin-x64": "npm:1.7.14" + "@swc/core-linux-arm-gnueabihf": "npm:1.7.14" + "@swc/core-linux-arm64-gnu": "npm:1.7.14" + "@swc/core-linux-arm64-musl": "npm:1.7.14" + "@swc/core-linux-x64-gnu": "npm:1.7.14" + "@swc/core-linux-x64-musl": "npm:1.7.14" + "@swc/core-win32-arm64-msvc": "npm:1.7.14" + "@swc/core-win32-ia32-msvc": "npm:1.7.14" + "@swc/core-win32-x64-msvc": "npm:1.7.14" + "@swc/counter": "npm:^0.1.3" + "@swc/types": "npm:^0.1.12" + peerDependencies: + "@swc/helpers": "*" + dependenciesMeta: + "@swc/core-darwin-arm64": + optional: true + "@swc/core-darwin-x64": + optional: true + "@swc/core-linux-arm-gnueabihf": + optional: true + "@swc/core-linux-arm64-gnu": + optional: true + "@swc/core-linux-arm64-musl": + optional: true + "@swc/core-linux-x64-gnu": + optional: true + "@swc/core-linux-x64-musl": + optional: true + "@swc/core-win32-arm64-msvc": + optional: true + "@swc/core-win32-ia32-msvc": + optional: true + "@swc/core-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: 10c0/d98e935a376d6358f53f16a269ee0021c00e2764cc7fabbc594904f283a97200a323b802d35eea034e44af67e8158f641f8ac7a8799b15312cb952765e03ba6b + languageName: node + linkType: hard + +"@swc/counter@npm:^0.1.3": + version: 0.1.3 + resolution: "@swc/counter@npm:0.1.3" + checksum: 10c0/8424f60f6bf8694cfd2a9bca45845bce29f26105cda8cf19cdb9fd3e78dc6338699e4db77a89ae449260bafa1cc6bec307e81e7fb96dbf7dcfce0eea55151356 + languageName: node + linkType: hard + +"@swc/types@npm:^0.1.12": + version: 0.1.12 + resolution: "@swc/types@npm:0.1.12" + dependencies: + "@swc/counter": "npm:^0.1.3" + checksum: 10c0/f95fea7dee8fc07f8389afbb9578f3d0cd84b429b1d0dbff7fd99b2ef06ed88e96bc33631f36c3bc0505d5a783bee1374acd84b8fc2593001219b6c2caba241b + languageName: node + linkType: hard + +"@szmarczak/http-timer@npm:^5.0.1": + version: 5.0.1 + resolution: "@szmarczak/http-timer@npm:5.0.1" + dependencies: + defer-to-connect: "npm:^2.0.1" + checksum: 10c0/4629d2fbb2ea67c2e9dc03af235c0991c79ebdddcbc19aed5d5732fb29ce01c13331e9b1a491584b9069bd6ecde6581dcbf871f11b7eefdebbab34de6cf2197e + languageName: node + linkType: hard + +"@tootallnate/quickjs-emscripten@npm:^0.23.0": + version: 0.23.0 + resolution: "@tootallnate/quickjs-emscripten@npm:0.23.0" + checksum: 10c0/2a939b781826fb5fd3edd0f2ec3b321d259d760464cf20611c9877205aaca3ccc0b7304dea68416baa0d568e82cd86b17d29548d1e5139fa3155a4a86a2b4b49 + languageName: node + linkType: hard + +"@types/eslint-config-prettier@npm:^6.11.3": + version: 6.11.3 + resolution: "@types/eslint-config-prettier@npm:6.11.3" + checksum: 10c0/e49ef5b2cf9bfa173d678f50b392511e5567743a864552257978fcf98a876adcd41bb2fa074505ab4aae94862fb349eda2cf41aec698a4a1db77a2731b1fa1c6 + languageName: node + linkType: hard + +"@types/eslint@npm:*": + version: 9.6.0 + resolution: "@types/eslint@npm:9.6.0" + dependencies: + "@types/estree": "npm:*" + "@types/json-schema": "npm:*" + checksum: 10c0/69301356bc73b85e381ae00931291de2e96d1cc49a112c592c74ee32b2f85412203dea6a333b4315fd9839bb14f364f265cbfe7743fc5a78492ee0326dd6a2c1 + languageName: node + linkType: hard + +"@types/eslint__js@npm:^8.42.3": + version: 8.42.3 + resolution: "@types/eslint__js@npm:8.42.3" + dependencies: + "@types/eslint": "npm:*" + checksum: 10c0/ccc5180b92155929a089ffb03ed62625216dcd5e46dd3197c6f82370ce8b52c7cb9df66c06b0a3017995409e023bc9eafe5a3f009e391960eacefaa1b62d9a56 + languageName: node + linkType: hard + +"@types/estree@npm:*, @types/estree@npm:1.0.5, @types/estree@npm:^1.0.0": + version: 1.0.5 + resolution: "@types/estree@npm:1.0.5" + checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d + languageName: node + linkType: hard + +"@types/http-cache-semantics@npm:^4.0.2": + version: 4.0.4 + resolution: "@types/http-cache-semantics@npm:4.0.4" + checksum: 10c0/51b72568b4b2863e0fe8d6ce8aad72a784b7510d72dc866215642da51d84945a9459fa89f49ec48f1e9a1752e6a78e85a4cda0ded06b1c73e727610c925f9ce6 + languageName: node + linkType: hard + +"@types/json-schema@npm:*": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + +"@types/lodash-es@npm:^4.17.12": + version: 4.17.12 + resolution: "@types/lodash-es@npm:4.17.12" + dependencies: + "@types/lodash": "npm:*" + checksum: 10c0/5d12d2cede07f07ab067541371ed1b838a33edb3c35cb81b73284e93c6fd0c4bbeaefee984e69294bffb53f62d7272c5d679fdba8e595ff71e11d00f2601dde0 + languageName: node + linkType: hard + +"@types/lodash@npm:*": + version: 4.17.7 + resolution: "@types/lodash@npm:4.17.7" + checksum: 10c0/40c965b5ffdcf7ff5c9105307ee08b782da228c01b5c0529122c554c64f6b7168fc8f11dc79aa7bae4e67e17efafaba685dc3a47e294dbf52a65ed2b67100561 + languageName: node + linkType: hard + +"@types/mustache@npm:^4": + version: 4.2.5 + resolution: "@types/mustache@npm:4.2.5" + checksum: 10c0/624975c39068d47407eadb89628aaff5ef60f3b7a71eef92a254310896a4e90518a01dcf71d95779ab2c986034a6ca5403d22fea237c67ff87f2e2b3fb794ea6 + languageName: node + linkType: hard + +"@types/needle@npm:^3.3.0": + version: 3.3.0 + resolution: "@types/needle@npm:3.3.0" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/bffc38fbc765b489e110650eb581495c912a18ad6592802712e4d45ec43875461818353aec18408e2317556dd3d8f13ba6c84f32be291c548025a1a3c7d6e0d4 + languageName: node + linkType: hard + +"@types/node-fetch@npm:^2.6.4": + version: 2.6.11 + resolution: "@types/node-fetch@npm:2.6.11" + dependencies: + "@types/node": "npm:*" + form-data: "npm:^4.0.0" + checksum: 10c0/5283d4e0bcc37a5b6d8e629aee880a4ffcfb33e089f4b903b2981b19c623972d1e64af7c3f9540ab990f0f5c89b9b5dda19c5bcb37a8e177079e93683bfd2f49 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 22.4.2 + resolution: "@types/node@npm:22.4.2" + dependencies: + undici-types: "npm:~6.19.2" + checksum: 10c0/db583e83230eed29eb73e24cc03637f5bdc8c01883ae0ba0a6ab9356065f93d0355a91ad1c89b985631006bd77c529d6a3ce042b3667b69c8851d05e1f965db1 + languageName: node + linkType: hard + +"@types/node@npm:^18.11.18": + version: 18.19.45 + resolution: "@types/node@npm:18.19.45" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 10c0/79c324176411dcfa92f76b0ffc0673aa4bd8da82d003b44633e927c9493cdc46c35f04c0873b096b23b12bab090a6bbdea21242b3bbb2ea5dc1d9bf72adaa04f + languageName: node + linkType: hard + +"@types/node@npm:^20.16.1": + version: 20.16.1 + resolution: "@types/node@npm:20.16.1" + dependencies: + undici-types: "npm:~6.19.2" + checksum: 10c0/cac13c0f42467df3254805a671ca9e74a6eb7c41568de972e26b10dcc448a45743aaf00e9e5fce4a9214da5bc8444fe902918e105dac5a224e24e83fd9989a97 + languageName: node + linkType: hard + +"@types/normalize-package-data@npm:^2.4.1": + version: 2.4.4 + resolution: "@types/normalize-package-data@npm:2.4.4" + checksum: 10c0/aef7bb9b015883d6f4119c423dd28c4bdc17b0e8a0ccf112c78b4fe0e91fbc4af7c6204b04bba0e199a57d2f3fbbd5b4a14bf8739bf9d2a39b2a0aad545e0f86 + languageName: node + linkType: hard + +"@types/object-hash@npm:^3.0.6": + version: 3.0.6 + resolution: "@types/object-hash@npm:3.0.6" + checksum: 10c0/a5450cafdbc33c840be2c36fb1ace96cce7aa580177cf68dbdbda80f18938207692d74c76403321c99f43460ac31981c4af7fe8b49eca1570f8e9a4c01eaf33d + languageName: node + linkType: hard + +"@types/retry@npm:0.12.0": + version: 0.12.0 + resolution: "@types/retry@npm:0.12.0" + checksum: 10c0/7c5c9086369826f569b83a4683661557cab1361bac0897a1cefa1a915ff739acd10ca0d62b01071046fe3f5a3f7f2aec80785fe283b75602dc6726781ea3e328 + languageName: node + linkType: hard + +"@types/turndown@npm:^5.0.5": + version: 5.0.5 + resolution: "@types/turndown@npm:5.0.5" + checksum: 10c0/d6b4f8451caf72399f36f810461baf5f3b5e958ff216388bb3324a9949079daad31d970a28a140b3571db8793908396e757329334f5dc8bcff414698b8c31113 + languageName: node + linkType: hard + +"@types/uuid@npm:^9.0.1": + version: 9.0.8 + resolution: "@types/uuid@npm:9.0.8" + checksum: 10c0/b411b93054cb1d4361919579ef3508a1f12bf15b5fdd97337d3d351bece6c921b52b6daeef89b62340fd73fd60da407878432a1af777f40648cbe53a01723489 + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:8.2.0": + version: 8.2.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.2.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.10.0" + "@typescript-eslint/scope-manager": "npm:8.2.0" + "@typescript-eslint/type-utils": "npm:8.2.0" + "@typescript-eslint/utils": "npm:8.2.0" + "@typescript-eslint/visitor-keys": "npm:8.2.0" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.3.1" + natural-compare: "npm:^1.4.0" + ts-api-utils: "npm:^1.3.0" + peerDependencies: + "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/17243ee1b34d78723fe3e1a308c64490eee49bd83301e3abe8a6f05bce05434d70f56caf75756b8cffa051154dc89cdf485114ede6781fc087f0aaca37a026ec + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:8.2.0": + version: 8.2.0 + resolution: "@typescript-eslint/parser@npm:8.2.0" + dependencies: + "@typescript-eslint/scope-manager": "npm:8.2.0" + "@typescript-eslint/types": "npm:8.2.0" + "@typescript-eslint/typescript-estree": "npm:8.2.0" + "@typescript-eslint/visitor-keys": "npm:8.2.0" + debug: "npm:^4.3.4" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/bb4ebc0a40b5e68c5287de17af3acf3045e2ef7886ebee8d1c4a6fd07bd6d55e9fc12bc7b89d07d15a2a4182cbf6380b50ad148d4a37e93d2e54930aa386a3bf + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.2.0": + version: 8.2.0 + resolution: "@typescript-eslint/scope-manager@npm:8.2.0" + dependencies: + "@typescript-eslint/types": "npm:8.2.0" + "@typescript-eslint/visitor-keys": "npm:8.2.0" + checksum: 10c0/8026e11d9cfbb674c62eb38929d08d42c4a373f3463c2591ed6603c496d3d00321f553edce47f1d7504b55fcbe9664ea2bdcaa3131c8c834bde1b1f07497af5d + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:8.2.0": + version: 8.2.0 + resolution: "@typescript-eslint/type-utils@npm:8.2.0" + dependencies: + "@typescript-eslint/typescript-estree": "npm:8.2.0" + "@typescript-eslint/utils": "npm:8.2.0" + debug: "npm:^4.3.4" + ts-api-utils: "npm:^1.3.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/5ff387d39fec3ba47af167ca3e48a200f87e4b97b010170245f495cd3d2e30fd0a5b2a9b27aae2ae929c99f92acabcd07315944dc6f9de963bad1c61ba9ea53c + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:8.2.0": + version: 8.2.0 + resolution: "@typescript-eslint/types@npm:8.2.0" + checksum: 10c0/2ffba0d0183dfdd2f859fb414013d17d009f5e886664823f973aaa1145243fceb52cfe26aa7c50208af7833b3703b7788337f1aab136c9a4eb36d905493847d1 + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:8.2.0": + version: 8.2.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.2.0" + dependencies: + "@typescript-eslint/types": "npm:8.2.0" + "@typescript-eslint/visitor-keys": "npm:8.2.0" + debug: "npm:^4.3.4" + globby: "npm:^11.1.0" + is-glob: "npm:^4.0.3" + minimatch: "npm:^9.0.4" + semver: "npm:^7.6.0" + ts-api-utils: "npm:^1.3.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/f49aabc78e396908307394812fdebc4015ca407983efc361be106d3e2d58971dec4a1a725362fcfbd637f3d8150baa0735eb5929fd170172b7f2a65e06eeb3d2 + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:8.2.0": + version: 8.2.0 + resolution: "@typescript-eslint/utils@npm:8.2.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:8.2.0" + "@typescript-eslint/types": "npm:8.2.0" + "@typescript-eslint/typescript-estree": "npm:8.2.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + checksum: 10c0/0f3d5cf804c2863ea9432ef76bfdb1cadbb244cbf8a64ac77c0e559c012a1e98382c4535354e54696c564c0abd9c10dffc78d38972c97035e963798d360d1830 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:8.2.0": + version: 8.2.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.2.0" + dependencies: + "@typescript-eslint/types": "npm:8.2.0" + eslint-visitor-keys: "npm:^3.4.3" + checksum: 10c0/788633bd2905c88ea2cf20d9e317a2bc992a70fcf725cb54bbe55a17c42138a6fe877c89fbda41a733e0e8ad6dce893163bada60509a1b856321f4329a316973 + languageName: node + linkType: hard + +"@vitest/expect@npm:2.0.5": + version: 2.0.5 + resolution: "@vitest/expect@npm:2.0.5" + dependencies: + "@vitest/spy": "npm:2.0.5" + "@vitest/utils": "npm:2.0.5" + chai: "npm:^5.1.1" + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/08cb1b0f106d16a5b60db733e3d436fa5eefc68571488eb570dfe4f599f214ab52e4342273b03dbe12331cc6c0cdc325ac6c94f651ad254cd62f3aa0e3d185aa + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:2.0.5, @vitest/pretty-format@npm:^2.0.5": + version: 2.0.5 + resolution: "@vitest/pretty-format@npm:2.0.5" + dependencies: + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/236c0798c5170a0b5ad5d4bd06118533738e820b4dd30079d8fbcb15baee949d41c60f42a9f769906c4a5ce366d7ef11279546070646c0efc03128c220c31f37 + languageName: node + linkType: hard + +"@vitest/runner@npm:2.0.5": + version: 2.0.5 + resolution: "@vitest/runner@npm:2.0.5" + dependencies: + "@vitest/utils": "npm:2.0.5" + pathe: "npm:^1.1.2" + checksum: 10c0/d0ed3302a7e015bf44b7c0df9d8f7da163659e082d86f9406944b5a31a61ab9ddc1de530e06176d1f4ef0bde994b44bff4c7dab62aacdc235c8fc04b98e4a72a + languageName: node + linkType: hard + +"@vitest/snapshot@npm:2.0.5": + version: 2.0.5 + resolution: "@vitest/snapshot@npm:2.0.5" + dependencies: + "@vitest/pretty-format": "npm:2.0.5" + magic-string: "npm:^0.30.10" + pathe: "npm:^1.1.2" + checksum: 10c0/7bf38474248f5ae0aac6afad511785d2b7a023ac5158803c2868fd172b5b9c1a569fb1dd64a09a49e43fd342cab71ea485ada89b7f08d37b1622a5a0ac00271d + languageName: node + linkType: hard + +"@vitest/spy@npm:2.0.5": + version: 2.0.5 + resolution: "@vitest/spy@npm:2.0.5" + dependencies: + tinyspy: "npm:^3.0.0" + checksum: 10c0/70634c21921eb271b54d2986c21d7ab6896a31c0f4f1d266940c9bafb8ac36237846d6736638cbf18b958bd98e5261b158a6944352742accfde50b7818ff655e + languageName: node + linkType: hard + +"@vitest/utils@npm:2.0.5": + version: 2.0.5 + resolution: "@vitest/utils@npm:2.0.5" + dependencies: + "@vitest/pretty-format": "npm:2.0.5" + estree-walker: "npm:^3.0.3" + loupe: "npm:^3.1.1" + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/0d1de748298f07a50281e1ba058b05dcd58da3280c14e6f016265e950bd79adab6b97822de8f0ea82d3070f585654801a9b1bcf26db4372e51cf7746bf86d73b + languageName: node + linkType: hard + +"JSONStream@npm:^1.3.5": + version: 1.3.5 + resolution: "JSONStream@npm:1.3.5" + dependencies: + jsonparse: "npm:^1.2.0" + through: "npm:>=2.2.7 <3" + bin: + JSONStream: ./bin.js + checksum: 10c0/0f54694da32224d57b715385d4a6b668d2117379d1f3223dc758459246cca58fdc4c628b83e8a8883334e454a0a30aa198ede77c788b55537c1844f686a751f2 + languageName: node + linkType: hard + +"abbrev@npm:^2.0.0": + version: 2.0.0 + resolution: "abbrev@npm:2.0.0" + checksum: 10c0/f742a5a107473946f426c691c08daba61a1d15942616f300b5d32fd735be88fef5cba24201757b6c407fd564555fb48c751cfa33519b2605c8a7aadd22baf372 + languageName: node + linkType: hard + +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: "npm:^5.0.0" + checksum: 10c0/90ccc50f010250152509a344eb2e71977fbf8db0ab8f1061197e3275ddf6c61a41a6edfd7b9409c664513131dd96e962065415325ef23efa5db931b382d24ca5 + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn@npm:^8.12.0": + version: 8.12.1 + resolution: "acorn@npm:8.12.1" + bin: + acorn: bin/acorn + checksum: 10c0/51fb26cd678f914e13287e886da2d7021f8c2bc0ccc95e03d3e0447ee278dd3b40b9c57dc222acd5881adcf26f3edc40901a4953403232129e3876793cd17386 + languageName: node + linkType: hard + +"add-stream@npm:^1.0.0": + version: 1.0.0 + resolution: "add-stream@npm:1.0.0" + checksum: 10c0/985014a14e76ca4cb24e0fc58bb1556794cf38c5c8937de335a10584f50a371dc48e1c34a59391c7eb9c1fc908b4b86764df5d2756f701df6ba95d1ca2f63ddc + languageName: node + linkType: hard + +"adm-zip@npm:^0.5.9": + version: 0.5.15 + resolution: "adm-zip@npm:0.5.15" + checksum: 10c0/40ad504176c4b7f41983d11f7ff28acd3ce5b9bd51a09356be2447cb1799de4a4d93f8b0f65f75814add17687445cc807bbcb5ec8f73f44af1aefe4509894a8c + languageName: node + linkType: hard + +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" + dependencies: + debug: "npm:^4.3.4" + checksum: 10c0/e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50 + languageName: node + linkType: hard + +"agentkeepalive@npm:^4.2.1": + version: 4.5.0 + resolution: "agentkeepalive@npm:4.5.0" + dependencies: + humanize-ms: "npm:^1.2.1" + checksum: 10c0/394ea19f9710f230722996e156607f48fdf3a345133b0b1823244b7989426c16019a428b56c82d3eabef616e938812981d9009f4792ecc66bd6a59e991c62612 + languageName: node + linkType: hard + +"aggregate-error@npm:^3.0.0": + version: 3.1.0 + resolution: "aggregate-error@npm:3.1.0" + dependencies: + clean-stack: "npm:^2.0.0" + indent-string: "npm:^4.0.0" + checksum: 10c0/a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039 + languageName: node + linkType: hard + +"ajv-formats@npm:^3.0.1": + version: 3.0.1 + resolution: "ajv-formats@npm:3.0.1" + dependencies: + ajv: "npm:^8.0.0" + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 10c0/168d6bca1ea9f163b41c8147bae537e67bd963357a5488a1eaf3abe8baa8eec806d4e45f15b10767e6020679315c7e1e5e6803088dfb84efa2b4e9353b83dd0a + languageName: node + linkType: hard + +"ajv@npm:^6.12.4": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 + languageName: node + linkType: hard + +"ajv@npm:^8.0.0, ajv@npm:^8.17.1": + version: 8.17.1 + resolution: "ajv@npm:8.17.1" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10c0/ec3ba10a573c6b60f94639ffc53526275917a2df6810e4ab5a6b959d87459f9ef3f00d5e7865b82677cb7d21590355b34da14d1d0b9c32d75f95a187e76fff35 + languageName: node + linkType: hard + +"ansi-align@npm:^3.0.1": + version: 3.0.1 + resolution: "ansi-align@npm:3.0.1" + dependencies: + string-width: "npm:^4.1.0" + checksum: 10c0/ad8b755a253a1bc8234eb341e0cec68a857ab18bf97ba2bda529e86f6e30460416523e0ec58c32e5c21f0ca470d779503244892873a5895dbd0c39c788e82467 + languageName: node + linkType: hard + +"ansi-colors@npm:^4.1.3": + version: 4.1.3 + resolution: "ansi-colors@npm:4.1.3" + checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9 + languageName: node + linkType: hard + +"ansi-escapes@npm:^4.3.2": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + +"ansi-escapes@npm:^7.0.0": + version: 7.0.0 + resolution: "ansi-escapes@npm:7.0.0" + dependencies: + environment: "npm:^1.0.0" + checksum: 10c0/86e51e36fabef18c9c004af0a280573e828900641cea35134a124d2715e0c5a473494ab4ce396614505da77638ae290ff72dd8002d9747d2ee53f5d6bbe336be + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: 10c0/cbe16dbd2c6b2735d1df7976a7070dd277326434f0212f43abf6d87674095d247968209babdaad31bb00882fa68807256ba9be340eec2f1004de14ca75f52a08 + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df + languageName: node + linkType: hard + +"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c + languageName: node + linkType: hard + +"any-promise@npm:^1.0.0": + version: 1.3.0 + resolution: "any-promise@npm:1.3.0" + checksum: 10c0/60f0298ed34c74fef50daab88e8dab786036ed5a7fad02e012ab57e376e0a0b4b29e83b95ea9b5e7d89df762f5f25119b83e00706ecaccb22cfbacee98d74889 + languageName: node + linkType: hard + +"anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"array-ify@npm:^1.0.0": + version: 1.0.0 + resolution: "array-ify@npm:1.0.0" + checksum: 10c0/75c9c072faac47bd61779c0c595e912fe660d338504ac70d10e39e1b8a4a0c9c87658703d619b9d1b70d324177ae29dc8d07dda0d0a15d005597bc4c5a59c70c + languageName: node + linkType: hard + +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 10c0/429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"ast-types@npm:^0.13.4": + version: 0.13.4 + resolution: "ast-types@npm:0.13.4" + dependencies: + tslib: "npm:^2.0.1" + checksum: 10c0/3a1a409764faa1471601a0ad01b3aa699292991aa9c8a30c7717002cabdf5d98008e7b53ae61f6e058f757fc6ba965e147967a93c13e62692c907d79cfb245f8 + languageName: node + linkType: hard + +"async-retry@npm:1.3.3": + version: 1.3.3 + resolution: "async-retry@npm:1.3.3" + dependencies: + retry: "npm:0.13.1" + checksum: 10c0/cabced4fb46f8737b95cc88dc9c0ff42656c62dc83ce0650864e891b6c155a063af08d62c446269b51256f6fbcb69a6563b80e76d0ea4a5117b0c0377b6b19d8 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"atomic-sleep@npm:^1.0.0": + version: 1.0.0 + resolution: "atomic-sleep@npm:1.0.0" + checksum: 10c0/e329a6665512736a9bbb073e1761b4ec102f7926cce35037753146a9db9c8104f5044c1662e4a863576ce544fb8be27cd2be6bc8c1a40147d03f31eb1cfb6e8a + languageName: node + linkType: hard + +"axios@npm:^1.4.0": + version: 1.7.4 + resolution: "axios@npm:1.7.4" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10c0/5ea1a93140ca1d49db25ef8e1bd8cfc59da6f9220159a944168860ad15a2743ea21c5df2967795acb15cbe81362f5b157fdebbea39d53117ca27658bab9f7f17 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf + languageName: node + linkType: hard + +"basic-ftp@npm:^5.0.2": + version: 5.0.5 + resolution: "basic-ftp@npm:5.0.5" + checksum: 10c0/be983a3997749856da87b839ffce6b8ed6c7dbf91ea991d5c980d8add275f9f2926c19f80217ac3e7f353815be879371d636407ca72b038cea8cab30e53928a6 + languageName: node + linkType: hard + +"bee-agent-framework@workspace:.": + version: 0.0.0-use.local + resolution: "bee-agent-framework@workspace:." + dependencies: + "@ai-zen/node-fetch-event-source": "npm:^2.1.4" + "@connectrpc/connect": "npm:^1.4.0" + "@connectrpc/connect-node": "npm:^1.4.0" + "@eslint/js": "npm:^9.9.0" + "@ibm-generative-ai/node-sdk": "npm:~3.2.1" + "@langchain/community": "npm:~0.2.28" + "@langchain/core": "npm:~0.2.27" + "@langchain/langgraph": "npm:~0.0.34" + "@release-it/conventional-changelog": "npm:^8.0.1" + "@rollup/plugin-commonjs": "npm:^26.0.1" + "@swc/core": "npm:^1.7.14" + "@types/eslint-config-prettier": "npm:^6.11.3" + "@types/eslint__js": "npm:^8.42.3" + "@types/mustache": "npm:^4" + "@types/needle": "npm:^3.3.0" + "@types/node": "npm:^20.16.1" + "@types/object-hash": "npm:^3.0.6" + "@types/turndown": "npm:^5.0.5" + ajv: "npm:^8.17.1" + ajv-formats: "npm:^3.0.1" + bee-proto: "npm:0.0.1" + dirty-json: "npm:0.9.2" + dotenv: "npm:^16.4.5" + duck-duck-scrape: "npm:^2.2.5" + eslint: "npm:^9.9.0" + eslint-config-prettier: "npm:^9.1.0" + eslint-plugin-unused-imports: "npm:^4.1.3" + fast-xml-parser: "npm:^4.4.1" + glob: "npm:^11.0.0" + header-generator: "npm:^2.1.54" + husky: "npm:^9.1.5" + joplin-turndown-plugin-gfm: "npm:^1.0.12" + langchain: "npm:~0.2.16" + lint-staged: "npm:^15.2.9" + mustache: "npm:^4.2.0" + object-hash: "npm:^3.0.0" + ollama: "npm:^0.5.8" + openai: "npm:^4.56.0" + openai-chat-tokens: "npm:^0.2.8" + openapi-fetch: "npm:^0.11.1" + openapi-typescript: "npm:^7.3.0" + p-queue: "npm:^8.0.1" + p-throttle: "npm:^6.2.0" + picocolors: "npm:^1.0.1" + pino: "npm:^9.3.2" + pino-pretty: "npm:^11.2.2" + pino-test: "npm:^1.0.1" + prettier: "npm:^3.3.3" + promise-based-task: "npm:^3.0.2" + release-it: "npm:^17.6.0" + remeda: "npm:^2.11.0" + rimraf: "npm:^6.0.1" + serialize-error: "npm:^11.0.3" + string-comparison: "npm:^1.3.0" + string-strip-html: "npm:^13.4.8" + strip-ansi: "npm:^7.1.0" + temp-dir: "npm:^3.0.0" + tsc-files: "npm:^1.1.4" + tsup: "npm:^8.2.4" + tsx: "npm:^4.17.0" + turndown: "npm:^7.2.0" + typescript: "npm:^5.5.4" + typescript-eslint: "npm:^8.2.0" + vite-tsconfig-paths: "npm:^5.0.1" + vitest: "npm:^2.0.5" + wikipedia: "npm:^2.1.2" + yaml: "npm:^2.5.0" + zod: "npm:^3.23.8" + zod-to-json-schema: "npm:^3.23.2" + peerDependencies: + "@ibm-generative-ai/node-sdk": ~3.2.1 + "@langchain/community": ~0.2.28 + "@langchain/core": ~0.2.27 + "@langchain/langgraph": ~0.0.34 + ollama: ^0.5.8 + openai: ^4.56.0 + openai-chat-tokens: ^0.2.8 + languageName: unknown + linkType: soft + +"bee-proto@npm:0.0.1": + version: 0.0.1 + resolution: "bee-proto@npm:0.0.1" + dependencies: + "@bufbuild/protobuf": "npm:^1.10.0" + "@connectrpc/connect": "npm:^1.4.0" + checksum: 10c0/abb4828e4b4391346b352478e1aa4025cc1f9864ada0b50ed98d99bd61c61376f0937820711d33ab930954d48770cd186000db66634ad7a221e2d493b768b58c + languageName: node + linkType: hard + +"before-after-hook@npm:^2.2.0": + version: 2.2.3 + resolution: "before-after-hook@npm:2.2.3" + checksum: 10c0/0488c4ae12df758ca9d49b3bb27b47fd559677965c52cae7b335784724fb8bf96c42b6e5ba7d7afcbc31facb0e294c3ef717cc41c5bc2f7bd9e76f8b90acd31c + languageName: node + linkType: hard + +"binary-extensions@npm:^2.0.0, binary-extensions@npm:^2.2.0": + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 + languageName: node + linkType: hard + +"bl@npm:^4.1.0": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: "npm:^5.5.0" + inherits: "npm:^2.0.4" + readable-stream: "npm:^3.4.0" + checksum: 10c0/02847e1d2cb089c9dc6958add42e3cdeaf07d13f575973963335ac0fdece563a50ac770ac4c8fa06492d2dd276f6cc3b7f08c7cd9c7a7ad0f8d388b2a28def5f + languageName: node + linkType: hard + +"boxen@npm:^7.1.1": + version: 7.1.1 + resolution: "boxen@npm:7.1.1" + dependencies: + ansi-align: "npm:^3.0.1" + camelcase: "npm:^7.0.1" + chalk: "npm:^5.2.0" + cli-boxes: "npm:^3.0.0" + string-width: "npm:^5.1.2" + type-fest: "npm:^2.13.0" + widest-line: "npm:^4.0.1" + wrap-ansi: "npm:^8.1.0" + checksum: 10c0/3a9891dc98ac40d582c9879e8165628258e2c70420c919e70fff0a53ccc7b42825e73cda6298199b2fbc1f41f5d5b93b492490ad2ae27623bed3897ddb4267f8 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.11 + resolution: "brace-expansion@npm:1.1.11" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f + languageName: node + linkType: hard + +"braces@npm:^3.0.3, braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"browserslist@npm:^4.21.1": + version: 4.23.3 + resolution: "browserslist@npm:4.23.3" + dependencies: + caniuse-lite: "npm:^1.0.30001646" + electron-to-chromium: "npm:^1.5.4" + node-releases: "npm:^2.0.18" + update-browserslist-db: "npm:^1.1.0" + bin: + browserslist: cli.js + checksum: 10c0/3063bfdf812815346447f4796c8f04601bf5d62003374305fd323c2a463e42776475bcc5309264e39bcf9a8605851e53560695991a623be988138b3ff8c66642 + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.1.13" + checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e + languageName: node + linkType: hard + +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.2.1" + checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0 + languageName: node + linkType: hard + +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 10c0/8e575981e79c2bcf14d8b1c027a3775c095d362d1382312f444a7c861b0e21513c0bd8db5bd2b16e50ba0709fa622d4eab6b53192d222120305e68359daece29 + languageName: node + linkType: hard + +"bundle-require@npm:^5.0.0": + version: 5.0.0 + resolution: "bundle-require@npm:5.0.0" + dependencies: + load-tsconfig: "npm:^0.2.3" + peerDependencies: + esbuild: ">=0.18" + checksum: 10c0/92c46df02586e0ebd66ee4831c9b5775adb3c32a43fe2b2aaf7bc675135c141f751de6a9a26b146d64c607c5b40f9eef5f10dce3c364f602d4bed268444c32c6 + languageName: node + linkType: hard + +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 + languageName: node + linkType: hard + +"cacache@npm:^18.0.0": + version: 18.0.4 + resolution: "cacache@npm:18.0.4" + dependencies: + "@npmcli/fs": "npm:^3.1.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^4.0.0" + ssri: "npm:^10.0.0" + tar: "npm:^6.1.11" + unique-filename: "npm:^3.0.0" + checksum: 10c0/6c055bafed9de4f3dcc64ac3dc7dd24e863210902b7c470eb9ce55a806309b3efff78033e3d8b4f7dcc5d467f2db43c6a2857aaaf26f0094b8a351d44c42179f + languageName: node + linkType: hard + +"cacheable-lookup@npm:^7.0.0": + version: 7.0.0 + resolution: "cacheable-lookup@npm:7.0.0" + checksum: 10c0/63a9c144c5b45cb5549251e3ea774c04d63063b29e469f7584171d059d3a88f650f47869a974e2d07de62116463d742c287a81a625e791539d987115cb081635 + languageName: node + linkType: hard + +"cacheable-request@npm:^10.2.8": + version: 10.2.14 + resolution: "cacheable-request@npm:10.2.14" + dependencies: + "@types/http-cache-semantics": "npm:^4.0.2" + get-stream: "npm:^6.0.1" + http-cache-semantics: "npm:^4.1.1" + keyv: "npm:^4.5.3" + mimic-response: "npm:^4.0.0" + normalize-url: "npm:^8.0.0" + responselike: "npm:^3.0.0" + checksum: 10c0/41b6658db369f20c03128227ecd219ca7ac52a9d24fc0f499cc9aa5d40c097b48b73553504cebd137024d957c0ddb5b67cf3ac1439b136667f3586257763f88d + languageName: node + linkType: hard + +"callsites@npm:^3.0.0, callsites@npm:^3.1.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"camelcase@npm:6": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + +"camelcase@npm:^4.1.0": + version: 4.1.0 + resolution: "camelcase@npm:4.1.0" + checksum: 10c0/54c0b6a85b54fb4e96a9d834a9d0d8f760fd608ab6752a6789042b5e1c38d3dd60f878d2c590d005046445d32d77f6e05e568a91fe8db3e282da0a1560d83058 + languageName: node + linkType: hard + +"camelcase@npm:^7.0.1": + version: 7.0.1 + resolution: "camelcase@npm:7.0.1" + checksum: 10c0/3adfc9a0e96d51b3a2f4efe90a84dad3e206aaa81dfc664f1bd568270e1bf3b010aad31f01db16345b4ffe1910e16ab411c7273a19a859addd1b98ef7cf4cfbd + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001646": + version: 1.0.30001651 + resolution: "caniuse-lite@npm:1.0.30001651" + checksum: 10c0/7821278952a6dbd17358e5d08083d258f092e2a530f5bc1840657cb140fbbc5ec44293bc888258c44a18a9570cde149ed05819ac8320b9710cf22f699891e6ad + languageName: node + linkType: hard + +"chai@npm:^5.1.1": + version: 5.1.1 + resolution: "chai@npm:5.1.1" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: 10c0/e7f00e5881e3d5224f08fe63966ed6566bd9fdde175863c7c16dd5240416de9b34c4a0dd925f4fd64ad56256ca6507d32cf6131c49e1db65c62578eb31d4566c + languageName: node + linkType: hard + +"chalk@npm:5.3.0, chalk@npm:^5.2.0, chalk@npm:^5.3.0, chalk@npm:~5.3.0": + version: 5.3.0 + resolution: "chalk@npm:5.3.0" + checksum: 10c0/8297d436b2c0f95801103ff2ef67268d362021b8210daf8ddbe349695333eb3610a71122172ff3b0272f1ef2cf7cc2c41fdaa4715f52e49ffe04c56340feed09 + languageName: node + linkType: hard + +"chalk@npm:^2.4.2": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + +"chalk@npm:^4.0.0, chalk@npm:^4.1.0": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"chardet@npm:^0.7.0": + version: 0.7.0 + resolution: "chardet@npm:0.7.0" + checksum: 10c0/96e4731b9ec8050cbb56ab684e8c48d6c33f7826b755802d14e3ebfdc51c57afeece3ea39bc6b09acc359e4363525388b915e16640c1378053820f5e70d0f27d + languageName: node + linkType: hard + +"check-error@npm:^2.1.1": + version: 2.1.1 + resolution: "check-error@npm:2.1.1" + checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e + languageName: node + linkType: hard + +"chokidar@npm:^3.6.0": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 + languageName: node + linkType: hard + +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: 10c0/594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6 + languageName: node + linkType: hard + +"ci-info@npm:^3.2.0": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a + languageName: node + linkType: hard + +"clean-stack@npm:^2.0.0": + version: 2.2.0 + resolution: "clean-stack@npm:2.2.0" + checksum: 10c0/1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1 + languageName: node + linkType: hard + +"cli-boxes@npm:^3.0.0": + version: 3.0.0 + resolution: "cli-boxes@npm:3.0.0" + checksum: 10c0/4db3e8fbfaf1aac4fb3a6cbe5a2d3fa048bee741a45371b906439b9ffc821c6e626b0f108bdcd3ddf126a4a319409aedcf39a0730573ff050fdd7b6731e99fb9 + languageName: node + linkType: hard + +"cli-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-cursor@npm:3.1.0" + dependencies: + restore-cursor: "npm:^3.1.0" + checksum: 10c0/92a2f98ff9037d09be3dfe1f0d749664797fb674bf388375a2207a1203b69d41847abf16434203e0089212479e47a358b13a0222ab9fccfe8e2644a7ccebd111 + languageName: node + linkType: hard + +"cli-cursor@npm:^4.0.0": + version: 4.0.0 + resolution: "cli-cursor@npm:4.0.0" + dependencies: + restore-cursor: "npm:^4.0.0" + checksum: 10c0/e776e8c3c6727300d0539b0d25160b2bb56aed1a63942753ba1826b012f337a6f4b7ace3548402e4f2f13b5e16bfd751be672c44b203205e7eca8be94afec42c + languageName: node + linkType: hard + +"cli-cursor@npm:^5.0.0": + version: 5.0.0 + resolution: "cli-cursor@npm:5.0.0" + dependencies: + restore-cursor: "npm:^5.0.0" + checksum: 10c0/7ec62f69b79f6734ab209a3e4dbdc8af7422d44d360a7cb1efa8a0887bbe466a6e625650c466fe4359aee44dbe2dc0b6994b583d40a05d0808a5cb193641d220 + languageName: node + linkType: hard + +"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 10c0/907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3 + languageName: node + linkType: hard + +"cli-truncate@npm:^4.0.0": + version: 4.0.0 + resolution: "cli-truncate@npm:4.0.0" + dependencies: + slice-ansi: "npm:^5.0.0" + string-width: "npm:^7.0.0" + checksum: 10c0/d7f0b73e3d9b88cb496e6c086df7410b541b56a43d18ade6a573c9c18bd001b1c3fba1ad578f741a4218fdc794d042385f8ac02c25e1c295a2d8b9f3cb86eb4c + languageName: node + linkType: hard + +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f + languageName: node + linkType: hard + +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: 10c0/2176952b3649293473999a95d7bebfc9dc96410f6cbd3d2595cf12fd401f63a4bf41a7adbfd3ab2ff09ed60cb9870c58c6acdd18b87767366fabfc163700f13b + languageName: node + linkType: hard + +"codsen-utils@npm:^1.6.4": + version: 1.6.4 + resolution: "codsen-utils@npm:1.6.4" + dependencies: + rfdc: "npm:^1.3.1" + checksum: 10c0/c2ca709b2c7cade614f9266df8542ecb770cad89dfcf4f17637134d4a8084554f97fbc05bce65b314f5199dfa9fba2f28cf9b704abb4e7525d4d57bec8c6b29a + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"colorette@npm:^1.2.0": + version: 1.4.0 + resolution: "colorette@npm:1.4.0" + checksum: 10c0/4955c8f7daafca8ae7081d672e4bd89d553bd5782b5846d5a7e05effe93c2f15f7e9c0cb46f341b59f579a39fcf436241ff79594899d75d5f3460c03d607fe9e + languageName: node + linkType: hard + +"colorette@npm:^2.0.20, colorette@npm:^2.0.7": + version: 2.0.20 + resolution: "colorette@npm:2.0.20" + checksum: 10c0/e94116ff33b0ff56f3b83b9ace895e5bf87c2a7a47b3401b8c3f3226e050d5ef76cf4072fb3325f9dc24d1698f9b730baf4e05eeaf861d74a1883073f4c98a40 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"commander@npm:^10.0.1": + version: 10.0.1 + resolution: "commander@npm:10.0.1" + checksum: 10c0/53f33d8927758a911094adadda4b2cbac111a5b377d8706700587650fd8f45b0bbe336de4b5c3fe47fd61f420a3d9bd452b6e0e6e5600a7e74d7bf0174f6efe3 + languageName: node + linkType: hard + +"commander@npm:^4.0.0": + version: 4.1.1 + resolution: "commander@npm:4.1.1" + checksum: 10c0/84a76c08fe6cc08c9c93f62ac573d2907d8e79138999312c92d4155bc2325d487d64d13f669b2000c9f8caf70493c1be2dac74fec3c51d5a04f8bc3ae1830bab + languageName: node + linkType: hard + +"commander@npm:~12.1.0": + version: 12.1.0 + resolution: "commander@npm:12.1.0" + checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9 + languageName: node + linkType: hard + +"commondir@npm:^1.0.1": + version: 1.0.1 + resolution: "commondir@npm:1.0.1" + checksum: 10c0/33a124960e471c25ee19280c9ce31ccc19574b566dc514fe4f4ca4c34fa8b0b57cf437671f5de380e11353ea9426213fca17687dd2ef03134fea2dbc53809fd6 + languageName: node + linkType: hard + +"compare-func@npm:^2.0.0": + version: 2.0.0 + resolution: "compare-func@npm:2.0.0" + dependencies: + array-ify: "npm:^1.0.0" + dot-prop: "npm:^5.1.0" + checksum: 10c0/78bd4dd4ed311a79bd264c9e13c36ed564cde657f1390e699e0f04b8eee1fc06ffb8698ce2dfb5fbe7342d509579c82d4e248f08915b708f77f7b72234086cc3 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"concat-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "concat-stream@npm:2.0.0" + dependencies: + buffer-from: "npm:^1.0.0" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.0.2" + typedarray: "npm:^0.0.6" + checksum: 10c0/29565dd9198fe1d8cf57f6cc71527dbc6ad67e12e4ac9401feb389c53042b2dceedf47034cbe702dfc4fd8df3ae7e6bfeeebe732cc4fa2674e484c13f04c219a + languageName: node + linkType: hard + +"config-chain@npm:^1.1.11": + version: 1.1.13 + resolution: "config-chain@npm:1.1.13" + dependencies: + ini: "npm:^1.3.4" + proto-list: "npm:~1.2.1" + checksum: 10c0/39d1df18739d7088736cc75695e98d7087aea43646351b028dfabd5508d79cf6ef4c5bcd90471f52cd87ae470d1c5490c0a8c1a292fbe6ee9ff688061ea0963e + languageName: node + linkType: hard + +"configstore@npm:^6.0.0": + version: 6.0.0 + resolution: "configstore@npm:6.0.0" + dependencies: + dot-prop: "npm:^6.0.1" + graceful-fs: "npm:^4.2.6" + unique-string: "npm:^3.0.0" + write-file-atomic: "npm:^3.0.3" + xdg-basedir: "npm:^5.0.1" + checksum: 10c0/6681a96038ab3e0397cbdf55e6e1624ac3dfa3afe955e219f683df060188a418bda043c9114a59a337e7aec9562b0a0c838ed7db24289e6d0c266bc8313b9580 + languageName: node + linkType: hard + +"consola@npm:^3.2.3": + version: 3.2.3 + resolution: "consola@npm:3.2.3" + checksum: 10c0/c606220524ec88a05bb1baf557e9e0e04a0c08a9c35d7a08652d99de195c4ddcb6572040a7df57a18ff38bbc13ce9880ad032d56630cef27bef72768ef0ac078 + languageName: node + linkType: hard + +"conventional-changelog-angular@npm:^7.0.0": + version: 7.0.0 + resolution: "conventional-changelog-angular@npm:7.0.0" + dependencies: + compare-func: "npm:^2.0.0" + checksum: 10c0/90e73e25e224059b02951b6703b5f8742dc2a82c1fea62163978e6735fd3ab04350897a8fc6f443ec6b672d6b66e28a0820e833e544a0101f38879e5e6289b7e + languageName: node + linkType: hard + +"conventional-changelog-atom@npm:^4.0.0": + version: 4.0.0 + resolution: "conventional-changelog-atom@npm:4.0.0" + checksum: 10c0/140e0708e69a4e7345e95bdb2875f8a13b44e1d29334a5be823a74c817d2b17acc3ab57757df20d0d23e224433d9e36c4fdf67a529394106e3b1ade4c0e4c6da + languageName: node + linkType: hard + +"conventional-changelog-codemirror@npm:^4.0.0": + version: 4.0.0 + resolution: "conventional-changelog-codemirror@npm:4.0.0" + checksum: 10c0/d568a13cce260632bc1e8aec463721a149e09aaa97149ab6d813c8c74ea7ea548cec4aaa721cae5704bf4bf95b7daa184ec069fcf112a219e848f1ef4e326091 + languageName: node + linkType: hard + +"conventional-changelog-conventionalcommits@npm:^7.0.2": + version: 7.0.2 + resolution: "conventional-changelog-conventionalcommits@npm:7.0.2" + dependencies: + compare-func: "npm:^2.0.0" + checksum: 10c0/3cb1eab35e37fc973cfb3aed0e159f54414e49b222988da1c2aa86cc8a87fe7531491bbb7657fe5fc4dc0e25f5b50e2065ba8ac71cc4c08eed9189102a2b81bd + languageName: node + linkType: hard + +"conventional-changelog-core@npm:^7.0.0": + version: 7.0.0 + resolution: "conventional-changelog-core@npm:7.0.0" + dependencies: + "@hutson/parse-repository-url": "npm:^5.0.0" + add-stream: "npm:^1.0.0" + conventional-changelog-writer: "npm:^7.0.0" + conventional-commits-parser: "npm:^5.0.0" + git-raw-commits: "npm:^4.0.0" + git-semver-tags: "npm:^7.0.0" + hosted-git-info: "npm:^7.0.0" + normalize-package-data: "npm:^6.0.0" + read-pkg: "npm:^8.0.0" + read-pkg-up: "npm:^10.0.0" + checksum: 10c0/3d5119faf3c154e57e2574b87320892637f4a26cf047827ec8917c227b7e1b3a6ee0ea00247e548f65100db013174d355744ff5b4b90c7c45855db109c24aa2a + languageName: node + linkType: hard + +"conventional-changelog-ember@npm:^4.0.0": + version: 4.0.0 + resolution: "conventional-changelog-ember@npm:4.0.0" + checksum: 10c0/f6f76a71cca0aa4919b750f679224cb891caa8d9522f9f6377a92e648e13e35cdcfea465c18309179abdbb662243321656f09f775996d636d57a81aa7bb6ff3c + languageName: node + linkType: hard + +"conventional-changelog-eslint@npm:^5.0.0": + version: 5.0.0 + resolution: "conventional-changelog-eslint@npm:5.0.0" + checksum: 10c0/e11239fcaf26a1be9df062f6fc750496f982c18f03e4eed7931b177e607337b86207f448fee8891fbdac449bcb9ed29dcc4da3ccb6fe4d5cd55ff4f9b7a65d8f + languageName: node + linkType: hard + +"conventional-changelog-express@npm:^4.0.0": + version: 4.0.0 + resolution: "conventional-changelog-express@npm:4.0.0" + checksum: 10c0/a9dc0eabe1fd9ae8361fc4833bb2d051b0280637d3b67c35855e2e206cdf299e8c9c80a49f9d8153ae6c3bd42462390ee861bff3afb91f44b133167040e8da21 + languageName: node + linkType: hard + +"conventional-changelog-jquery@npm:^5.0.0": + version: 5.0.0 + resolution: "conventional-changelog-jquery@npm:5.0.0" + checksum: 10c0/82b6b8b65d484d15bbe08d65e948005879fcd4d1e01d50515ca04b0951f3245f78c6075c30f6ea29239f30c547570a86c0b8ee4ac0afaeacd548ee0a6506b7cb + languageName: node + linkType: hard + +"conventional-changelog-jshint@npm:^4.0.0": + version: 4.0.0 + resolution: "conventional-changelog-jshint@npm:4.0.0" + dependencies: + compare-func: "npm:^2.0.0" + checksum: 10c0/87683278208351ceb01cb547acf3725f077d54deed6c51b2f49cf566921d09f7ce724cccb7e19046564f41d473061fd2717852a863f36ba5e7ff5f788b4d79a9 + languageName: node + linkType: hard + +"conventional-changelog-preset-loader@npm:^4.1.0": + version: 4.1.0 + resolution: "conventional-changelog-preset-loader@npm:4.1.0" + checksum: 10c0/7854ddeca740d2e478f0e8aa6591dd224c7e094863cbefada2f219459d8e267580ab73d0a0f566a7f37382896ec0f502e9908123af866fd6d7fbc560edd1ee68 + languageName: node + linkType: hard + +"conventional-changelog-writer@npm:^7.0.0": + version: 7.0.1 + resolution: "conventional-changelog-writer@npm:7.0.1" + dependencies: + conventional-commits-filter: "npm:^4.0.0" + handlebars: "npm:^4.7.7" + json-stringify-safe: "npm:^5.0.1" + meow: "npm:^12.0.1" + semver: "npm:^7.5.2" + split2: "npm:^4.0.0" + bin: + conventional-changelog-writer: cli.mjs + checksum: 10c0/ec51708c33860777f2b85f1df588aed918cab08919146cdfac8f271e31c0caee22c5c50df78e4ce358022e58f65c8de77fd6a5fb529f4bb5ba27c2d1e072750f + languageName: node + linkType: hard + +"conventional-changelog@npm:^5.1.0": + version: 5.1.0 + resolution: "conventional-changelog@npm:5.1.0" + dependencies: + conventional-changelog-angular: "npm:^7.0.0" + conventional-changelog-atom: "npm:^4.0.0" + conventional-changelog-codemirror: "npm:^4.0.0" + conventional-changelog-conventionalcommits: "npm:^7.0.2" + conventional-changelog-core: "npm:^7.0.0" + conventional-changelog-ember: "npm:^4.0.0" + conventional-changelog-eslint: "npm:^5.0.0" + conventional-changelog-express: "npm:^4.0.0" + conventional-changelog-jquery: "npm:^5.0.0" + conventional-changelog-jshint: "npm:^4.0.0" + conventional-changelog-preset-loader: "npm:^4.1.0" + checksum: 10c0/931b3cf5b70e2cedbc8942b25176146b9ebb7534ba908454fccdc6dd8097442a79fe150976a854f448306e1fd2788f1daac0cf066ca163ce8f119289594c8918 + languageName: node + linkType: hard + +"conventional-commits-filter@npm:^4.0.0": + version: 4.0.0 + resolution: "conventional-commits-filter@npm:4.0.0" + checksum: 10c0/b26ea11ebb38218cb3cbbaf7d68b0f7c3b0eb7ad69afe9c9431d91e784acbebaeda7a095127ae5a7f8b75087d62b44e8e69d63426ff02b49f7dd504755934247 + languageName: node + linkType: hard + +"conventional-commits-parser@npm:^5.0.0": + version: 5.0.0 + resolution: "conventional-commits-parser@npm:5.0.0" + dependencies: + JSONStream: "npm:^1.3.5" + is-text-path: "npm:^2.0.0" + meow: "npm:^12.0.1" + split2: "npm:^4.0.0" + bin: + conventional-commits-parser: cli.mjs + checksum: 10c0/c9e542f4884119a96a6bf3311ff62cdee55762d8547f4c745ae3ebdc50afe4ba7691e165e34827d5cf63283cbd93ab69917afd7922423075b123d5d9a7a82ed2 + languageName: node + linkType: hard + +"conventional-recommended-bump@npm:^9.0.0": + version: 9.0.0 + resolution: "conventional-recommended-bump@npm:9.0.0" + dependencies: + conventional-changelog-preset-loader: "npm:^4.1.0" + conventional-commits-filter: "npm:^4.0.0" + conventional-commits-parser: "npm:^5.0.0" + git-raw-commits: "npm:^4.0.0" + git-semver-tags: "npm:^7.0.0" + meow: "npm:^12.0.1" + bin: + conventional-recommended-bump: cli.mjs + checksum: 10c0/ceec7dcfddfc6508d0ba3debef471b47d4db3a2112e269d5d736202b226651b31fcbd4790c9403b5b14e6501365527b8c2c4ce8836c6a09faf07cb7d03c1fe07 + languageName: node + linkType: hard + +"cosmiconfig@npm:9.0.0": + version: 9.0.0 + resolution: "cosmiconfig@npm:9.0.0" + dependencies: + env-paths: "npm:^2.2.1" + import-fresh: "npm:^3.3.0" + js-yaml: "npm:^4.1.0" + parse-json: "npm:^5.2.0" + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/1c1703be4f02a250b1d6ca3267e408ce16abfe8364193891afc94c2d5c060b69611fdc8d97af74b7e6d5d1aac0ab2fb94d6b079573146bc2d756c2484ce5f0ee + languageName: node + linkType: hard + +"cross-fetch@npm:^4.0.0": + version: 4.0.0 + resolution: "cross-fetch@npm:4.0.0" + dependencies: + node-fetch: "npm:^2.6.12" + checksum: 10c0/386727dc4c6b044746086aced959ff21101abb85c43df5e1d151547ccb6f338f86dec3f28b9dbddfa8ff5b9ec8662ed2263ad4607a93b2dc354fb7fe3bbb898a + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": + version: 7.0.3 + resolution: "cross-spawn@npm:7.0.3" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750 + languageName: node + linkType: hard + +"crypto-random-string@npm:^4.0.0": + version: 4.0.0 + resolution: "crypto-random-string@npm:4.0.0" + dependencies: + type-fest: "npm:^1.0.1" + checksum: 10c0/16e11a3c8140398f5408b7fded35a961b9423c5dac39a60cbbd08bd3f0e07d7de130e87262adea7db03ec1a7a4b7551054e0db07ee5408b012bac5400cfc07a5 + languageName: node + linkType: hard + +"dargs@npm:^8.0.0": + version: 8.1.0 + resolution: "dargs@npm:8.1.0" + checksum: 10c0/08cbd1ee4ac1a16fb7700e761af2e3e22d1bdc04ac4f851926f552dde8f9e57714c0d04013c2cca1cda0cba8fb637e0f93ad15d5285547a939dd1989ee06a82d + languageName: node + linkType: hard + +"data-uri-to-buffer@npm:^4.0.0": + version: 4.0.1 + resolution: "data-uri-to-buffer@npm:4.0.1" + checksum: 10c0/20a6b93107597530d71d4cb285acee17f66bcdfc03fd81040921a81252f19db27588d87fc8fc69e1950c55cfb0bf8ae40d0e5e21d907230813eb5d5a7f9eb45b + languageName: node + linkType: hard + +"data-uri-to-buffer@npm:^6.0.2": + version: 6.0.2 + resolution: "data-uri-to-buffer@npm:6.0.2" + checksum: 10c0/f76922bf895b3d7d443059ff278c9cc5efc89d70b8b80cd9de0aa79b3adc6d7a17948eefb8692e30398c43635f70ece1673d6085cc9eba2878dbc6c6da5292ac + languageName: node + linkType: hard + +"dateformat@npm:^4.6.3": + version: 4.6.3 + resolution: "dateformat@npm:4.6.3" + checksum: 10c0/e2023b905e8cfe2eb8444fb558562b524807a51cdfe712570f360f873271600b5c94aebffaf11efb285e2c072264a7cf243eadb68f3eba0f8cc85fb86cd25df6 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:~4.3.6": + version: 4.3.6 + resolution: "debug@npm:4.3.6" + dependencies: + ms: "npm:2.1.2" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/3293416bff072389c101697d4611c402a6bacd1900ac20c0492f61a9cdd6b3b29750fc7f5e299f8058469ef60ff8fb79b86395a30374fbd2490113c1c7112285 + languageName: node + linkType: hard + +"decamelize@npm:1.2.0": + version: 1.2.0 + resolution: "decamelize@npm:1.2.0" + checksum: 10c0/85c39fe8fbf0482d4a1e224ef0119db5c1897f8503bcef8b826adff7a1b11414972f6fef2d7dec2ee0b4be3863cf64ac1439137ae9e6af23a3d8dcbe26a5b4b2 + languageName: node + linkType: hard + +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: "npm:^3.1.0" + checksum: 10c0/bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e + languageName: node + linkType: hard + +"deep-eql@npm:^5.0.1": + version: 5.0.2 + resolution: "deep-eql@npm:5.0.2" + checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 + languageName: node + linkType: hard + +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + +"default-browser-id@npm:^5.0.0": + version: 5.0.0 + resolution: "default-browser-id@npm:5.0.0" + checksum: 10c0/957fb886502594c8e645e812dfe93dba30ed82e8460d20ce39c53c5b0f3e2afb6ceaec2249083b90bdfbb4cb0f34e1f73fde3d68cac00becdbcfd894156b5ead + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.2.1 + resolution: "default-browser@npm:5.2.1" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10c0/73f17dc3c58026c55bb5538749597db31f9561c0193cd98604144b704a981c95a466f8ecc3c2db63d8bfd04fb0d426904834cfc91ae510c6aeb97e13c5167c4d + languageName: node + linkType: hard + +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: "npm:^1.0.2" + checksum: 10c0/9cfbe498f5c8ed733775db62dfd585780387d93c17477949e1670bfcfb9346e0281ce8c4bf9f4ac1fc0f9b851113bd6dc9e41182ea1644ccd97de639fa13c35a + languageName: node + linkType: hard + +"defer-to-connect@npm:^2.0.1": + version: 2.0.1 + resolution: "defer-to-connect@npm:2.0.1" + checksum: 10c0/625ce28e1b5ad10cf77057b9a6a727bf84780c17660f6644dab61dd34c23de3001f03cedc401f7d30a4ed9965c2e8a7336e220a329146f2cf85d4eddea429782 + languageName: node + linkType: hard + +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10c0/5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + +"degenerator@npm:^5.0.0": + version: 5.0.1 + resolution: "degenerator@npm:5.0.1" + dependencies: + ast-types: "npm:^0.13.4" + escodegen: "npm:^2.1.0" + esprima: "npm:^4.0.1" + checksum: 10c0/e48d8a651edeb512a648711a09afec269aac6de97d442a4bb9cf121a66877e0eec11b9727100a10252335c0666ae1c84a8bc1e3a3f47788742c975064d2c7b1c + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"deprecation@npm:^2.0.0": + version: 2.3.1 + resolution: "deprecation@npm:2.3.1" + checksum: 10c0/23d688ba66b74d09b908c40a76179418acbeeb0bfdf218c8075c58ad8d0c315130cb91aa3dffb623aa3a411a3569ce56c6460de6c8d69071c17fe6dd2442f032 + languageName: node + linkType: hard + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: "npm:^4.0.0" + checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c + languageName: node + linkType: hard + +"dirty-json@npm:0.9.2": + version: 0.9.2 + resolution: "dirty-json@npm:0.9.2" + dependencies: + lex: "npm:^1.7.9" + unescape-js: "npm:^1.1.4" + utf8: "npm:^3.0.0" + checksum: 10c0/c0dffbc48bf147da0de56d5526bc60ac66afc1b5f9157f8f8839220ccdd340347a9c51e1bcde64ea8565db5ecfc6c74fbbcd25af9721695cfab85a58094382db + languageName: node + linkType: hard + +"dot-prop@npm:^5.1.0": + version: 5.3.0 + resolution: "dot-prop@npm:5.3.0" + dependencies: + is-obj: "npm:^2.0.0" + checksum: 10c0/93f0d343ef87fe8869320e62f2459f7e70f49c6098d948cc47e060f4a3f827d0ad61e83cb82f2bd90cd5b9571b8d334289978a43c0f98fea4f0e99ee8faa0599 + languageName: node + linkType: hard + +"dot-prop@npm:^6.0.1": + version: 6.0.1 + resolution: "dot-prop@npm:6.0.1" + dependencies: + is-obj: "npm:^2.0.0" + checksum: 10c0/30e51ec6408978a6951b21e7bc4938aad01a86f2fdf779efe52330205c6bb8a8ea12f35925c2029d6dc9d1df22f916f32f828ce1e9b259b1371c580541c22b5a + languageName: node + linkType: hard + +"dotenv@npm:^16.4.5": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f + languageName: node + linkType: hard + +"duck-duck-scrape@npm:^2.2.5": + version: 2.2.5 + resolution: "duck-duck-scrape@npm:2.2.5" + dependencies: + html-entities: "npm:^2.3.3" + needle: "npm:^3.2.0" + checksum: 10c0/eff42214c48dd0533c75ddc49aab8ff4fd40387a97a15752f285f7e9450779951633dbc5d06057cbf1ff49210f7e3c6c859a1803860897236c9b1ec0bbabbf92 + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.4": + version: 1.5.13 + resolution: "electron-to-chromium@npm:1.5.13" + checksum: 10c0/1d88ac39447e1d718c4296f92fe89836df4688daf2d362d6c49108136795f05a56dd9c950f1c6715e0395fa037c3b5f5ea686c543fdc90e6d74a005877c45022 + languageName: node + linkType: hard + +"emoji-regex@npm:^10.3.0": + version: 10.3.0 + resolution: "emoji-regex@npm:10.3.0" + checksum: 10c0/b4838e8dcdceb44cf47f59abe352c25ff4fe7857acaf5fb51097c427f6f75b44d052eb907a7a3b86f86bc4eae3a93f5c2b7460abe79c407307e6212d65c91163 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"end-of-stream@npm:^1.1.0": + version: 1.4.4 + resolution: "end-of-stream@npm:1.4.4" + dependencies: + once: "npm:^1.4.0" + checksum: 10c0/870b423afb2d54bb8d243c63e07c170409d41e20b47eeef0727547aea5740bd6717aca45597a9f2745525667a6b804c1e7bede41f856818faee5806dd9ff3975 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"environment@npm:^1.0.0": + version: 1.1.0 + resolution: "environment@npm:1.1.0" + checksum: 10c0/fb26434b0b581ab397039e51ff3c92b34924a98b2039dcb47e41b7bca577b9dbf134a8eadb364415c74464b682e2d3afe1a4c0eb9873dc44ea814c5d3103331d + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1, error-ex@npm:^1.3.2": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce + languageName: node + linkType: hard + +"esbuild@npm:^0.21.3": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": "npm:0.21.5" + "@esbuild/android-arm": "npm:0.21.5" + "@esbuild/android-arm64": "npm:0.21.5" + "@esbuild/android-x64": "npm:0.21.5" + "@esbuild/darwin-arm64": "npm:0.21.5" + "@esbuild/darwin-x64": "npm:0.21.5" + "@esbuild/freebsd-arm64": "npm:0.21.5" + "@esbuild/freebsd-x64": "npm:0.21.5" + "@esbuild/linux-arm": "npm:0.21.5" + "@esbuild/linux-arm64": "npm:0.21.5" + "@esbuild/linux-ia32": "npm:0.21.5" + "@esbuild/linux-loong64": "npm:0.21.5" + "@esbuild/linux-mips64el": "npm:0.21.5" + "@esbuild/linux-ppc64": "npm:0.21.5" + "@esbuild/linux-riscv64": "npm:0.21.5" + "@esbuild/linux-s390x": "npm:0.21.5" + "@esbuild/linux-x64": "npm:0.21.5" + "@esbuild/netbsd-x64": "npm:0.21.5" + "@esbuild/openbsd-x64": "npm:0.21.5" + "@esbuild/sunos-x64": "npm:0.21.5" + "@esbuild/win32-arm64": "npm:0.21.5" + "@esbuild/win32-ia32": "npm:0.21.5" + "@esbuild/win32-x64": "npm:0.21.5" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/fa08508adf683c3f399e8a014a6382a6b65542213431e26206c0720e536b31c09b50798747c2a105a4bbba1d9767b8d3615a74c2f7bf1ddf6d836cd11eb672de + languageName: node + linkType: hard + +"esbuild@npm:^0.23.0, esbuild@npm:~0.23.0": + version: 0.23.1 + resolution: "esbuild@npm:0.23.1" + dependencies: + "@esbuild/aix-ppc64": "npm:0.23.1" + "@esbuild/android-arm": "npm:0.23.1" + "@esbuild/android-arm64": "npm:0.23.1" + "@esbuild/android-x64": "npm:0.23.1" + "@esbuild/darwin-arm64": "npm:0.23.1" + "@esbuild/darwin-x64": "npm:0.23.1" + "@esbuild/freebsd-arm64": "npm:0.23.1" + "@esbuild/freebsd-x64": "npm:0.23.1" + "@esbuild/linux-arm": "npm:0.23.1" + "@esbuild/linux-arm64": "npm:0.23.1" + "@esbuild/linux-ia32": "npm:0.23.1" + "@esbuild/linux-loong64": "npm:0.23.1" + "@esbuild/linux-mips64el": "npm:0.23.1" + "@esbuild/linux-ppc64": "npm:0.23.1" + "@esbuild/linux-riscv64": "npm:0.23.1" + "@esbuild/linux-s390x": "npm:0.23.1" + "@esbuild/linux-x64": "npm:0.23.1" + "@esbuild/netbsd-x64": "npm:0.23.1" + "@esbuild/openbsd-arm64": "npm:0.23.1" + "@esbuild/openbsd-x64": "npm:0.23.1" + "@esbuild/sunos-x64": "npm:0.23.1" + "@esbuild/win32-arm64": "npm:0.23.1" + "@esbuild/win32-ia32": "npm:0.23.1" + "@esbuild/win32-x64": "npm:0.23.1" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/08c2ed1105cc3c5e3a24a771e35532fe6089dd24a39c10097899072cef4a99f20860e41e9294e000d86380f353b04d8c50af482483d7f69f5208481cce61eec7 + languageName: node + linkType: hard + +"escalade@npm:^3.1.2": + version: 3.1.2 + resolution: "escalade@npm:3.1.2" + checksum: 10c0/6b4adafecd0682f3aa1cd1106b8fff30e492c7015b178bc81b2d2f75106dabea6c6d6e8508fc491bd58e597c74abb0e8e2368f943ecb9393d4162e3c2f3cf287 + languageName: node + linkType: hard + +"escape-goat@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-goat@npm:4.0.0" + checksum: 10c0/9d2a8314e2370f2dd9436d177f6b3b1773525df8f895c8f3e1acb716f5fd6b10b336cb1cd9862d4709b36eb207dbe33664838deca9c6d55b8371be4eebb972f6 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"escodegen@npm:^2.1.0": + version: 2.1.0 + resolution: "escodegen@npm:2.1.0" + dependencies: + esprima: "npm:^4.0.1" + estraverse: "npm:^5.2.0" + esutils: "npm:^2.0.2" + source-map: "npm:~0.6.1" + dependenciesMeta: + source-map: + optional: true + bin: + escodegen: bin/escodegen.js + esgenerate: bin/esgenerate.js + checksum: 10c0/e1450a1f75f67d35c061bf0d60888b15f62ab63aef9df1901cffc81cffbbb9e8b3de237c5502cf8613a017c1df3a3003881307c78835a1ab54d8c8d2206e01d3 + languageName: node + linkType: hard + +"eslint-config-prettier@npm:^9.1.0": + version: 9.1.0 + resolution: "eslint-config-prettier@npm:9.1.0" + peerDependencies: + eslint: ">=7.0.0" + bin: + eslint-config-prettier: bin/cli.js + checksum: 10c0/6d332694b36bc9ac6fdb18d3ca2f6ac42afa2ad61f0493e89226950a7091e38981b66bac2b47ba39d15b73fff2cd32c78b850a9cf9eed9ca9a96bfb2f3a2f10d + languageName: node + linkType: hard + +"eslint-plugin-unused-imports@npm:^4.1.3": + version: 4.1.3 + resolution: "eslint-plugin-unused-imports@npm:4.1.3" + peerDependencies: + "@typescript-eslint/eslint-plugin": ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + "@typescript-eslint/eslint-plugin": + optional: true + checksum: 10c0/e30120e274554b99756fb4c2af123ca03f502fc55bc7e5b095fa9278b96cb32f1e405cf2e533aa8b0f8ca28c504ecf15daed9e4b5fccb172316e256a994fdebe + languageName: node + linkType: hard + +"eslint-scope@npm:^8.0.2": + version: 8.0.2 + resolution: "eslint-scope@npm:8.0.2" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/477f820647c8755229da913025b4567347fd1f0bf7cbdf3a256efff26a7e2e130433df052bd9e3d014025423dc00489bea47eb341002b15553673379c1a7dc36 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^4.0.0": + version: 4.0.0 + resolution: "eslint-visitor-keys@npm:4.0.0" + checksum: 10c0/76619f42cf162705a1515a6868e6fc7567e185c7063a05621a8ac4c3b850d022661262c21d9f1fc1d144ecf0d5d64d70a3f43c15c3fc969a61ace0fb25698cf5 + languageName: node + linkType: hard + +"eslint@npm:^9.9.0": + version: 9.9.0 + resolution: "eslint@npm:9.9.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@eslint-community/regexpp": "npm:^4.11.0" + "@eslint/config-array": "npm:^0.17.1" + "@eslint/eslintrc": "npm:^3.1.0" + "@eslint/js": "npm:9.9.0" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@humanwhocodes/retry": "npm:^0.3.0" + "@nodelib/fs.walk": "npm:^1.2.8" + ajv: "npm:^6.12.4" + chalk: "npm:^4.0.0" + cross-spawn: "npm:^7.0.2" + debug: "npm:^4.3.2" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^8.0.2" + eslint-visitor-keys: "npm:^4.0.0" + espree: "npm:^10.1.0" + esquery: "npm:^1.5.0" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^8.0.0" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + is-path-inside: "npm:^3.0.3" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + levn: "npm:^0.4.1" + lodash.merge: "npm:^4.6.2" + minimatch: "npm:^3.1.2" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + strip-ansi: "npm:^6.0.1" + text-table: "npm:^0.2.0" + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + bin: + eslint: bin/eslint.js + checksum: 10c0/3a22f68c99d75dcbafe6e2fef18d2b5bbcc960c2437f48a414ccf9ca214254733a18e6b79d07bbd374a2369a648413e421aabd07b11be3de5a44d5a4b9997877 + languageName: node + linkType: hard + +"espree@npm:^10.0.1, espree@npm:^10.1.0": + version: 10.1.0 + resolution: "espree@npm:10.1.0" + dependencies: + acorn: "npm:^8.12.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^4.0.0" + checksum: 10c0/52e6feaa77a31a6038f0c0e3fce93010a4625701925b0715cd54a2ae190b3275053a0717db698697b32653788ac04845e489d6773b508d6c2e8752f3c57470a0 + languageName: node + linkType: hard + +"esprima@npm:^4.0.1": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"esquery@npm:^1.5.0": + version: 1.6.0 + resolution: "esquery@npm:1.6.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"estree-walker@npm:^2.0.2": + version: 2.0.2 + resolution: "estree-walker@npm:2.0.2" + checksum: 10c0/53a6c54e2019b8c914dc395890153ffdc2322781acf4bd7d1a32d7aedc1710807bdcd866ac133903d5629ec601fbb50abe8c2e5553c7f5a0afdd9b6af6c945af + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 10c0/0255d9f936215fd206156fd4caa9e8d35e62075d720dc7d847e89b417e5e62cf1ce6c9b4e0a1633a9256de0efefaf9f8d26924b1f3c8620cffb9db78e7d3076b + languageName: node + linkType: hard + +"eventemitter3@npm:5.x, eventemitter3@npm:^5.0.1": + version: 5.0.1 + resolution: "eventemitter3@npm:5.0.1" + checksum: 10c0/4ba5c00c506e6c786b4d6262cfbce90ddc14c10d4667e5c83ae993c9de88aa856033994dd2b35b83e8dc1170e224e66a319fa80adc4c32adcd2379bbc75da814 + languageName: node + linkType: hard + +"eventemitter3@npm:^4.0.4": + version: 4.0.7 + resolution: "eventemitter3@npm:4.0.7" + checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b + languageName: node + linkType: hard + +"events@npm:^3.3.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 + languageName: node + linkType: hard + +"execa@npm:8.0.1, execa@npm:^8.0.1, execa@npm:~8.0.1": + version: 8.0.1 + resolution: "execa@npm:8.0.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^8.0.1" + human-signals: "npm:^5.0.0" + is-stream: "npm:^3.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^5.1.0" + onetime: "npm:^6.0.0" + signal-exit: "npm:^4.1.0" + strip-final-newline: "npm:^3.0.0" + checksum: 10c0/2c52d8775f5bf103ce8eec9c7ab3059909ba350a5164744e9947ed14a53f51687c040a250bda833f906d1283aa8803975b84e6c8f7a7c42f99dc8ef80250d1af + languageName: node + linkType: hard + +"execa@npm:^5.1.1": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.0" + human-signals: "npm:^2.1.0" + is-stream: "npm:^2.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^4.0.1" + onetime: "npm:^5.1.2" + signal-exit: "npm:^3.0.3" + strip-final-newline: "npm:^2.0.0" + checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 10c0/160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579 + languageName: node + linkType: hard + +"expr-eval@npm:^2.0.2": + version: 2.0.2 + resolution: "expr-eval@npm:2.0.2" + checksum: 10c0/642f112ff28ea34574c595c3ad73ccd8e638498879a4dd28620c4dabebab2e11987a851266ba81883dae85a5800e0c93b3d06f81718b71a215f831534646e4f2 + languageName: node + linkType: hard + +"external-editor@npm:^3.1.0": + version: 3.1.0 + resolution: "external-editor@npm:3.1.0" + dependencies: + chardet: "npm:^0.7.0" + iconv-lite: "npm:^0.4.24" + tmp: "npm:^0.0.33" + checksum: 10c0/c98f1ba3efdfa3c561db4447ff366a6adb5c1e2581462522c56a18bf90dfe4da382f9cd1feee3e330108c3595a854b218272539f311ba1b3298f841eb0fbf339 + languageName: node + linkType: hard + +"fast-copy@npm:^3.0.2": + version: 3.0.2 + resolution: "fast-copy@npm:3.0.2" + checksum: 10c0/02e8b9fd03c8c024d2987760ce126456a0e17470850b51e11a1c3254eed6832e4733ded2d93316c82bc0b36aeb991ad1ff48d1ba95effe7add7c3ab8d8eb554a + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2": + version: 3.3.2 + resolution: "fast-glob@npm:3.3.2" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.4" + checksum: 10c0/42baad7b9cd40b63e42039132bde27ca2cb3a4950d0a0f9abe4639ea1aa9d3e3b40f98b1fe31cbc0cc17b664c9ea7447d911a152fa34ec5b72977b125a6fc845 + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + +"fast-redact@npm:^3.1.1": + version: 3.5.0 + resolution: "fast-redact@npm:3.5.0" + checksum: 10c0/7e2ce4aad6e7535e0775bf12bd3e4f2e53d8051d8b630e0fa9e67f68cb0b0e6070d2f7a94b1d0522ef07e32f7c7cda5755e2b677a6538f1e9070ca053c42343a + languageName: node + linkType: hard + +"fast-safe-stringify@npm:^2.1.1": + version: 2.1.1 + resolution: "fast-safe-stringify@npm:2.1.1" + checksum: 10c0/d90ec1c963394919828872f21edaa3ad6f1dddd288d2bd4e977027afff09f5db40f94e39536d4646f7e01761d704d72d51dce5af1b93717f3489ef808f5f4e4d + languageName: node + linkType: hard + +"fast-uri@npm:^3.0.1": + version: 3.0.1 + resolution: "fast-uri@npm:3.0.1" + checksum: 10c0/3cd46d6006083b14ca61ffe9a05b8eef75ef87e9574b6f68f2e17ecf4daa7aaadeff44e3f0f7a0ef4e0f7e7c20fc07beec49ff14dc72d0b500f00386592f2d10 + languageName: node + linkType: hard + +"fast-xml-parser@npm:^4.4.1": + version: 4.4.1 + resolution: "fast-xml-parser@npm:4.4.1" + dependencies: + strnum: "npm:^1.0.5" + bin: + fxparser: src/cli/cli.js + checksum: 10c0/7f334841fe41bfb0bf5d920904ccad09cefc4b5e61eaf4c225bf1e1bb69ee77ef2147d8942f783ee8249e154d1ca8a858e10bda78a5d78b8bed3f48dcee9bf33 + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.17.1 + resolution: "fastq@npm:1.17.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/1095f16cea45fb3beff558bb3afa74ca7a9250f5a670b65db7ed585f92b4b48381445cd328b3d87323da81e43232b5d5978a8201bde84e0cd514310f1ea6da34 + languageName: node + linkType: hard + +"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": + version: 3.2.0 + resolution: "fetch-blob@npm:3.2.0" + dependencies: + node-domexception: "npm:^1.0.0" + web-streams-polyfill: "npm:^3.0.3" + checksum: 10c0/60054bf47bfa10fb0ba6cb7742acec2f37c1f56344f79a70bb8b1c48d77675927c720ff3191fa546410a0442c998d27ab05e9144c32d530d8a52fbe68f843b69 + languageName: node + linkType: hard + +"fetch-retry@npm:^5.0.6": + version: 5.0.6 + resolution: "fetch-retry@npm:5.0.6" + checksum: 10c0/349f50db631039630e915f70c763469cb696f3ac92ca6f63823109334a2bc62f63670b8c5a5c7e0195c39df517e60ef385cc5264f4c4904d0c6707d371fa8999 + languageName: node + linkType: hard + +"file-entry-cache@npm:^8.0.0": + version: 8.0.0 + resolution: "file-entry-cache@npm:8.0.0" + dependencies: + flat-cache: "npm:^4.0.0" + checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"find-up@npm:^6.3.0": + version: 6.3.0 + resolution: "find-up@npm:6.3.0" + dependencies: + locate-path: "npm:^7.1.0" + path-exists: "npm:^5.0.0" + checksum: 10c0/07e0314362d316b2b13f7f11ea4692d5191e718ca3f7264110127520f3347996349bf9e16805abae3e196805814bc66ef4bff2b8904dc4a6476085fc9b0eba07 + languageName: node + linkType: hard + +"flat-cache@npm:^4.0.0": + version: 4.0.1 + resolution: "flat-cache@npm:4.0.1" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.4" + checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc + languageName: node + linkType: hard + +"flat@npm:^5.0.2": + version: 5.0.2 + resolution: "flat@npm:5.0.2" + bin: + flat: cli.js + checksum: 10c0/f178b13482f0cd80c7fede05f4d10585b1f2fdebf26e12edc138e32d3150c6ea6482b7f12813a1091143bad52bb6d3596bca51a162257a21163c0ff438baa5fe + languageName: node + linkType: hard + +"flatted@npm:^3.2.9": + version: 3.3.1 + resolution: "flatted@npm:3.3.1" + checksum: 10c0/324166b125ee07d4ca9bcf3a5f98d915d5db4f39d711fba640a3178b959919aae1f7cfd8aabcfef5826ed8aa8a2aa14cc85b2d7d18ff638ddf4ae3df39573eaf + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.6": + version: 1.15.6 + resolution: "follow-redirects@npm:1.15.6" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071 + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.3.0 + resolution: "foreground-child@npm:3.3.0" + dependencies: + cross-spawn: "npm:^7.0.0" + signal-exit: "npm:^4.0.1" + checksum: 10c0/028f1d41000553fcfa6c4bb5c372963bf3d9bf0b1f25a87d1a6253014343fb69dfb1b42d9625d7cf44c8ba429940f3d0ff718b62105d4d4a4f6ef8ca0a53faa2 + languageName: node + linkType: hard + +"form-data-encoder@npm:1.7.2": + version: 1.7.2 + resolution: "form-data-encoder@npm:1.7.2" + checksum: 10c0/56553768037b6d55d9de524f97fe70555f0e415e781cb56fc457a68263de3d40fadea2304d4beef2d40b1a851269bd7854e42c362107071892cb5238debe9464 + languageName: node + linkType: hard + +"form-data-encoder@npm:^2.1.2": + version: 2.1.4 + resolution: "form-data-encoder@npm:2.1.4" + checksum: 10c0/4c06ae2b79ad693a59938dc49ebd020ecb58e4584860a90a230f80a68b026483b022ba5e4143cff06ae5ac8fd446a0b500fabc87bbac3d1f62f2757f8dabcaf7 + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + mime-types: "npm:^2.1.12" + checksum: 10c0/cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e + languageName: node + linkType: hard + +"formdata-node@npm:^4.3.2": + version: 4.4.1 + resolution: "formdata-node@npm:4.4.1" + dependencies: + node-domexception: "npm:1.0.0" + web-streams-polyfill: "npm:4.0.0-beta.3" + checksum: 10c0/74151e7b228ffb33b565cec69182694ad07cc3fdd9126a8240468bb70a8ba66e97e097072b60bcb08729b24c7ce3fd3e0bd7f1f80df6f9f662b9656786e76f6a + languageName: node + linkType: hard + +"formdata-polyfill@npm:^4.0.10": + version: 4.0.10 + resolution: "formdata-polyfill@npm:4.0.10" + dependencies: + fetch-blob: "npm:^3.1.2" + checksum: 10c0/5392ec484f9ce0d5e0d52fb5a78e7486637d516179b0eb84d81389d7eccf9ca2f663079da56f761355c0a65792810e3b345dc24db9a8bbbcf24ef3c8c88570c6 + languageName: node + linkType: hard + +"fs-extra@npm:^11.2.0": + version: 11.2.0 + resolution: "fs-extra@npm:11.2.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/d77a9a9efe60532d2e790e938c81a02c1b24904ef7a3efb3990b835514465ba720e99a6ea56fd5e2db53b4695319b644d76d5a0e9988a2beef80aa7b1da63398 + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"generative-bayesian-network@npm:^2.1.54": + version: 2.1.54 + resolution: "generative-bayesian-network@npm:2.1.54" + dependencies: + adm-zip: "npm:^0.5.9" + tslib: "npm:^2.4.0" + checksum: 10c0/6f1a4437c59b500734cc47bcc30990255cf59ddca8dee9df50b1ff3844451287fb2c83d89cb7418d11e3145afc1838b908b3095600296c38a157e3eeee02cea8 + languageName: node + linkType: hard + +"get-east-asian-width@npm:^1.0.0": + version: 1.2.0 + resolution: "get-east-asian-width@npm:1.2.0" + checksum: 10c0/914b1e217cf38436c24b4c60b4c45289e39a45bf9e65ef9fd343c2815a1a02b8a0215aeec8bf9c07c516089004b6e3826332481f40a09529fcadbf6e579f286b + languageName: node + linkType: hard + +"get-func-name@npm:^2.0.1": + version: 2.0.2 + resolution: "get-func-name@npm:2.0.2" + checksum: 10c0/89830fd07623fa73429a711b9daecdb304386d237c71268007f788f113505ef1d4cc2d0b9680e072c5082490aec9df5d7758bf5ac6f1c37062855e8e3dc0b9df + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 + languageName: node + linkType: hard + +"get-stream@npm:^8.0.1": + version: 8.0.1 + resolution: "get-stream@npm:8.0.1" + checksum: 10c0/5c2181e98202b9dae0bb4a849979291043e5892eb40312b47f0c22b9414fc9b28a3b6063d2375705eb24abc41ecf97894d9a51f64ff021511b504477b27b4290 + languageName: node + linkType: hard + +"get-tsconfig@npm:^4.7.5": + version: 4.7.6 + resolution: "get-tsconfig@npm:4.7.6" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/2240e1b13e996dfbb947d177f422f83d09d1f93c9ce16959ebb3c2bdf8bdf4f04f98eba043859172da1685f9c7071091f0acfa964ebbe4780394d83b7dc3f58a + languageName: node + linkType: hard + +"get-uri@npm:^6.0.1": + version: 6.0.3 + resolution: "get-uri@npm:6.0.3" + dependencies: + basic-ftp: "npm:^5.0.2" + data-uri-to-buffer: "npm:^6.0.2" + debug: "npm:^4.3.4" + fs-extra: "npm:^11.2.0" + checksum: 10c0/8d801c462cd5b9c171d4d9e5f17afce3d9ebfbbfb006a88e3e768ce0071a8e2e59ee1ce822915fc43b9d6b83fde7b8d1c9648330ae89778fa41ad774df8ee0ac + languageName: node + linkType: hard + +"git-raw-commits@npm:^4.0.0": + version: 4.0.0 + resolution: "git-raw-commits@npm:4.0.0" + dependencies: + dargs: "npm:^8.0.0" + meow: "npm:^12.0.1" + split2: "npm:^4.0.0" + bin: + git-raw-commits: cli.mjs + checksum: 10c0/ab51335d9e55692fce8e42788013dba7a7e7bf9f5bf0622c8cd7ddc9206489e66bb939563fca4edb3aa87477e2118f052702aad1933b13c6fa738af7f29884f0 + languageName: node + linkType: hard + +"git-semver-tags@npm:^7.0.0": + version: 7.0.1 + resolution: "git-semver-tags@npm:7.0.1" + dependencies: + meow: "npm:^12.0.1" + semver: "npm:^7.5.2" + bin: + git-semver-tags: cli.mjs + checksum: 10c0/6eec918f6324248faad98d8846cc8c73a73d735a182af3b2073e58a75c828487da0bbb6ae33d5b0302f006eed1af93b4a3ed732fcfc53152623ba5ee21504205 + languageName: node + linkType: hard + +"git-up@npm:^7.0.0": + version: 7.0.0 + resolution: "git-up@npm:7.0.0" + dependencies: + is-ssh: "npm:^1.4.0" + parse-url: "npm:^8.1.0" + checksum: 10c0/a3fa02e1a63c7c824b5ebbf23f4a9a6b34dd80031114c5dd8adb7ef53493642e39d3d80dfef4025a452128400c35c2c138d20a0f6ae5d7d7ef70d9ba13083d34 + languageName: node + linkType: hard + +"git-url-parse@npm:14.0.0": + version: 14.0.0 + resolution: "git-url-parse@npm:14.0.0" + dependencies: + git-up: "npm:^7.0.0" + checksum: 10c0/d360cf23c6278e302b74603f3dc490c3fe22e533d58b7f35e0295fad9af209ce5046a55950ccbf2f0d18de7931faefb4353e3f3fd3dda87fce77b409d48e0ba9 + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.1": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + languageName: node + linkType: hard + +"glob@npm:^11.0.0": + version: 11.0.0 + resolution: "glob@npm:11.0.0" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^4.0.1" + minimatch: "npm:^10.0.0" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^2.0.0" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/419866015d8795258a8ac51de5b9d1a99c72634fc3ead93338e4da388e89773ab21681e494eac0fbc4250b003451ca3110bb4f1c9393d15d14466270094fdb4e + languageName: node + linkType: hard + +"glob@npm:^7.0.0": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe + languageName: node + linkType: hard + +"global-directory@npm:^4.0.1": + version: 4.0.1 + resolution: "global-directory@npm:4.0.1" + dependencies: + ini: "npm:4.1.1" + checksum: 10c0/f9cbeef41db4876f94dd0bac1c1b4282a7de9c16350ecaaf83e7b2dd777b32704cc25beeb1170b5a63c42a2c9abfade74d46357fe0133e933218bc89e613d4b2 + languageName: node + linkType: hard + +"globals@npm:^14.0.0": + version: 14.0.0 + resolution: "globals@npm:14.0.0" + checksum: 10c0/b96ff42620c9231ad468d4c58ff42afee7777ee1c963013ff8aabe095a451d0ceeb8dcd8ef4cbd64d2538cef45f787a78ba3a9574f4a634438963e334471302d + languageName: node + linkType: hard + +"globby@npm:14.0.2": + version: 14.0.2 + resolution: "globby@npm:14.0.2" + dependencies: + "@sindresorhus/merge-streams": "npm:^2.1.0" + fast-glob: "npm:^3.3.2" + ignore: "npm:^5.2.4" + path-type: "npm:^5.0.0" + slash: "npm:^5.1.0" + unicorn-magic: "npm:^0.1.0" + checksum: 10c0/3f771cd683b8794db1e7ebc8b6b888d43496d93a82aad4e9d974620f578581210b6c5a6e75ea29573ed16a1345222fab6e9b877a8d1ed56eeb147e09f69c6f78 + languageName: node + linkType: hard + +"globby@npm:^11.1.0": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: "npm:^2.1.0" + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.2.9" + ignore: "npm:^5.2.0" + merge2: "npm:^1.4.1" + slash: "npm:^3.0.0" + checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 + languageName: node + linkType: hard + +"globrex@npm:^0.1.2": + version: 0.1.2 + resolution: "globrex@npm:0.1.2" + checksum: 10c0/a54c029520cf58bda1d8884f72bd49b4cd74e977883268d931fd83bcbd1a9eb96d57c7dbd4ad80148fb9247467ebfb9b215630b2ed7563b2a8de02e1ff7f89d1 + languageName: node + linkType: hard + +"got@npm:13.0.0": + version: 13.0.0 + resolution: "got@npm:13.0.0" + dependencies: + "@sindresorhus/is": "npm:^5.2.0" + "@szmarczak/http-timer": "npm:^5.0.1" + cacheable-lookup: "npm:^7.0.0" + cacheable-request: "npm:^10.2.8" + decompress-response: "npm:^6.0.0" + form-data-encoder: "npm:^2.1.2" + get-stream: "npm:^6.0.1" + http2-wrapper: "npm:^2.1.10" + lowercase-keys: "npm:^3.0.0" + p-cancelable: "npm:^3.0.0" + responselike: "npm:^3.0.0" + checksum: 10c0/d6a4648dc46f1f9df2637b8730d4e664349a93cb6df62c66dfbb48f7887ba79742a1cc90739a4eb1c15f790ca838ff641c5cdecdc877993627274aeb0f02b92d + languageName: node + linkType: hard + +"graceful-fs@npm:4.2.10": + version: 4.2.10 + resolution: "graceful-fs@npm:4.2.10" + checksum: 10c0/4223a833e38e1d0d2aea630c2433cfb94ddc07dfc11d511dbd6be1d16688c5be848acc31f9a5d0d0ddbfb56d2ee5a6ae0278aceeb0ca6a13f27e06b9956fb952 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 + languageName: node + linkType: hard + +"handlebars@npm:^4.7.7": + version: 4.7.8 + resolution: "handlebars@npm:4.7.8" + dependencies: + minimist: "npm:^1.2.5" + neo-async: "npm:^2.6.2" + source-map: "npm:^0.6.1" + uglify-js: "npm:^3.1.4" + wordwrap: "npm:^1.0.0" + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: 10c0/7aff423ea38a14bb379316f3857fe0df3c5d66119270944247f155ba1f08e07a92b340c58edaa00cfe985c21508870ee5183e0634dcb53dd405f35c93ef7f10d + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"header-generator@npm:^2.1.54": + version: 2.1.54 + resolution: "header-generator@npm:2.1.54" + dependencies: + browserslist: "npm:^4.21.1" + generative-bayesian-network: "npm:^2.1.54" + ow: "npm:^0.28.1" + tslib: "npm:^2.4.0" + checksum: 10c0/6fa48709a29d380eca894434d689ef0fb8fcecd19decb994bf0f09a58224e968cfbd581f05369d051fbe220531d6eb1b0f5f2a369344541cd8df900d0823caaf + languageName: node + linkType: hard + +"help-me@npm:^5.0.0": + version: 5.0.0 + resolution: "help-me@npm:5.0.0" + checksum: 10c0/054c0e2e9ae2231c85ab5e04f75109b9d068ffcc54e58fb22079822a5ace8ff3d02c66fd45379c902ad5ab825e5d2e1451fcc2f7eab1eb49e7d488133ba4cacb + languageName: node + linkType: hard + +"hosted-git-info@npm:^7.0.0": + version: 7.0.2 + resolution: "hosted-git-info@npm:7.0.2" + dependencies: + lru-cache: "npm:^10.0.1" + checksum: 10c0/b19dbd92d3c0b4b0f1513cf79b0fc189f54d6af2129eeb201de2e9baaa711f1936929c848b866d9c8667a0f956f34bf4f07418c12be1ee9ca74fd9246335ca1f + languageName: node + linkType: hard + +"html-entities@npm:^2.3.3, html-entities@npm:^2.5.2": + version: 2.5.2 + resolution: "html-entities@npm:2.5.2" + checksum: 10c0/f20ffb4326606245c439c231de40a7c560607f639bf40ffbfb36b4c70729fd95d7964209045f1a4e62fe17f2364cef3d6e49b02ea09016f207fde51c2211e481 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"http-status-codes@npm:^2.3.0": + version: 2.3.0 + resolution: "http-status-codes@npm:2.3.0" + checksum: 10c0/c2412188929e8eed6623eef468c62d0c3c082919c03e9b74fd79cfd060d11783dba44603e38a3cee52d26563fe32005913eaf6120aa8ba907da1238f3eaad5fe + languageName: node + linkType: hard + +"http2-wrapper@npm:^2.1.10": + version: 2.2.1 + resolution: "http2-wrapper@npm:2.2.1" + dependencies: + quick-lru: "npm:^5.1.1" + resolve-alpn: "npm:^1.2.0" + checksum: 10c0/7207201d3c6e53e72e510c9b8912e4f3e468d3ecc0cf3bf52682f2aac9cd99358b896d1da4467380adc151cf97c412bedc59dc13dae90c523f42053a7449eedb + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.3, https-proxy-agent@npm:^7.0.4, https-proxy-agent@npm:^7.0.5": + version: 7.0.5 + resolution: "https-proxy-agent@npm:7.0.5" + dependencies: + agent-base: "npm:^7.0.2" + debug: "npm:4" + checksum: 10c0/2490e3acec397abeb88807db52cac59102d5ed758feee6df6112ab3ccd8325e8a1ce8bce6f4b66e5470eca102d31e425ace904242e4fa28dbe0c59c4bafa7b2c + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a + languageName: node + linkType: hard + +"human-signals@npm:^5.0.0": + version: 5.0.0 + resolution: "human-signals@npm:5.0.0" + checksum: 10c0/5a9359073fe17a8b58e5a085e9a39a950366d9f00217c4ff5878bd312e09d80f460536ea6a3f260b5943a01fe55c158d1cea3fc7bee3d0520aeef04f6d915c82 + languageName: node + linkType: hard + +"humanize-ms@npm:^1.2.1": + version: 1.2.1 + resolution: "humanize-ms@npm:1.2.1" + dependencies: + ms: "npm:^2.0.0" + checksum: 10c0/f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a + languageName: node + linkType: hard + +"husky@npm:^9.1.5": + version: 9.1.5 + resolution: "husky@npm:9.1.5" + bin: + husky: bin.js + checksum: 10c0/f42efb95a026303eb880898760f802d88409780dd72f17781d2dfc302177d4f80b641cf1f1694f53f6d97c536c7397684133d8c8fe4a4426f7460186a7d1c6b8 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + +"ignore@npm:^5.2.0, ignore@npm:^5.2.4, ignore@npm:^5.3.1": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + +"import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": + version: 3.3.0 + resolution: "import-fresh@npm:3.3.0" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/7f882953aa6b740d1f0e384d0547158bc86efbf2eea0f1483b8900a6f65c5a5123c2cf09b0d542cc419d0b98a759ecaeb394237e97ea427f2da221dc3cd80cc3 + languageName: node + linkType: hard + +"import-lazy@npm:^4.0.0": + version: 4.0.0 + resolution: "import-lazy@npm:4.0.0" + checksum: 10c0/a3520313e2c31f25c0b06aa66d167f329832b68a4f957d7c9daf6e0fa41822b6e84948191648b9b9d8ca82f94740cdf15eecf2401a5b42cd1c33fd84f2225cca + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f + languageName: node + linkType: hard + +"index-to-position@npm:^0.1.2": + version: 0.1.2 + resolution: "index-to-position@npm:0.1.2" + checksum: 10c0/7c91bde8bafc22684b74a7a24915bee4691cba48352ddb4ebe3b20a3a87bc0fa7a05f586137245ca8f92222a11f341f7631ff7f38cd78a523505d2d02dbfa257 + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"infobox-parser@npm:^3.6.2": + version: 3.6.4 + resolution: "infobox-parser@npm:3.6.4" + dependencies: + camelcase: "npm:^4.1.0" + checksum: 10c0/1595db38ef274b2ebfef0f0e5b3e389f2349c3c2418735b52ea7805fdf592d1fa1befe2d551e7d83f4cd7318b51cb08252d5e36c01989de42ab49cabfe364cc4 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:^2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"ini@npm:4.1.1": + version: 4.1.1 + resolution: "ini@npm:4.1.1" + checksum: 10c0/7fddc8dfd3e63567d4fdd5d999d1bf8a8487f1479d0b34a1d01f28d391a9228d261e19abc38e1a6a1ceb3400c727204fce05725d5eb598dfcf2077a1e3afe211 + languageName: node + linkType: hard + +"ini@npm:^1.3.4, ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a + languageName: node + linkType: hard + +"inquirer@npm:9.3.2": + version: 9.3.2 + resolution: "inquirer@npm:9.3.2" + dependencies: + "@inquirer/figures": "npm:^1.0.3" + ansi-escapes: "npm:^4.3.2" + cli-width: "npm:^4.1.0" + external-editor: "npm:^3.1.0" + mute-stream: "npm:1.0.0" + ora: "npm:^5.4.1" + run-async: "npm:^3.0.0" + rxjs: "npm:^7.8.1" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.1" + checksum: 10c0/7e09361e102f98c2cfaebe6427250ad9ebfff018f48480f0f537ce039ac77fdc04093fb690c45ef3c246dc0b6afa6af437f1da10bd2899593403bcdbe7541580 + languageName: node + linkType: hard + +"interpret@npm:^1.0.0": + version: 1.4.0 + resolution: "interpret@npm:1.4.0" + checksum: 10c0/08c5ad30032edeec638485bc3f6db7d0094d9b3e85e0f950866600af3c52e9fd69715416d29564731c479d9f4d43ff3e4d302a178196bdc0e6837ec147640450 + languageName: node + linkType: hard + +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: "npm:^2.0.0" + checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 + languageName: node + linkType: hard + +"is-ci@npm:3.0.1": + version: 3.0.1 + resolution: "is-ci@npm:3.0.1" + dependencies: + ci-info: "npm:^3.2.0" + bin: + is-ci: bin.js + checksum: 10c0/0e81caa62f4520d4088a5bef6d6337d773828a88610346c4b1119fb50c842587ed8bef1e5d9a656835a599e7209405b5761ddf2339668f2d0f4e889a92fe6051 + languageName: node + linkType: hard + +"is-core-module@npm:^2.13.0": + version: 2.15.0 + resolution: "is-core-module@npm:2.15.0" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/da161f3d9906f459486da65609b2f1a2dfdc60887c689c234d04e88a062cb7920fa5be5fb7ab08dc43b732929653c4135ef05bf77888ae2a9040ce76815eb7b1 + languageName: node + linkType: hard + +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10c0/d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^4.0.0": + version: 4.0.0 + resolution: "is-fullwidth-code-point@npm:4.0.0" + checksum: 10c0/df2a717e813567db0f659c306d61f2f804d480752526886954a2a3e2246c7745fd07a52b5fecf2b68caf0a6c79dcdace6166fdf29cc76ed9975cc334f0a018b8 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^5.0.0": + version: 5.0.0 + resolution: "is-fullwidth-code-point@npm:5.0.0" + dependencies: + get-east-asian-width: "npm:^1.0.0" + checksum: 10c0/cd591b27d43d76b05fa65ed03eddce57a16e1eca0b7797ff7255de97019bcaf0219acfc0c4f7af13319e13541f2a53c0ace476f442b13267b9a6a7568f2b65c8 + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-in-ci@npm:^0.1.0": + version: 0.1.0 + resolution: "is-in-ci@npm:0.1.0" + bin: + is-in-ci: cli.js + checksum: 10c0/0895b6ecf8abc18a07611382184a3fbe2a8424c11e8a6fd915fcee950d7027d6a3734068636c86bc084828465bf2878fdcd60a8f4fe06d70ff42e10f5cf8bb73 + languageName: node + linkType: hard + +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10c0/a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + +"is-installed-globally@npm:^1.0.0": + version: 1.0.0 + resolution: "is-installed-globally@npm:1.0.0" + dependencies: + global-directory: "npm:^4.0.1" + is-path-inside: "npm:^4.0.0" + checksum: 10c0/5f57745b6e75b2e9e707a26470d0cb74291d9be33c0fe0dc06c6955fe086bc2ca0a8960631b1ecb9677100eac90af33e911aec7a2c0b88097d702bfa3b76486d + languageName: node + linkType: hard + +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: 10c0/dd47904dbf286cd20aa58c5192161be1a67138485b9836d5a70433b21a45442e9611b8498b8ab1f839fc962c7620667a50535fdfb4a6bc7989b8858645c06b4d + languageName: node + linkType: hard + +"is-interactive@npm:^2.0.0": + version: 2.0.0 + resolution: "is-interactive@npm:2.0.0" + checksum: 10c0/801c8f6064f85199dc6bf99b5dd98db3282e930c3bc197b32f2c5b89313bb578a07d1b8a01365c4348c2927229234f3681eb861b9c2c92bee72ff397390fa600 + languageName: node + linkType: hard + +"is-lambda@npm:^1.0.1": + version: 1.0.1 + resolution: "is-lambda@npm:1.0.1" + checksum: 10c0/85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d + languageName: node + linkType: hard + +"is-npm@npm:^6.0.0": + version: 6.0.0 + resolution: "is-npm@npm:6.0.0" + checksum: 10c0/1f064c66325cba6e494783bee4e635caa2655aad7f853a0e045d086e0bb7d83d2d6cdf1745dc9a7c7c93dacbf816fbee1f8d9179b02d5d01674d4f92541dc0d9 + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-obj@npm:^2.0.0": + version: 2.0.0 + resolution: "is-obj@npm:2.0.0" + checksum: 10c0/85044ed7ba8bd169e2c2af3a178cacb92a97aa75de9569d02efef7f443a824b5e153eba72b9ae3aca6f8ce81955271aa2dc7da67a8b720575d3e38104208cb4e + languageName: node + linkType: hard + +"is-path-inside@npm:^3.0.3": + version: 3.0.3 + resolution: "is-path-inside@npm:3.0.3" + checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 + languageName: node + linkType: hard + +"is-path-inside@npm:^4.0.0": + version: 4.0.0 + resolution: "is-path-inside@npm:4.0.0" + checksum: 10c0/51188d7e2b1d907a9a5f7c18d99a90b60870b951ed87cf97595d9aaa429d4c010652c3350bcbf31182e7f4b0eab9a1860b43e16729b13cb1a44baaa6cdb64c46 + languageName: node + linkType: hard + +"is-reference@npm:1.2.1": + version: 1.2.1 + resolution: "is-reference@npm:1.2.1" + dependencies: + "@types/estree": "npm:*" + checksum: 10c0/7dc819fc8de7790264a0a5d531164f9f5b9ef5aa1cd05f35322d14db39c8a2ec78fd5d4bf57f9789f3ddd2b3abeea7728432b759636157a42db12a9e8c3b549b + languageName: node + linkType: hard + +"is-ssh@npm:^1.4.0": + version: 1.4.0 + resolution: "is-ssh@npm:1.4.0" + dependencies: + protocols: "npm:^2.0.1" + checksum: 10c0/3eb30d1bcb4507cd25562e7ac61a1c0aa31772134c67cec9c3afe6f4d57ec17e8c2892600a608e8e583f32f53f36465b8968c0305f2855cfbff95acfd049e113 + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"is-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "is-stream@npm:3.0.0" + checksum: 10c0/eb2f7127af02ee9aa2a0237b730e47ac2de0d4e76a4a905a50a11557f2339df5765eaea4ceb8029f1efa978586abe776908720bfcb1900c20c6ec5145f6f29d8 + languageName: node + linkType: hard + +"is-text-path@npm:^2.0.0": + version: 2.0.0 + resolution: "is-text-path@npm:2.0.0" + dependencies: + text-extensions: "npm:^2.0.0" + checksum: 10c0/e3c470e1262a3a54aa0fca1c0300b2659a7aed155714be6b643f88822c03bcfa6659b491f7a05c5acd3c1a3d6d42bab47e1bdd35bcc3a25973c4f26b2928bc1a + languageName: node + linkType: hard + +"is-typedarray@npm:^1.0.0": + version: 1.0.0 + resolution: "is-typedarray@npm:1.0.0" + checksum: 10c0/4c096275ba041a17a13cca33ac21c16bc4fd2d7d7eb94525e7cd2c2f2c1a3ab956e37622290642501ff4310601e413b675cf399ad6db49855527d2163b3eeeec + languageName: node + linkType: hard + +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: 10c0/00cbe3455c3756be68d2542c416cab888aebd5012781d6819749fefb15162ff23e38501fe681b3d751c73e8ff561ac09a5293eba6f58fdf0178462ce6dcb3453 + languageName: node + linkType: hard + +"is-unicode-supported@npm:^1.3.0": + version: 1.3.0 + resolution: "is-unicode-supported@npm:1.3.0" + checksum: 10c0/b8674ea95d869f6faabddc6a484767207058b91aea0250803cbf1221345cb0c56f466d4ecea375dc77f6633d248d33c47bd296fb8f4cdba0b4edba8917e83d8a + languageName: node + linkType: hard + +"is-unicode-supported@npm:^2.0.0": + version: 2.0.0 + resolution: "is-unicode-supported@npm:2.0.0" + checksum: 10c0/3013dfb8265fe9f9a0d1e9433fc4e766595631a8d85d60876c457b4bedc066768dab1477c553d02e2f626d88a4e019162706e04263c94d74994ef636a33b5f94 + languageName: node + linkType: hard + +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10c0/d3317c11995690a32c362100225e22ba793678fe8732660c6de511ae71a0ff05b06980cf21f98a6bf40d7be0e9e9506f859abe00a1118287d63e53d0a3d06947 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"issue-parser@npm:7.0.1": + version: 7.0.1 + resolution: "issue-parser@npm:7.0.1" + dependencies: + lodash.capitalize: "npm:^4.2.1" + lodash.escaperegexp: "npm:^4.1.2" + lodash.isplainobject: "npm:^4.0.6" + lodash.isstring: "npm:^4.0.1" + lodash.uniqby: "npm:^4.7.0" + checksum: 10c0/1b2dad16081ae423bb96143132701e89aa8f6345ab0a10f692594ddf5699b514adccaaaf24d7c59afc977c447895bdee15fff2dfc9d6015e177f6966b06f5dcb + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 + languageName: node + linkType: hard + +"jackspeak@npm:^4.0.1": + version: 4.0.1 + resolution: "jackspeak@npm:4.0.1" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/c87997d9c9c5b7366259b1f2a444ef148692f8eedad5307caca939babbb60af2b47d306e5c63bf9d5fefbab2ab48d4da275188c3de525d0e716cc21b784bbccb + languageName: node + linkType: hard + +"joplin-turndown-plugin-gfm@npm:^1.0.12": + version: 1.0.12 + resolution: "joplin-turndown-plugin-gfm@npm:1.0.12" + checksum: 10c0/76e18b0ad68fed49d1b564ae5d89b5ffd1771a36b4369e95c37381d5cbd44f085ade435bdd6e6fd9a21fe07f65902b4dfef742c6890c04652bbe50c1a8c9a418 + languageName: node + linkType: hard + +"joycon@npm:^3.1.1": + version: 3.1.1 + resolution: "joycon@npm:3.1.1" + checksum: 10c0/131fb1e98c9065d067fd49b6e685487ac4ad4d254191d7aa2c9e3b90f4e9ca70430c43cad001602bdbdabcf58717d3b5c5b7461c1bd8e39478c8de706b3fe6ae + languageName: node + linkType: hard + +"js-levenshtein@npm:^1.1.6": + version: 1.1.6 + resolution: "js-levenshtein@npm:1.1.6" + checksum: 10c0/14045735325ea1fd87f434a74b11d8a14380f090f154747e613529c7cff68b5ee607f5230fa40665d5fb6125a3791f4c223f73b9feca754f989b059f5c05864f + languageName: node + linkType: hard + +"js-tiktoken@npm:^1.0.12, js-tiktoken@npm:^1.0.7": + version: 1.0.14 + resolution: "js-tiktoken@npm:1.0.14" + dependencies: + base64-js: "npm:^1.5.1" + checksum: 10c0/63a88ebdca8f2d0c00581085251001d3c2784fa52388ce059097296e2cdc6b5335055bd3f8f8106969353dc4654fcc88257a16238f985425ae0d39da218230fa + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + languageName: node + linkType: hard + +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^3.0.0": + version: 3.0.2 + resolution: "json-parse-even-better-errors@npm:3.0.2" + checksum: 10c0/147f12b005768abe9fab78d2521ce2b7e1381a118413d634a40e6d907d7d10f5e9a05e47141e96d6853af7cc36d2c834d0a014251be48791e037ff2f13d2b94b + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + +"json-stringify-safe@npm:^5.0.1": + version: 5.0.1 + resolution: "json-stringify-safe@npm:5.0.1" + checksum: 10c0/7dbf35cd0411d1d648dceb6d59ce5857ec939e52e4afc37601aa3da611f0987d5cee5b38d58329ceddf3ed48bd7215229c8d52059ab01f2444a338bf24ed0f37 + languageName: node + linkType: hard + +"jsonfile@npm:^6.0.1": + version: 6.1.0 + resolution: "jsonfile@npm:6.1.0" + dependencies: + graceful-fs: "npm:^4.1.6" + universalify: "npm:^2.0.0" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10c0/4f95b5e8a5622b1e9e8f33c96b7ef3158122f595998114d1e7f03985649ea99cb3cd99ce1ed1831ae94c8c8543ab45ebd044207612f31a56fd08462140e46865 + languageName: node + linkType: hard + +"jsonparse@npm:^1.2.0": + version: 1.3.1 + resolution: "jsonparse@npm:1.3.1" + checksum: 10c0/89bc68080cd0a0e276d4b5ab1b79cacd68f562467008d176dc23e16e97d4efec9e21741d92ba5087a8433526a45a7e6a9d5ef25408696c402ca1cfbc01a90bf0 + languageName: node + linkType: hard + +"jsonpointer@npm:^5.0.1": + version: 5.0.1 + resolution: "jsonpointer@npm:5.0.1" + checksum: 10c0/89929e58b400fcb96928c0504fcf4fc3f919d81e9543ceb055df125538470ee25290bb4984251e172e6ef8fcc55761eb998c118da763a82051ad89d4cb073fe7 + languageName: node + linkType: hard + +"keyv@npm:^4.5.3, keyv@npm:^4.5.4": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + +"ky@npm:^1.2.0": + version: 1.7.0 + resolution: "ky@npm:1.7.0" + checksum: 10c0/13623345f02f9ccd683cf8680a0d766638fc3baf62b44893bfa8ef253ab0d5a759d1a76adc0ee04d4a16f96aaccca360aff5b079d0925c6c32cab59d9fa965ee + languageName: node + linkType: hard + +"langchain@npm:~0.2.16, langchain@npm:~0.2.3": + version: 0.2.16 + resolution: "langchain@npm:0.2.16" + dependencies: + "@langchain/core": "npm:>=0.2.21 <0.3.0" + "@langchain/openai": "npm:>=0.1.0 <0.3.0" + "@langchain/textsplitters": "npm:~0.0.0" + binary-extensions: "npm:^2.2.0" + js-tiktoken: "npm:^1.0.12" + js-yaml: "npm:^4.1.0" + jsonpointer: "npm:^5.0.1" + langsmith: "npm:~0.1.40" + openapi-types: "npm:^12.1.3" + p-retry: "npm:4" + uuid: "npm:^10.0.0" + yaml: "npm:^2.2.1" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.22.3" + peerDependencies: + "@aws-sdk/client-s3": "*" + "@aws-sdk/client-sagemaker-runtime": "*" + "@aws-sdk/client-sfn": "*" + "@aws-sdk/credential-provider-node": "*" + "@azure/storage-blob": "*" + "@browserbasehq/sdk": "*" + "@gomomento/sdk": "*" + "@gomomento/sdk-core": "*" + "@gomomento/sdk-web": ^1.51.1 + "@langchain/anthropic": "*" + "@langchain/aws": "*" + "@langchain/cohere": "*" + "@langchain/community": "*" + "@langchain/google-genai": "*" + "@langchain/google-vertexai": "*" + "@langchain/groq": "*" + "@langchain/mistralai": "*" + "@langchain/ollama": "*" + "@mendable/firecrawl-js": "*" + "@notionhq/client": "*" + "@pinecone-database/pinecone": "*" + "@supabase/supabase-js": "*" + "@vercel/kv": "*" + "@xata.io/client": "*" + apify-client: "*" + assemblyai: "*" + axios: "*" + cheerio: "*" + chromadb: "*" + convex: "*" + couchbase: "*" + d3-dsv: "*" + epub2: "*" + fast-xml-parser: "*" + handlebars: ^4.7.8 + html-to-text: "*" + ignore: "*" + ioredis: "*" + jsdom: "*" + mammoth: "*" + mongodb: "*" + node-llama-cpp: "*" + notion-to-md: "*" + officeparser: "*" + pdf-parse: "*" + peggy: ^3.0.2 + playwright: "*" + puppeteer: "*" + pyodide: ^0.24.1 + redis: "*" + sonix-speech-recognition: "*" + srt-parser-2: "*" + typeorm: "*" + weaviate-ts-client: "*" + web-auth-library: "*" + ws: "*" + youtube-transcript: "*" + youtubei.js: "*" + peerDependenciesMeta: + "@aws-sdk/client-s3": + optional: true + "@aws-sdk/client-sagemaker-runtime": + optional: true + "@aws-sdk/client-sfn": + optional: true + "@aws-sdk/credential-provider-node": + optional: true + "@azure/storage-blob": + optional: true + "@browserbasehq/sdk": + optional: true + "@gomomento/sdk": + optional: true + "@gomomento/sdk-core": + optional: true + "@gomomento/sdk-web": + optional: true + "@langchain/anthropic": + optional: true + "@langchain/aws": + optional: true + "@langchain/cohere": + optional: true + "@langchain/community": + optional: true + "@langchain/google-genai": + optional: true + "@langchain/google-vertexai": + optional: true + "@langchain/groq": + optional: true + "@langchain/mistralai": + optional: true + "@langchain/ollama": + optional: true + "@mendable/firecrawl-js": + optional: true + "@notionhq/client": + optional: true + "@pinecone-database/pinecone": + optional: true + "@supabase/supabase-js": + optional: true + "@vercel/kv": + optional: true + "@xata.io/client": + optional: true + apify-client: + optional: true + assemblyai: + optional: true + axios: + optional: true + cheerio: + optional: true + chromadb: + optional: true + convex: + optional: true + couchbase: + optional: true + d3-dsv: + optional: true + epub2: + optional: true + faiss-node: + optional: true + fast-xml-parser: + optional: true + handlebars: + optional: true + html-to-text: + optional: true + ignore: + optional: true + ioredis: + optional: true + jsdom: + optional: true + mammoth: + optional: true + mongodb: + optional: true + node-llama-cpp: + optional: true + notion-to-md: + optional: true + officeparser: + optional: true + pdf-parse: + optional: true + peggy: + optional: true + playwright: + optional: true + puppeteer: + optional: true + pyodide: + optional: true + redis: + optional: true + sonix-speech-recognition: + optional: true + srt-parser-2: + optional: true + typeorm: + optional: true + weaviate-ts-client: + optional: true + web-auth-library: + optional: true + ws: + optional: true + youtube-transcript: + optional: true + youtubei.js: + optional: true + checksum: 10c0/18964f1b23b5add0cb0da1b6ea561ad5f74da9d1f700d57ba24daa2d45dfa3b6dbc51b1f9217136bf1650062bf16f44da052bf424b2f67f372520568b4c650f2 + languageName: node + linkType: hard + +"langsmith@npm:~0.1.30, langsmith@npm:~0.1.39, langsmith@npm:~0.1.40": + version: 0.1.42 + resolution: "langsmith@npm:0.1.42" + dependencies: + "@types/uuid": "npm:^9.0.1" + commander: "npm:^10.0.1" + p-queue: "npm:^6.6.2" + p-retry: "npm:4" + semver: "npm:^7.6.3" + uuid: "npm:^9.0.0" + peerDependencies: + "@langchain/core": "*" + langchain: "*" + openai: "*" + peerDependenciesMeta: + "@langchain/core": + optional: true + langchain: + optional: true + openai: + optional: true + checksum: 10c0/cdec4fe4adf5dfb73e46f2ed2a0bd414bd198ce90887caeb494db628f5c54f8ecf5808f9a52263344e392185f4c48ee8812d3c46eb395b4cbadb3f2c38ef7e3b + languageName: node + linkType: hard + +"latest-version@npm:^9.0.0": + version: 9.0.0 + resolution: "latest-version@npm:9.0.0" + dependencies: + package-json: "npm:^10.0.0" + checksum: 10c0/643cfda3a58dfb3af221a2950e433393d28a5adbe225d1cbbb358dbcbb04e9f8dce15b892f8ae3e3156f50693428dbd7ca13a69edfbdfcd94e62519480d7041e + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"lex@npm:^1.7.9": + version: 1.7.9 + resolution: "lex@npm:1.7.9" + checksum: 10c0/87a5454af195ae130907be7af255d39f39cb6dc8df3e7619b50f4161b6f46d9ff0dfb65e1d74abb738a5523b42d1f3dc7361bf8f219a3db438a703ce27e76c2d + languageName: node + linkType: hard + +"lilconfig@npm:^3.1.1, lilconfig@npm:~3.1.2": + version: 3.1.2 + resolution: "lilconfig@npm:3.1.2" + checksum: 10c0/f059630b1a9bddaeba83059db00c672b64dc14074e9f232adce32b38ca1b5686ab737eb665c5ba3c32f147f0002b4bee7311ad0386a9b98547b5623e87071fbe + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"lines-and-columns@npm:^2.0.3": + version: 2.0.4 + resolution: "lines-and-columns@npm:2.0.4" + checksum: 10c0/4db28bf065cd7ad897c0700f22d3d0d7c5ed6777e138861c601c496d545340df3fc19e18bd04ff8d95a246a245eb55685b82ca2f8c2ca53a008e9c5316250379 + languageName: node + linkType: hard + +"lint-staged@npm:^15.2.9": + version: 15.2.9 + resolution: "lint-staged@npm:15.2.9" + dependencies: + chalk: "npm:~5.3.0" + commander: "npm:~12.1.0" + debug: "npm:~4.3.6" + execa: "npm:~8.0.1" + lilconfig: "npm:~3.1.2" + listr2: "npm:~8.2.4" + micromatch: "npm:~4.0.7" + pidtree: "npm:~0.6.0" + string-argv: "npm:~0.3.2" + yaml: "npm:~2.5.0" + bin: + lint-staged: bin/lint-staged.js + checksum: 10c0/820c622378b62b826974af17f1747e2a4b0556e4fb99d101af89ad298d392ff079f580fdc576f16a27e975d726b95d73495fd524139402ff654c4649ef2f1a6a + languageName: node + linkType: hard + +"listr2@npm:~8.2.4": + version: 8.2.4 + resolution: "listr2@npm:8.2.4" + dependencies: + cli-truncate: "npm:^4.0.0" + colorette: "npm:^2.0.20" + eventemitter3: "npm:^5.0.1" + log-update: "npm:^6.1.0" + rfdc: "npm:^1.4.1" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/df5b129e9767de1997973cec6103cd4bd6fc3b3367685b7c23048d12b61d5b7e44fecd8a3d3534c0e1c963bd5ac43ca501d14712f46fa101050037be323a5c16 + languageName: node + linkType: hard + +"load-tsconfig@npm:^0.2.3": + version: 0.2.5 + resolution: "load-tsconfig@npm:0.2.5" + checksum: 10c0/bf2823dd26389d3497b6567f07435c5a7a58d9df82e879b0b3892f87d8db26900f84c85bc329ef41c0540c0d6a448d1c23ddc64a80f3ff6838b940f3915a3fcb + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"locate-path@npm:^7.1.0": + version: 7.2.0 + resolution: "locate-path@npm:7.2.0" + dependencies: + p-locate: "npm:^6.0.0" + checksum: 10c0/139e8a7fe11cfbd7f20db03923cacfa5db9e14fa14887ea121345597472b4a63c1a42a8a5187defeeff6acf98fd568da7382aa39682d38f0af27433953a97751 + languageName: node + linkType: hard + +"lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: 10c0/fb407355f7e6cd523a9383e76e6b455321f0f153a6c9625e21a8827d10c54c2a2341bd2ae8d034358b60e07325e1330c14c224ff582d04612a46a4f0479ff2f2 + languageName: node + linkType: hard + +"lodash.capitalize@npm:^4.2.1": + version: 4.2.1 + resolution: "lodash.capitalize@npm:4.2.1" + checksum: 10c0/b289326497c2e24d6b8afa2af2ca4e068ef6ef007ade36bfb6f70af77ce10ea3f090eeee947d5fdcf2db4bcfa4703c8c10a5857a2b39e308bddfd1d11ad35970 + languageName: node + linkType: hard + +"lodash.escaperegexp@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.escaperegexp@npm:4.1.2" + checksum: 10c0/484ad4067fa9119bb0f7c19a36ab143d0173a081314993fe977bd00cf2a3c6a487ce417a10f6bac598d968364f992153315f0dbe25c9e38e3eb7581dd333e087 + languageName: node + linkType: hard + +"lodash.isequal@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.isequal@npm:4.5.0" + checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f + languageName: node + linkType: hard + +"lodash.isplainobject@npm:^4.0.6": + version: 4.0.6 + resolution: "lodash.isplainobject@npm:4.0.6" + checksum: 10c0/afd70b5c450d1e09f32a737bed06ff85b873ecd3d3d3400458725283e3f2e0bb6bf48e67dbe7a309eb371a822b16a26cca4a63c8c52db3fc7dc9d5f9dd324cbb + languageName: node + linkType: hard + +"lodash.isstring@npm:^4.0.1": + version: 4.0.1 + resolution: "lodash.isstring@npm:4.0.1" + checksum: 10c0/09eaf980a283f9eef58ef95b30ec7fee61df4d6bf4aba3b5f096869cc58f24c9da17900febc8ffd67819b4e29de29793190e88dc96983db92d84c95fa85d1c92 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 + languageName: node + linkType: hard + +"lodash.sortby@npm:^4.7.0": + version: 4.7.0 + resolution: "lodash.sortby@npm:4.7.0" + checksum: 10c0/fc48fb54ff7669f33bb32997cab9460757ee99fafaf72400b261c3e10fde21538e47d8cfcbe6a25a31bcb5b7b727c27d52626386fc2de24eb059a6d64a89cdf5 + languageName: node + linkType: hard + +"lodash.uniqby@npm:^4.7.0": + version: 4.7.0 + resolution: "lodash.uniqby@npm:4.7.0" + checksum: 10c0/c505c0de20ca759599a2ba38710e8fb95ff2d2028e24d86c901ef2c74be8056518571b9b754bfb75053b2818d30dd02243e4a4621a6940c206bbb3f7626db656 + languageName: node + linkType: hard + +"lodash@npm:4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c + languageName: node + linkType: hard + +"log-symbols@npm:^4.1.0": + version: 4.1.0 + resolution: "log-symbols@npm:4.1.0" + dependencies: + chalk: "npm:^4.1.0" + is-unicode-supported: "npm:^0.1.0" + checksum: 10c0/67f445a9ffa76db1989d0fa98586e5bc2fd5247260dafb8ad93d9f0ccd5896d53fb830b0e54dade5ad838b9de2006c826831a3c528913093af20dff8bd24aca6 + languageName: node + linkType: hard + +"log-symbols@npm:^6.0.0": + version: 6.0.0 + resolution: "log-symbols@npm:6.0.0" + dependencies: + chalk: "npm:^5.3.0" + is-unicode-supported: "npm:^1.3.0" + checksum: 10c0/36636cacedba8f067d2deb4aad44e91a89d9efb3ead27e1846e7b82c9a10ea2e3a7bd6ce28a7ca616bebc60954ff25c67b0f92d20a6a746bb3cc52c3701891f6 + languageName: node + linkType: hard + +"log-update@npm:^6.1.0": + version: 6.1.0 + resolution: "log-update@npm:6.1.0" + dependencies: + ansi-escapes: "npm:^7.0.0" + cli-cursor: "npm:^5.0.0" + slice-ansi: "npm:^7.1.0" + strip-ansi: "npm:^7.1.0" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/4b350c0a83d7753fea34dcac6cd797d1dc9603291565de009baa4aa91c0447eab0d3815a05c8ec9ac04fdfffb43c82adcdb03ec1fceafd8518e1a8c1cff4ff89 + languageName: node + linkType: hard + +"loupe@npm:^3.1.0, loupe@npm:^3.1.1": + version: 3.1.1 + resolution: "loupe@npm:3.1.1" + dependencies: + get-func-name: "npm:^2.0.1" + checksum: 10c0/99f88badc47e894016df0c403de846fedfea61154aadabbf776c8428dd59e8d8378007135d385d737de32ae47980af07d22ba7bec5ef7beebd721de9baa0a0af + languageName: node + linkType: hard + +"lowercase-keys@npm:^3.0.0": + version: 3.0.0 + resolution: "lowercase-keys@npm:3.0.0" + checksum: 10c0/ef62b9fa5690ab0a6e4ef40c94efce68e3ed124f583cc3be38b26ff871da0178a28b9a84ce0c209653bb25ca135520ab87fea7cd411a54ac4899cb2f30501430 + languageName: node + linkType: hard + +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0": + version: 11.0.0 + resolution: "lru-cache@npm:11.0.0" + checksum: 10c0/827ff0e0739f9b0f30f92f5a5fc97c6a2bd3ae32c0452bc58cb7411d6c589d49536073027293f2d1f02d0c2e72b63b162f238df7e9ff6f4cc0345f92afec4d1d + languageName: node + linkType: hard + +"lru-cache@npm:^7.14.1": + version: 7.18.3 + resolution: "lru-cache@npm:7.18.3" + checksum: 10c0/b3a452b491433db885beed95041eb104c157ef7794b9c9b4d647be503be91769d11206bb573849a16b4cc0d03cbd15ffd22df7960997788b74c1d399ac7a4fed + languageName: node + linkType: hard + +"macos-release@npm:^3.1.0": + version: 3.3.0 + resolution: "macos-release@npm:3.3.0" + checksum: 10c0/e95a483ba8751280b8c3a8f466c8f57769e85b22a29ed7159bddee5ef7eaf00569f7940d66eeac253c49239b083af2e4c839ba4bfc73df332874f763e6f166cf + languageName: node + linkType: hard + +"magic-string@npm:^0.30.10, magic-string@npm:^0.30.3": + version: 0.30.11 + resolution: "magic-string@npm:0.30.11" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + checksum: 10c0/b9eb370773d0bd90ca11a848753409d8e5309b1ad56d2a1aa49d6649da710a6d2fe7237ad1a643c5a5d3800de2b9946ed9690acdfc00e6cc1aeafff3ab1752c4 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^13.0.0": + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" + dependencies: + "@npmcli/agent": "npm:^2.0.0" + cacache: "npm:^18.0.0" + http-cache-semantics: "npm:^4.1.1" + is-lambda: "npm:^1.0.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^3.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^0.6.3" + proc-log: "npm:^4.2.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^10.0.0" + checksum: 10c0/df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e + languageName: node + linkType: hard + +"meow@npm:^12.0.1": + version: 12.1.1 + resolution: "meow@npm:12.1.1" + checksum: 10c0/a125ca99a32e2306e2f4cbe651a0d27f6eb67918d43a075f6e80b35e9bf372ebf0fc3a9fbc201cbbc9516444b6265fb3c9f80c5b7ebd32f548aa93eb7c28e088 + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"micromatch@npm:^4.0.4, micromatch@npm:~4.0.7": + version: 4.0.7 + resolution: "micromatch@npm:4.0.7" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10c0/58fa99bc5265edec206e9163a1d2cec5fabc46a5b473c45f4a700adce88c2520456ae35f2b301e4410fb3afb27e9521fb2813f6fc96be0a48a89430e0916a772 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:2.1.35, mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"mimic-fn@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-fn@npm:4.0.0" + checksum: 10c0/de9cc32be9996fd941e512248338e43407f63f6d497abe8441fa33447d922e927de54d4cc3c1a3c6d652857acd770389d5a3823f311a744132760ce2be15ccbf + languageName: node + linkType: hard + +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: 10c0/f3d9464dd1816ecf6bdf2aec6ba32c0728022039d992f178237d8e289b48764fee4131319e72eedd4f7f094e22ded0af836c3187a7edc4595d28dd74368fd81d + languageName: node + linkType: hard + +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: 10c0/0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362 + languageName: node + linkType: hard + +"mimic-response@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-response@npm:4.0.0" + checksum: 10c0/761d788d2668ae9292c489605ffd4fad220f442fbae6832adce5ebad086d691e906a6d5240c290293c7a11e99fbdbbef04abbbed498bf8699a4ee0f31315e3fb + languageName: node + linkType: hard + +"minimatch@npm:^10.0.0": + version: 10.0.1 + resolution: "minimatch@npm:10.0.1" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/e6c29a81fe83e1877ad51348306be2e8aeca18c88fdee7a99df44322314279e15799e41d7cb274e4e8bb0b451a3bc622d6182e157dfa1717d6cda75e9cd8cd5d + languageName: node + linkType: hard + +"minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minimatch@npm:^5.0.1": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^3.0.0": + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^2.1.2" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" + dependencies: + minipass: "npm:^3.0.0" + yallist: "npm:^4.0.0" + checksum: 10c0/64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78 + languageName: node + linkType: hard + +"mkdirp@npm:^1.0.3": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf + languageName: node + linkType: hard + +"ms@npm:2.1.2": + version: 2.1.2 + resolution: "ms@npm:2.1.2" + checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc + languageName: node + linkType: hard + +"ms@npm:^2.0.0": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"mustache@npm:^4.2.0": + version: 4.2.0 + resolution: "mustache@npm:4.2.0" + bin: + mustache: bin/mustache + checksum: 10c0/1f8197e8a19e63645a786581d58c41df7853da26702dbc005193e2437c98ca49b255345c173d50c08fe4b4dbb363e53cb655ecc570791f8deb09887248dd34a2 + languageName: node + linkType: hard + +"mute-stream@npm:1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 10c0/dce2a9ccda171ec979a3b4f869a102b1343dee35e920146776780de182f16eae459644d187e38d59a3d37adf85685e1c17c38cf7bfda7e39a9880f7a1d10a74c + languageName: node + linkType: hard + +"mz@npm:^2.7.0": + version: 2.7.0 + resolution: "mz@npm:2.7.0" + dependencies: + any-promise: "npm:^1.0.0" + object-assign: "npm:^4.0.1" + thenify-all: "npm:^1.0.0" + checksum: 10c0/103114e93f87362f0b56ab5b2e7245051ad0276b646e3902c98397d18bb8f4a77f2ea4a2c9d3ad516034ea3a56553b60d3f5f78220001ca4c404bd711bd0af39 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.7": + version: 3.3.7 + resolution: "nanoid@npm:3.3.7" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/e3fb661aa083454f40500473bb69eedb85dc160e763150b9a2c567c7e9ff560ce028a9f833123b618a6ea742e311138b591910e795614a629029e86e180660f3 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"needle@npm:^3.2.0": + version: 3.3.1 + resolution: "needle@npm:3.3.1" + dependencies: + iconv-lite: "npm:^0.6.3" + sax: "npm:^1.2.4" + bin: + needle: bin/needle + checksum: 10c0/233b9315d47b735867d03e7a018fb665ee6cacf3a83b991b19538019cf42b538a3e85ca745c840b4c5e9a0ffdca76472f941363bf7c166214ae8cbc650fd4d39 + languageName: node + linkType: hard + +"negotiator@npm:^0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 + languageName: node + linkType: hard + +"neo-async@npm:^2.6.2": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: 10c0/c2f5a604a54a8ec5438a342e1f356dff4bc33ccccdb6dc668d94fe8e5eccfc9d2c2eea6064b0967a767ba63b33763f51ccf2cd2441b461a7322656c1f06b3f5d + languageName: node + linkType: hard + +"netmask@npm:^2.0.2": + version: 2.0.2 + resolution: "netmask@npm:2.0.2" + checksum: 10c0/cafd28388e698e1138ace947929f842944d0f1c0b87d3fa2601a61b38dc89397d33c0ce2c8e7b99e968584b91d15f6810b91bef3f3826adf71b1833b61d4bf4f + languageName: node + linkType: hard + +"new-github-release-url@npm:2.0.0": + version: 2.0.0 + resolution: "new-github-release-url@npm:2.0.0" + dependencies: + type-fest: "npm:^2.5.1" + checksum: 10c0/9faec009b8b403efbc407f45306d07de5cc58e09df5b00bdd55b01384cd18b0fd29a97aef6915428ba3b5abb0a5c132c3507468c0c3c101e8d737c1337386786 + languageName: node + linkType: hard + +"node-domexception@npm:1.0.0, node-domexception@npm:^1.0.0": + version: 1.0.0 + resolution: "node-domexception@npm:1.0.0" + checksum: 10c0/5e5d63cda29856402df9472335af4bb13875e1927ad3be861dc5ebde38917aecbf9ae337923777af52a48c426b70148815e890a5d72760f1b4d758cc671b1a2b + languageName: node + linkType: hard + +"node-fetch@npm:3.3.2": + version: 3.3.2 + resolution: "node-fetch@npm:3.3.2" + dependencies: + data-uri-to-buffer: "npm:^4.0.0" + fetch-blob: "npm:^3.1.4" + formdata-polyfill: "npm:^4.0.10" + checksum: 10c0/f3d5e56190562221398c9f5750198b34cf6113aa304e34ee97c94fd300ec578b25b2c2906edba922050fce983338fde0d5d34fcb0fc3336ade5bd0e429ad7538 + languageName: node + linkType: hard + +"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.7": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 10.2.0 + resolution: "node-gyp@npm:10.2.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^13.0.0" + nopt: "npm:^7.0.0" + proc-log: "npm:^4.1.0" + semver: "npm:^7.3.5" + tar: "npm:^6.2.1" + which: "npm:^4.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/00630d67dbd09a45aee0a5d55c05e3916ca9e6d427ee4f7bc392d2d3dc5fad7449b21fc098dd38260a53d9dcc9c879b36704a1994235d4707e7271af7e9a835b + languageName: node + linkType: hard + +"node-releases@npm:^2.0.18": + version: 2.0.18 + resolution: "node-releases@npm:2.0.18" + checksum: 10c0/786ac9db9d7226339e1dc84bbb42007cb054a346bd9257e6aa154d294f01bc6a6cddb1348fa099f079be6580acbb470e3c048effd5f719325abd0179e566fd27 + languageName: node + linkType: hard + +"nopt@npm:^7.0.0": + version: 7.2.1 + resolution: "nopt@npm:7.2.1" + dependencies: + abbrev: "npm:^2.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81 + languageName: node + linkType: hard + +"normalize-package-data@npm:^6.0.0": + version: 6.0.2 + resolution: "normalize-package-data@npm:6.0.2" + dependencies: + hosted-git-info: "npm:^7.0.0" + semver: "npm:^7.3.5" + validate-npm-package-license: "npm:^3.0.4" + checksum: 10c0/7e32174e7f5575ede6d3d449593247183880122b4967d4ae6edb28cea5769ca025defda54fc91ec0e3c972fdb5ab11f9284606ba278826171b264cb16a9311ef + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"normalize-url@npm:^8.0.0": + version: 8.0.1 + resolution: "normalize-url@npm:8.0.1" + checksum: 10c0/eb439231c4b84430f187530e6fdac605c5048ef4ec556447a10c00a91fc69b52d8d8298d9d608e68d3e0f7dc2d812d3455edf425e0f215993667c3183bcab1ef + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac + languageName: node + linkType: hard + +"npm-run-path@npm:^5.1.0": + version: 5.3.0 + resolution: "npm-run-path@npm:5.3.0" + dependencies: + path-key: "npm:^4.0.0" + checksum: 10c0/124df74820c40c2eb9a8612a254ea1d557ddfab1581c3e751f825e3e366d9f00b0d76a3c94ecd8398e7f3eee193018622677e95816e8491f0797b21e30b2deba + languageName: node + linkType: hard + +"object-assign@npm:^4.0.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-hash@npm:^3.0.0": + version: 3.0.0 + resolution: "object-hash@npm:3.0.0" + checksum: 10c0/a06844537107b960c1c8b96cd2ac8592a265186bfa0f6ccafe0d34eabdb526f6fa81da1f37c43df7ed13b12a4ae3457a16071603bcd39d8beddb5f08c37b0f47 + languageName: node + linkType: hard + +"ollama@npm:^0.5.8": + version: 0.5.8 + resolution: "ollama@npm:0.5.8" + dependencies: + whatwg-fetch: "npm:^3.6.20" + checksum: 10c0/3c794350501ee1436161f4479bddcef5919557ccaea03e8ea03724aa5cd7476343ecf585993e14430e2c459cecc1821ab738cfaff3c086c05320ddeaffa9416c + languageName: node + linkType: hard + +"on-exit-leak-free@npm:^2.1.0": + version: 2.1.2 + resolution: "on-exit-leak-free@npm:2.1.2" + checksum: 10c0/faea2e1c9d696ecee919026c32be8d6a633a7ac1240b3b87e944a380e8a11dc9c95c4a1f8fb0568de7ab8db3823e790f12bda45296b1d111e341aad3922a0570 + languageName: node + linkType: hard + +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"onetime@npm:^5.1.0, onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"onetime@npm:^6.0.0": + version: 6.0.0 + resolution: "onetime@npm:6.0.0" + dependencies: + mimic-fn: "npm:^4.0.0" + checksum: 10c0/4eef7c6abfef697dd4479345a4100c382d73c149d2d56170a54a07418c50816937ad09500e1ed1e79d235989d073a9bade8557122aee24f0576ecde0f392bb6c + languageName: node + linkType: hard + +"onetime@npm:^7.0.0": + version: 7.0.0 + resolution: "onetime@npm:7.0.0" + dependencies: + mimic-function: "npm:^5.0.0" + checksum: 10c0/5cb9179d74b63f52a196a2e7037ba2b9a893245a5532d3f44360012005c9cadb60851d56716ebff18a6f47129dab7168022445df47c2aff3b276d92585ed1221 + languageName: node + linkType: hard + +"open@npm:10.1.0": + version: 10.1.0 + resolution: "open@npm:10.1.0" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + is-wsl: "npm:^3.1.0" + checksum: 10c0/c86d0b94503d5f735f674158d5c5d339c25ec2927562f00ee74590727292ed23e1b8d9336cb41ffa7e1fa4d3641d29b199b4ea37c78cb557d72b511743e90ebb + languageName: node + linkType: hard + +"openai-chat-tokens@npm:^0.2.8": + version: 0.2.8 + resolution: "openai-chat-tokens@npm:0.2.8" + dependencies: + js-tiktoken: "npm:^1.0.7" + checksum: 10c0/b415fda706b408f29b4584998990f29ad7f80f2ac1e84179a0976742ba8a80859fedeae5745a9bfe73443d95960b77328610074952ad198a18bc0e5c0ceb5b7b + languageName: node + linkType: hard + +"openai@npm:^4.55.0, openai@npm:^4.56.0": + version: 4.56.0 + resolution: "openai@npm:4.56.0" + dependencies: + "@types/node": "npm:^18.11.18" + "@types/node-fetch": "npm:^2.6.4" + abort-controller: "npm:^3.0.0" + agentkeepalive: "npm:^4.2.1" + form-data-encoder: "npm:1.7.2" + formdata-node: "npm:^4.3.2" + node-fetch: "npm:^2.6.7" + peerDependencies: + zod: ^3.23.8 + peerDependenciesMeta: + zod: + optional: true + bin: + openai: bin/cli + checksum: 10c0/d1a5ddda89ab95ad319c8d5af5a0e998916971cc453d22ace58475ffe34a624b37ff2e5fc614d489252c0de2f475ef11197b581836f6ac78c95c12089e6fa316 + languageName: node + linkType: hard + +"openapi-fetch@npm:^0.11.1": + version: 0.11.1 + resolution: "openapi-fetch@npm:0.11.1" + dependencies: + openapi-typescript-helpers: "npm:^0.0.12" + checksum: 10c0/eeafe8f7f8346db9ddcc4addaa86ee81e9c289cb0a3f6e78d9caf259f71057a03cedd453efbfe23472e470fd46f5a90bb73e8e1658f023269eaaa4ba2be0f545 + languageName: node + linkType: hard + +"openapi-fetch@npm:^0.8.2": + version: 0.8.2 + resolution: "openapi-fetch@npm:0.8.2" + dependencies: + openapi-typescript-helpers: "npm:^0.0.5" + checksum: 10c0/b8c43cf559f06a29a6a7d0da4a5a0130b5b42c6ce3ce75afe2db81104ae686374bd29e9d115fb119ca905d2194fe686c4aab8145a60f50cab6984b48f39b16a3 + languageName: node + linkType: hard + +"openapi-types@npm:^12.1.3": + version: 12.1.3 + resolution: "openapi-types@npm:12.1.3" + checksum: 10c0/4ad4eb91ea834c237edfa6ab31394e87e00c888fc2918009763389c00d02342345195d6f302d61c3fd807f17723cd48df29b47b538b68375b3827b3758cd520f + languageName: node + linkType: hard + +"openapi-typescript-helpers@npm:^0.0.12": + version: 0.0.12 + resolution: "openapi-typescript-helpers@npm:0.0.12" + checksum: 10c0/4fe8851ad49a3aa52be0edf086c989559a8a8023c36a4a3c939d07f18faa0903ea47ecad4eefa1475e863332e7873821d6c0ed6a90f7dca0004a7340b3868afc + languageName: node + linkType: hard + +"openapi-typescript-helpers@npm:^0.0.5": + version: 0.0.5 + resolution: "openapi-typescript-helpers@npm:0.0.5" + checksum: 10c0/0394192a00d014acf5fd174bc3646c0a5abe8861ed3e76ecec26f2f8a6b17b09e16051b09d454239cb481ef53590f3778d8a3f3582a128d7b7f681555a76971a + languageName: node + linkType: hard + +"openapi-typescript@npm:^7.3.0": + version: 7.3.0 + resolution: "openapi-typescript@npm:7.3.0" + dependencies: + "@redocly/openapi-core": "npm:^1.16.0" + ansi-colors: "npm:^4.1.3" + parse-json: "npm:^8.1.0" + supports-color: "npm:^9.4.0" + yargs-parser: "npm:^21.1.1" + peerDependencies: + typescript: ^5.x + bin: + openapi-typescript: bin/cli.js + checksum: 10c0/432d7a549aa4a224ae51e6236e01bd2ac50934d2ed3a283c1c7a3342b86d64f29cf5d5576881704f095be1d2d6cb718ec918747f4e941ef7bfb101520f85f4fb + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 + languageName: node + linkType: hard + +"ora@npm:8.0.1": + version: 8.0.1 + resolution: "ora@npm:8.0.1" + dependencies: + chalk: "npm:^5.3.0" + cli-cursor: "npm:^4.0.0" + cli-spinners: "npm:^2.9.2" + is-interactive: "npm:^2.0.0" + is-unicode-supported: "npm:^2.0.0" + log-symbols: "npm:^6.0.0" + stdin-discarder: "npm:^0.2.1" + string-width: "npm:^7.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/7a94c075a7f182a6ace80c3505b945520ab16e05ebe536a714a3d61e51dd8f777c75c8be920e157e0c60ada6fe89bca37376897fb4d486bea5771229be992097 + languageName: node + linkType: hard + +"ora@npm:^5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" + dependencies: + bl: "npm:^4.1.0" + chalk: "npm:^4.1.0" + cli-cursor: "npm:^3.1.0" + cli-spinners: "npm:^2.5.0" + is-interactive: "npm:^1.0.0" + is-unicode-supported: "npm:^0.1.0" + log-symbols: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + wcwidth: "npm:^1.0.1" + checksum: 10c0/10ff14aace236d0e2f044193362b22edce4784add08b779eccc8f8ef97195cae1248db8ec1ec5f5ff076f91acbe573f5f42a98c19b78dba8c54eefff983cae85 + languageName: node + linkType: hard + +"os-name@npm:5.1.0": + version: 5.1.0 + resolution: "os-name@npm:5.1.0" + dependencies: + macos-release: "npm:^3.1.0" + windows-release: "npm:^5.0.1" + checksum: 10c0/6a0b8b767783fe55e41ddd6347147389b08ab9ad4a64355189844cefa3081a5d1fb77504eaac931b883e7fd73baf6013e0cc3fc86bb5d2190683073669db5572 + languageName: node + linkType: hard + +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 10c0/f438450224f8e2687605a8dd318f0db694b6293c5d835ae509a69e97c8de38b6994645337e5577f5001115470414638978cc49da1cdcc25106dad8738dc69990 + languageName: node + linkType: hard + +"ow@npm:^0.28.1": + version: 0.28.2 + resolution: "ow@npm:0.28.2" + dependencies: + "@sindresorhus/is": "npm:^4.2.0" + callsites: "npm:^3.1.0" + dot-prop: "npm:^6.0.1" + lodash.isequal: "npm:^4.5.0" + vali-date: "npm:^1.0.0" + checksum: 10c0/8d0de10fd3aa1ab69dd844ace087718c31ceb1a25cf79d38a5be4d0a5da46f960b6bc15a95405747899b882fb51dcf5a502d7e6508005d1c57e157d12fa17cdd + languageName: node + linkType: hard + +"p-cancelable@npm:^3.0.0": + version: 3.0.0 + resolution: "p-cancelable@npm:3.0.0" + checksum: 10c0/948fd4f8e87b956d9afc2c6c7392de9113dac817cb1cecf4143f7a3d4c57ab5673614a80be3aba91ceec5e4b69fd8c869852d7e8048bc3d9273c4c36ce14b9aa + languageName: node + linkType: hard + +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 10c0/6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-limit@npm:^4.0.0": + version: 4.0.0 + resolution: "p-limit@npm:4.0.0" + dependencies: + yocto-queue: "npm:^1.0.0" + checksum: 10c0/a56af34a77f8df2ff61ddfb29431044557fcbcb7642d5a3233143ebba805fc7306ac1d448de724352861cb99de934bc9ab74f0d16fe6a5460bdbdf938de875ad + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"p-locate@npm:^6.0.0": + version: 6.0.0 + resolution: "p-locate@npm:6.0.0" + dependencies: + p-limit: "npm:^4.0.0" + checksum: 10c0/d72fa2f41adce59c198270aa4d3c832536c87a1806e0f69dffb7c1a7ca998fb053915ca833d90f166a8c082d3859eabfed95f01698a3214c20df6bb8de046312 + languageName: node + linkType: hard + +"p-map@npm:^4.0.0": + version: 4.0.0 + resolution: "p-map@npm:4.0.0" + dependencies: + aggregate-error: "npm:^3.0.0" + checksum: 10c0/592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75 + languageName: node + linkType: hard + +"p-queue-compat@npm:^1.0.225": + version: 1.0.225 + resolution: "p-queue-compat@npm:1.0.225" + dependencies: + eventemitter3: "npm:5.x" + p-timeout-compat: "npm:^1.0.3" + checksum: 10c0/47d8ae58e6f25747ed80c8c97388cbab7c6db321f6d85e5a93a4268cc6c940251e3a626cd56e30e4b9846313cc387434dd958dbfd99ccb401951606fa077ee6d + languageName: node + linkType: hard + +"p-queue@npm:^6.6.2": + version: 6.6.2 + resolution: "p-queue@npm:6.6.2" + dependencies: + eventemitter3: "npm:^4.0.4" + p-timeout: "npm:^3.2.0" + checksum: 10c0/5739ecf5806bbeadf8e463793d5e3004d08bb3f6177bd1a44a005da8fd81bb90f80e4633e1fb6f1dfd35ee663a5c0229abe26aebb36f547ad5a858347c7b0d3e + languageName: node + linkType: hard + +"p-queue@npm:^8.0.1": + version: 8.0.1 + resolution: "p-queue@npm:8.0.1" + dependencies: + eventemitter3: "npm:^5.0.1" + p-timeout: "npm:^6.1.2" + checksum: 10c0/fe185bc8bbd32d17a5f6dba090077b1bb326b008b4ec9b0646c57a32a6984035aa8ece909a6d0de7f6c4640296dc288197f430e7394cdc76a26d862339494616 + languageName: node + linkType: hard + +"p-retry@npm:4": + version: 4.6.2 + resolution: "p-retry@npm:4.6.2" + dependencies: + "@types/retry": "npm:0.12.0" + retry: "npm:^0.13.1" + checksum: 10c0/d58512f120f1590cfedb4c2e0c42cb3fa66f3cea8a4646632fcb834c56055bb7a6f138aa57b20cc236fb207c9d694e362e0b5c2b14d9b062f67e8925580c73b0 + languageName: node + linkType: hard + +"p-throttle@npm:^6.2.0": + version: 6.2.0 + resolution: "p-throttle@npm:6.2.0" + checksum: 10c0/3be65f66eb21137be78b8d18a5240117312b942e3aa788f838ac4be785ab3c40b64ee34b2c393cd948ec7845c0a00241f446395b98ff4754e718fe54fdee0b00 + languageName: node + linkType: hard + +"p-timeout-compat@npm:^1.0.3": + version: 1.0.4 + resolution: "p-timeout-compat@npm:1.0.4" + checksum: 10c0/d047b3ae6e7d76c5d5db7a4609045afae7c82a8dd004b872ae7c944be0546ab12ee864daa9a9b9dc8df17852227dc22cb9daf7ccc6b6f7c3ba613957e8e00d45 + languageName: node + linkType: hard + +"p-timeout@npm:^3.2.0": + version: 3.2.0 + resolution: "p-timeout@npm:3.2.0" + dependencies: + p-finally: "npm:^1.0.0" + checksum: 10c0/524b393711a6ba8e1d48137c5924749f29c93d70b671e6db761afa784726572ca06149c715632da8f70c090073afb2af1c05730303f915604fd38ee207b70a61 + languageName: node + linkType: hard + +"p-timeout@npm:^6.1.2": + version: 6.1.2 + resolution: "p-timeout@npm:6.1.2" + checksum: 10c0/d46b90a9a5fb7c650a5c56dd5cf7102ea9ab6ce998defa2b3d4672789aaec4e2f45b3b0b5a4a3e17a0fb94301ad5dd26da7d8728402e48db2022ad1847594d19 + languageName: node + linkType: hard + +"pac-proxy-agent@npm:^7.0.1": + version: 7.0.2 + resolution: "pac-proxy-agent@npm:7.0.2" + dependencies: + "@tootallnate/quickjs-emscripten": "npm:^0.23.0" + agent-base: "npm:^7.0.2" + debug: "npm:^4.3.4" + get-uri: "npm:^6.0.1" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.5" + pac-resolver: "npm:^7.0.1" + socks-proxy-agent: "npm:^8.0.4" + checksum: 10c0/1ef0812bb860d2c695aa3a8604acdb4239b8074183c9fdb9bdf3747b8b28bbb88f22269d3ca95cae825c8ed0ca82681e6692c0e304c961fe004231e579d1ca91 + languageName: node + linkType: hard + +"pac-resolver@npm:^7.0.1": + version: 7.0.1 + resolution: "pac-resolver@npm:7.0.1" + dependencies: + degenerator: "npm:^5.0.0" + netmask: "npm:^2.0.2" + checksum: 10c0/5f3edd1dd10fded31e7d1f95776442c3ee51aa098c28b74ede4927d9677ebe7cebb2636750c24e945f5b84445e41ae39093d3a1014a994e5ceb9f0b1b88ebff5 + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0": + version: 1.0.0 + resolution: "package-json-from-dist@npm:1.0.0" + checksum: 10c0/e3ffaf6ac1040ab6082a658230c041ad14e72fabe99076a2081bb1d5d41210f11872403fc09082daf4387fc0baa6577f96c9c0e94c90c394fd57794b66aa4033 + languageName: node + linkType: hard + +"package-json@npm:^10.0.0": + version: 10.0.1 + resolution: "package-json@npm:10.0.1" + dependencies: + ky: "npm:^1.2.0" + registry-auth-token: "npm:^5.0.2" + registry-url: "npm:^6.0.1" + semver: "npm:^7.6.0" + checksum: 10c0/4a55648d820496326730a7b149fd3fd8382e96f3d6def5ec687f46b75063894acf06b21f79832b40bb094c821d97f532cb0f009f85c4102d0084b488d4f492d3 + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-json@npm:^5.2.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"parse-json@npm:^7.0.0": + version: 7.1.1 + resolution: "parse-json@npm:7.1.1" + dependencies: + "@babel/code-frame": "npm:^7.21.4" + error-ex: "npm:^1.3.2" + json-parse-even-better-errors: "npm:^3.0.0" + lines-and-columns: "npm:^2.0.3" + type-fest: "npm:^3.8.0" + checksum: 10c0/a85ebc7430af7763fa52eb456d7efd35c35be5b06f04d8d80c37d0d33312ac6cdff12647acb9c95448dcc8b907dfafa81fb126e094aa132b0abc2a71b9df51d5 + languageName: node + linkType: hard + +"parse-json@npm:^8.1.0": + version: 8.1.0 + resolution: "parse-json@npm:8.1.0" + dependencies: + "@babel/code-frame": "npm:^7.22.13" + index-to-position: "npm:^0.1.2" + type-fest: "npm:^4.7.1" + checksum: 10c0/39a49acafc1c41a763df2599a826eb77873a44b098a5f2ba548843229b334a16ff9d613d0381328e58031b0afaabc18ed2a01337a6522911ac7a81828df58bcb + languageName: node + linkType: hard + +"parse-path@npm:^7.0.0": + version: 7.0.0 + resolution: "parse-path@npm:7.0.0" + dependencies: + protocols: "npm:^2.0.0" + checksum: 10c0/e7646f6b998b083bbd40102643d803557ce4ae18ae1704e6cc7ae2525ea7c5400f4a3635aca3244cfe65ce4dd0ff77db1142dde4d080e8a80c364c4b3e8fe8d2 + languageName: node + linkType: hard + +"parse-url@npm:^8.1.0": + version: 8.1.0 + resolution: "parse-url@npm:8.1.0" + dependencies: + parse-path: "npm:^7.0.0" + checksum: 10c0/68b95afdf4bbf72e57c7ab66f8757c935fff888f7e2b0f1e06098b4faa19e06b6b743bddaed5bc8df4f0c2de6fc475355d787373b2fdd40092be9e4e4b996648 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-exists@npm:^5.0.0": + version: 5.0.0 + resolution: "path-exists@npm:5.0.0" + checksum: 10c0/b170f3060b31604cde93eefdb7392b89d832dfbc1bed717c9718cbe0f230c1669b7e75f87e19901da2250b84d092989a0f9e44d2ef41deb09aa3ad28e691a40a + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-key@npm:^4.0.0": + version: 4.0.0 + resolution: "path-key@npm:4.0.0" + checksum: 10c0/794efeef32863a65ac312f3c0b0a99f921f3e827ff63afa5cb09a377e202c262b671f7b3832a4e64731003fa94af0263713962d317b9887bd1e0c48a342efba3 + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + +"path-scurry@npm:^2.0.0": + version: 2.0.0 + resolution: "path-scurry@npm:2.0.0" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/3da4adedaa8e7ef8d6dc4f35a0ff8f05a9b4d8365f2b28047752b62d4c1ad73eec21e37b1579ef2d075920157856a3b52ae8309c480a6f1a8bbe06ff8e52b33c + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"path-type@npm:^5.0.0": + version: 5.0.0 + resolution: "path-type@npm:5.0.0" + checksum: 10c0/e8f4b15111bf483900c75609e5e74e3fcb79f2ddb73e41470028fcd3e4b5162ec65da9907be077ee5012c18801ff7fffb35f9f37a077f3f81d85a0b7d6578efd + languageName: node + linkType: hard + +"pathe@npm:^1.1.2": + version: 1.1.2 + resolution: "pathe@npm:1.1.2" + checksum: 10c0/64ee0a4e587fb0f208d9777a6c56e4f9050039268faaaaecd50e959ef01bf847b7872785c36483fa5cdcdbdfdb31fef2ff222684d4fc21c330ab60395c681897 + languageName: node + linkType: hard + +"pathval@npm:^2.0.0": + version: 2.0.0 + resolution: "pathval@npm:2.0.0" + checksum: 10c0/602e4ee347fba8a599115af2ccd8179836a63c925c23e04bd056d0674a64b39e3a081b643cc7bc0b84390517df2d800a46fcc5598d42c155fe4977095c2f77c5 + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1": + version: 1.0.1 + resolution: "picocolors@npm:1.0.1" + checksum: 10c0/c63cdad2bf812ef0d66c8db29583802355d4ca67b9285d846f390cc15c2f6ccb94e8cb7eb6a6e97fc5990a6d3ad4ae42d86c84d3146e667c739a4234ed50d400 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"pidtree@npm:~0.6.0": + version: 0.6.0 + resolution: "pidtree@npm:0.6.0" + bin: + pidtree: bin/pidtree.js + checksum: 10c0/0829ec4e9209e230f74ebf4265f5ccc9ebfb488334b525cb13f86ff801dca44b362c41252cd43ae4d7653a10a5c6ab3be39d2c79064d6895e0d78dc50a5ed6e9 + languageName: node + linkType: hard + +"pino-abstract-transport@npm:^1.0.0, pino-abstract-transport@npm:^1.2.0": + version: 1.2.0 + resolution: "pino-abstract-transport@npm:1.2.0" + dependencies: + readable-stream: "npm:^4.0.0" + split2: "npm:^4.0.0" + checksum: 10c0/b4ab59529b7a91f488440147fc58ee0827a6c1c5ca3627292339354b1381072c1a6bfa9b46d03ad27872589e8477ecf74da12cf286e1e6b665ac64a3b806bf07 + languageName: node + linkType: hard + +"pino-pretty@npm:^11.2.2": + version: 11.2.2 + resolution: "pino-pretty@npm:11.2.2" + dependencies: + colorette: "npm:^2.0.7" + dateformat: "npm:^4.6.3" + fast-copy: "npm:^3.0.2" + fast-safe-stringify: "npm:^2.1.1" + help-me: "npm:^5.0.0" + joycon: "npm:^3.1.1" + minimist: "npm:^1.2.6" + on-exit-leak-free: "npm:^2.1.0" + pino-abstract-transport: "npm:^1.0.0" + pump: "npm:^3.0.0" + readable-stream: "npm:^4.0.0" + secure-json-parse: "npm:^2.4.0" + sonic-boom: "npm:^4.0.1" + strip-json-comments: "npm:^3.1.1" + bin: + pino-pretty: bin.js + checksum: 10c0/3ce1769907886a5584f6c8123d9bc987712ad10a375797733a0fe95a238df587dac8e2b709bab291c4e30d41b0cf65808c708c96f8eb98b2778b6df60afa7e66 + languageName: node + linkType: hard + +"pino-std-serializers@npm:^7.0.0": + version: 7.0.0 + resolution: "pino-std-serializers@npm:7.0.0" + checksum: 10c0/73e694d542e8de94445a03a98396cf383306de41fd75ecc07085d57ed7a57896198508a0dec6eefad8d701044af21eb27253ccc352586a03cf0d4a0bd25b4133 + languageName: node + linkType: hard + +"pino-test@npm:^1.0.1": + version: 1.0.1 + resolution: "pino-test@npm:1.0.1" + dependencies: + split2: "npm:^4.2.0" + checksum: 10c0/96b75364f9348db9971d1f4fa11eaf31398fc9f3872ffbe49aea59a732aefed555132c9bb8052fe1435cfbf50c0009fe0d9bd1849ad6fb7a9ae91080a3bde4fb + languageName: node + linkType: hard + +"pino@npm:^9.3.2": + version: 9.3.2 + resolution: "pino@npm:9.3.2" + dependencies: + atomic-sleep: "npm:^1.0.0" + fast-redact: "npm:^3.1.1" + on-exit-leak-free: "npm:^2.1.0" + pino-abstract-transport: "npm:^1.2.0" + pino-std-serializers: "npm:^7.0.0" + process-warning: "npm:^4.0.0" + quick-format-unescaped: "npm:^4.0.3" + real-require: "npm:^0.2.0" + safe-stable-stringify: "npm:^2.3.1" + sonic-boom: "npm:^4.0.1" + thread-stream: "npm:^3.0.0" + bin: + pino: bin.js + checksum: 10c0/698eb2ebfcc4252da9d035fcf9c999bf27615b66ebc47f9b3d7e942750e50ebe38429e6457abcf8014d70125964ddf114e696cb8225b480d9930271708e3fb52 + languageName: node + linkType: hard + +"pirates@npm:^4.0.1": + version: 4.0.6 + resolution: "pirates@npm:4.0.6" + checksum: 10c0/00d5fa51f8dded94d7429700fb91a0c1ead00ae2c7fd27089f0c5b63e6eca36197fe46384631872690a66f390c5e27198e99006ab77ae472692ab9c2ca903f36 + languageName: node + linkType: hard + +"pluralize@npm:^8.0.0": + version: 8.0.0 + resolution: "pluralize@npm:8.0.0" + checksum: 10c0/2044cfc34b2e8c88b73379ea4a36fc577db04f651c2909041b054c981cd863dd5373ebd030123ab058d194ae615d3a97cfdac653991e499d10caf592e8b3dc33 + languageName: node + linkType: hard + +"postcss-load-config@npm:^6.0.1": + version: 6.0.1 + resolution: "postcss-load-config@npm:6.0.1" + dependencies: + lilconfig: "npm:^3.1.1" + peerDependencies: + jiti: ">=1.21.0" + postcss: ">=8.0.9" + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + checksum: 10c0/74173a58816dac84e44853f7afbd283f4ef13ca0b6baeba27701214beec33f9e309b128f8102e2b173e8d45ecba45d279a9be94b46bf48d219626aa9b5730848 + languageName: node + linkType: hard + +"postcss@npm:^8.4.41": + version: 8.4.41 + resolution: "postcss@npm:8.4.41" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.0.1" + source-map-js: "npm:^1.2.0" + checksum: 10c0/c1828fc59e7ec1a3bf52b3a42f615dba53c67960ed82a81df6441b485fe43c20aba7f4e7c55425762fd99c594ecabbaaba8cf5b30fd79dfec5b52a9f63a2d690 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + +"prettier@npm:^3.3.3": + version: 3.3.3 + resolution: "prettier@npm:3.3.3" + bin: + prettier: bin/prettier.cjs + checksum: 10c0/b85828b08e7505716324e4245549b9205c0cacb25342a030ba8885aba2039a115dbcf75a0b7ca3b37bc9d101ee61fab8113fc69ca3359f2a226f1ecc07ad2e26 + languageName: node + linkType: hard + +"proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9 + languageName: node + linkType: hard + +"process-warning@npm:^4.0.0": + version: 4.0.0 + resolution: "process-warning@npm:4.0.0" + checksum: 10c0/5312a72b69d37a1b82ad03f3dfa0090dab3804a8fd995d06c28e3c002852bd82f5584217d9f4a3f197892bb2afc22d57e2c662c7e906b5abb48c0380c7b0880d + languageName: node + linkType: hard + +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3 + languageName: node + linkType: hard + +"promise-based-task@npm:^3.0.2": + version: 3.0.2 + resolution: "promise-based-task@npm:3.0.2" + checksum: 10c0/b41e0bf2eebdde2f9dd89bd0f239af38d379cce0fc69a189cf4cbee7f7fe0071bb16520903bbe9e750ce1b8e6de18952e0ca3262d4b0c2cc3dd8c291c5e36c1b + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"proto-list@npm:~1.2.1": + version: 1.2.4 + resolution: "proto-list@npm:1.2.4" + checksum: 10c0/b9179f99394ec8a68b8afc817690185f3b03933f7b46ce2e22c1930dc84b60d09f5ad222beab4e59e58c6c039c7f7fcf620397235ef441a356f31f9744010e12 + languageName: node + linkType: hard + +"protocols@npm:^2.0.0, protocols@npm:^2.0.1": + version: 2.0.1 + resolution: "protocols@npm:2.0.1" + checksum: 10c0/016cc58a596e401004a028a2f7005e3444bf89ee8f606409c411719374d1e8bba0464fc142a065cce0d19f41669b2f7ffe25a8bde4f16ce3b6eb01fabc51f2e7 + languageName: node + linkType: hard + +"proxy-agent@npm:6.4.0": + version: 6.4.0 + resolution: "proxy-agent@npm:6.4.0" + dependencies: + agent-base: "npm:^7.0.2" + debug: "npm:^4.3.4" + http-proxy-agent: "npm:^7.0.1" + https-proxy-agent: "npm:^7.0.3" + lru-cache: "npm:^7.14.1" + pac-proxy-agent: "npm:^7.0.1" + proxy-from-env: "npm:^1.1.0" + socks-proxy-agent: "npm:^8.0.2" + checksum: 10c0/0c5b85cacf67eec9d8add025a5e577b2c895672e4187079ec41b0ee2a6dacd90e69a837936cb3ac141dd92b05b50a325b9bfe86ab0dc3b904011aa3bcf406fc0 + languageName: node + linkType: hard + +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.0 + resolution: "pump@npm:3.0.0" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10c0/bbdeda4f747cdf47db97428f3a135728669e56a0ae5f354a9ac5b74556556f5446a46f720a8f14ca2ece5be9b4d5d23c346db02b555f46739934cc6c093a5478 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"pupa@npm:^3.1.0": + version: 3.1.0 + resolution: "pupa@npm:3.1.0" + dependencies: + escape-goat: "npm:^4.0.0" + checksum: 10c0/02afa6e4547a733484206aaa8f8eb3fbfb12d3dd17d7ca4fa1ea390a7da2cb8f381e38868bbf68009c4d372f8f6059f553171b6a712d8f2802c7cd43d513f06c + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"quick-format-unescaped@npm:^4.0.3": + version: 4.0.4 + resolution: "quick-format-unescaped@npm:4.0.4" + checksum: 10c0/fe5acc6f775b172ca5b4373df26f7e4fd347975578199e7d74b2ae4077f0af05baa27d231de1e80e8f72d88275ccc6028568a7a8c9ee5e7368ace0e18eff93a4 + languageName: node + linkType: hard + +"quick-lru@npm:^5.1.1": + version: 5.1.1 + resolution: "quick-lru@npm:5.1.1" + checksum: 10c0/a24cba5da8cec30d70d2484be37622580f64765fb6390a928b17f60cd69e8dbd32a954b3ff9176fa1b86d86ff2ba05252fae55dc4d40d0291c60412b0ad096da + languageName: node + linkType: hard + +"ranges-apply@npm:^7.0.16": + version: 7.0.16 + resolution: "ranges-apply@npm:7.0.16" + dependencies: + ranges-merge: "npm:^9.0.15" + tiny-invariant: "npm:^1.3.3" + checksum: 10c0/0d8796f6b72170c6c08ecf57b2df8a12ab645416176bea0d0dc3b7cc2aa68142843f25bbc5e256d3569b2e74648e5f0821f88f732a77a5d3483385426428eaa2 + languageName: node + linkType: hard + +"ranges-merge@npm:^9.0.15": + version: 9.0.15 + resolution: "ranges-merge@npm:9.0.15" + dependencies: + ranges-push: "npm:^7.0.15" + ranges-sort: "npm:^6.0.11" + checksum: 10c0/2963c3dcd149cd7c684d1f3ec190f4850fd2d34b7e0611263a87712c45dc92e136fd58c8be5e9136c3c4513a90f22de524d0b9da99bb15354d9f480c5f6409f5 + languageName: node + linkType: hard + +"ranges-push@npm:^7.0.15": + version: 7.0.15 + resolution: "ranges-push@npm:7.0.15" + dependencies: + codsen-utils: "npm:^1.6.4" + ranges-sort: "npm:^6.0.11" + string-collapse-leading-whitespace: "npm:^7.0.7" + string-trim-spaces-only: "npm:^5.0.10" + checksum: 10c0/b83e514243bc21bfd3b80f6757faf9b0850933b8f7ae9130d2bb09dedfc3d57a8721163af57b6d904b402f0f9167cf34f67e36d56b1222ccebb7b20621e277be + languageName: node + linkType: hard + +"ranges-sort@npm:^6.0.11": + version: 6.0.11 + resolution: "ranges-sort@npm:6.0.11" + checksum: 10c0/fb4f80a29a49e1bbad5cc5ce2c6371f807c82bebd1ca4f8da01b6fd5131aa5cc19ae333b468d5f1c7a3601da150770e1a2995e7036a3d79a4dbace015d4676fd + languageName: node + linkType: hard + +"rc@npm:1.2.8": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: "npm:^0.6.0" + ini: "npm:~1.3.0" + minimist: "npm:^1.2.0" + strip-json-comments: "npm:~2.0.1" + bin: + rc: ./cli.js + checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 + languageName: node + linkType: hard + +"read-pkg-up@npm:^10.0.0": + version: 10.1.0 + resolution: "read-pkg-up@npm:10.1.0" + dependencies: + find-up: "npm:^6.3.0" + read-pkg: "npm:^8.1.0" + type-fest: "npm:^4.2.0" + checksum: 10c0/16a96ad664ff1a983e30aea2bd9490b65e4c6f29fa54c6b2a89c9f1474847b3a181c902c50c724678d5146043fd731d98aa2d8f9d6857e0ce84a30cbfc8a6b21 + languageName: node + linkType: hard + +"read-pkg@npm:^8.0.0, read-pkg@npm:^8.1.0": + version: 8.1.0 + resolution: "read-pkg@npm:8.1.0" + dependencies: + "@types/normalize-package-data": "npm:^2.4.1" + normalize-package-data: "npm:^6.0.0" + parse-json: "npm:^7.0.0" + type-fest: "npm:^4.2.0" + checksum: 10c0/e50846bbfbe73f4b8fd8c23c523b2e9f1d78467297a870ff94a9e6db7eb65445a4a392bf2896b7566c1715d36492d92d368f1c4b38996dd3942fd1865eb22936 + languageName: node + linkType: hard + +"readable-stream@npm:^3.0.2, readable-stream@npm:^3.4.0": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: "npm:^2.0.3" + string_decoder: "npm:^1.1.1" + util-deprecate: "npm:^1.0.1" + checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 + languageName: node + linkType: hard + +"readable-stream@npm:^4.0.0": + version: 4.5.2 + resolution: "readable-stream@npm:4.5.2" + dependencies: + abort-controller: "npm:^3.0.0" + buffer: "npm:^6.0.3" + events: "npm:^3.3.0" + process: "npm:^0.11.10" + string_decoder: "npm:^1.3.0" + checksum: 10c0/a2c80e0e53aabd91d7df0330929e32d0a73219f9477dbbb18472f6fdd6a11a699fc5d172a1beff98d50eae4f1496c950ffa85b7cc2c4c196963f289a5f39275d + languageName: node + linkType: hard + +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: "npm:^2.2.1" + checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b + languageName: node + linkType: hard + +"real-require@npm:^0.2.0": + version: 0.2.0 + resolution: "real-require@npm:0.2.0" + checksum: 10c0/23eea5623642f0477412ef8b91acd3969015a1501ed34992ada0e3af521d3c865bb2fe4cdbfec5fe4b505f6d1ef6a03e5c3652520837a8c3b53decff7e74b6a0 + languageName: node + linkType: hard + +"rechoir@npm:^0.6.2": + version: 0.6.2 + resolution: "rechoir@npm:0.6.2" + dependencies: + resolve: "npm:^1.1.6" + checksum: 10c0/22c4bb32f4934a9468468b608417194f7e3ceba9a508512125b16082c64f161915a28467562368eeb15dc16058eb5b7c13a20b9eb29ff9927d1ebb3b5aa83e84 + languageName: node + linkType: hard + +"registry-auth-token@npm:^5.0.2": + version: 5.0.2 + resolution: "registry-auth-token@npm:5.0.2" + dependencies: + "@pnpm/npm-conf": "npm:^2.1.0" + checksum: 10c0/20fc2225681cc54ae7304b31ebad5a708063b1949593f02dfe5fb402bc1fc28890cecec6497ea396ba86d6cca8a8480715926dfef8cf1f2f11e6f6cc0a1b4bde + languageName: node + linkType: hard + +"registry-url@npm:^6.0.1": + version: 6.0.1 + resolution: "registry-url@npm:6.0.1" + dependencies: + rc: "npm:1.2.8" + checksum: 10c0/66e2221c8113fc35ee9d23fe58cb516fc8d556a189fb8d6f1011a02efccc846c4c9b5075b4027b99a5d5c9ad1345ac37f297bea3c0ca30d607ec8084bf561b90 + languageName: node + linkType: hard + +"release-it@npm:^17.6.0": + version: 17.6.0 + resolution: "release-it@npm:17.6.0" + dependencies: + "@iarna/toml": "npm:2.2.5" + "@octokit/rest": "npm:20.1.1" + async-retry: "npm:1.3.3" + chalk: "npm:5.3.0" + cosmiconfig: "npm:9.0.0" + execa: "npm:8.0.1" + git-url-parse: "npm:14.0.0" + globby: "npm:14.0.2" + got: "npm:13.0.0" + inquirer: "npm:9.3.2" + is-ci: "npm:3.0.1" + issue-parser: "npm:7.0.1" + lodash: "npm:4.17.21" + mime-types: "npm:2.1.35" + new-github-release-url: "npm:2.0.0" + node-fetch: "npm:3.3.2" + open: "npm:10.1.0" + ora: "npm:8.0.1" + os-name: "npm:5.1.0" + proxy-agent: "npm:6.4.0" + semver: "npm:7.6.2" + shelljs: "npm:0.8.5" + update-notifier: "npm:7.1.0" + url-join: "npm:5.0.0" + wildcard-match: "npm:5.1.3" + yargs-parser: "npm:21.1.1" + bin: + release-it: bin/release-it.js + checksum: 10c0/7e78f0d0ebd94f5398ff30c87bd60ae649af679009786c06fd5020451a84cc0799a1acef7e6e269071a9165ac5fe5aaeeb136e432ef8a003a55a517f3b7c0d73 + languageName: node + linkType: hard + +"remeda@npm:^2.11.0": + version: 2.11.0 + resolution: "remeda@npm:2.11.0" + dependencies: + type-fest: "npm:^4.23.0" + checksum: 10c0/cb2ff3e48fca2a5d341c7243546d432d14b465ddba9fa04898d200836cf12f75caa8bcdcc48774e790f152ff07ae35b2594a152ec849fa428490acf153fa15e7 + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 + languageName: node + linkType: hard + +"resolve-alpn@npm:^1.2.0": + version: 1.2.1 + resolution: "resolve-alpn@npm:1.2.1" + checksum: 10c0/b70b29c1843bc39781ef946c8cd4482e6d425976599c0f9c138cec8209e4e0736161bf39319b01676a847000085dfdaf63583c6fb4427bf751a10635bd2aa0c4 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab + languageName: node + linkType: hard + +"resolve@npm:^1.1.6": + version: 1.22.8 + resolution: "resolve@npm:1.22.8" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin": + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 + languageName: node + linkType: hard + +"responselike@npm:^3.0.0": + version: 3.0.0 + resolution: "responselike@npm:3.0.0" + dependencies: + lowercase-keys: "npm:^3.0.0" + checksum: 10c0/8af27153f7e47aa2c07a5f2d538cb1e5872995f0e9ff77def858ecce5c3fe677d42b824a62cde502e56d275ab832b0a8bd350d5cd6b467ac0425214ac12ae658 + languageName: node + linkType: hard + +"restore-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "restore-cursor@npm:3.1.0" + dependencies: + onetime: "npm:^5.1.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/8051a371d6aa67ff21625fa94e2357bd81ffdc96267f3fb0fc4aaf4534028343836548ef34c240ffa8c25b280ca35eb36be00b3cb2133fa4f51896d7e73c6b4f + languageName: node + linkType: hard + +"restore-cursor@npm:^4.0.0": + version: 4.0.0 + resolution: "restore-cursor@npm:4.0.0" + dependencies: + onetime: "npm:^5.1.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/6f7da8c5e422ac26aa38354870b1afac09963572cf2879443540449068cb43476e9cbccf6f8de3e0171e0d6f7f533c2bc1a0a008003c9a525bbc098e89041318 + languageName: node + linkType: hard + +"restore-cursor@npm:^5.0.0": + version: 5.1.0 + resolution: "restore-cursor@npm:5.1.0" + dependencies: + onetime: "npm:^7.0.0" + signal-exit: "npm:^4.1.0" + checksum: 10c0/c2ba89131eea791d1b25205bdfdc86699767e2b88dee2a590b1a6caa51737deac8bad0260a5ded2f7c074b7db2f3a626bcf1fcf3cdf35974cbeea5e2e6764f60 + languageName: node + linkType: hard + +"retry@npm:0.13.1, retry@npm:^0.13.1": + version: 0.13.1 + resolution: "retry@npm:0.13.1" + checksum: 10c0/9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.0.4 + resolution: "reusify@npm:1.0.4" + checksum: 10c0/c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 + languageName: node + linkType: hard + +"rfdc@npm:^1.3.1, rfdc@npm:^1.4.1": + version: 1.4.1 + resolution: "rfdc@npm:1.4.1" + checksum: 10c0/4614e4292356cafade0b6031527eea9bc90f2372a22c012313be1dcc69a3b90c7338158b414539be863fa95bfcb2ddcd0587be696841af4e6679d85e62c060c7 + languageName: node + linkType: hard + +"rimraf@npm:^6.0.1": + version: 6.0.1 + resolution: "rimraf@npm:6.0.1" + dependencies: + glob: "npm:^11.0.0" + package-json-from-dist: "npm:^1.0.0" + bin: + rimraf: dist/esm/bin.mjs + checksum: 10c0/b30b6b072771f0d1e73b4ca5f37bb2944ee09375be9db5f558fcd3310000d29dfcfa93cf7734d75295ad5a7486dc8e40f63089ced1722a664539ffc0c3ece8c6 + languageName: node + linkType: hard + +"rollup@npm:^4.19.0, rollup@npm:^4.20.0": + version: 4.21.0 + resolution: "rollup@npm:4.21.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.21.0" + "@rollup/rollup-android-arm64": "npm:4.21.0" + "@rollup/rollup-darwin-arm64": "npm:4.21.0" + "@rollup/rollup-darwin-x64": "npm:4.21.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.21.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.21.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.21.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.21.0" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.21.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.21.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.21.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.21.0" + "@rollup/rollup-linux-x64-musl": "npm:4.21.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.21.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.21.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.21.0" + "@types/estree": "npm:1.0.5" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/984beb858da245c5e3a9027d6d87e67ad6443f1b46eab07685b861d9e49da5856693265c62a6f8262c36d11c9092713a96a9124f43e6de6698eb84d77118496a + languageName: node + linkType: hard + +"run-applescript@npm:^7.0.0": + version: 7.0.0 + resolution: "run-applescript@npm:7.0.0" + checksum: 10c0/bd821bbf154b8e6c8ecffeaf0c33cebbb78eb2987476c3f6b420d67ab4c5301faa905dec99ded76ebb3a7042b4e440189ae6d85bbbd3fc6e8d493347ecda8bfe + languageName: node + linkType: hard + +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 10c0/b18b562ae37c3020083dcaae29642e4cc360c824fbfb6b7d50d809a9d5227bb986152d09310255842c8dce40526e82ca768f02f00806c91ba92a8dfa6159cb85 + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"rxjs@npm:^7.8.1": + version: 7.8.1 + resolution: "rxjs@npm:7.8.1" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10c0/3c49c1ecd66170b175c9cacf5cef67f8914dcbc7cd0162855538d365c83fea631167cacb644b3ce533b2ea0e9a4d0b12175186985f89d75abe73dbd8f7f06f68 + languageName: node + linkType: hard + +"safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safe-stable-stringify@npm:^2.3.1": + version: 2.4.3 + resolution: "safe-stable-stringify@npm:2.4.3" + checksum: 10c0/81dede06b8f2ae794efd868b1e281e3c9000e57b39801c6c162267eb9efda17bd7a9eafa7379e1f1cacd528d4ced7c80d7460ad26f62ada7c9e01dec61b2e768 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"sax@npm:^1.2.4": + version: 1.4.1 + resolution: "sax@npm:1.4.1" + checksum: 10c0/6bf86318a254c5d898ede6bd3ded15daf68ae08a5495a2739564eb265cd13bcc64a07ab466fb204f67ce472bb534eb8612dac587435515169593f4fffa11de7c + languageName: node + linkType: hard + +"secure-json-parse@npm:^2.4.0": + version: 2.7.0 + resolution: "secure-json-parse@npm:2.7.0" + checksum: 10c0/f57eb6a44a38a3eeaf3548228585d769d788f59007454214fab9ed7f01fbf2e0f1929111da6db28cf0bcc1a2e89db5219a59e83eeaec3a54e413a0197ce879e4 + languageName: node + linkType: hard + +"semver-diff@npm:^4.0.0": + version: 4.0.0 + resolution: "semver-diff@npm:4.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/3ed1bb22f39b4b6e98785bb066e821eabb9445d3b23e092866c50e7df8b9bd3eda617b242f81db4159586e0e39b0deb908dd160a24f783bd6f52095b22cd68ea + languageName: node + linkType: hard + +"semver@npm:7.6.2": + version: 7.6.2 + resolution: "semver@npm:7.6.2" + bin: + semver: bin/semver.js + checksum: 10c0/97d3441e97ace8be4b1976433d1c32658f6afaff09f143e52c593bae7eef33de19e3e369c88bd985ce1042c6f441c80c6803078d1de2a9988080b66684cbb30c + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3": + version: 7.6.3 + resolution: "semver@npm:7.6.3" + bin: + semver: bin/semver.js + checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf + languageName: node + linkType: hard + +"serialize-error@npm:^11.0.3": + version: 11.0.3 + resolution: "serialize-error@npm:11.0.3" + dependencies: + type-fest: "npm:^2.12.2" + checksum: 10c0/7263603883b8936650819f0fd5150d41427b317432678b21722c54b85367ae15b8552865eb7f3f39ba71a32a003730a2e2e971e6909431eb54db70a3ef8eca17 + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"shelljs@npm:0.8.5": + version: 0.8.5 + resolution: "shelljs@npm:0.8.5" + dependencies: + glob: "npm:^7.0.0" + interpret: "npm:^1.0.0" + rechoir: "npm:^0.6.2" + bin: + shjs: bin/shjs + checksum: 10c0/feb25289a12e4bcd04c40ddfab51aff98a3729f5c2602d5b1a1b95f6819ec7804ac8147ebd8d9a85dfab69d501bcf92d7acef03247320f51c1552cec8d8e2382 + languageName: node + linkType: hard + +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + +"slash@npm:^5.1.0": + version: 5.1.0 + resolution: "slash@npm:5.1.0" + checksum: 10c0/eb48b815caf0bdc390d0519d41b9e0556a14380f6799c72ba35caf03544d501d18befdeeef074bc9c052acf69654bc9e0d79d7f1de0866284137a40805299eb3 + languageName: node + linkType: hard + +"slice-ansi@npm:^5.0.0": + version: 5.0.0 + resolution: "slice-ansi@npm:5.0.0" + dependencies: + ansi-styles: "npm:^6.0.0" + is-fullwidth-code-point: "npm:^4.0.0" + checksum: 10c0/2d4d40b2a9d5cf4e8caae3f698fe24ae31a4d778701724f578e984dcb485ec8c49f0c04dab59c401821e80fcdfe89cace9c66693b0244e40ec485d72e543914f + languageName: node + linkType: hard + +"slice-ansi@npm:^7.1.0": + version: 7.1.0 + resolution: "slice-ansi@npm:7.1.0" + dependencies: + ansi-styles: "npm:^6.2.1" + is-fullwidth-code-point: "npm:^5.0.0" + checksum: 10c0/631c971d4abf56cf880f034d43fcc44ff883624867bf11ecbd538c47343911d734a4656d7bc02362b40b89d765652a7f935595441e519b59e2ad3f4d5d6fe7ca + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.2, socks-proxy-agent@npm:^8.0.3, socks-proxy-agent@npm:^8.0.4": + version: 8.0.4 + resolution: "socks-proxy-agent@npm:8.0.4" + dependencies: + agent-base: "npm:^7.1.1" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/345593bb21b95b0508e63e703c84da11549f0a2657d6b4e3ee3612c312cb3a907eac10e53b23ede3557c6601d63252103494caa306b66560f43af7b98f53957a + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.3 + resolution: "socks@npm:2.8.3" + dependencies: + ip-address: "npm:^9.0.5" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7 + languageName: node + linkType: hard + +"sonic-boom@npm:^4.0.1": + version: 4.0.1 + resolution: "sonic-boom@npm:4.0.1" + dependencies: + atomic-sleep: "npm:^1.0.0" + checksum: 10c0/7b467f2bc8af7ff60bf210382f21c59728cc4b769af9b62c31dd88723f5cc472752d2320736cc366acc7c765ddd5bec3072c033b0faf249923f576a7453ba9d3 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.0": + version: 1.2.0 + resolution: "source-map-js@npm:1.2.0" + checksum: 10c0/7e5f896ac10a3a50fe2898e5009c58ff0dc102dcb056ed27a354623a0ece8954d4b2649e1a1b2b52ef2e161d26f8859c7710350930751640e71e374fe2d321a4 + languageName: node + linkType: hard + +"source-map@npm:0.8.0-beta.0": + version: 0.8.0-beta.0 + resolution: "source-map@npm:0.8.0-beta.0" + dependencies: + whatwg-url: "npm:^7.0.0" + checksum: 10c0/fb4d9bde9a9fdb2c29b10e5eae6c71d10e09ef467e1afb75fdec2eb7e11fa5b343a2af553f74f18b695dbc0b81f9da2e9fa3d7a317d5985e9939499ec6087835 + languageName: node + linkType: hard + +"source-map@npm:^0.6.1, source-map@npm:~0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard + +"spdx-correct@npm:^3.0.0": + version: 3.2.0 + resolution: "spdx-correct@npm:3.2.0" + dependencies: + spdx-expression-parse: "npm:^3.0.0" + spdx-license-ids: "npm:^3.0.0" + checksum: 10c0/49208f008618b9119208b0dadc9208a3a55053f4fd6a0ae8116861bd22696fc50f4142a35ebfdb389e05ccf2de8ad142573fefc9e26f670522d899f7b2fe7386 + languageName: node + linkType: hard + +"spdx-exceptions@npm:^2.1.0": + version: 2.5.0 + resolution: "spdx-exceptions@npm:2.5.0" + checksum: 10c0/37217b7762ee0ea0d8b7d0c29fd48b7e4dfb94096b109d6255b589c561f57da93bf4e328c0290046115961b9209a8051ad9f525e48d433082fc79f496a4ea940 + languageName: node + linkType: hard + +"spdx-expression-parse@npm:^3.0.0": + version: 3.0.1 + resolution: "spdx-expression-parse@npm:3.0.1" + dependencies: + spdx-exceptions: "npm:^2.1.0" + spdx-license-ids: "npm:^3.0.0" + checksum: 10c0/6f8a41c87759fa184a58713b86c6a8b028250f158159f1d03ed9d1b6ee4d9eefdc74181c8ddc581a341aa971c3e7b79e30b59c23b05d2436d5de1c30bdef7171 + languageName: node + linkType: hard + +"spdx-license-ids@npm:^3.0.0": + version: 3.0.20 + resolution: "spdx-license-ids@npm:3.0.20" + checksum: 10c0/bdff7534fad6ef59be49becda1edc3fb7f5b3d6f296a715516ab9d972b8ad59af2c34b2003e01db8970d4c673d185ff696ba74c6b61d3bf327e2b3eac22c297c + languageName: node + linkType: hard + +"split2@npm:^4.0.0, split2@npm:^4.2.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 10c0/b292beb8ce9215f8c642bb68be6249c5a4c7f332fc8ecadae7be5cbdf1ea95addc95f0459ef2e7ad9d45fd1064698a097e4eb211c83e772b49bc0ee423e91534 + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec + languageName: node + linkType: hard + +"ssri@npm:^10.0.0": + version: 10.0.6 + resolution: "ssri@npm:10.0.6" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227 + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + +"std-env@npm:^3.7.0": + version: 3.7.0 + resolution: "std-env@npm:3.7.0" + checksum: 10c0/60edf2d130a4feb7002974af3d5a5f3343558d1ccf8d9b9934d225c638606884db4a20d2fe6440a09605bca282af6b042ae8070a10490c0800d69e82e478f41e + languageName: node + linkType: hard + +"stdin-discarder@npm:^0.2.1": + version: 0.2.2 + resolution: "stdin-discarder@npm:0.2.2" + checksum: 10c0/c78375e82e956d7a64be6e63c809c7f058f5303efcaf62ea48350af072bacdb99c06cba39209b45a071c1acbd49116af30df1df9abb448df78a6005b72f10537 + languageName: node + linkType: hard + +"string-argv@npm:~0.3.2": + version: 0.3.2 + resolution: "string-argv@npm:0.3.2" + checksum: 10c0/75c02a83759ad1722e040b86823909d9a2fc75d15dd71ec4b537c3560746e33b5f5a07f7332d1e3f88319909f82190843aa2f0a0d8c8d591ec08e93d5b8dec82 + languageName: node + linkType: hard + +"string-collapse-leading-whitespace@npm:^7.0.7": + version: 7.0.7 + resolution: "string-collapse-leading-whitespace@npm:7.0.7" + checksum: 10c0/f54c5a650c2d64b9c6d1b8a48366620f242958fbc289f52c94e18d80b7cf63476baa7b03a8d4f8a4e6bffb0916867c9d0cbbcbb91344f69d4b9073f941dba24c + languageName: node + linkType: hard + +"string-comparison@npm:^1.3.0": + version: 1.3.0 + resolution: "string-comparison@npm:1.3.0" + checksum: 10c0/9118ea5d33cc3e9761b12b481e44a75a2e632564b33a511c24cc753105e844f2e0c5997ec5210d2a233a5669a635aaff65f74aa156a5205fba7cd0e81c53ceab + languageName: node + linkType: hard + +"string-left-right@npm:^6.0.17": + version: 6.0.17 + resolution: "string-left-right@npm:6.0.17" + dependencies: + codsen-utils: "npm:^1.6.4" + rfdc: "npm:^1.3.1" + checksum: 10c0/d07830f8027c8bd518fb82ed58bd5fbb2c8acc3d8c8c61b73488ca9126f86e21acfed999e533cf84533fcd7b9f770cb002f7306333380d5deb9a77e2d85bf463 + languageName: node + linkType: hard + +"string-strip-html@npm:^13.4.8": + version: 13.4.8 + resolution: "string-strip-html@npm:13.4.8" + dependencies: + "@types/lodash-es": "npm:^4.17.12" + codsen-utils: "npm:^1.6.4" + html-entities: "npm:^2.5.2" + lodash-es: "npm:^4.17.21" + ranges-apply: "npm:^7.0.16" + ranges-push: "npm:^7.0.15" + string-left-right: "npm:^6.0.17" + checksum: 10c0/e93f104ce7a86ce5124fbfbd10374728a9488b55c8f16f22fb91800df39eb03bb789eaf41d32c5d01b7f5cea5a3f3d1398bb58cb73be3b6083dec0c6852c328f + languageName: node + linkType: hard + +"string-trim-spaces-only@npm:^5.0.10": + version: 5.0.10 + resolution: "string-trim-spaces-only@npm:5.0.10" + checksum: 10c0/23a1480ab58acd3b5bec20cb5a8a01ab0592304c068cf8438dd45a66633b04feec5099737168fbe1730a504fb40be9d564a35938bef1db325a9c0a20fe0e9ddb + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"string-width@npm:^7.0.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 + languageName: node + linkType: hard + +"string.fromcodepoint@npm:^0.2.1": + version: 0.2.1 + resolution: "string.fromcodepoint@npm:0.2.1" + checksum: 10c0/2e26c7370daea0725f2cc3b0a2e4b84613c44b68130ad2afa1364b51fd48ebdfe6390086807d7b5e95d58e8a872aca46a53bbc182c549cd74c0ee9b46de32b02 + languageName: node + linkType: hard + +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: "npm:~5.2.0" + checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f + languageName: node + linkType: hard + +"strip-final-newline@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-final-newline@npm:3.0.0" + checksum: 10c0/a771a17901427bac6293fd416db7577e2bc1c34a19d38351e9d5478c3c415f523f391003b42ed475f27e33a78233035df183525395f731d3bfb8cdcbd4da08ce + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 + languageName: node + linkType: hard + +"strnum@npm:^1.0.5": + version: 1.0.5 + resolution: "strnum@npm:1.0.5" + checksum: 10c0/64fb8cc2effbd585a6821faa73ad97d4b553c8927e49086a162ffd2cc818787643390b89d567460a8e74300148d11ac052e21c921ef2049f2987f4b1b89a7ff1 + languageName: node + linkType: hard + +"sucrase@npm:^3.35.0": + version: 3.35.0 + resolution: "sucrase@npm:3.35.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.2" + commander: "npm:^4.0.0" + glob: "npm:^10.3.10" + lines-and-columns: "npm:^1.1.6" + mz: "npm:^2.7.0" + pirates: "npm:^4.0.1" + ts-interface-checker: "npm:^0.1.9" + bin: + sucrase: bin/sucrase + sucrase-node: bin/sucrase-node + checksum: 10c0/ac85f3359d2c2ecbf5febca6a24ae9bf96c931f05fde533c22a94f59c6a74895e5d5f0e871878dfd59c2697a75ebb04e4b2224ef0bfc24ca1210735c2ec191ef + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-color@npm:^9.4.0": + version: 9.4.0 + resolution: "supports-color@npm:9.4.0" + checksum: 10c0/6c24e6b2b64c6a60e5248490cfa50de5924da32cf09ae357ad8ebbf305cc5d2717ba705a9d4cb397d80bbf39417e8fdc8d7a0ce18bd0041bf7b5b456229164e4 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"tar@npm:^6.1.11, tar@npm:^6.2.1": + version: 6.2.1 + resolution: "tar@npm:6.2.1" + dependencies: + chownr: "npm:^2.0.0" + fs-minipass: "npm:^2.0.0" + minipass: "npm:^5.0.0" + minizlib: "npm:^2.1.1" + mkdirp: "npm:^1.0.3" + yallist: "npm:^4.0.0" + checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 + languageName: node + linkType: hard + +"temp-dir@npm:^3.0.0": + version: 3.0.0 + resolution: "temp-dir@npm:3.0.0" + checksum: 10c0/a86978a400984cd5f315b77ebf3fe53bb58c61f192278cafcb1f3fb32d584a21dc8e08b93171d7874b7cc972234d3455c467306cc1bfc4524b622e5ad3bfd671 + languageName: node + linkType: hard + +"text-extensions@npm:^2.0.0": + version: 2.4.0 + resolution: "text-extensions@npm:2.4.0" + checksum: 10c0/6790e7ee72ad4d54f2e96c50a13e158bb57ce840dddc770e80960ed1550115c57bdc2cee45d5354d7b4f269636f5ca06aab4d6e0281556c841389aa837b23fcb + languageName: node + linkType: hard + +"text-table@npm:^0.2.0": + version: 0.2.0 + resolution: "text-table@npm:0.2.0" + checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c + languageName: node + linkType: hard + +"thenify-all@npm:^1.0.0": + version: 1.6.0 + resolution: "thenify-all@npm:1.6.0" + dependencies: + thenify: "npm:>= 3.1.0 < 4" + checksum: 10c0/9b896a22735e8122754fe70f1d65f7ee691c1d70b1f116fda04fea103d0f9b356e3676cb789506e3909ae0486a79a476e4914b0f92472c2e093d206aed4b7d6b + languageName: node + linkType: hard + +"thenify@npm:>= 3.1.0 < 4": + version: 3.3.1 + resolution: "thenify@npm:3.3.1" + dependencies: + any-promise: "npm:^1.0.0" + checksum: 10c0/f375aeb2b05c100a456a30bc3ed07ef03a39cbdefe02e0403fb714b8c7e57eeaad1a2f5c4ecfb9ce554ce3db9c2b024eba144843cd9e344566d9fcee73b04767 + languageName: node + linkType: hard + +"thread-stream@npm:^3.0.0": + version: 3.1.0 + resolution: "thread-stream@npm:3.1.0" + dependencies: + real-require: "npm:^0.2.0" + checksum: 10c0/c36118379940b77a6ef3e6f4d5dd31e97b8210c3f7b9a54eb8fe6358ab173f6d0acfaf69b9c3db024b948c0c5fd2a7df93e2e49151af02076b35ada3205ec9a6 + languageName: node + linkType: hard + +"through@npm:>=2.2.7 <3": + version: 2.3.8 + resolution: "through@npm:2.3.8" + checksum: 10c0/4b09f3774099de0d4df26d95c5821a62faee32c7e96fb1f4ebd54a2d7c11c57fe88b0a0d49cf375de5fee5ae6bf4eb56dbbf29d07366864e2ee805349970d3cc + languageName: node + linkType: hard + +"tiny-invariant@npm:^1.3.3": + version: 1.3.3 + resolution: "tiny-invariant@npm:1.3.3" + checksum: 10c0/65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a + languageName: node + linkType: hard + +"tinybench@npm:^2.8.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinypool@npm:^1.0.0": + version: 1.0.1 + resolution: "tinypool@npm:1.0.1" + checksum: 10c0/90939d6a03f1519c61007bf416632dc1f0b9c1a9dd673c179ccd9e36a408437384f984fc86555a5d040d45b595abc299c3bb39d354439e98a090766b5952e73d + languageName: node + linkType: hard + +"tinyrainbow@npm:^1.2.0": + version: 1.2.0 + resolution: "tinyrainbow@npm:1.2.0" + checksum: 10c0/7f78a4b997e5ba0f5ecb75e7ed786f30bab9063716e7dff24dd84013fb338802e43d176cb21ed12480561f5649a82184cf31efb296601a29d38145b1cdb4c192 + languageName: node + linkType: hard + +"tinyspy@npm:^3.0.0": + version: 3.0.0 + resolution: "tinyspy@npm:3.0.0" + checksum: 10c0/eb0dec264aa5370efd3d29743825eb115ed7f1ef8a72a431e9a75d5c9e7d67e99d04b0d61d86b8cd70c79ec27863f241ad0317bc453f78762e0cbd76d2c332d0 + languageName: node + linkType: hard + +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" + dependencies: + os-tmpdir: "npm:~1.0.2" + checksum: 10c0/69863947b8c29cabad43fe0ce65cec5bb4b481d15d4b4b21e036b060b3edbf3bc7a5541de1bacb437bb3f7c4538f669752627fdf9b4aaf034cebd172ba373408 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"tr46@npm:^1.0.1": + version: 1.0.1 + resolution: "tr46@npm:1.0.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/41525c2ccce86e3ef30af6fa5e1464e6d8bb4286a58ea8db09228f598889581ef62347153f6636cd41553dc41685bdfad0a9d032ef58df9fbb0792b3447d0f04 + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 + languageName: node + linkType: hard + +"tree-kill@npm:^1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 10c0/7b1b7c7f17608a8f8d20a162e7957ac1ef6cd1636db1aba92f4e072dc31818c2ff0efac1e3d91064ede67ed5dc57c565420531a8134090a12ac10cf792ab14d2 + languageName: node + linkType: hard + +"ts-api-utils@npm:^1.3.0": + version: 1.3.0 + resolution: "ts-api-utils@npm:1.3.0" + peerDependencies: + typescript: ">=4.2.0" + checksum: 10c0/f54a0ba9ed56ce66baea90a3fa087a484002e807f28a8ccb2d070c75e76bde64bd0f6dce98b3802834156306050871b67eec325cb4e918015a360a3f0868c77c + languageName: node + linkType: hard + +"ts-interface-checker@npm:^0.1.9": + version: 0.1.13 + resolution: "ts-interface-checker@npm:0.1.13" + checksum: 10c0/232509f1b84192d07b81d1e9b9677088e590ac1303436da1e92b296e9be8e31ea042e3e1fd3d29b1742ad2c959e95afe30f63117b8f1bc3a3850070a5142fea7 + languageName: node + linkType: hard + +"tsc-files@npm:^1.1.4": + version: 1.1.4 + resolution: "tsc-files@npm:1.1.4" + peerDependencies: + typescript: ">=3" + bin: + tsc-files: cli.js + checksum: 10c0/f7bc34c736e58dbd50c8f5fcd075704ad7f48efa670aaf388b7bee3386b8a2e6bef17cab5e6f8ff95bff7c2e1fa3baecb498576333b649c4258a9779dda3e708 + languageName: node + linkType: hard + +"tsconfck@npm:^3.0.3": + version: 3.1.1 + resolution: "tsconfck@npm:3.1.1" + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + bin: + tsconfck: bin/tsconfck.js + checksum: 10c0/e133eb308ba37e8db8dbac1905bddaaf4a62f0e01aa88143e19867e274a877b86b35cf69c9a0172ca3e7d1a4bb32400381ac7f7a1429e34250a8d7ae55aee3e7 + languageName: node + linkType: hard + +"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a + languageName: node + linkType: hard + +"tsup@npm:^8.2.4": + version: 8.2.4 + resolution: "tsup@npm:8.2.4" + dependencies: + bundle-require: "npm:^5.0.0" + cac: "npm:^6.7.14" + chokidar: "npm:^3.6.0" + consola: "npm:^3.2.3" + debug: "npm:^4.3.5" + esbuild: "npm:^0.23.0" + execa: "npm:^5.1.1" + globby: "npm:^11.1.0" + joycon: "npm:^3.1.1" + picocolors: "npm:^1.0.1" + postcss-load-config: "npm:^6.0.1" + resolve-from: "npm:^5.0.0" + rollup: "npm:^4.19.0" + source-map: "npm:0.8.0-beta.0" + sucrase: "npm:^3.35.0" + tree-kill: "npm:^1.2.2" + peerDependencies: + "@microsoft/api-extractor": ^7.36.0 + "@swc/core": ^1 + postcss: ^8.4.12 + typescript: ">=4.5.0" + peerDependenciesMeta: + "@microsoft/api-extractor": + optional: true + "@swc/core": + optional: true + postcss: + optional: true + typescript: + optional: true + bin: + tsup: dist/cli-default.js + tsup-node: dist/cli-node.js + checksum: 10c0/0fa967ae0feb483528ae52fd9988d7931a092b3645a456e23b62ac03a0556da6f569fb2a77b3baf50ff1d12b9afac38aa9d29f15eb82c8a2b4faeda8362858da + languageName: node + linkType: hard + +"tsx@npm:^4.17.0": + version: 4.17.0 + resolution: "tsx@npm:4.17.0" + dependencies: + esbuild: "npm:~0.23.0" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.5" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 10c0/ad720b81d6447c7695d24c27947fa1a2b6db9d2ef03216389edd6fa0006aa479bc0d8348a1ac9975a08edef4ce791ff5629a24d8dccbb0987f42e5407785cfa4 + languageName: node + linkType: hard + +"turndown@npm:^7.2.0": + version: 7.2.0 + resolution: "turndown@npm:7.2.0" + dependencies: + "@mixmark-io/domino": "npm:^2.2.0" + checksum: 10c0/6abcdcdf9d35cd79d7a8100a7de1d2226b921d5bd99e73ac14a7ead39c059978f519378913375efb04c68bcfc40f7ffe2dee0ce9ae4d54dc1235b12856a78d4e + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + +"type-fest@npm:^1.0.1": + version: 1.4.0 + resolution: "type-fest@npm:1.4.0" + checksum: 10c0/a3c0f4ee28ff6ddf800d769eafafcdeab32efa38763c1a1b8daeae681920f6e345d7920bf277245235561d8117dab765cb5f829c76b713b4c9de0998a5397141 + languageName: node + linkType: hard + +"type-fest@npm:^2.12.2, type-fest@npm:^2.13.0, type-fest@npm:^2.5.1": + version: 2.19.0 + resolution: "type-fest@npm:2.19.0" + checksum: 10c0/a5a7ecf2e654251613218c215c7493574594951c08e52ab9881c9df6a6da0aeca7528c213c622bc374b4e0cb5c443aa3ab758da4e3c959783ce884c3194e12cb + languageName: node + linkType: hard + +"type-fest@npm:^3.8.0": + version: 3.13.1 + resolution: "type-fest@npm:3.13.1" + checksum: 10c0/547d22186f73a8c04590b70dcf63baff390078c75ea8acd366bbd510fd0646e348bd1970e47ecf795b7cff0b41d26e9c475c1fedd6ef5c45c82075fbf916b629 + languageName: node + linkType: hard + +"type-fest@npm:^4.2.0, type-fest@npm:^4.23.0, type-fest@npm:^4.7.1": + version: 4.25.0 + resolution: "type-fest@npm:4.25.0" + checksum: 10c0/1187b30d74e72f4b0b44a3493d2c1c2a9dc46423961c8250bd1535e976c4b8afc3916f6b4b90d7f56ed5b2f36d1645b05c318b4915fe4909a8a66890bda1d68d + languageName: node + linkType: hard + +"typedarray-to-buffer@npm:^3.1.5": + version: 3.1.5 + resolution: "typedarray-to-buffer@npm:3.1.5" + dependencies: + is-typedarray: "npm:^1.0.0" + checksum: 10c0/4ac5b7a93d604edabf3ac58d3a2f7e07487e9f6e98195a080e81dbffdc4127817f470f219d794a843b87052cedef102b53ac9b539855380b8c2172054b7d5027 + languageName: node + linkType: hard + +"typedarray@npm:^0.0.6": + version: 0.0.6 + resolution: "typedarray@npm:0.0.6" + checksum: 10c0/6005cb31df50eef8b1f3c780eb71a17925f3038a100d82f9406ac2ad1de5eb59f8e6decbdc145b3a1f8e5836e17b0c0002fb698b9fe2516b8f9f9ff602d36412 + languageName: node + linkType: hard + +"typescript-eslint@npm:^8.2.0": + version: 8.2.0 + resolution: "typescript-eslint@npm:8.2.0" + dependencies: + "@typescript-eslint/eslint-plugin": "npm:8.2.0" + "@typescript-eslint/parser": "npm:8.2.0" + "@typescript-eslint/utils": "npm:8.2.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/545569026b3da15625ae827844ce6c529c033286807b8adeb1652bef83f1c8ce900253fc5c0813600145ae4f1ba9cc265e4731902bbb9227d1e7331db9d82637 + languageName: node + linkType: hard + +"typescript@npm:^5.5.4": + version: 5.5.4 + resolution: "typescript@npm:5.5.4" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/422be60f89e661eab29ac488c974b6cc0a660fb2228003b297c3d10c32c90f3bcffc1009b43876a082515a3c376b1eefcce823d6e78982e6878408b9a923199c + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.5.4#optional!builtin": + version: 5.5.4 + resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=5adc0c" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/10dd9881baba22763de859e8050d6cb6e2db854197495c6f1929b08d1eb2b2b00d0b5d9b0bcee8472f1c3f4a7ef6a5d7ebe0cfd703f853aa5ae465b8404bc1ba + languageName: node + linkType: hard + +"uglify-js@npm:^3.1.4": + version: 3.19.2 + resolution: "uglify-js@npm:3.19.2" + bin: + uglifyjs: bin/uglifyjs + checksum: 10c0/51dbe1304a91cac5daa01f6a2d4ecd545fab7b7d0625e11590b923e95a6d2263b3481dcea974abfc0282b33d2c76f74f1196a992df07eae0847175bc39ea45bb + languageName: node + linkType: hard + +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 + languageName: node + linkType: hard + +"undici-types@npm:~6.19.2": + version: 6.19.8 + resolution: "undici-types@npm:6.19.8" + checksum: 10c0/078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344 + languageName: node + linkType: hard + +"undici@npm:^5.28.3": + version: 5.28.4 + resolution: "undici@npm:5.28.4" + dependencies: + "@fastify/busboy": "npm:^2.0.0" + checksum: 10c0/08d0f2596553aa0a54ca6e8e9c7f45aef7d042c60918564e3a142d449eda165a80196f6ef19ea2ef2e6446959e293095d8e40af1236f0d67223b06afac5ecad7 + languageName: node + linkType: hard + +"unescape-js@npm:^1.1.4": + version: 1.1.4 + resolution: "unescape-js@npm:1.1.4" + dependencies: + string.fromcodepoint: "npm:^0.2.1" + checksum: 10c0/4f7cda5c524cb4392d482eba11762dbc43ff8cd0d0d88c4deecdacb7ec04d9162595406f66c5fbe9a6a565aabf7f2f1cc1889d44d805b1e8326deb7b3b279484 + languageName: node + linkType: hard + +"unicorn-magic@npm:^0.1.0": + version: 0.1.0 + resolution: "unicorn-magic@npm:0.1.0" + checksum: 10c0/e4ed0de05b0a05e735c7d8a2930881e5efcfc3ec897204d5d33e7e6247f4c31eac92e383a15d9a6bccb7319b4271ee4bea946e211bf14951fec6ff2cbbb66a92 + languageName: node + linkType: hard + +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" + dependencies: + unique-slug: "npm:^4.0.0" + checksum: 10c0/6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f + languageName: node + linkType: hard + +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635 + languageName: node + linkType: hard + +"unique-string@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-string@npm:3.0.0" + dependencies: + crypto-random-string: "npm:^4.0.0" + checksum: 10c0/b35ea034b161b2a573666ec16c93076b4b6106b8b16c2415808d747ab3a0566b5db0c4be231d4b11cfbc16d7fd915c9d8a45884bff0e2db11b799775b2e1e017 + languageName: node + linkType: hard + +"universal-user-agent@npm:^6.0.0": + version: 6.0.1 + resolution: "universal-user-agent@npm:6.0.1" + checksum: 10c0/5c9c46ffe19a975e11e6443640ed4c9e0ce48fcc7203325757a8414ac49940ebb0f4667f2b1fa561489d1eb22cb2d05a0f7c82ec20c5cba42e58e188fb19b187 + languageName: node + linkType: hard + +"universalify@npm:^2.0.0": + version: 2.0.1 + resolution: "universalify@npm:2.0.1" + checksum: 10c0/73e8ee3809041ca8b818efb141801a1004e3fc0002727f1531f4de613ea281b494a40909596dae4a042a4fb6cd385af5d4db2e137b1362e0e91384b828effd3a + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.1.0": + version: 1.1.0 + resolution: "update-browserslist-db@npm:1.1.0" + dependencies: + escalade: "npm:^3.1.2" + picocolors: "npm:^1.0.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/a7452de47785842736fb71547651c5bbe5b4dc1e3722ccf48a704b7b34e4dcf633991eaa8e4a6a517ffb738b3252eede3773bef673ef9021baa26b056d63a5b9 + languageName: node + linkType: hard + +"update-notifier@npm:7.1.0": + version: 7.1.0 + resolution: "update-notifier@npm:7.1.0" + dependencies: + boxen: "npm:^7.1.1" + chalk: "npm:^5.3.0" + configstore: "npm:^6.0.0" + import-lazy: "npm:^4.0.0" + is-in-ci: "npm:^0.1.0" + is-installed-globally: "npm:^1.0.0" + is-npm: "npm:^6.0.0" + latest-version: "npm:^9.0.0" + pupa: "npm:^3.1.0" + semver: "npm:^7.6.2" + semver-diff: "npm:^4.0.0" + xdg-basedir: "npm:^5.1.0" + checksum: 10c0/8b18ac717dc6b0df4f71ea379bfab74d37efe983da6f25fe1de71fb8ec11a5fb12c991ccdcedbd5c38cf7dc22e116dd8e2798db63cc79951e973a1a4a9560a33 + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"url-join@npm:5.0.0": + version: 5.0.0 + resolution: "url-join@npm:5.0.0" + checksum: 10c0/ed2b166b4b5a98adcf6828a48b6bd6df1dac4c8a464a73cf4d8e2457ed410dd8da6be0d24855b86026cd7f5c5a3657c1b7b2c7a7c5b8870af17635a41387b04c + languageName: node + linkType: hard + +"utf8@npm:^3.0.0": + version: 3.0.0 + resolution: "utf8@npm:3.0.0" + checksum: 10c0/675d008bab65fc463ce718d5cae8fd4c063540f269e4f25afebce643098439d53e7164bb1f193e0c3852825c7e3e32fbd8641163d19a618dbb53f1f09acb0d5a + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "uuid@npm:10.0.0" + bin: + uuid: dist/bin/uuid + checksum: 10c0/eab18c27fe4ab9fb9709a5d5f40119b45f2ec8314f8d4cf12ce27e4c6f4ffa4a6321dc7db6c515068fa373c075b49691ba969f0010bf37f44c37ca40cd6bf7fe + languageName: node + linkType: hard + +"uuid@npm:^9.0.0": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b + languageName: node + linkType: hard + +"vali-date@npm:^1.0.0": + version: 1.0.0 + resolution: "vali-date@npm:1.0.0" + checksum: 10c0/5755215f6734caab535f60af0a32bbbf2052c61b1a40668d773df78fd3754e4fe9da2ea5466731505f3e0a599acc209d5578c4b70488ed120fb03f0c2ab06449 + languageName: node + linkType: hard + +"validate-npm-package-license@npm:^3.0.4": + version: 3.0.4 + resolution: "validate-npm-package-license@npm:3.0.4" + dependencies: + spdx-correct: "npm:^3.0.0" + spdx-expression-parse: "npm:^3.0.0" + checksum: 10c0/7b91e455a8de9a0beaa9fe961e536b677da7f48c9a493edf4d4d4a87fd80a7a10267d438723364e432c2fcd00b5650b5378275cded362383ef570276e6312f4f + languageName: node + linkType: hard + +"vite-node@npm:2.0.5": + version: 2.0.5 + resolution: "vite-node@npm:2.0.5" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.3.5" + pathe: "npm:^1.1.2" + tinyrainbow: "npm:^1.2.0" + vite: "npm:^5.0.0" + bin: + vite-node: vite-node.mjs + checksum: 10c0/affcc58ae8d45bce3e8bc3b5767acd57c24441634e2cd967cf97f4e5ed2bcead1714b60150cdf7ee153ebad47659c5cd419883207e1a95b69790331e3243749f + languageName: node + linkType: hard + +"vite-tsconfig-paths@npm:^5.0.1": + version: 5.0.1 + resolution: "vite-tsconfig-paths@npm:5.0.1" + dependencies: + debug: "npm:^4.1.1" + globrex: "npm:^0.1.2" + tsconfck: "npm:^3.0.3" + peerDependencies: + vite: "*" + peerDependenciesMeta: + vite: + optional: true + checksum: 10c0/3c68a4d5df21ed4ef81749c20e91c5978989ed06bffc01688b3f1a0fe65951b461a68f0c017ad930a088cfe7a8cc04d0c8d955dfb8719d5edc7fb0ba9bf38a73 + languageName: node + linkType: hard + +"vite@npm:^5.0.0": + version: 5.4.2 + resolution: "vite@npm:5.4.2" + dependencies: + esbuild: "npm:^0.21.3" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.41" + rollup: "npm:^4.20.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/23e347ca8aa6f0a774227e4eb7abae228f12c6806a727b046aa75e7ee37ffc2d68cff74360e12a42c347f79adc294e2363bc723b957bf4b382b5a8fb39e4df9d + languageName: node + linkType: hard + +"vitest@npm:^2.0.5": + version: 2.0.5 + resolution: "vitest@npm:2.0.5" + dependencies: + "@ampproject/remapping": "npm:^2.3.0" + "@vitest/expect": "npm:2.0.5" + "@vitest/pretty-format": "npm:^2.0.5" + "@vitest/runner": "npm:2.0.5" + "@vitest/snapshot": "npm:2.0.5" + "@vitest/spy": "npm:2.0.5" + "@vitest/utils": "npm:2.0.5" + chai: "npm:^5.1.1" + debug: "npm:^4.3.5" + execa: "npm:^8.0.1" + magic-string: "npm:^0.30.10" + pathe: "npm:^1.1.2" + std-env: "npm:^3.7.0" + tinybench: "npm:^2.8.0" + tinypool: "npm:^1.0.0" + tinyrainbow: "npm:^1.2.0" + vite: "npm:^5.0.0" + vite-node: "npm:2.0.5" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/node": ^18.0.0 || >=20.0.0 + "@vitest/browser": 2.0.5 + "@vitest/ui": 2.0.5 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/b4e6cca00816bf967a8589111ded72faa12f92f94ccdd0dcd0698ffcfdfc52ec662753f66b387549c600ac699b993fd952efbd99dc57fcf4d1c69a2f1022b259 + languageName: node + linkType: hard + +"wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: "npm:^1.0.3" + checksum: 10c0/5b61ca583a95e2dd85d7078400190efd452e05751a64accb8c06ce4db65d7e0b0cde9917d705e826a2e05cc2548f61efde115ffa374c3e436d04be45c889e5b4 + languageName: node + linkType: hard + +"web-streams-polyfill@npm:4.0.0-beta.3": + version: 4.0.0-beta.3 + resolution: "web-streams-polyfill@npm:4.0.0-beta.3" + checksum: 10c0/a9596779db2766990117ed3a158e0b0e9f69b887a6d6ba0779940259e95f99dc3922e534acc3e5a117b5f5905300f527d6fbf8a9f0957faf1d8e585ce3452e8e + languageName: node + linkType: hard + +"web-streams-polyfill@npm:^3.0.3": + version: 3.3.3 + resolution: "web-streams-polyfill@npm:3.3.3" + checksum: 10c0/64e855c47f6c8330b5436147db1c75cb7e7474d924166800e8e2aab5eb6c76aac4981a84261dd2982b3e754490900b99791c80ae1407a9fa0dcff74f82ea3a7f + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db + languageName: node + linkType: hard + +"webidl-conversions@npm:^4.0.2": + version: 4.0.2 + resolution: "webidl-conversions@npm:4.0.2" + checksum: 10c0/def5c5ac3479286dffcb604547628b2e6b46c5c5b8a8cfaa8c71dc3bafc85859bde5fbe89467ff861f571ab38987cf6ab3d6e7c80b39b999e50e803c12f3164f + languageName: node + linkType: hard + +"whatwg-fetch@npm:^3.6.20": + version: 3.6.20 + resolution: "whatwg-fetch@npm:3.6.20" + checksum: 10c0/fa972dd14091321d38f36a4d062298df58c2248393ef9e8b154493c347c62e2756e25be29c16277396046d6eaa4b11bd174f34e6403fff6aaca9fb30fa1ff46d + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 + languageName: node + linkType: hard + +"whatwg-url@npm:^7.0.0": + version: 7.1.0 + resolution: "whatwg-url@npm:7.1.0" + dependencies: + lodash.sortby: "npm:^4.7.0" + tr46: "npm:^1.0.1" + webidl-conversions: "npm:^4.0.2" + checksum: 10c0/2785fe4647690e5a0225a79509ba5e21fdf4a71f9de3eabdba1192483fe006fc79961198e0b99f82751557309f17fc5a07d4d83c251aa5b2f85ba71e674cbee9 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^4.0.0": + version: 4.0.0 + resolution: "which@npm:4.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a + languageName: node + linkType: hard + +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + +"widest-line@npm:^4.0.1": + version: 4.0.1 + resolution: "widest-line@npm:4.0.1" + dependencies: + string-width: "npm:^5.0.1" + checksum: 10c0/7da9525ba45eaf3e4ed1a20f3dcb9b85bd9443962450694dae950f4bdd752839747bbc14713522b0b93080007de8e8af677a61a8c2114aa553ad52bde72d0f9c + languageName: node + linkType: hard + +"wikipedia@npm:^2.1.2": + version: 2.1.2 + resolution: "wikipedia@npm:2.1.2" + dependencies: + axios: "npm:^1.4.0" + infobox-parser: "npm:^3.6.2" + checksum: 10c0/6256951596f3dcf2d01820e65e2d320d32116638c8f5270e6e9493278549ce2afa5cd2c794c876bf4c2fdd5fe4bb870cfaee53fe19528cdf3b3e4edb7a6bb477 + languageName: node + linkType: hard + +"wildcard-match@npm:5.1.3": + version: 5.1.3 + resolution: "wildcard-match@npm:5.1.3" + checksum: 10c0/55de1699d704c02db2b21014e2f2b1e40f2ea592f4df108d3f1ada1b2ff70ddd169c5ff5e775e5dd3f1d507adca1f3c3b23a0051c3ed28f8cd3722c23e214255 + languageName: node + linkType: hard + +"windows-release@npm:^5.0.1": + version: 5.1.1 + resolution: "windows-release@npm:5.1.1" + dependencies: + execa: "npm:^5.1.1" + checksum: 10c0/934fcd8620fc7cedec6939601c5735a9589d03fa0500874860e13797cc934d48771a97f677d5c162b4c8d72a594bbd522a69b6a1fcd0bc7ff8dfbbdbc1146ba5 + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 + languageName: node + linkType: hard + +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 10c0/7ed2e44f3c33c5c3e3771134d2b0aee4314c9e49c749e37f464bf69f2bcdf0cbf9419ca638098e2717cff4875c47f56a007532f6111c3319f557a2ca91278e92 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + +"wrap-ansi@npm:^9.0.0": + version: 9.0.0 + resolution: "wrap-ansi@npm:9.0.0" + dependencies: + ansi-styles: "npm:^6.2.1" + string-width: "npm:^7.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/a139b818da9573677548dd463bd626a5a5286271211eb6e4e82f34a4f643191d74e6d4a9bb0a3c26ec90e6f904f679e0569674ac099ea12378a8b98e20706066 + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"write-file-atomic@npm:^3.0.3": + version: 3.0.3 + resolution: "write-file-atomic@npm:3.0.3" + dependencies: + imurmurhash: "npm:^0.1.4" + is-typedarray: "npm:^1.0.0" + signal-exit: "npm:^3.0.2" + typedarray-to-buffer: "npm:^3.1.5" + checksum: 10c0/7fb67affd811c7a1221bed0c905c26e28f0041e138fb19ccf02db57a0ef93ea69220959af3906b920f9b0411d1914474cdd90b93a96e5cd9e8368d9777caac0e + languageName: node + linkType: hard + +"xdg-basedir@npm:^5.0.1, xdg-basedir@npm:^5.1.0": + version: 5.1.0 + resolution: "xdg-basedir@npm:5.1.0" + checksum: 10c0/c88efabc71ffd996ba9ad8923a8cc1c7c020a03e2c59f0ffa72e06be9e724ad2a0fccef488757bc6ed3d8849d753dd25082d1035d95cb179e79eae4d034d0b80 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yaml-ast-parser@npm:0.0.43": + version: 0.0.43 + resolution: "yaml-ast-parser@npm:0.0.43" + checksum: 10c0/4d2f1e761067b2c6abdd882279a406f879258787af470a6d4a659cb79cb2ab056b870b25f1f80f46ed556e8b499d611d247806376f53edf3412f72c0a8ea2e98 + languageName: node + linkType: hard + +"yaml@npm:^2.2.1, yaml@npm:^2.3.3, yaml@npm:^2.5.0, yaml@npm:~2.5.0": + version: 2.5.0 + resolution: "yaml@npm:2.5.0" + bin: + yaml: bin.mjs + checksum: 10c0/771a1df083c8217cf04ef49f87244ae2dd7d7457094425e793b8f056159f167602ce172aa32d6bca21f787d24ec724aee3cecde938f6643564117bd151452631 + languageName: node + linkType: hard + +"yargs-parser@npm:21.1.1, yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard + +"yocto-queue@npm:^1.0.0": + version: 1.1.1 + resolution: "yocto-queue@npm:1.1.1" + checksum: 10c0/cb287fe5e6acfa82690acb43c283de34e945c571a78a939774f6eaba7c285bacdf6c90fbc16ce530060863984c906d2b4c6ceb069c94d1e0a06d5f2b458e2a92 + languageName: node + linkType: hard + +"yoctocolors-cjs@npm:^2.1.1": + version: 2.1.2 + resolution: "yoctocolors-cjs@npm:2.1.2" + checksum: 10c0/a0e36eb88fea2c7981eab22d1ba45e15d8d268626e6c4143305e2c1628fa17ebfaa40cd306161a8ce04c0a60ee0262058eab12567493d5eb1409780853454c6f + languageName: node + linkType: hard + +"zod-to-json-schema@npm:^3.22.3, zod-to-json-schema@npm:^3.22.5, zod-to-json-schema@npm:^3.23.2": + version: 3.23.2 + resolution: "zod-to-json-schema@npm:3.23.2" + peerDependencies: + zod: ^3.23.3 + checksum: 10c0/7c9a4756a5eba231d2c354528eb8338a96489a2f804d1a8619e1baa10dde4178fc9f1b4f369e965d858b19a226dcb3375e5b38b5e822c52e2d7ad529d2a6912d + languageName: node + linkType: hard + +"zod@npm:^3.22.3, zod@npm:^3.22.4, zod@npm:^3.23.8": + version: 3.23.8 + resolution: "zod@npm:3.23.8" + checksum: 10c0/8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69 + languageName: node + linkType: hard