diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md old mode 100755 new mode 100644 diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index e558827c0..000000000 --- a/.golangci.yml +++ /dev/null @@ -1,92 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You 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. -# - -linters-settings: - govet: - shadow: true - golint: - min-confidence: 0 - gocyclo: - min-complexity: 10 - maligned: - suggest-new: true - dupl: - threshold: 100 - goconst: - min-len: 2 - min-occurrences: 2 - depguard: - list-type: blacklist - packages: - # logging is allowed only by logutils.Log, logrus - # is allowed to use only in logutils package - - github.com/sirupsen/logrus - misspell: - locale: US - lll: - line-length: 140 - goimports: - local-prefixes: github.com/golangci/golangci-lint - gocritic: - enabled-tags: - - performance - - style - - experimental - disabled-checks: - - wrapperFunc - -linters: - disable-all: true - enable: - - govet - - staticcheck - - ineffassign - - misspell - - asciicheck - - bodyclose - - rowserrcheck - - gofmt - - durationcheck - - sqlclosecheck - -run: - -issues: - exclude-dirs: - - test/testdata_etc - - pkg/golinters/goanalysis/(checker|passes) - exclude-rules: - - text: "weak cryptographic primitive" - linters: - - gosec - - linters: - - staticcheck - text: "SA1019:" - - path: _test\.go - linters: - - errcheck - - gosec - - rowserrcheck - - govet - -# golangci.com configuration -# https://github.com/golangci/golangci/wiki/Configuration -service: - golangci-lint-version: 1.57.x # use the fixed version to not introduce new linters unexpectedly - prepare: - - echo "here I can run custom commands, but no preparation needed for this repo" - diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..35410cacd --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/incubator-seata-go.iml b/.idea/incubator-seata-go.iml new file mode 100644 index 000000000..5e764c4f0 --- /dev/null +++ b/.idea/incubator-seata-go.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..b9ca23b2c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.licenserc.yaml b/.licenserc.yaml deleted file mode 100644 index c42dae304..000000000 --- a/.licenserc.yaml +++ /dev/null @@ -1,78 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You 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. -# - -header: # `header` section is configurations for source codes license header. - license: - spdx-id: Apache-2.0 # the spdx id of the license, it's convenient when your license is standard SPDX license. - copyright-owner: Apache Software Foundation # the copyright owner to replace the [owner] in the `spdx-id` template. - content: | # `license` will be used as the content when `fix` command needs to insert a license header. - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You 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. - # `pattern` is optional regexp if all the file headers are the same as `license` or the license of `spdx-id` and `copyright-owner`. - pattern: | - Licensed to the Apache Software Foundation under one or more contributor - license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright - ownership. The Apache Software Foundation licenses this file to you 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. - paths: # `paths` are the path list that will be checked (and fixed) by license-eye, default is ['**']. - - '**' - - paths-ignore: # `paths-ignore` are the path list that will be ignored by license-eye. - - 'licenses' - - '**/go.mod' - - '**/go.sum' - - 'LICENSE' - - 'DISCLAIMER' - - 'NOTICE' - - '.github' - comment: on-failure # on what condition license-eye will comment on the pull request, `on-failure`, `always`, `never`. - - language: - Go: - extensions: - - ".go" - comment_style_id: SlashAsterisk - - # license-location-threshold specifies the index threshold where the license header can be located, - # after all, a "header" cannot be TOO far from the file start. - license-location-threshold: 80 - -dependency: - files: - - go.mod diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index f3d90ad45..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,212 +0,0 @@ - -# Contributing to seata-go - -It is warmly welcomed if you have interest to hack on seata-go. First, we encourage this kind of willing very much. And here is a list of contributing guide for you. - -[[中文贡献文档](./CONTRIBUTING_CN.md)] - -## Topics - -* [Reporting general issues](#reporting-general-issues) -* [Code and doc contribution](#code-and-doc-contribution) -* [Test case contribution](#test-case-contribution) -* [Engage to help anything](#engage-to-help-anything) -* [Code Style](#code-style) - -## Reporting general issues - -To be honest, we regard every user of seata-go as a very kind contributor. After experiencing seata-go, you may have some feedback for the project. Then feel free to open an issue via [NEW ISSUE](https://github.com/apache/incubator-seata-go/issues/new/choose). - -Since we collaborate project seata-go in a distributed way, we appreciate **WELL-WRITTEN**, **DETAILED**, **EXPLICIT** issue reports. To make the communication more efficient, we wish everyone could search if your issue is an existing one in the searching list. If you find it existing, please add your details in comments under the existing issue instead of opening a brand new one. - -To make the issue details as standard as possible, we setup an [ISSUE TEMPLATE](./.github/ISSUE_TEMPLATE) for issue reporters. Please **BE SURE** to follow the instructions to fill fields in template. - -There are a lot of cases when you could open an issue: - -* bug report -* feature request -* performance issues -* feature proposal -* feature design -* help wanted -* doc incomplete -* test improvement -* any questions on project -* and so on - -Also we must remind that when filling a new issue, please remember to remove the sensitive data from your post. Sensitive data could be password, secret key, network locations, private business data and so on. - -## Code and doc contribution - -Every action to make project seata-go better is encouraged. On GitHub, every improvement for seata-go could be via a PR (short for pull request). - -* If you find a typo, try to fix it! -* If you find a bug, try to fix it! -* If you find some redundant codes, try to remove them! -* If you find some test cases missing, try to add them! -* If you could enhance a feature, please **DO NOT** hesitate! -* If you find code implicit, try to add comments to make it clear! -* If you find code ugly, try to refactor that! -* If you can help to improve documents, it could not be better! -* If you find document incorrect, just do it and fix that! -* ... - -Actually it is impossible to list them completely. Just remember one principle: - -> WE ARE LOOKING FORWARD TO ANY PR FROM YOU. - -Since you are ready to improve seata-go with a PR, we suggest you could take a look at the PR rules here. - -* [Workspace Preparation](#workspace-preparation) -* [Branch Definition](#branch-definition) -* [Commit Rules](#commit-rules) -* [PR Description](#pr-description) - -### Workspace Preparation - -To put forward a PR, we assume you have registered a GitHub ID. Then you could finish the preparation in the following steps: - -1. **FORK** seata-go to your repository. To make this work, you just need to click the button Fork in right-left of [seata/seata](https://github.com/apache/incubator-seata-go) main page. Then you will end up with your repository in `https://github.com//seata-go`, in which `your-username` is your GitHub username. - -1. **CLONE** your own repository to develop locally. Use `git clone git@github.com:/seata-go.git` to clone repository to your local machine. Then you can create new branches to finish the change you wish to make. - -1. **Set Remote** upstream to be `git@github.com:apache/seata-go.git` using the following two commands: - -```bash -git remote add upstream git@github.com:apache/seata-go.git -git remote set-url --push upstream no-pushing -``` - -With this remote setting, you can check your git remote configuration like this: - -```shell -$ git remote -v -origin git@github.com:/seata-go.git (fetch) -origin git@github.com:/seata-go.git (push) -upstream git@github.com:apache/seata-go.git (fetch) -upstream no-pushing (push) -``` - -Adding this, we can easily synchronize local branches with upstream branches. - -### Branch Definition - -Right now we assume every contribution via pull request is for [branch develop](https://github.com/apache/incubator-seata-go/tree/master) in seata-go. Before contributing, be aware of branch definition would help a lot. - -As a contributor, keep in mind again that every contribution via pull request is for branch develop. While in project seata-go, there are several other branches, we generally call them release branches(such as 0.6.0,0.6.1), feature branches, hotfix branches and master branch. - -When officially releasing a version, there will be a release branch and named with the version number. - -After the release, we will merge the commit of the release branch into the master branch. - -When we find that there is a bug in a certain version, we will decide to fix it in a later version or fix it in a specific hotfix version. When we decide to fix the hotfix version, we will checkout the hotfix branch based on the corresponding release branch, perform code repair and verification, and merge it into the develop branch and the master branch. - -For larger features, we will pull out the feature branch for development and verification. - - -### Commit Rules - -Actually in seata-go, we take two rules serious when committing: - -* [Commit Message](#commit-message) -* [Commit Content](#commit-content) - -#### Commit Message - -Commit message could help reviewers better understand what is the purpose of submitted PR. It could help accelerate the code review procedure as well. We encourage contributors to use **EXPLICIT** commit message rather than ambiguous message. In general, we advocate the following commit message type: - -* docs: xxxx. For example, "docs: add docs about seata-go cluster installation". -* feature: xxxx.For example, "feature: support oracle in AT mode". -* bugfix: xxxx. For example, "bugfix: fix panic when input nil parameter". -* optimize: xxxx. For example, "optimize: simplify to make codes more readable". -* test: xxx. For example, "test: add unit test case for func InsertIntoArray". -* other readable and explicit expression ways. - -On the other side, we discourage contributors from committing message like the following ways: - -* ~~fix bug~~ -* ~~update~~ -* ~~add doc~~ - -If you get lost, please see [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/) for a start. - -#### Commit Content - -Commit content represents all content changes included in one commit. We had better include things in one single commit which could support reviewer's complete review without any other commits' help. In another word, contents in one single commit can pass the CI to avoid code mess. In brief, there are three minor rules for us to keep in mind: - -* avoid very large change in a commit; -* complete and reviewable for each commit. -* check git config(`user.name`, `user.email`) when committing to ensure that it is associated with your GitHub ID. - -```bash -git config --get user.name -git config --get user.email -``` - -* when submitting pr, please add a brief description of the current changes to the dev.md file under the 'changes/' folder - - -In addition, in the code change part, we suggest that all contributors should read the [code style of seata-go](#code-style). - -No matter commit message, or commit content, we do take more emphasis on code review. - - -#### Format your code - -Before submitting the code, execute the script of formatting the code under the project root directory: - -~~~shell -sh goimports.sh -~~~ - -### PR Description - -PR is the only way to make change to seata-go project files. To help reviewers better get your purpose, PR description could not be too detailed. We encourage contributors to follow the [PR template](./.github/pull-request-template.md) to finish the pull request. - -## Test case contribution - -Any test case will be welcomed. Currently, seata-go functional test cases are of high priority. - -- For unit tests, create a XXX_test.go file in the file directory of go file - -## Engage to help anything - -We choose GitHub as the primary place for seata-go to collaborate. So the latest updates of seata-go are always here. Although contributions via PR is an explicit way to help, we still call for any other ways. - -* reply to other's issues if you could; -* help solve other user's problems; -* help review other's PR design; -* help review other's codes in PR; -* discuss about seata-go to make things clearer; -* advocate seata-go technology beyond GitHub; -* write blogs on seata-go and so on. - - -## Code Style - -Seata-go code style reference [uber-go/guide](https://github.com/uber-go/guide) 。 - - -### IDE Plugin Install(not necessary) - -*It is not necessary to install, if you want to find a problem when you are coding.* - -install go fmt 和 goimports plugin,detailed reference:https://github.com/golang/tools - - -In a word, **ANY HELP IS CONTRIBUTION.** diff --git a/CONTRIBUTING_CN.md b/CONTRIBUTING_CN.md deleted file mode 100644 index 2a2a596e7..000000000 --- a/CONTRIBUTING_CN.md +++ /dev/null @@ -1,210 +0,0 @@ - - -# 为 seata-go 做贡献 - -如果你有兴趣为 seata-go 贡献代码,我们会热烈欢迎。首先,我们非常鼓励这种意愿。这是为您提供的贡献指南列表。 - -[[English Contributing Document](./CONTRIBUTING.md)] - -## 话题 - -* [报告一般问题](#报告一般问题) -* [代码和文档贡献](#代码和文档贡献) -* [测试用例贡献](#测试用例贡献) -* [参与帮助任何事情](#参与帮助任何事情) -* [代码风格](#代码风格) - -## 报告一般问题 - -老实说我们把每一个 seata-go 用户都视为非常善良的贡献者。在体验了 seata-go 之后,您可能会对项目有一些反馈。然后随时通过 [NEW ISSUE](https://github.com/apache/incubator-seata-go/issues/new/choose)打开一个问题。 - -因为我们在一个分布式的方式合作项目 seata-go,我们欣赏写得很好的,详细的,准确的问题报告。为了让沟通更高效,我们希望每个人都可以搜索您的问题是否在搜索列表中。如果您发现它存在,请在现有问题下的评论中添加您的详细信息,而不是打开一个全新的问题。 - -为了使问题细节尽可能标准,我们为问题报告者设置了一个[问题模板](./.github/ISSUE_TEMPLATE) 请务必按照说明填写模板中的字段。 - -有很多情况你可以打开一个问题: - -* 错误报告 -* 功能要求 -* 性能问题 -* 功能提案 -* 功能设计 -* 需要帮助 -* 文档不完整 -* 测试改进 -* 关于项目的任何问题 -* 等等 - -另外我们必须提醒的是,在填写新问题时,请记住从您的帖子中删除敏感数据。敏感数据可能是密码、密钥、网络位置、私人业务数据等。 - -## 代码和文档贡献 - -鼓励采取一切措施使 seata-go 项目变得更好。在 GitHub 上,seata-go 的每项改进都可以通过 PR(Pull Request 的缩写)实现。 - -* 如果您发现错别字,请尝试修复它! -* 如果您发现错误,请尝试修复它! -* 如果您发现一些多余的代码,请尝试删除它们! -* 如果您发现缺少一些测试用例,请尝试添加它们! -* 如果您可以增强功能,请**不要**犹豫! -* 如果您发现代码晦涩难懂,请尝试添加注释以使其更加易读! -* 如果您发现代码丑陋,请尝试重构它! -* 如果您能帮助改进文档,那就再好不过了! -* 如果您发现文档不正确,只需执行并修复它! -* ... - -实际上不可能完整地列出它们。记住一个原则: - -> 我们期待您的任何PR。 - -由于您已准备好通过 PR 改进 seata-go,我们建议您可以在此处查看 PR 规则。 - -* [工作区准备](#工作区准备) -* [分支定义](#分支定义) -* [提交规则](#提交规则) -* [PR说明](#PR说明) - -### 工作区准备 - -为了提出 PR,我们假设你已经注册了一个 GitHub ID。然后您可以通过以下步骤完成准备工作: - -1. **FORK** seata-go 到您的存储库。要完成这项工作,您只需单击 [apache/seata-go](https://github.com/apache/incubator-seata-go) 主页右侧的 Fork 按钮。然后你将在 中得到你的存储库`https://github.com//seata-go`,其中 your-username 是你的 GitHub 用户名。 - -2. **克隆** 您自己的存储库以在本地开发. 用于 `git clone git@github.com:/seata-go.git` 将存储库克隆到本地计算机。 然后您可以创建新分支来完成您希望进行的更改。 - -3. **设置远程** 将上游设置为 `git@github.com:apache/seata-go.git` 使用以下两个命令: - -```bash -git remote add upstream git@github.com:apache/seata-go.git -git remote set-url --push upstream no-pushing -``` - -使用此远程设置,您可以像这样检查您的 git 远程配置: - -```shell -$ git remote -v -origin git@github.com:/seata-go.git (fetch) -origin git@github.com:/seata-go.git (push) -upstream git@github.com:apache/seata-go.git (fetch) -upstream no-pushing (push) -``` - -添加这个,我们可以轻松地将本地分支与上游分支同步。 - -### 分支定义 - -现在我们假设通过拉取请求的每个贡献都是针对 seata-go 中的 [开发分支](https://github.com/apache/incubator-seata-go/tree/master) 。在贡献之前,请注意分支定义会很有帮助。 - -作为贡献者,请再次记住,通过拉取请求的每个贡献都是针对分支开发的。而在 seata-go 项目中,还有其他几个分支,我们一般称它们为 release 分支(如0.6.0、0.6.1)、feature 分支、hotfix 分支和 master 分支。 - -当正式发布一个版本时,会有一个发布分支并以版本号命名。 - -在发布之后,我们会将发布分支的提交合并到主分支中。 - -当我们发现某个版本有 bug 时,我们会决定在以后的版本中修复它,或者在特定的 hotfix 版本中修复它。当我们决定修复 hotfix 版本时,我们会根据对应的 release 分支 checkout hotfix 分支,进行代码修复和验证,合并到 develop 分支和 master 分支。 - -对于较大的功能,我们将拉出功能分支进行开发和验证。 - - -### 提交规则 - -实际上,在 seata-go 中,我们在提交时会认真对待两条规则: - -* [提交消息](#提交消息) -* [提交内容](#提交内容) - -#### 提交消息 - -提交消息可以帮助审稿人更好地理解提交 PR 的目的是什么。它还可以帮助加快代码审查过程。我们鼓励贡献者使用显式的提交信息,而不是模糊的信息。一般来说,我们提倡以下提交消息类型: - -* docs: xxxx. For example, "docs: add docs about seata-go cluster installation". -* feature: xxxx.For example, "feature: support oracle in AT mode". -* bugfix: xxxx. For example, "bugfix: fix panic when input nil parameter". -* optimize: xxxx. For example, "optimize: simplify to make codes more readable". -* test: xxx. For example, "test: add unit test case for func InsertIntoArray". -* 其他可读和显式的表达方式。 - -另一方面,我们不鼓励贡献者通过以下方式提交消息: - -* ~~修复错误~~ -* ~~更新~~ -* ~~添加文档~~ - -如果你不知道该怎么做,请参阅 [如何编写 Git 提交消息](http://chris.beams.io/posts/git-commit/) 作为开始。 - -#### 提交内容 - -提交内容表示一次提交中包含的所有内容更改。我们最好在一次提交中包含可以支持审阅者完整审查的内容,而无需任何其他提交的帮助。换句话说,一次提交中的内容可以通过 CI 以避免代码混乱。简而言之,我们需要牢记三个小规则: - -* 避免在提交中进行非常大的更改; -* 每次提交都完整且可审查。 -* 提交时检查 git config(`user.name`, `user.email`) 以确保它与您的 GitHub ID 相关联。 - -```bash -git config --get user.name -git config --get user.email -``` - -* 提交pr时,请在'changes/'文件夹下的dev.md文件中添加当前更改的简要说明 - - -另外,在代码变更部分,我们建议所有贡献者阅读 seata-go 的 [代码风格](#代码风格)。 - -无论是提交信息,还是提交内容,我们都更加重视代码审查。 - -#### 格式化代码 - -提交代码之前,在项目根目录下执行格式化代码的脚本: - -~~~shell -sh goimports.sh -~~~ - - -### PR说明 - -PR 是更改 seata-go 项目文件的唯一方法。为了帮助审查人更好地理解你的目的,PR 描述不能太详细。我们鼓励贡献者遵循 [PR 模板](./.github/PULL_REQUEST_TEMPLATE.md) 来完成拉取请求。 - -## 测试用例贡献 - -任何测试用例都会受到欢迎。目前,seata-go 功能测试用例是高优先级的。 - -* 对于单元测试,在文件目录下创建一个 xxx_test.go 文件即可 - -## 参与帮助任何事情 - -我们选择 GitHub 作为 seata-go 协作的主要场所。所以 seata-go 的最新更新总是在这里。尽管通过 PR 贡献是一种明确的帮助方式,但我们仍然呼吁其他方式。 - -* 如果可以的话,回复别人的问题; -* 帮助解决其他用户的问题; -* 帮助审查他人的 PR 设计; -* 帮助审查其他人在 PR 中的代码; -* 讨论 seata-go以使事情更清楚; -* 在Github之外宣传 seata-go 技术; -* 写关于 seata-go 的博客等等。 - - -## 代码风格 - -Seata-go 代码风格参考[uber-go/guide](https://github.com/uber-go/guide) 。 - - -### IDE插件安装(非必须) - -*没有必要安装,如果你想在编码的时候发现问题。* - -安装 go fmt 和 goimports 插件,详情参考:https://github.com/golang/tools diff --git a/DISCLAIMER b/DISCLAIMER deleted file mode 100644 index 1add77f78..000000000 --- a/DISCLAIMER +++ /dev/null @@ -1,10 +0,0 @@ -Apache Seata (incubating) is an effort undergoing incubation at the Apache -Software Foundation (ASF), sponsored by the Apache Incubator PMC. - -Incubation is required of all newly accepted projects until a further review -indicates that the infrastructure, communications, and decision making process -have stabilized in a manner consistent with other successful ASF projects. - -While incubation status is not necessarily a reflection of the completeness -or stability of the code, it does indicate that the project has yet to be -fully endorsed by the ASF. \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 261eeb9e9..000000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - 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/NOTICE b/NOTICE deleted file mode 100644 index 3cd4a947b..000000000 --- a/NOTICE +++ /dev/null @@ -1,5 +0,0 @@ -Apache Seata (Incubating) -Copyright 2023-2025 The Apache Software Foundation - -This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). diff --git a/README.md b/README.md deleted file mode 100644 index 31a205000..000000000 --- a/README.md +++ /dev/null @@ -1,160 +0,0 @@ - -
- -
- -# Seata-go: Simple Extensible Autonomous Transaction Architecture(Go version) - -[![CI](https://github.com/apache/incubator-seata-go/actions/workflows/build.yml/badge.svg)](https://github.com/apache/incubator-seata-go/actions/workflows/build.yml) [![license](https://img.shields.io/github/license/apache/incubator-seata-go.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) [![codecov](https://codecov.io/gh/apache/incubator-seata-go/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/incubator-seata-go) [简体中文 ZH](./README_ZH.md) - -## What is Seata-go? - -Apache Seata(incubating) is a very mature distributed transaction framework, and is the de facto standard platform for distributed transaction technology in the Java field. Seata-go is the implementation version of go language in Seata multilingual ecosystem, which realizes the interoperability between Java and Go, so that Go developers can also use seata-go to realize distributed transactions. Please visit the [official website of Seata](https://seata.apache.org/) to view the quick start and documentation. - -The principle of seata-go is consistent with that of Seata-java, which is composed of TM, RM and TC. The functions of TC are reused in Java, and the functions of TM and RM have been connected with Seata-java. -### Distributed Transaction Problem in Microservices - -Let's imagine a traditional monolithic application. Its business is built up with 3 modules. They use a single local data source. - -Naturally, data consistency will be guaranteed by the local transaction. - -![Monolithic App](https://img.alicdn.com/imgextra/i3/O1CN01FTtjyG1H4vvVh1sNY_!!6000000000705-0-tps-1106-678.jpg) - -Things have changed in a microservices architecture. The 3 modules mentioned above are designed to be 3 services on top of 3 different data sources ([Pattern: Database per service](http://microservices.io/patterns/data/database-per-service.html)). Data consistency within every single service is naturally guaranteed by the local transaction. - -**But how about the whole business logic scope?** - -![Microservices Problem](https://img.alicdn.com/imgextra/i1/O1CN01DXkc3o1te9mnJcHOr_!!6000000005926-0-tps-1268-804.jpg) - -### How Seata-go do? - -Seata-go is just a solution to the problem mentioned above. - -![Seata solution](https://img.alicdn.com/imgextra/i1/O1CN01FheliH1k5VHIRob3p_!!6000000004632-0-tps-1534-908.jpg) - -Firstly, how to define a **Distributed Transaction**? - -We say, a **Distributed Transaction** is a **Global Transaction** which is made up with a batch of **Branch Transaction**, and normally **Branch Transaction** is just **Local Transaction**. - -![Global & Branch](https://cdn.nlark.com/lark/0/2018/png/18862/1545015454979-a18e16f6-ed41-44f1-9c7a-bd82c4d5ff99.png) - -There are three roles in Seata-go: - -- **Transaction Coordinator(TC):** Maintain status of global and branch transactions, drive the global commit or rollback. -- **Transaction Manager(TM):** Define the scope of global transaction: begin a global transaction, commit or rollback a global transaction. -- **Resource Manager(RM):** Manage resources that branch transactions working on, talk to TC for registering branch transactions and reporting status of branch transactions, and drive the branch transaction commit or rollback. - -![Model](https://cdn.nlark.com/lark/0/2018/png/18862/1545013915286-4a90f0df-5fda-41e1-91e0-2aa3d331c035.png) - -A typical lifecycle of Seata-go managed distributed transaction: - -1. TM asks TC to begin a new global transaction. TC generates an XID representing the global transaction. -2. XID is propagated through microservices' invoke chain. -3. RM registers local transaction as a branch of the corresponding global transaction of XID to TC. -4. TM asks TC for committing or rollbacking the corresponding global transaction of XID. -5. TC drives all branch transactions under the corresponding global transaction of XID to finish branch committing or rollbacking. - -![Typical Process](https://cdn.nlark.com/lark/0/2018/png/18862/1545296917881-26fabeb9-71fa-4f3e-8a7a-fc317d3389f4.png) - -For more details about principle and design, please go to [Seata wiki page](https://github.com/apache/incubator-seata/wiki). - -### History - -##### Alibaba - -- **TXC**: Taobao Transaction Constructor. Alibaba middleware team started this project since 2014 to meet the distributed transaction problems caused by application architecture change from monolithic to microservices. -- **GTS**: Global Transaction Service. TXC as an Aliyun middleware product with new name GTS was published since 2016. -- **Fescar**: we started the open source project Fescar based on TXC/GTS since 2019 to work closely with the community in the future. - - -##### Ant Financial - -- **XTS**: Extended Transaction Service. Ant Financial middleware team developed the distributed transaction middleware since 2007, which is widely used in Ant Financial and solves the problems of data consistency across databases and services. - -- **DTX**: Distributed Transaction Extended. Since 2013, XTS has been published on the Ant Financial Cloud, with the name of DTX . - - -##### Seata Community - -- **Seata** :Simple Extensible Autonomous Transaction Architecture. Ant Financial joins Fescar, which make it to be a more neutral and open community for distributed transaction, and Fescar be renamed to Seata. - - - -## How to run? - -```go -go get seata.apache.org/seata-go@v2.0.0 - -``` - -if you want to know how to use and integrate seata-go, please refer to [apache/seata-go-samples](https://github.com/apache/incubator-seata-go-samples) - -## How to find the latest version -Visit Seata-Go's GitHub Releases page - -Open: -https://github.com/seata/seata-go/releases - -The latest tag / release is the latest stable version. - - -## Documentation - - -You can view the full documentation from Seata Official Website: [Seata Website page](https://seata.apache.org/zh-cn/docs/overview/what-is-seata). - -## Reporting bugs - -Please follow the [template](.github/ISSUE_TEMPLATE/BUG_REPORT_TEMPLATE.md) for reporting any issues. - -## Security - -Please do not use our public issue tracker but refer to our [security policy](https://github.com/apache/incubator-seata/blob/2.x/SECURITY.md) - -## Contributing - -Seata-go is currently in the construction stage. Welcome colleagues in the industry to join the group and work with us to promote the construction of seata-go! If you want to contribute code to seata-go, you can refer to the [**code contribution Specification**](./CONTRIBUTING_CN.md) document to understand the specifications of the community, or you can join our community DingTalk group: 33069364 and communicate together! - - -## Contact - -* Mailing list: - * dev@seata.apache.org , for dev/user discussion. [subscribe](mailto:dev-subscribe@seata.apache.org), [unsubscribe](mailto:dev-unsubscribe@seata.apache.org), [archive](https://lists.apache.org/list.html?dev@seata.apache.org) -* Online chat: - -| Dingtalk group | Wechat official account | QQ group | Wechat assistant | -|:---------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------:| -| | | | | - -## Seata ecosystem - -* [Seata Website](https://github.com/apache/incubator-seata.github.io) - Seata official website -* [Seata](https://github.com/apache/incubator-seata)- Seata client and server -* [Seata GoLang](https://github.com/apache/incubator-seata-go) - Seata GoLang client and server -* [Seata Samples](https://github.com/apache/incubator-seata-samples) - Samples for Seata -* [Seata GoLang Samples](https://github.com/apache/incubator-seata-go-samples) - Samples for Seata GoLang -* [Seata K8s](https://github.com/apache/incubator-seata-k8s) - Seata integration with k8s -* [Seata CLI](https://github.com/apache/incubator-seata-ctl) - CLI tool for Seata - -## Contributors - -This project exists thanks to all the people who contribute. [[Contributors](https://github.com/apache/incubator-seata-go/graphs/contributors)]. - -## License - -Seata-go is under the Apache 2.0 license. See the [LICENSE](https://github.com/apache/incubator-seata-go/blob/master/LICENSE) file for details. diff --git a/README_ZH.md b/README_ZH.md deleted file mode 100644 index 818cb49f1..000000000 --- a/README_ZH.md +++ /dev/null @@ -1,164 +0,0 @@ - - -
- - - -
-# Seata-go:简单可扩展的自主事务架构(Go 语言版本) - -[![CI](https://github.com/apache/incubator-seata-go/actions/workflows/build.yml/badge.svg)](https://github.com/apache/incubator-seata-go/actions/workflows/build.yml) [![license](https://img.shields.io/github/license/apache/incubator-seata-go.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) [![codecov](https://codecov.io/gh/apache/incubator-seata-go/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/incubator-seata-go) [English](./README.md) - - -## 什么是 Seata-go? - - -Apache Seata(incubating)是一个非常成熟的分布式事务框架,是 Java 领域事实上的标准分布式事务平台。Seata-go 是其在多语言生态中 Go 语言的实现版本,实现了 Java 与 Go 之间的互操作,使得 Go 开发者也可以使用 seata-go 实现分布式事务。请访问 [Seata 官网](https://seata.apache.org/) 获取快速入门和文档。 - -Seata-go 的原理与 Seata-java 一致,由 TM、RM 和 TC 三部分组成。其中 TC 功能复用 Java 的实现,而 TM 和 RM 则已与 Seata-java 对接。 - -### 微服务中的分布式事务问题 - -假设我们有一个传统的单体应用,其业务由三个模块组成,使用一个本地数据源,自然由本地事务保障数据一致性。 -![Monolithic App](https://img.alicdn.com/imgextra/i3/O1CN01FTtjyG1H4vvVh1sNY_!!6000000000705-0-tps-1106-678.jpg) -但在微服务架构中,这三个模块被设计成了三个不同的服务,分别使用不同的数据源([数据库每服务模式](http://microservices.io/patterns/data/database-per-service.html))。单个服务内的数据一致性可以通过本地事务保障。 - -但**整个业务范围的一致性如何保障?** -![Microservices Problem](https://img.alicdn.com/imgextra/i1/O1CN01DXkc3o1te9mnJcHOr_!!6000000005926-0-tps-1268-804.jpg) - - -### Seata-go 如何做? - -Seata-go 就是为了解决上述问题而生。 -![Seata solution](https://img.alicdn.com/imgextra/i1/O1CN01FheliH1k5VHIRob3p_!!6000000004632-0-tps-1534-908.jpg) -首先,如何定义一个**分布式事务**? -我们认为,分布式事务是一个**全局事务**,由一组**分支事务**组成,而**分支事务**通常就是**本地事务**。 -![Global & Branch](https://cdn.nlark.com/lark/0/2018/png/18862/1545015454979-a18e16f6-ed41-44f1-9c7a-bd82c4d5ff99.png) - - -Seata-go 中有三个角色: - -- **事务协调器(TC)**:维护全局和分支事务的状态,驱动全局提交或回滚。 - -- **事务管理器(TM)**:定义全局事务的范围,开始、提交或回滚全局事务。 - -- **资源管理器(RM)**:管理分支事务所处理的资源,与 TC 通信注册分支事务并报告状态,驱动分支事务提交或回滚。 - ![Model](https://cdn.nlark.com/lark/0/2018/png/18862/1545013915286-4a90f0df-5fda-41e1-91e0-2aa3d331c035.png) - - -Seata-go 分布式事务的典型生命周期如下: - - - -1. TM 请求 TC 开启一个新的全局事务,TC 生成表示全局事务的 XID。 - -2. XID 在微服务调用链中传播。 - -3. RM 将本地事务注册为该 XID 对应的全局事务的分支事务。 - -4. TM 请求 TC 提交或回滚该 XID 对应的全局事务。 - -5. TC 驱动该 XID 对应的所有分支事务进行提交或回滚。 - -![Typical Process](https://cdn.nlark.com/lark/0/2018/png/18862/1545296917881-26fabeb9-71fa-4f3e-8a7a-fc317d3389f4.png) - -更多原理和设计细节,请参阅 [Seata Wiki 页面](https://github.com/apache/incubator-seata/wiki)。 - - - -### 历史背景 - -##### 阿里巴巴 - -- **TXC**:淘宝事务构建器。2014 年阿里中间件团队启动,用于应对从单体架构转向微服务带来的分布式事务问题。 - -- **GTS**:全局事务服务。2016 年 TXC 在阿里云上线,更名为 GTS。 - -- **Fescar**:2019 年开始基于 TXC/GTS 启动开源项目 Fescar,与社区合作。 - -##### 蚂蚁金服 - -- **XTS**:扩展事务服务。自 2007 年开始开发的分布式事务中间件,被广泛应用于蚂蚁金服,解决跨库跨服务数据一致性问题。 - -- **DTX**:分布式事务扩展。2013 年起在蚂蚁金服云发布,命名为 DTX。 -##### Seata 社区 - -- **Seata**:简单可扩展的自主事务架构。蚂蚁金服加入 Fescar,使其成为一个更加中立开放的社区,Fescar 更名为 Seata。 - -## 如何运行? -```go - -go get seata.apache.org/seata-go@2.0.0 - -``` -如果你想了解如何使用和集成 seata-go,请参考 [apache/seata-go-samples](https://github.com/apache/incubator-seata-go-samples) -## 如何查找最新版本 -访问 Seata-Go 的 GitHub Releases 页面 - -打开:https://github.com/seata/seata-go/releases - -最新的 tag / release 就是目前的最新稳定版本。 -## 文档 - -你可以访问 Seata 官方网站获取完整文档:[Seata 官网](https://seata.apache.org/zh-cn/docs/overview/what-is-seata) -## 问题报告 - -若有问题,请遵循 [模板](./.github/ISSUE_TEMPLATE/BUG_REPORT_TEMPLATE.md) 报告问题。 - -## 安全 - -请勿使用公开的问题跟踪器,详情请参阅我们的 [安全政策](https://github.com/apache/incubator-seata/blob/2.x/SECURITY.md) - -## 参与贡献 -Seata-go 当前处于建设阶段,欢迎业界同仁加入我们,共同推进 Seata-go 建设!如果你想为 Seata-go 贡献代码,请参考 [代码贡献规范](./CONTRIBUTING_CN.md),也可以加入我们的社区钉钉群:33069364 一起交流! - -## 联系方式 - -* 邮件列表:   - -  * dev@seata.apache.org - 用于开发/用户讨论   [订阅](mailto:dev-subscribe@seata.apache.org)、[取消订阅](mailto:dev-unsubscribe@seata.apache.org)、[归档](https://lists.apache.org/list.html?dev@seata.apache.org) - -* 在线交流:   - -| 钉钉 | 微信公众号 | QQ | 微信助手 | -|:---------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------:| -| | | | | - - -## Seata 生态系统 - -* [Seata 官网](https://github.com/apache/incubator-seata.github.io)- Seata官方网站 - -* [Seata](https://github.com/apache/incubator-seata) - Seata 客户端和服务端 - -* [Seata GoLang](https://github.com/apache/incubator-seata-go) - Seata Go 客户端和服务端 - -* [Seata 示例](https://github.com/apache/incubator-seata-samples)- Seata 示例 - -* [Seata Go 示例](https://github.com/apache/incubator-seata-go-samples)- Seata GoLang 示例 - -* [Seata K8s 集成](https://github.com/apache/incubator-seata-k8s)- Seata 与 K8S 集成 - -* [Seata 命令行工具](https://github.com/apache/incubator-seata-ctl)- Seata CLI 工具 - -## 贡献者 - -Seata-go感谢所有贡献者的付出。[[贡献者列表](https://github.com/apache/incubator-seata-go/graphs/contributors)] -## 许可证 - -Seata-go 使用 Apache 2.0 协议,详情请查看 [LICENSE 文件](https://github.com/apache/incubator-seata-go/blob/master/LICENSE) diff --git a/a2a/api/proto/v1/a2a.proto b/a2a/api/proto/v1/a2a.proto new file mode 100644 index 000000000..5c0538c27 --- /dev/null +++ b/a2a/api/proto/v1/a2a.proto @@ -0,0 +1,783 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You 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. + +// Older protoc compilers don't understand edition yet. + + +syntax = "proto3"; +package a2a.v1; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "A2a.V1"; +option go_package = "google.golang.org/a2a/v1"; +option java_multiple_files = true; +option java_outer_classname = "A2A"; +option java_package = "com.google.a2a.v1"; + +// A2AService defines the gRPC version of the A2A protocol. This has a slightly +// different shape than the JSONRPC version to better conform to AIP-127, +// where appropriate. The nouns are AgentCard, Message, Task and +// TaskPushNotificationConfig. +// - Messages are not a standard resource so there is no get/delete/update/list +// interface, only a send and stream custom methods. +// - Tasks have a get interface and custom cancel and subscribe methods. +// - TaskPushNotificationConfig are a resource whose parent is a task. +// They have get, list and create methods. +// - AgentCard is a static resource with only a get method. +service A2AService { + // Send a message to the agent. This is a blocking call that will return the + // task once it is completed, or a LRO if requested. + rpc SendMessage(SendMessageRequest) returns (SendMessageResponse) { + option (google.api.http) = { + post: "/v1/message:send" + body: "*" + }; + } + // SendStreamingMessage is a streaming call that will return a stream of + // task update events until the Task is in an interrupted or terminal state. + rpc SendStreamingMessage(SendMessageRequest) returns (stream StreamResponse) { + option (google.api.http) = { + post: "/v1/message:stream" + body: "*" + }; + } + + // Get the current state of a task from the agent. + rpc GetTask(GetTaskRequest) returns (Task) { + option (google.api.http) = { + get: "/v1/{name=tasks/*}" + }; + option (google.api.method_signature) = "name"; + } + // Cancel a task from the agent. If supported one should expect no + // more task updates for the task. + rpc CancelTask(CancelTaskRequest) returns (Task) { + option (google.api.http) = { + post: "/v1/{name=tasks/*}:cancel" + body: "*" + }; + } + // TaskSubscription is a streaming call that will return a stream of task + // update events. This attaches the stream to an existing in process task. + // If the task is complete the stream will return the completed task (like + // GetTask) and close the stream. + rpc TaskSubscription(TaskSubscriptionRequest) + returns (stream StreamResponse) { + option (google.api.http) = { + get: "/v1/{name=tasks/*}:subscribe" + }; + } + + // List tasks with optional filtering + rpc ListTasks(ListTasksRequest) returns (ListTasksResponse) { + option (google.api.http) = { + get: "/v1/tasks" + }; + } + + // Set a push notification config for a task. + rpc CreateTaskPushNotification(CreateTaskPushNotificationConfigRequest) + returns (TaskPushNotificationConfig) { + option (google.api.http) = { + post: "/v1/{parent=tasks/*/pushNotificationConfigs}" + body: "config" + }; + option (google.api.method_signature) = "parent,config"; + } + // Get a push notification config for a task. + rpc GetTaskPushNotification(GetTaskPushNotificationConfigRequest) + returns (TaskPushNotificationConfig) { + option (google.api.http) = { + get: "/v1/{name=tasks/*/pushNotificationConfigs/*}" + }; + option (google.api.method_signature) = "name"; + } + // Get a list of push notifications configured for a task. + rpc ListTaskPushNotification(ListTaskPushNotificationConfigRequest) + returns (ListTaskPushNotificationConfigResponse) { + option (google.api.http) = { + get: "/v1/{parent=tasks/*}/pushNotificationConfigs" + }; + option (google.api.method_signature) = "parent"; + } + // GetAgentCard returns the agent card for the agent. + rpc GetAgentCard(GetAgentCardRequest) returns (AgentCard) { + option (google.api.http) = { + get: "/v1/card" + }; + } + // Delete a push notification config for a task. + rpc DeleteTaskPushNotification(DeleteTaskPushNotificationConfigRequest) + returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1/{name=tasks/*/pushNotificationConfigs/*}" + }; + option (google.api.method_signature) = "name"; + } +} + +///////// Data Model //////////// + +// Configuration of a send message request. +message SendMessageConfiguration { + // The output modes that the agent is expected to respond with. + repeated string accepted_output_modes = 1; + // A configuration of a webhook that can be used to receive updates + PushNotificationConfig push_notification = 2; + // The maximum number of messages to include in the history. if 0, the + // history will be unlimited. + int32 history_length = 3; + // If true, the message will be blocking until the task is completed. If + // false, the message will be non-blocking and the task will be returned + // immediately. It is the caller's responsibility to check for any task + // updates. + bool blocking = 4; +} + +// Task is the core unit of action for A2A. It has a current status +// and when results are created for the task they are stored in the +// artifact. If there are multiple turns for a task, these are stored in +// history. +message Task { + // Unique identifier (e.g. UUID) for the task, generated by the server for a + // new task. + string id = 1; + // Unique identifier (e.g. UUID) for the contextual collection of interactions + // (tasks and messages). Created by the A2A server. + string context_id = 2; + // The current status of a Task, including state and a message. + TaskStatus status = 3; + // A set of output artifacts for a Task. + repeated Artifact artifacts = 4; + // protolint:disable REPEATED_FIELD_NAMES_PLURALIZED + // The history of interactions from a task. + repeated Message history = 5; + // protolint:enable REPEATED_FIELD_NAMES_PLURALIZED + // Type identifier for A2A protocol, should be "task" + string kind = 6; + // A key/value object to store custom metadata about a task. + google.protobuf.Struct metadata = 7; +} + +// The set of states a Task can be in. +enum TaskState { + TASK_STATE_UNSPECIFIED = 0; + // Represents the status that acknowledges a task is created + TASK_STATE_SUBMITTED = 1; + // Represents the status that a task is actively being processed + TASK_STATE_WORKING = 2; + // Represents the status a task is finished. This is a terminal state + TASK_STATE_COMPLETED = 3; + // Represents the status a task is done but failed. This is a terminal state + TASK_STATE_FAILED = 4; + // Represents the status a task was cancelled before it finished. + // This is a terminal state. + TASK_STATE_CANCELLED = 5; + // Represents the status that the task requires information to complete. + // This is an interrupted state. + TASK_STATE_INPUT_REQUIRED = 6; + // Represents the status that the agent has decided to not perform the task. + // This may be done during initial task creation or later once an agent + // has determined it can't or won't proceed. This is a terminal state. + TASK_STATE_REJECTED = 7; + // Represents the state that some authentication is needed from the upstream + // client. Authentication is expected to come out-of-band thus this is not + // an interrupted or terminal state. + TASK_STATE_AUTH_REQUIRED = 8; +} + +// A container for the status of a task +message TaskStatus { + // The current state of this task + TaskState state = 1; + // A message associated with the status. + Message update = 2 [json_name = "message"]; + // Timestamp when the status was recorded. + // Example: "2023-10-27T10:00:00Z" + google.protobuf.Timestamp timestamp = 3; +} + +// Part represents a container for a section of communication content. +// Parts can be purely textual, some sort of file (image, video, etc) or +// a structured data blob (i.e. JSON). +message Part { + oneof part { + string text = 1; + FilePart file = 2; + DataPart data = 3; + } + // Optional metadata associated with this part. + google.protobuf.Struct metadata = 4; +} + +// FilePart represents the different ways files can be provided. If files are +// small, directly feeding the bytes is supported via file_with_bytes. If the +// file is large, the agent should read the content as appropriate directly +// from the file_with_uri source. +message FilePart { + oneof file { + string file_with_uri = 1; + bytes file_with_bytes = 2; + } + string mime_type = 3; + string name = 4; +} + +// DataPart represents a structured blob. This is most commonly a JSON payload. +message DataPart { + google.protobuf.Struct data = 1; +} + +enum Role { + ROLE_UNSPECIFIED = 0; + // USER role refers to communication from the client to the server. + ROLE_USER = 1; + // AGENT role refers to communication from the server to the client. + ROLE_AGENT = 2; +} + +// Message is one unit of communication between client and server. It is +// associated with a context and optionally a task. Since the server is +// responsible for the context definition, it must always provide a context_id +// in its messages. The client can optionally provide the context_id if it +// knows the context to associate the message to. Similarly for task_id, +// except the server decides if a task is created and whether to include the +// task_id. +message Message { + // The unique identifier (e.g. UUID)of the message. This is required and + // created by the message creator. + string message_id = 1; + // The context id of the message. This is optional and if set, the message + // will be associated with the given context. + string context_id = 2; + // The task id of the message. This is optional and if set, the message + // will be associated with the given task. + string task_id = 3; + // A role for the message. + Role role = 4; + // protolint:disable REPEATED_FIELD_NAMES_PLURALIZED + // Parts is the container of the message content. + repeated Part content = 5; + // protolint:enable REPEATED_FIELD_NAMES_PLURALIZED + // Type identifier for A2A protocol, should be "message" + string kind = 6; + // Any optional metadata to provide along with the message. + google.protobuf.Struct metadata = 7; + // The URIs of extensions that are present or contributed to this Message. + repeated string extensions = 8; +} + +// Artifacts are the container for task completed results. These are similar +// to Messages but are intended to be the product of a task, as opposed to +// point-to-point communication. +message Artifact { + // Unique identifier (e.g. UUID) for the artifact. It must be at least unique + // within a task. + string artifact_id = 1; + // A human readable name for the artifact. + string name = 3; + // A human readable description of the artifact, optional. + string description = 4; + // The content of the artifact. + repeated Part parts = 5; + // Optional metadata included with the artifact. + google.protobuf.Struct metadata = 6; + // The URIs of extensions that are present or contributed to this Artifact. + repeated string extensions = 7; +} + +// TaskStatusUpdateEvent is a delta even on a task indicating that a task +// has changed. +message TaskStatusUpdateEvent { + // The id of the task that is changed + string task_id = 1; + // The id of the context that the task belongs to + string context_id = 2; + // The new status of the task. + TaskStatus status = 3; + // Whether this is the last status update expected for this task. + bool final = 4; + // Optional metadata to associate with the task update. + google.protobuf.Struct metadata = 5; +} + +// TaskArtifactUpdateEvent represents a task delta where an artifact has +// been generated. +message TaskArtifactUpdateEvent { + // The id of the task for this artifact + string task_id = 1; + // The id of the context that this task belongs too + string context_id = 2; + // The artifact itself + Artifact artifact = 3; + // Whether this should be appended to a prior one produced + bool append = 4; + // Whether this represents the last part of an artifact + bool last_chunk = 5; + // Optional metadata associated with the artifact update. + google.protobuf.Struct metadata = 6; +} + +// Configuration for setting up push notifications for task updates. +message PushNotificationConfig { + // A unique identifier (e.g. UUID) for this push notification. + string id = 1; + // Url to send the notification too + string url = 2; + // Token unique for this task/session + string token = 3; + // Information about the authentication to sent with the notification + AuthenticationInfo authentication = 4; +} + +// Defines authentication details, used for push notifications. +message AuthenticationInfo { + // Supported authentication schemes - e.g. Basic, Bearer, etc + repeated string schemes = 1; + // Optional credentials + string credentials = 2; +} + +// Defines additional transport information for the agent. +message AgentInterface { + // The url this interface is found at. + string url = 1; + // The transport supported this url. This is an open form string, to be + // easily extended for many transport protocols. The core ones officially + // supported are JSONRPC, GRPC and HTTP+JSON. + string transport = 2; +} + +// AgentCard conveys key information: +// - Overall details (version, name, description, uses) +// - Skills; a set of actions/solutions the agent can perform +// - Default modalities/content types supported by the agent. +// - Authentication requirements +// Next ID: 19 +message AgentCard { + // The version of the A2A protocol this agent supports. + string protocol_version = 16; + // A human readable name for the agent. + // Example: "Recipe Agent" + string name = 1; + // A description of the agent's domain of action/solution space. + // Example: "Agent that helps users with recipes and cooking." + string description = 2; + // A URL to the address the agent is hosted at. This represents the + // preferred endpoint as declared by the agent. + string url = 3; + // The transport of the preferred endpoint. If empty, defaults to JSONRPC. + string preferred_transport = 14; + // Announcement of additional supported transports. Client can use any of + // the supported transports. + repeated AgentInterface additional_interfaces = 15; + // The service provider of the agent. + AgentProvider provider = 4; + // The version of the agent. + // Example: "1.0.0" + string version = 5; + // A url to provide additional documentation about the agent. + string documentation_url = 6; + // A2A Capability set supported by the agent. + AgentCapabilities capabilities = 7; + // The security scheme details used for authenticating with this agent. + map security_schemes = 8; + // protolint:disable REPEATED_FIELD_NAMES_PLURALIZED + // Security requirements for contacting the agent. + // This list can be seen as an OR of ANDs. Each object in the list describes + // one possible set of security requirements that must be present on a + // request. This allows specifying, for example, "callers must either use + // OAuth OR an API Key AND mTLS." + // Example: + // security { + // schemes { key: "oauth" value { list: ["read"] } } + // } + // security { + // schemes { key: "api-key" } + // schemes { key: "mtls" } + // } + repeated Security security = 9; + // protolint:enable REPEATED_FIELD_NAMES_PLURALIZED + // The set of interaction modes that the agent supports across all skills. + // This can be overridden per skill. Defined as mime types. + repeated string default_input_modes = 10; + // The mime types supported as outputs from this agent. + repeated string default_output_modes = 11; + // Skills represent a unit of ability an agent can perform. This may + // somewhat abstract but represents a more focused set of actions that the + // agent is highly likely to succeed at. + repeated AgentSkill skills = 12; + // Whether the agent supports providing an extended agent card when + // the user is authenticated, i.e. is the card from .well-known + // different than the card from GetAgentCard. + bool supports_authenticated_extended_card = 13; + // JSON Web Signatures computed for this AgentCard. + repeated AgentCardSignature signatures = 17; + // An optional URL to an icon for the agent. + string icon_url = 18; +} + +// Represents information about the service provider of an agent. +message AgentProvider { + // The providers reference url + // Example: "https://ai.google.dev" + string url = 1; + // The providers organization name + // Example: "Google" + string organization = 2; +} + +// Defines the A2A feature set supported by the agent +message AgentCapabilities { + // If the agent will support streaming responses + bool streaming = 1; + // If the agent can send push notifications to the clients webhook + bool push_notifications = 2; + // If the agent supports state transition history tracking + bool state_transition_history = 3; + // Extensions supported by this agent. + repeated AgentExtension extensions = 4; +} + +// A declaration of an extension supported by an Agent. +message AgentExtension { + // The URI of the extension. + // Example: "https://developers.google.com/identity/protocols/oauth2" + string uri = 1; + // A description of how this agent uses this extension. + // Example: "Google OAuth 2.0 authentication" + string description = 2; + // Whether the client must follow specific requirements of the extension. + // Example: false + bool required = 3; + // Optional configuration for the extension. + google.protobuf.Struct params = 4; +} + +// AgentSkill represents a unit of action/solution that the agent can perform. +// One can think of this as a type of highly reliable solution that an agent +// can be tasked to provide. Agents have the autonomy to choose how and when +// to use specific skills, but clients should have confidence that if the +// skill is defined that unit of action can be reliably performed. +message AgentSkill { + // Unique identifier of the skill within this agent. + string id = 1; + // A human readable name for the skill. + string name = 2; + // A human (or llm) readable description of the skill + // details and behaviors. + string description = 3; + // A set of tags for the skill to enhance categorization/utilization. + // Example: ["cooking", "customer support", "billing"] + repeated string tags = 4; + // A set of example queries that this skill is designed to address. + // These examples should help the caller to understand how to craft requests + // to the agent to achieve specific goals. + // Example: ["I need a recipe for bread"] + repeated string examples = 5; + // Possible input modalities supported. + repeated string input_modes = 6; + // Possible output modalities produced + repeated string output_modes = 7; + // protolint:disable REPEATED_FIELD_NAMES_PLURALIZED + // Security schemes necessary for the agent to leverage this skill. + // As in the overall AgentCard.security, this list represents a logical OR of + // security requirement objects. Each object is a set of security schemes + // that must be used together (a logical AND). + repeated Security security = 8; + // protolint:enable REPEATED_FIELD_NAMES_PLURALIZED +} + +// AgentCardSignature represents a JWS signature of an AgentCard. +// This follows the JSON format of an RFC 7515 JSON Web Signature (JWS). +message AgentCardSignature { + // The protected JWS header for the signature. This is always a + // base64url-encoded JSON object. Required. + string protected = 1 [(google.api.field_behavior) = REQUIRED]; + // The computed signature, base64url-encoded. Required. + string signature = 2 [(google.api.field_behavior) = REQUIRED]; + // The unprotected JWS header values. + google.protobuf.Struct header = 3; +} + +message TaskPushNotificationConfig { + // The resource name of the config. + // Format: tasks/{task_id}/pushNotificationConfigs/{config_id} + string name = 1; + // The push notification configuration details. + PushNotificationConfig push_notification_config = 2; +} + +// protolint:disable REPEATED_FIELD_NAMES_PLURALIZED +message StringList { + repeated string list = 1; +} +// protolint:enable REPEATED_FIELD_NAMES_PLURALIZED + +message Security { + map schemes = 1; +} + +message SecurityScheme { + oneof scheme { + APIKeySecurityScheme api_key_security_scheme = 1; + HTTPAuthSecurityScheme http_auth_security_scheme = 2; + OAuth2SecurityScheme oauth2_security_scheme = 3; + OpenIdConnectSecurityScheme open_id_connect_security_scheme = 4; + MutualTlsSecurityScheme mtls_security_scheme = 5; + } +} + +message APIKeySecurityScheme { + // Description of this security scheme. + string description = 1; + // Location of the API key, valid values are "query", "header", or "cookie" + string location = 2; + // Name of the header, query or cookie parameter to be used. + string name = 3; +} + +message HTTPAuthSecurityScheme { + // Description of this security scheme. + string description = 1; + // The name of the HTTP Authentication scheme to be used in the + // Authorization header as defined in RFC7235. The values used SHOULD be + // registered in the IANA Authentication Scheme registry. + // The value is case-insensitive, as defined in RFC7235. + string scheme = 2; + // A hint to the client to identify how the bearer token is formatted. + // Bearer tokens are usually generated by an authorization server, so + // this information is primarily for documentation purposes. + string bearer_format = 3; +} + +message OAuth2SecurityScheme { + // Description of this security scheme. + string description = 1; + // An object containing configuration information for the flow types supported + OAuthFlows flows = 2; + // URL to the oauth2 authorization server metadata + // [RFC8414](https://datatracker.ietf.org/doc/html/rfc8414). TLS is required. + string oauth2_metadata_url = 3; +} + +message OpenIdConnectSecurityScheme { + // Description of this security scheme. + string description = 1; + // Well-known URL to discover the [[OpenID-Connect-Discovery]] provider + // metadata. + string open_id_connect_url = 2; +} + +message MutualTlsSecurityScheme { + // Description of this security scheme. + string description = 1; +} + +message OAuthFlows { + oneof flow { + AuthorizationCodeOAuthFlow authorization_code = 1; + ClientCredentialsOAuthFlow client_credentials = 2; + ImplicitOAuthFlow implicit = 3; + PasswordOAuthFlow password = 4; + } +} + +message AuthorizationCodeOAuthFlow { + // The authorization URL to be used for this flow. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS + string authorization_url = 1; + // The token URL to be used for this flow. This MUST be in the form of a URL. + // The OAuth2 standard requires the use of TLS. + string token_url = 2; + // The URL to be used for obtaining refresh tokens. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS. + string refresh_url = 3; + // The available scopes for the OAuth2 security scheme. A map between the + // scope name and a short description for it. The map MAY be empty. + map scopes = 4; +} + +message ClientCredentialsOAuthFlow { + // The token URL to be used for this flow. This MUST be in the form of a URL. + // The OAuth2 standard requires the use of TLS. + string token_url = 1; + // The URL to be used for obtaining refresh tokens. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS. + string refresh_url = 2; + // The available scopes for the OAuth2 security scheme. A map between the + // scope name and a short description for it. The map MAY be empty. + map scopes = 3; +} + +message ImplicitOAuthFlow { + // The authorization URL to be used for this flow. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS + string authorization_url = 1; + // The URL to be used for obtaining refresh tokens. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS. + string refresh_url = 2; + // The available scopes for the OAuth2 security scheme. A map between the + // scope name and a short description for it. The map MAY be empty. + map scopes = 3; +} + +message PasswordOAuthFlow { + // The token URL to be used for this flow. This MUST be in the form of a URL. + // The OAuth2 standard requires the use of TLS. + string token_url = 1; + // The URL to be used for obtaining refresh tokens. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS. + string refresh_url = 2; + // The available scopes for the OAuth2 security scheme. A map between the + // scope name and a short description for it. The map MAY be empty. + map scopes = 3; +} + +///////////// Request Messages /////////// +message SendMessageRequest { + // The message to send to the agent. + Message request = 1 + [(google.api.field_behavior) = REQUIRED, json_name = "message"]; + // Configuration for the send request. + SendMessageConfiguration configuration = 2; + // Optional metadata for the request. + google.protobuf.Struct metadata = 3; +} + +message GetTaskRequest { + // The resource name of the task. + // Format: tasks/{task_id} + string name = 1 [(google.api.field_behavior) = REQUIRED]; + // The number of most recent messages from the task's history to retrieve. + int32 history_length = 2; +} + +message CancelTaskRequest { + // The resource name of the task to cancel. + // Format: tasks/{task_id} + string name = 1; +} + +message ListTasksRequest { + // Optional filter by context ID + string context_id = 1; + // Optional filter by task state + repeated TaskState states = 2; + // Maximum number of tasks to return + int32 page_size = 3; + // Token for pagination + string page_token = 4; +} + +message ListTasksResponse { + // The list of tasks + repeated Task tasks = 1; + // Token for next page (if more tasks available) + string next_page_token = 2; +} + +message GetTaskPushNotificationConfigRequest { + // The resource name of the config to retrieve. + // Format: tasks/{task_id}/pushNotificationConfigs/{config_id} + string name = 1; +} + +message DeleteTaskPushNotificationConfigRequest { + // The resource name of the config to delete. + // Format: tasks/{task_id}/pushNotificationConfigs/{config_id} + string name = 1; +} + +message CreateTaskPushNotificationConfigRequest { + // The parent task resource for this config. + // Format: tasks/{task_id} + string parent = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + // The ID for the new config. + string config_id = 2 [(google.api.field_behavior) = REQUIRED]; + // The configuration to create. + TaskPushNotificationConfig config = 3 + [(google.api.field_behavior) = REQUIRED]; +} + +message TaskSubscriptionRequest { + // The resource name of the task to subscribe to. + // Format: tasks/{task_id} + string name = 1; +} + +message ListTaskPushNotificationConfigRequest { + // The parent task resource. + // Format: tasks/{task_id} + string parent = 1; + // For AIP-158 these fields are present. Usually not used/needed. + // The maximum number of configurations to return. + // If unspecified, all configs will be returned. + int32 page_size = 2; + + // A page token received from a previous + // ListTaskPushNotificationConfigRequest call. + // Provide this to retrieve the subsequent page. + // When paginating, all other parameters provided to + // `ListTaskPushNotificationConfigRequest` must match the call that provided + // the page token. + string page_token = 3; +} + +message GetAgentCardRequest { + // Empty. Added to fix linter violation. +} + +//////// Response Messages /////////// +message SendMessageResponse { + oneof payload { + Task task = 1; + Message msg = 2 [json_name = "message"]; + } +} + +// The stream response for a message. The stream should be one of the following +// sequences: +// If the response is a message, the stream should contain one, and only one, +// message and then close +// If the response is a task lifecycle, the first response should be a Task +// object followed by zero or more TaskStatusUpdateEvents and +// TaskArtifactUpdateEvents. The stream should complete when the Task +// if in an interrupted or terminal state. A stream that ends before these +// conditions are met are +message StreamResponse { + oneof payload { + Task task = 1; + Message msg = 2 [json_name = "message"]; + TaskStatusUpdateEvent status_update = 3; + TaskArtifactUpdateEvent artifact_update = 4; + } +} + +message ListTaskPushNotificationConfigResponse { + // The list of push notification configurations. + repeated TaskPushNotificationConfig configs = 1; + // A token, which can be sent as `page_token` to retrieve the next page. + // If this field is omitted, there are no subsequent pages. + string next_page_token = 2; +} diff --git a/a2a/examples/simple-chat/client/debug_grpc.go b/a2a/examples/simple-chat/client/debug_grpc.go new file mode 100644 index 000000000..7809a140d --- /dev/null +++ b/a2a/examples/simple-chat/client/debug_grpc.go @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package main + +import ( + "context" + "fmt" + "log" + + "seata-go-ai-a2a/pkg/transport/grpc" + "seata-go-ai-a2a/pkg/types" +) + +func main() { + client, err := grpc.NewClient(&grpc.ClientConfig{Address: "localhost:9090"}) + if err != nil { + log.Fatal(err) + } + defer client.Close() + + req := &types.SendMessageRequest{ + Request: &types.Message{ + Role: types.RoleUser, + Parts: []types.Part{&types.TextPart{Text: "Hello, can you tell me the time?"}}, + }, + Configuration: &types.SendMessageConfiguration{Blocking: true}, + } + + task, err := client.SendMessage(context.Background(), req) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Task ID: %s\n", task.ID) + fmt.Printf("Task State: %v\n", task.Status.State) + fmt.Printf("History entries: %d\n", len(task.History)) + + for i, msg := range task.History { + fmt.Printf("Message %d - Role: %v\n", i, msg.Role) + for j, part := range msg.Parts { + if textPart, ok := part.(*types.TextPart); ok { + fmt.Printf(" Part %d (Text): %s\n", j, textPart.Text) + } + } + } + + fmt.Printf("Artifacts: %d\n", len(task.Artifacts)) + for i, artifact := range task.Artifacts { + fmt.Printf("Artifact %d - Name: %s\n", i, artifact.Name) + } +} diff --git a/a2a/examples/simple-chat/client/go.mod b/a2a/examples/simple-chat/client/go.mod new file mode 100644 index 000000000..b1dd682b6 --- /dev/null +++ b/a2a/examples/simple-chat/client/go.mod @@ -0,0 +1,21 @@ +module client + +go 1.24.5 + +require seata-go-ai-a2a v0.0.0 + +require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/redis/go-redis/v9 v9.14.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect + google.golang.org/grpc v1.75.1 // indirect + google.golang.org/protobuf v1.36.9 // indirect +) + +replace seata-go-ai-a2a => ../../.. diff --git a/a2a/examples/simple-chat/client/go.sum b/a2a/examples/simple-chat/client/go.sum new file mode 100644 index 000000000..9231509cf --- /dev/null +++ b/a2a/examples/simple-chat/client/go.sum @@ -0,0 +1,56 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE= +github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/a2a/examples/simple-chat/client/main.go b/a2a/examples/simple-chat/client/main.go new file mode 100644 index 000000000..9f939dbde --- /dev/null +++ b/a2a/examples/simple-chat/client/main.go @@ -0,0 +1,275 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package main + +import ( + "bufio" + "context" + "fmt" + "log" + "os" + "strings" + + "seata-go-ai-a2a/pkg/transport/grpc" + "seata-go-ai-a2a/pkg/transport/rest" + "seata-go-ai-a2a/pkg/types" +) + +func main() { + if len(os.Args) < 2 { + fmt.Println("Usage: client [grpc|rest|interactive]") + fmt.Println(" grpc - Test gRPC client") + fmt.Println(" rest - Test REST client") + fmt.Println(" interactive - Interactive chat mode (REST)") + os.Exit(1) + } + + mode := os.Args[1] + + switch mode { + case "grpc": + testGRPCClient() + case "rest": + testRESTClient() + case "interactive": + interactiveChat() + default: + fmt.Printf("Unknown mode: %s\n", mode) + os.Exit(1) + } +} + +// testGRPCClient demonstrates gRPC client usage +func testGRPCClient() { + log.Println("🔌 Testing gRPC Client...") + + // Create gRPC client + client, err := grpc.NewClient(&grpc.ClientConfig{ + Address: "localhost:9090", + }) + if err != nil { + log.Fatalf("Failed to create gRPC client: %v", err) + } + defer client.Close() + + ctx := context.Background() + + // Test 1: Get Agent Card + log.Println("\n📋 Getting Agent Card...") + card, err := client.GetAgentCard(ctx) + if err != nil { + log.Fatalf("Failed to get agent card: %v", err) + } + fmt.Printf("Agent: %s v%s\n", card.Name, card.Version) + fmt.Printf("Description: %s\n", card.Description) + fmt.Printf("Skills: %d available\n", len(card.Skills)) + + // Test 2: Send Messages + messages := []string{ + "Hello there!", + "What time is it?", + "Can you show me some code?", + "Tell me a joke", + "Thank you!", + } + + for i, content := range messages { + log.Printf("\n💬 Test %d: Sending message...", i+1) + + req := &types.SendMessageRequest{ + Request: &types.Message{ + Role: types.RoleUser, + Parts: []types.Part{ + &types.TextPart{Text: content}, + }, + }, + Configuration: &types.SendMessageConfiguration{ + Blocking: true, + }, + } + + task, err := client.SendMessage(ctx, req) + if err != nil { + log.Printf("❌ Failed to send message: %v", err) + continue + } + + fmt.Printf("📨 User: %s\n", content) + fmt.Printf("🤖 Assistant: %s\n", getLastAssistantMessage(task)) + if len(task.Artifacts) > 0 { + fmt.Printf("📎 Artifacts: %d attached\n", len(task.Artifacts)) + for _, artifact := range task.Artifacts { + fmt.Printf(" - %s\n", artifact.Name) + } + } + } + + log.Println("\n✅ gRPC client test completed!") +} + +// testRESTClient demonstrates REST client usage +func testRESTClient() { + log.Println("🌐 Testing REST Client...") + + // Create REST client + client := rest.NewClient(&rest.ClientConfig{ + BaseURL: "http://localhost:8080", + }) + + ctx := context.Background() + + // Test 1: Get Agent Card + log.Println("\n📋 Getting Agent Card...") + card, err := client.GetAgentCard(ctx) + if err != nil { + log.Fatalf("Failed to get agent card: %v", err) + } + fmt.Printf("Agent: %s v%s\n", card.Name, card.Version) + fmt.Printf("Description: %s\n", card.Description) + + // Test 2: Send Message + log.Println("\n💬 Sending test message...") + + req := &types.SendMessageRequest{ + Request: &types.Message{ + Role: types.RoleUser, + Parts: []types.Part{ + &types.TextPart{Text: "Hello from REST client! What can you do?"}, + }, + }, + Configuration: &types.SendMessageConfiguration{ + Blocking: true, + }, + } + + response, err := client.SendMessage(ctx, req) + if err != nil { + log.Fatalf("Failed to send message: %v", err) + } + + fmt.Printf("📨 User: %s\n", getTextFromMessage(req.Request)) + fmt.Printf("🤖 Assistant: %s\n", getLastAssistantMessage(response.Task)) + + log.Println("\n✅ REST client test completed!") +} + +// interactiveChat provides an interactive chat experience +func interactiveChat() { + log.Println("💬 Starting Interactive Chat (REST)...") + log.Println("Type 'quit' or 'exit' to stop") + log.Println("---") + + // Create REST client + client := rest.NewClient(&rest.ClientConfig{ + BaseURL: "http://localhost:8080", + }) + ctx := context.Background() + + // Get agent info + card, err := client.GetAgentCard(ctx) + if err != nil { + log.Fatalf("Failed to get agent card: %v", err) + } + fmt.Printf("🤖 Connected to: %s\n", card.Name) + fmt.Printf("💡 %s\n\n", card.Description) + + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Print("You: ") + if !scanner.Scan() { + break + } + + input := strings.TrimSpace(scanner.Text()) + if input == "" { + continue + } + + if input == "quit" || input == "exit" { + break + } + + // Send message + req := &types.SendMessageRequest{ + Request: &types.Message{ + Role: types.RoleUser, + Parts: []types.Part{ + &types.TextPart{Text: input}, + }, + }, + Configuration: &types.SendMessageConfiguration{ + Blocking: true, + }, + } + + response, err := client.SendMessage(ctx, req) + if err != nil { + fmt.Printf("❌ Error: %v\n\n", err) + continue + } + + fmt.Printf("Assistant: %s\n", getLastAssistantMessage(response.Task)) + + if len(response.Task.Artifacts) > 0 { + fmt.Printf("📎 %d artifact(s) attached:\n", len(response.Task.Artifacts)) + for _, artifact := range response.Task.Artifacts { + fmt.Printf(" 📄 %s:\n", artifact.Name) + content := getTextFromMessage(&types.Message{Parts: artifact.Parts}) + if len(content) < 200 { + fmt.Printf(" %s\n", content) + } else { + fmt.Printf(" %s...\n", content[:200]) + } + } + } + fmt.Println() + } + + log.Println("👋 Chat ended. Goodbye!") +} + +// getLastAssistantMessage extracts the last assistant message from task history +func getLastAssistantMessage(task *types.Task) string { + if task == nil || len(task.History) == 0 { + return "No response" + } + + // Find the last agent message + for i := len(task.History) - 1; i >= 0; i-- { + if task.History[i].Role == types.RoleAgent { + return getTextFromMessage(task.History[i]) + } + } + + return "No assistant response found" +} + +// getTextFromMessage extracts text content from message parts +func getTextFromMessage(msg *types.Message) string { + if msg == nil || len(msg.Parts) == 0 { + return "" + } + + for _, part := range msg.Parts { + if textPart, ok := part.(*types.TextPart); ok { + return textPart.Text + } + } + return "" +} diff --git a/a2a/examples/simple-chat/server/go.mod b/a2a/examples/simple-chat/server/go.mod new file mode 100644 index 000000000..4459869ef --- /dev/null +++ b/a2a/examples/simple-chat/server/go.mod @@ -0,0 +1,32 @@ +module server + +go 1.24.5 + +require seata-go-ai-a2a v0.0.0 + +require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.3 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/jwx/v2 v2.1.6 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/redis/go-redis/v9 v9.14.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect + google.golang.org/grpc v1.75.1 // indirect + google.golang.org/protobuf v1.36.9 // indirect +) + +replace seata-go-ai-a2a => ../../.. diff --git a/a2a/examples/simple-chat/server/go.sum b/a2a/examples/simple-chat/server/go.sum new file mode 100644 index 000000000..3140a3e39 --- /dev/null +++ b/a2a/examples/simple-chat/server/go.sum @@ -0,0 +1,84 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/lestrrat-go/blackmagic v1.0.3 h1:94HXkVLxkZO9vJI/w2u1T0DAoprShFd13xtnSINtDWs= +github.com/lestrrat-go/blackmagic v1.0.3/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA= +github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE= +github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/a2a/examples/simple-chat/server/handler.go b/a2a/examples/simple-chat/server/handler.go new file mode 100644 index 000000000..2a7d16180 --- /dev/null +++ b/a2a/examples/simple-chat/server/handler.go @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package main + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "seata-go-ai-a2a/pkg/handler" + "seata-go-ai-a2a/pkg/types" +) + +// EchoMessageHandler implements a simple echo chat handler that responds to messages +type EchoMessageHandler struct{} + +// HandleMessage processes incoming messages and provides intelligent responses +func (h *EchoMessageHandler) HandleMessage(ctx context.Context, req *handler.MessageRequest) (*handler.MessageResponse, error) { + // Get message content from first text part + messageContent := h.getMessageContent(req.Message) + log.Printf("Processing message: %s", messageContent) + + // Update task state to working + err := req.TaskOperations.UpdateTaskState(types.TaskStateWorking, &types.Message{ + Role: types.RoleAgent, + Parts: []types.Part{ + &types.TextPart{Text: "Processing your message..."}, + }, + }) + if err != nil { + log.Printf("Failed to update task state: %v", err) + } + + // Simulate some processing time + time.Sleep(500 * time.Millisecond) + + // Analyze the message content and generate appropriate response + response := h.generateResponse(messageContent) + + // Create response message + responseMsg := &types.Message{ + Role: types.RoleAgent, + Parts: []types.Part{ + &types.TextPart{Text: response}, + }, + } + + // Create artifact if the user asks for code + if strings.Contains(strings.ToLower(messageContent), "code") || + strings.Contains(strings.ToLower(messageContent), "program") { + artifact := &types.Artifact{ + ArtifactID: "example-code", + Name: "Example Code", + Description: "Simple Python example", + Parts: []types.Part{ + &types.TextPart{Text: "print('Hello from A2A!')\nfor i in range(3):\n print(f'Count: {i}')"}, + }, + } + + err := req.TaskOperations.AddArtifact(artifact) + if err != nil { + log.Printf("Failed to add artifact: %v", err) + } + } + + // Update task state to completed + err = req.TaskOperations.UpdateTaskState(types.TaskStateCompleted, responseMsg) + if err != nil { + log.Printf("Failed to complete task: %v", err) + } + + // Get the updated task after processing + history, err := req.TaskOperations.GetTaskHistory() + if err != nil { + log.Printf("Failed to get task history: %v", err) + history = []*types.Message{req.Message, responseMsg} // Fallback to basic history + } + + // Create updated task object to return + updatedTask := &types.Task{ + ID: req.TaskID, + ContextID: req.ContextID, + Status: &types.TaskStatus{ + State: types.TaskStateCompleted, + Update: responseMsg, + }, + History: history, + Artifacts: []*types.Artifact{}, // Will be populated if artifacts were added + Kind: "task", + Metadata: req.Metadata, + } + + // Add artifacts if any were created + if strings.Contains(strings.ToLower(messageContent), "code") || + strings.Contains(strings.ToLower(messageContent), "program") { + // The artifact should already be added via TaskOperations.AddArtifact + // But we need to include it in our response + artifact := &types.Artifact{ + ArtifactID: "example-code", + Name: "Example Code", + Description: "Simple Python example", + Parts: []types.Part{ + &types.TextPart{Text: "print('Hello from A2A!')\\nfor i in range(3):\\n print(f'Count: {i}')"}, + }, + } + updatedTask.Artifacts = []*types.Artifact{artifact} + } + + // Return response with updated task + return &handler.MessageResponse{ + Mode: handler.ResponseModeTask, + Result: &handler.ResponseResult{ + Data: updatedTask, + }, + }, nil +} + +// HandleCancel handles task cancellation +func (h *EchoMessageHandler) HandleCancel(ctx context.Context, taskID string) error { + log.Printf("Cancelling task: %s", taskID) + return nil +} + +// getMessageContent extracts text content from message parts +func (h *EchoMessageHandler) getMessageContent(msg *types.Message) string { + if msg == nil || len(msg.Parts) == 0 { + return "" + } + + for _, part := range msg.Parts { + if textPart, ok := part.(*types.TextPart); ok { + return textPart.Text + } + } + return "" +} + +// generateResponse creates contextual responses based on message content +func (h *EchoMessageHandler) generateResponse(content string) string { + content = strings.ToLower(content) + + switch { + case strings.Contains(content, "hello") || strings.Contains(content, "hi"): + return "Hello! I'm your friendly A2A assistant. How can I help you today?" + + case strings.Contains(content, "how are you") || strings.Contains(content, "how do you do"): + return "I'm doing great! I'm an A2A agent ready to help you with various tasks." + + case strings.Contains(content, "time") || strings.Contains(content, "date"): + return fmt.Sprintf("The current time is: %s", time.Now().Format("2006-01-02 15:04:05")) + + case strings.Contains(content, "weather"): + return "I don't have access to real weather data, but I can help you with other tasks!" + + case strings.Contains(content, "code") || strings.Contains(content, "program"): + return "I can help with coding! I've created a simple Python example for you. Check the artifacts section for the code." + + case strings.Contains(content, "joke"): + return "Why don't scientists trust atoms? Because they make up everything! 😄" + + case strings.Contains(content, "help") || strings.Contains(content, "what can you do"): + return "I can help you with:\n- General conversation\n- Providing current time\n- Creating simple code examples\n- Telling jokes\n- And much more! Just ask me anything." + + case strings.Contains(content, "thank") || strings.Contains(content, "thanks"): + return "You're very welcome! I'm happy to help. Feel free to ask me anything else!" + + default: + return fmt.Sprintf("I received your message: \"%s\"\n\nI'm an echo assistant that can help with various tasks. Try asking me for help, the time, a joke, or some code!", content) + } +} diff --git a/a2a/examples/simple-chat/server/main.go b/a2a/examples/simple-chat/server/main.go new file mode 100644 index 000000000..cd4961a19 --- /dev/null +++ b/a2a/examples/simple-chat/server/main.go @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package main + +import ( + "context" + "log" + "os" + "os/signal" + "syscall" + + "seata-go-ai-a2a/pkg/a2a" +) + +func main() { + log.Println("Starting Simple Chat A2A Server...") + + // Create message handler and agent card provider + messageHandler := &EchoMessageHandler{} + cardProvider := NewChatAgentCardProvider() + + // Get the base agent card for server initialization + baseCard, err := cardProvider.GetAgentCard(context.Background()) + if err != nil { + log.Fatalf("Failed to get base agent card: %v", err) + } + + // Create A2A server with handlers + server, err := a2a.NewServerWithHandlers(baseCard, messageHandler, cardProvider) + if err != nil { + log.Fatalf("Failed to create A2A server: %v", err) + } + + // Start the server + log.Println("Starting server on :8080 (HTTP/REST/JSON-RPC) and :9090 (gRPC)...") + if err := server.Start(); err != nil { + log.Fatalf("Failed to start server: %v", err) + } + + log.Println("✅ Server started successfully!") + log.Println("Available endpoints:") + log.Println(" - gRPC: localhost:9090") + log.Println(" - HTTP REST: http://localhost:8080/api/v1/") + log.Println(" - JSON-RPC: http://localhost:8080/jsonrpc") + log.Println(" - Agent Card: http://localhost:8080/api/v1/agent-card") + log.Println("") + log.Println("Try these example requests:") + log.Println(" curl -X POST http://localhost:8080/api/v1/messages -H 'Content-Type: application/json' -d '{\"message\":{\"role\":1,\"parts\":[{\"type\":\"text\",\"data\":{\"text\":\"Hello!\"}}]}}'") + log.Println(" curl http://localhost:8080/api/v1/agent-card") + log.Println("") + log.Println("Press Ctrl+C to stop the server") + + // Wait for interrupt signal + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + <-c + + log.Println("Shutting down server...") + if err := server.Stop(); err != nil { + log.Printf("Error during shutdown: %v", err) + } + log.Println("Server stopped") +} diff --git a/a2a/examples/simple-chat/server/provider.go b/a2a/examples/simple-chat/server/provider.go new file mode 100644 index 000000000..f3a4af268 --- /dev/null +++ b/a2a/examples/simple-chat/server/provider.go @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package main + +import ( + "context" + + "seata-go-ai-a2a/pkg/handler" + "seata-go-ai-a2a/pkg/types" +) + +// ChatAgentCardProvider provides dynamic agent card information +type ChatAgentCardProvider struct { + baseCard *types.AgentCard +} + +// NewChatAgentCardProvider creates a new agent card provider +func NewChatAgentCardProvider() *ChatAgentCardProvider { + // Create base agent card using the builder + card := handler.NewAgentCardBuilder(). + WithBasicInfo( + "Simple Chat Assistant", + "A friendly A2A chat assistant that can help with various tasks including conversation, time queries, code examples, and jokes.", + "1.0.0", + ). + WithURL("http://localhost:8080"). + WithProvider("A2A Examples", "seata-go-ai-a2a"). + WithCapabilities(true, false). // streaming=true, pushNotifications=false + WithDefaultInputModes([]string{"text"}). + WithDefaultOutputModes([]string{"text", "artifact"}). + WithSkill( + "general-conversation", + "General Conversation", + "Engage in natural conversation and provide helpful responses", + []string{"text"}, + []string{"text"}, + ). + WithSkill( + "time-info", + "Time Information", + "Provide current time and date information", + []string{"text"}, + []string{"text"}, + ). + WithSkill( + "code-generation", + "Code Generation", + "Generate simple code examples and programming snippets", + []string{"text"}, + []string{"text", "artifact"}, + ). + WithSkill( + "joke-telling", + "Joke Telling", + "Tell jokes and provide light entertainment", + []string{"text"}, + []string{"text"}, + ). + WithDocumentationURL("examples/README.md"). + WithIconURL("https://example.com/chat-icon.png"). + Build() + + return &ChatAgentCardProvider{ + baseCard: card, + } +} + +// GetAgentCard returns the standard agent card +func (p *ChatAgentCardProvider) GetAgentCard(ctx context.Context) (*types.AgentCard, error) { + return p.baseCard, nil +} + +// GetAgentCardForUser returns a user-specific agent card (could be customized based on user) +func (p *ChatAgentCardProvider) GetAgentCardForUser(ctx context.Context, userInfo *handler.UserInfo) (*types.AgentCard, error) { + // For this example, we return the same card for all users + // In a real implementation, you might customize based on userInfo + return p.baseCard, nil +} diff --git a/a2a/go.mod b/a2a/go.mod new file mode 100644 index 000000000..971995b1f --- /dev/null +++ b/a2a/go.mod @@ -0,0 +1,45 @@ +module seata-go-ai-a2a + +go 1.24.5 + +require ( + github.com/golang-jwt/jwt/v5 v5.3.0 + github.com/google/uuid v1.6.0 + github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb + github.com/lestrrat-go/jwx/v2 v2.1.6 + github.com/prometheus/client_golang v1.23.2 + github.com/redis/go-redis/v9 v9.14.0 + github.com/stretchr/testify v1.11.1 + golang.org/x/crypto v0.42.0 + google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 + google.golang.org/grpc v1.75.1 + google.golang.org/protobuf v1.36.9 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.3 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/segmentio/asm v1.2.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect + gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/a2a/go.sum b/a2a/go.sum new file mode 100644 index 000000000..b57dad062 --- /dev/null +++ b/a2a/go.sum @@ -0,0 +1,117 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb h1:tsEKRC3PU9rMw18w/uAptoijhgG4EvlA5kfJPtwrMDk= +github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lestrrat-go/blackmagic v1.0.3 h1:94HXkVLxkZO9vJI/w2u1T0DAoprShFd13xtnSINtDWs= +github.com/lestrrat-go/blackmagic v1.0.3/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA= +github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE= +github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY= +gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/a2a/pkg/a2a/a2a.go b/a2a/pkg/a2a/a2a.go new file mode 100644 index 000000000..883dbd45c --- /dev/null +++ b/a2a/pkg/a2a/a2a.go @@ -0,0 +1,526 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +// Package a2a provides a unified SDK for the Application-to-Application (A2A) protocol. +// +// This package implements a complete A2A protocol stack with: +// - Multi-transport support (gRPC, REST, JSON-RPC, SSE) +// - Comprehensive authentication (JWT, API Key, Basic, mTLS) +// - JWS signature validation for Agent Cards +// - Task management with pluggable storage +// - Rate limiting and audit logging +// - Simple APIs for quick setup +// - Production-ready security features +// +// Quick Start - Server: +// +// // Simple server +// server, err := a2a.QuickServer("My Agent", "A helpful AI agent", 8080) +// if err != nil { +// log.Fatal(err) +// } +// defer server.Stop() +// +// // Server with authentication +// apiKeys := map[string]string{"user1": "secret123"} +// server, err := a2a.QuickServerWithAuth("My Agent", "Description", 8080, apiKeys) +// +// // Advanced server +// server, err := a2a.NewSimpleServer("My Agent", "Description"). +// WithPort(8080). +// WithJWTAuth("https://auth.example.com/.well-known/jwks.json", "my-app", "issuer"). +// WithStreamingSupport(). +// WithRedisStore("redis://localhost:6379"). +// BuildAndStart() +// +// Quick Start - Client: +// +// // Simple client +// client, err := a2a.QuickClient("http://localhost:8080") +// if err != nil { +// log.Fatal(err) +// } +// defer client.Close() +// +// // Send a message +// message := a2a.TextMessage("Hello, how can you help me?") +// task, err := client.SendMessage(context.Background(), message, nil) +// if err != nil { +// log.Fatal(err) +// } +// +// // Wait for completion +// for { +// updatedTask, err := client.GetTask(context.Background(), task.ID) +// if err != nil { +// log.Fatal(err) +// } +// +// if updatedTask.Status.State.IsTerminal() { +// if updatedTask.Status.State == types.TaskStateCompleted { +// log.Printf("Task completed: %+v", updatedTask.Artifacts) +// } +// break +// } +// +// time.Sleep(time.Second) +// } +// +// Agent Card Creation: +// +// agentCard := a2a.NewAgentCard("My Agent", "A helpful AI agent"). +// WithURL("http://localhost:8080"). +// WithProvider("My Company", "https://company.com"). +// WithStreamingSupport(). +// AddAPIKeyAuth("apikey", "API Key Authentication", "header", "X-API-Key"). +// AddSkill("help", "General Help", "Provides general assistance", +// []string{"help", "support"}, []string{"How can I help you?"}). +// Build() +// +// Security Features: +// +// // Authentication middleware +// authManager := a2a.NewAuthManager() +// authMiddleware := a2a.NewAuthMiddleware(authManager, jwsValidator, false) +// +// // Apply to HTTP handler +// handler = authMiddleware.HTTPMiddleware()(handler) +// +// // Apply to gRPC server +// grpcServer := grpc.NewServer( +// grpc.UnaryInterceptor(authMiddleware.GRPCUnaryInterceptor()), +// grpc.StreamInterceptor(authMiddleware.GRPCStreamInterceptor()), +// ) +// +// JWS Validation: +// +// // Validate agent card signatures +// jwsValidator := jws.NewValidator() +// jwsMiddleware := a2a.NewJWSMiddleware(jwsValidator, trustedJWKSURLs, true) +// err := jwsMiddleware.ValidateAgentCard(agentCard) +package a2a + +import ( + "context" + "crypto" + "fmt" + "log" + "time" + + "golang.org/x/crypto/bcrypt" + + // Import all necessary subpackages for convenience + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/auth/authenticator" + "seata-go-ai-a2a/pkg/auth/jws" + "seata-go-ai-a2a/pkg/task" + "seata-go-ai-a2a/pkg/task/store" + "seata-go-ai-a2a/pkg/types" +) + +// Version information +const ( + Version = "1.0.0" + ProtocolVersion = "1.0" + SDKName = "grpc-a2a-sdk" +) + +// A2A represents the main SDK interface +type A2A struct { + server *Server + client *Client +} + +// SDKConfig represents the main SDK configuration +type SDKConfig struct { + // Server configuration (optional) + ServerConfig *ServerConfig `json:"serverConfig,omitempty"` + + // Client configuration (optional) + ClientConfig *ClientConfig `json:"clientConfig,omitempty"` + + // Global settings + LogLevel string `json:"logLevel,omitempty"` // "debug", "info", "warn", "error" + EnableMetrics bool `json:"enableMetrics,omitempty"` +} + +// NewA2A creates a new A2A SDK instance +func NewA2A(config *SDKConfig) (*A2A, error) { + if config == nil { + return nil, fmt.Errorf("SDK config is required") + } + + a2a := &A2A{} + + // Initialize server if configured + if config.ServerConfig != nil { + server, err := NewServer(config.ServerConfig) + if err != nil { + return nil, fmt.Errorf("failed to create server: %w", err) + } + a2a.server = server + } + + // Initialize client if configured + if config.ClientConfig != nil { + client, err := NewClient(config.ClientConfig) + if err != nil { + return nil, fmt.Errorf("failed to create client: %w", err) + } + a2a.client = client + } + + return a2a, nil +} + +// GetServer returns the server instance (may be nil) +func (a *A2A) GetServer() *Server { + return a.server +} + +// GetClient returns the client instance (may be nil) +func (a *A2A) GetClient() *Client { + return a.client +} + +// Start starts the SDK (starts server if present) +func (a *A2A) Start() error { + if a.server != nil { + return a.server.Start() + } + return nil +} + +// Stop stops the SDK (stops server and closes client) +func (a *A2A) Stop() error { + var errs []error + + if a.server != nil { + if err := a.server.Stop(); err != nil { + errs = append(errs, err) + } + } + + if a.client != nil { + if err := a.client.Close(); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return fmt.Errorf("errors stopping SDK: %v", errs) + } + + return nil +} + +// Factory functions for common components + +// CreateJWTAuthenticator creates a JWT authenticator with the given configuration +func CreateJWTAuthenticator(name, jwksURL, audience, issuer string) auth.Authenticator { + config := &authenticator.JWTAuthenticatorConfig{ + Name: name, + JWKSURL: jwksURL, + Audience: audience, + Issuer: issuer, + } + return authenticator.NewJWTAuthenticator(config) +} + +// CreateAPIKeyAuthenticator creates an API key authenticator +func CreateAPIKeyAuthenticator(name string, apiKeys map[string]string) auth.Authenticator { + // Convert simple map to APIKeyInfo map + keys := make(map[string]*authenticator.APIKeyInfo) + for key, userID := range apiKeys { + keys[key] = &authenticator.APIKeyInfo{ + UserID: userID, + Name: userID, + Scopes: []string{"read", "write"}, + } + } + + config := &authenticator.APIKeyAuthenticatorConfig{ + Name: name, + Keys: keys, + Schemes: []*types.APIKeySecurityScheme{ + { + Description: "API Key Authentication", + Location: "header", + Name: "X-API-Key", + }, + }, + } + return authenticator.NewAPIKeyAuthenticator(config) +} + +// CreateBasicAuthenticator creates a basic auth authenticator +func CreateBasicAuthenticator(name string, users map[string]string) auth.Authenticator { + // Convert simple map to BasicUserInfo map + userInfos := make(map[string]*authenticator.BasicUserInfo) + for username, password := range users { + // Hash password with bcrypt + hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + userInfos[username] = &authenticator.BasicUserInfo{ + UserID: username, + Username: username, + PasswordHash: string(hashedPassword), + Name: username, + Scopes: []string{"read", "write"}, + CreatedAt: time.Now(), + } + } + + config := &authenticator.BasicAuthenticatorConfig{ + Name: name, + Users: userInfos, + } + return authenticator.NewBasicAuthenticator(config) +} + +// CreateJWSSigner creates a JWS signer for signing agent cards +func CreateJWSSigner() *jws.Signer { + return jws.NewSigner() +} + +// CreateJWSValidator creates a JWS validator for validating agent cards +func CreateJWSValidator() *jws.Validator { + return jws.NewValidator() +} + +// CreateTaskManager creates a task manager with the specified store +func CreateTaskManager(storeType, connectionString string) (*task.TaskManager, error) { + var taskStore store.TaskStore + + switch storeType { + case "memory": + taskStore = store.NewMemoryTaskStore() + case "redis": + if connectionString == "" { + return nil, fmt.Errorf("connection string required for redis store") + } + // For Redis store, need to create client first + // This is simplified - in production, parse connection string properly + return nil, fmt.Errorf("redis task store requires proper redis client configuration") + default: + return nil, fmt.Errorf("unsupported store type: %s", storeType) + } + + config := task.DefaultConfig() + + return task.NewTaskManager(taskStore, config), nil +} + +// Convenience functions for common agent card configurations + +// CreateBasicAgentCard creates a basic agent card with minimal configuration +func CreateBasicAgentCard(name, description, url string) *types.AgentCard { + return NewAgentCard(name, description). + WithURL(url). + Build() +} + +// CreateAPIKeyAgentCard creates an agent card with API key authentication +func CreateAPIKeyAgentCard(name, description, url string) *types.AgentCard { + return NewAgentCard(name, description). + WithURL(url). + AddAPIKeyAuth("apikey", "API Key Authentication", "header", "X-API-Key"). + RequireAuth(map[string][]string{"apikey": {}}). + Build() +} + +// CreateJWTAgentCard creates an agent card with JWT authentication +func CreateJWTAgentCard(name, description, url string) *types.AgentCard { + return NewAgentCard(name, description). + WithURL(url). + AddBearerAuth("jwt", "JWT Authentication", "JWT"). + RequireAuth(map[string][]string{"jwt": {}}). + Build() +} + +// CreateStreamingAgentCard creates an agent card with streaming support +func CreateStreamingAgentCard(name, description, url string) *types.AgentCard { + card := NewAgentCard(name, description). + WithURL(url). + WithPushNotifications(). + AddInterface(url+"/events", "sse"). + Build() + + // Enable streaming manually + card.Capabilities.Streaming = true + return card +} + +// SignAgentCard signs an agent card with JWS +func SignAgentCard(agentCard *types.AgentCard, privateKey crypto.PrivateKey, keyID, jwksURL string) error { + signer := CreateJWSSigner() + signature, err := signer.SignAgentCard(context.Background(), agentCard, privateKey, keyID, jwksURL) + if err != nil { + return err + } + + agentCard.Signatures = []*types.AgentCardSignature{signature} + return nil +} + +// ValidateAgentCardJWS validates the JWS signature of an agent card +func ValidateAgentCardJWS(agentCard *types.AgentCard, trustedJWKSURLs []string) error { + validator := CreateJWSValidator() + defer validator.Close() + + middleware := NewJWSMiddleware(validator, trustedJWKSURLs, true) + return middleware.ValidateAgentCard(agentCard) +} + +// Error types and utilities + +// SDKError represents an SDK-specific error +type SDKError struct { + Code string `json:"code"` + Message string `json:"message"` + Details string `json:"details,omitempty"` +} + +func (e *SDKError) Error() string { + if e.Details != "" { + return fmt.Sprintf("[%s] %s: %s", e.Code, e.Message, e.Details) + } + return fmt.Sprintf("[%s] %s", e.Code, e.Message) +} + +// Common error codes +const ( + ErrCodeInvalidConfig = "INVALID_CONFIG" + ErrCodeServerStartFailed = "SERVER_START_FAILED" + ErrCodeClientConnFailed = "CLIENT_CONNECTION_FAILED" + ErrCodeAuthFailed = "AUTHENTICATION_FAILED" + ErrCodeJWSValidationFailed = "JWS_VALIDATION_FAILED" +) + +// NewSDKError creates a new SDK error +func NewSDKError(code, message, details string) *SDKError { + return &SDKError{ + Code: code, + Message: message, + Details: details, + } +} + +// Info provides SDK information +func Info() map[string]interface{} { + return map[string]interface{}{ + "sdk_name": SDKName, + "sdk_version": Version, + "protocol_version": ProtocolVersion, + "supported_transports": []string{"grpc", "rest", "jsonrpc", "sse"}, + "supported_auth": []string{"jwt", "apikey", "basic", "mtls"}, + "features": map[string]bool{ + "streaming": true, + "push_notifications": true, + "jws_validation": true, + "rate_limiting": true, + "audit_logging": true, + "multiple_transports": true, + "pluggable_storage": true, + }, + } +} + +// LogSDKInfo logs SDK information +func LogSDKInfo() { + info := Info() + log.Printf("A2A SDK %s initialized", info["sdk_version"]) + log.Printf("Protocol version: %s", info["protocol_version"]) + log.Printf("Supported transports: %v", info["supported_transports"]) + log.Printf("Supported authentication: %v", info["supported_auth"]) +} + +// Default configurations for common use cases + +// DefaultServerConfig returns a default server configuration +func DefaultServerConfig(name, description string) *ServerConfig { + return &ServerConfig{ + AgentCard: &types.AgentCard{ + ProtocolVersion: ProtocolVersion, + Name: name, + Description: description, + Version: "1.0.0", + PreferredTransport: "rest", + Capabilities: &types.AgentCapabilities{ + Streaming: false, + PushNotifications: false, + StateTransitionHistory: true, + }, + }, + ListenAddr: ":8080", + GRPCAddr: ":9090", + EnableGRPC: true, + EnableREST: true, + EnableJSONRPC: true, + EnableSSE: false, + TaskStore: "memory", + EnableRateLimit: false, + EnableAuditLog: false, + Authenticators: []*AuthenticatorConfig{}, + } +} + +// DefaultClientConfig returns a default client configuration +func DefaultClientConfig(agentURL string) *ClientConfig { + return &ClientConfig{ + AgentURL: agentURL, + PreferredTransport: "rest", + ValidateJWS: false, + SkipAgentCardFetch: false, + Streaming: false, + MaxRetries: 3, + } +} + +// Production-ready configurations + +// ProductionServerConfig returns a production-ready server configuration +func ProductionServerConfig(name, description, url string) *ServerConfig { + config := DefaultServerConfig(name, description) + config.AgentCard.URL = url + config.EnableRateLimit = true + config.RateLimit = 1000 // 1000 requests per minute + config.EnableAuditLog = true + config.TaskStore = "redis" // Requires Redis URL to be set + + return config +} + +// ProductionClientConfig returns a production-ready client configuration +func ProductionClientConfig(agentURL string) *ClientConfig { + config := DefaultClientConfig(agentURL) + config.ValidateJWS = true + config.Streaming = true + + return config +} + +// Helper function to create a complete production setup +func NewProductionA2A(serverName, serverDesc, serverURL, clientURL string) (*A2A, error) { + config := &SDKConfig{ + ServerConfig: ProductionServerConfig(serverName, serverDesc, serverURL), + ClientConfig: ProductionClientConfig(clientURL), + LogLevel: "info", + EnableMetrics: true, + } + + return NewA2A(config) +} diff --git a/a2a/pkg/a2a/auth_manager.go b/a2a/pkg/a2a/auth_manager.go new file mode 100644 index 000000000..f2ab5c323 --- /dev/null +++ b/a2a/pkg/a2a/auth_manager.go @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package a2a + +import ( + "context" + "fmt" + "sync" + + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/types" + + "google.golang.org/grpc/metadata" +) + +// AuthManager manages multiple authenticators and provides unified authentication +type AuthManager struct { + authenticators []auth.Authenticator + mu sync.RWMutex +} + +// NewAuthManager creates a new authentication manager +func NewAuthManager() *AuthManager { + return &AuthManager{ + authenticators: make([]auth.Authenticator, 0), + } +} + +// AddAuthenticator adds an authenticator to the manager +func (am *AuthManager) AddAuthenticator(authenticator auth.Authenticator) { + am.mu.Lock() + defer am.mu.Unlock() + + am.authenticators = append(am.authenticators, authenticator) +} + +// Authenticate attempts to authenticate using all configured authenticators +func (am *AuthManager) Authenticate(ctx context.Context, md metadata.MD) (auth.User, error) { + am.mu.RLock() + defer am.mu.RUnlock() + + if len(am.authenticators) == 0 { + // No authenticators configured, allow anonymous access + return &auth.UnauthenticatedUser{}, nil + } + + var lastError error + + // Try each authenticator + for _, authenticator := range am.authenticators { + user, err := authenticator.Authenticate(ctx, md) + if err == nil && user.IsAuthenticated() { + return user, nil + } + + if err != nil { + lastError = err + } + } + + // If no authenticator succeeded, return the last error + if lastError != nil { + return &auth.UnauthenticatedUser{}, lastError + } + + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Authentication failed with all configured authenticators", + } +} + +// GetAuthenticatorForScheme returns the first authenticator that supports the given scheme +func (am *AuthManager) GetAuthenticatorForScheme(scheme types.SecurityScheme) auth.Authenticator { + am.mu.RLock() + defer am.mu.RUnlock() + + for _, authenticator := range am.authenticators { + if authenticator.SupportsScheme(scheme) { + return authenticator + } + } + + return nil +} + +// ListAuthenticators returns the names of all configured authenticators +func (am *AuthManager) ListAuthenticators() []string { + am.mu.RLock() + defer am.mu.RUnlock() + + names := make([]string, len(am.authenticators)) + for i, authenticator := range am.authenticators { + names[i] = authenticator.Name() + } + + return names +} + +// ValidateAgentCardSecurity validates that the agent card's security requirements can be satisfied +func (am *AuthManager) ValidateAgentCardSecurity(agentCard *types.AgentCard) error { + if agentCard.Security == nil || len(agentCard.Security) == 0 { + // No security requirements + return nil + } + + am.mu.RLock() + defer am.mu.RUnlock() + + // Check each security requirement + for _, security := range agentCard.Security { + satisfied := false + + for schemeName := range security.Schemes { + // Look up the scheme in the agent card's security schemes + if scheme, exists := agentCard.SecuritySchemes[schemeName]; exists { + // Check if we have an authenticator that supports this scheme + if am.GetAuthenticatorForScheme(scheme) != nil { + satisfied = true + break + } + } + } + + if !satisfied { + return fmt.Errorf("no authenticator available for security requirement with schemes: %v", + getSchemeNames(security.Schemes)) + } + } + + return nil +} + +// getSchemeNames extracts scheme names from a security schemes map +func getSchemeNames(schemes map[string][]string) []string { + names := make([]string, 0, len(schemes)) + for name := range schemes { + names = append(names, name) + } + return names +} diff --git a/a2a/pkg/a2a/client.go b/a2a/pkg/a2a/client.go new file mode 100644 index 000000000..3271976b8 --- /dev/null +++ b/a2a/pkg/a2a/client.go @@ -0,0 +1,618 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package a2a + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "sync" + "time" + + "seata-go-ai-a2a/pkg/auth/jws" + "seata-go-ai-a2a/pkg/transport/grpc" + "seata-go-ai-a2a/pkg/transport/jsonrpc" + "seata-go-ai-a2a/pkg/transport/rest" + "seata-go-ai-a2a/pkg/transport/sse" + "seata-go-ai-a2a/pkg/types" +) + +// ClientConfig represents the configuration for the A2A client +type ClientConfig struct { + // Target agent information + AgentURL string `json:"agentUrl"` // Agent's base URL + PreferredTransport string `json:"preferredTransport"` // "grpc", "rest", "jsonrpc", "sse" + + // Discovery and AgentCard + AgentCard *types.AgentCard `json:"-"` // Pre-fetched agent card + SkipAgentCardFetch bool `json:"skipAgentCardFetch"` // Skip fetching agent card + + // Authentication Configuration + AuthConfig *ClientAuthConfig `json:"authConfig"` + + // Transport-specific configurations + HTTPClient *http.Client `json:"-"` // Custom HTTP client + GRPCOptions []interface{} `json:"-"` // gRPC dial options + + // Timeouts + RequestTimeout time.Duration `json:"requestTimeout"` // Default: 30s + ConnectTimeout time.Duration `json:"connectTimeout"` // Default: 10s + + // JWS Validation + ValidateJWS bool `json:"validateJws"` // Validate agent card JWS + TrustedJWKSURLs []string `json:"trustedJwksUrls"` // Trusted JWKS URLs for validation + + // Client capabilities + Streaming bool `json:"streaming"` // Support streaming + + // Retry configuration + MaxRetries int `json:"maxRetries"` // Default: 3 + RetryDelay time.Duration `json:"retryDelay"` // Default: 1s +} + +// ClientAuthConfig represents authentication configuration for the client +type ClientAuthConfig struct { + Type string `json:"type"` // "bearer", "apikey", "basic", "mtls" + Credentials map[string]string `json:"credentials"` // Type-specific credentials + + // OAuth2/JWT specific + TokenURL string `json:"tokenUrl,omitempty"` + ClientID string `json:"clientId,omitempty"` + ClientSecret string `json:"clientSecret,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +// Client represents the unified A2A client +type Client struct { + config *ClientConfig + agentCard *types.AgentCard + jwsValidator *jws.Validator + + // Transport clients + grpcClient *grpc.Client + restClient *rest.Client + jsonrpcClient *jsonrpc.Client + sseClient *sse.Client + httpClient *http.Client + + // Active transport client + activeTransport string + + // Authentication + authHeaders map[string]string + authMutex sync.RWMutex + + // Lifecycle + mu sync.RWMutex + closed bool +} + +// NewClient creates a new A2A client with the given configuration +func NewClient(config *ClientConfig) (*Client, error) { + if err := validateClientConfig(config); err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + + setClientConfigDefaults(config) + + client := &Client{ + config: config, + jwsValidator: jws.NewValidator(), + httpClient: config.HTTPClient, + authHeaders: make(map[string]string), + } + + if client.httpClient == nil { + client.httpClient = &http.Client{ + Timeout: config.RequestTimeout, + } + } + + // Fetch and validate agent card if not provided + if !config.SkipAgentCardFetch { + if err := client.fetchAgentCard(); err != nil { + return nil, fmt.Errorf("failed to fetch agent card: %w", err) + } + + if config.ValidateJWS { + if err := client.validateAgentCardJWS(); err != nil { + return nil, fmt.Errorf("failed to validate agent card JWS: %w", err) + } + } + } else if config.AgentCard != nil { + client.agentCard = config.AgentCard + } + + // Initialize authentication + if err := client.initAuth(); err != nil { + return nil, fmt.Errorf("failed to initialize authentication: %w", err) + } + + // Initialize transport client + if err := client.initTransport(); err != nil { + return nil, fmt.Errorf("failed to initialize transport: %w", err) + } + + return client, nil +} + +// SendMessage sends a message to the agent and returns the created task +func (c *Client) SendMessage(ctx context.Context, message *types.Message, metadata map[string]any) (*types.Task, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.closed { + return nil, fmt.Errorf("client is closed") + } + + req := &types.SendMessageRequest{ + Request: message, + Metadata: metadata, + } + + switch c.activeTransport { + case "grpc": + if c.grpcClient == nil { + return nil, fmt.Errorf("gRPC client not initialized") + } + return c.grpcClient.SendMessage(ctx, req) + + case "rest": + if c.restClient == nil { + return nil, fmt.Errorf("REST client not initialized") + } + resp, err := c.restClient.SendMessage(ctx, req) + if err != nil { + return nil, err + } + return resp.Task, nil + + case "jsonrpc": + if c.jsonrpcClient == nil { + return nil, fmt.Errorf("JSON-RPC client not initialized") + } + // For now, assume jsonrpc client returns Task directly or we need to implement it + return nil, fmt.Errorf("JSON-RPC SendMessage not fully implemented") + + default: + return nil, fmt.Errorf("unsupported transport: %s", c.activeTransport) + } +} + +// GetTask retrieves a task by ID +func (c *Client) GetTask(ctx context.Context, taskID string) (*types.Task, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.closed { + return nil, fmt.Errorf("client is closed") + } + + req := &types.GetTaskRequest{ + Name: fmt.Sprintf("tasks/%s", taskID), + } + + switch c.activeTransport { + case "grpc": + return c.grpcClient.GetTask(ctx, req) + case "rest": + return c.restClient.GetTask(ctx, req) + case "jsonrpc": + return c.jsonrpcClient.GetTask(ctx, req) + default: + return nil, fmt.Errorf("unsupported transport: %s", c.activeTransport) + } +} + +// CancelTask cancels a task by ID +func (c *Client) CancelTask(ctx context.Context, taskID string) (*types.Task, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.closed { + return nil, fmt.Errorf("client is closed") + } + + req := &types.CancelTaskRequest{ + Name: fmt.Sprintf("tasks/%s", taskID), + } + + switch c.activeTransport { + case "grpc": + return c.grpcClient.CancelTask(ctx, req) + case "rest": + return c.restClient.CancelTask(ctx, req) + case "jsonrpc": + return c.jsonrpcClient.CancelTask(ctx, req) + default: + return nil, fmt.Errorf("unsupported transport: %s", c.activeTransport) + } +} + +// ListTasks lists tasks with optional filtering +func (c *Client) ListTasks(ctx context.Context, req *types.ListTasksRequest) (*types.ListTasksResponse, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.closed { + return nil, fmt.Errorf("client is closed") + } + + switch c.activeTransport { + case "grpc": + return c.grpcClient.ListTasks(ctx, req) + case "rest": + return c.restClient.ListTasks(ctx, req) + case "jsonrpc": + return c.jsonrpcClient.ListTasks(ctx, req) + default: + return nil, fmt.Errorf("unsupported transport: %s", c.activeTransport) + } +} + +// GetAgentCard returns the fetched agent card +func (c *Client) GetAgentCard() *types.AgentCard { + return c.agentCard +} + +// StreamTaskUpdates creates a stream to receive task updates (if supported) +func (c *Client) StreamTaskUpdates(ctx context.Context, taskID string) (<-chan *types.Task, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.closed { + return nil, fmt.Errorf("client is closed") + } + + // For now, return not implemented for all transports + // This would need to be implemented based on actual transport capabilities + return nil, fmt.Errorf("streaming not yet implemented") +} + +// Close closes the client and releases resources +func (c *Client) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return nil + } + + var errs []error + + // Close transport clients + if c.grpcClient != nil { + if err := c.grpcClient.Close(); err != nil { + errs = append(errs, err) + } + } + + if c.restClient != nil { + if err := c.restClient.Close(); err != nil { + errs = append(errs, err) + } + } + + if c.jsonrpcClient != nil { + if err := c.jsonrpcClient.Close(); err != nil { + errs = append(errs, err) + } + } + + if c.sseClient != nil { + if err := c.sseClient.Close(); err != nil { + errs = append(errs, err) + } + } + + // Close JWS validator + if c.jwsValidator != nil { + c.jwsValidator.Close() + } + + c.closed = true + + if len(errs) > 0 { + return fmt.Errorf("errors closing client: %v", errs) + } + + return nil +} + +// fetchAgentCard fetches the agent card from the agent URL +func (c *Client) fetchAgentCard() error { + agentCardURL := c.config.AgentURL + if !isValidURL(agentCardURL) { + // Try to construct a valid URL + if u, err := url.Parse(agentCardURL); err == nil { + u.Path = "/agentCard" + agentCardURL = u.String() + } else { + agentCardURL = agentCardURL + "/agentCard" + } + } + + ctx, cancel := context.WithTimeout(context.Background(), c.config.RequestTimeout) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, "GET", agentCardURL, nil) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + // Add authentication headers if configured + c.authMutex.RLock() + for key, value := range c.authHeaders { + req.Header.Set(key, value) + } + c.authMutex.RUnlock() + + resp, err := c.httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to fetch agent card: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to fetch agent card: status %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + var agentCard types.AgentCard + if err := json.Unmarshal(body, &agentCard); err != nil { + return fmt.Errorf("failed to unmarshal agent card: %w", err) + } + + c.agentCard = &agentCard + return nil +} + +// validateAgentCardJWS validates the JWS signature on the agent card +func (c *Client) validateAgentCardJWS() error { + if c.agentCard == nil { + return fmt.Errorf("no agent card to validate") + } + + if len(c.agentCard.Signatures) == 0 { + return fmt.Errorf("agent card has no signatures") + } + + // Create a copy of the agent card without signatures for validation + agentCardCopy := *c.agentCard + agentCardCopy.Signatures = nil + + payload, err := json.Marshal(agentCardCopy) + if err != nil { + return fmt.Errorf("failed to marshal agent card for validation: %w", err) + } + + // Validate each signature + for i, signature := range c.agentCard.Signatures { + if err := c.jwsValidator.ValidateSignature(context.Background(), payload, signature); err != nil { + return fmt.Errorf("failed to validate signature %d: %w", i, err) + } + } + + return nil +} + +// initAuth initializes authentication based on configuration +func (c *Client) initAuth() error { + if c.config.AuthConfig == nil { + return nil + } + + c.authMutex.Lock() + defer c.authMutex.Unlock() + + switch c.config.AuthConfig.Type { + case "bearer": + if token, ok := c.config.AuthConfig.Credentials["token"]; ok { + c.authHeaders["Authorization"] = "Bearer " + token + } + + case "apikey": + if key, ok := c.config.AuthConfig.Credentials["key"]; ok { + if location, ok := c.config.AuthConfig.Credentials["location"]; ok { + switch location { + case "header": + headerName := c.config.AuthConfig.Credentials["name"] + if headerName == "" { + headerName = "X-API-Key" + } + c.authHeaders[headerName] = key + case "query": + // Query parameter auth will be handled at request time + // Store in credentials for now + } + } + } + + case "basic": + if username, ok := c.config.AuthConfig.Credentials["username"]; ok { + if password, ok := c.config.AuthConfig.Credentials["password"]; ok { + c.authHeaders["Authorization"] = "Basic " + encodeBasicAuth(username, password) + } + } + } + + return nil +} + +// initTransport initializes the appropriate transport client +func (c *Client) initTransport() error { + preferredTransport := c.config.PreferredTransport + + // If no preferred transport specified, determine from agent card + if preferredTransport == "" && c.agentCard != nil { + preferredTransport = c.agentCard.PreferredTransport + } + + // Default to REST if still not specified + if preferredTransport == "" { + preferredTransport = "rest" + } + + switch preferredTransport { + case "grpc": + return c.initGRPCClient() + case "rest": + return c.initRESTClient() + case "jsonrpc": + return c.initJSONRPCClient() + case "sse": + return c.initSSEClient() + default: + return fmt.Errorf("unsupported transport: %s", preferredTransport) + } +} + +// initGRPCClient initializes the gRPC client +func (c *Client) initGRPCClient() error { + grpcAddr := extractGRPCAddress(c.config.AgentURL, c.agentCard) + if grpcAddr == "" { + return fmt.Errorf("no gRPC address available") + } + + config := &grpc.ClientConfig{ + Address: grpcAddr, + Timeout: c.config.RequestTimeout, + } + + var err error + c.grpcClient, err = grpc.NewClient(config) + if err != nil { + return err + } + + c.activeTransport = "grpc" + return nil +} + +// initRESTClient initializes the REST client +func (c *Client) initRESTClient() error { + config := &rest.ClientConfig{ + BaseURL: c.config.AgentURL, + Timeout: c.config.RequestTimeout, + } + + c.restClient = rest.NewClient(config) + + c.activeTransport = "rest" + return nil +} + +// initJSONRPCClient initializes the JSON-RPC client +func (c *Client) initJSONRPCClient() error { + jsonrpcURL := c.config.AgentURL + "/jsonrpc" + + config := &jsonrpc.ClientConfig{ + Endpoint: jsonrpcURL, + Timeout: c.config.RequestTimeout, + } + + c.jsonrpcClient = jsonrpc.NewClient(config) + + c.activeTransport = "jsonrpc" + return nil +} + +// initSSEClient initializes the SSE client +func (c *Client) initSSEClient() error { + sseURL := c.config.AgentURL + "/events" + + config := &sse.ClientConfig{ + BaseURL: sseURL, + Timeout: 0, + } + + c.sseClient = sse.NewClient(config) + + c.activeTransport = "sse" + return nil +} + +// Helper functions +func validateClientConfig(config *ClientConfig) error { + if config == nil { + return fmt.Errorf("config is required") + } + + if config.AgentURL == "" { + return fmt.Errorf("agent URL is required") + } + + return nil +} + +func setClientConfigDefaults(config *ClientConfig) { + if config.RequestTimeout == 0 { + config.RequestTimeout = 30 * time.Second + } + + if config.ConnectTimeout == 0 { + config.ConnectTimeout = 10 * time.Second + } + + if config.MaxRetries == 0 { + config.MaxRetries = 3 + } + + if config.RetryDelay == 0 { + config.RetryDelay = time.Second + } +} + +func isValidURL(str string) bool { + u, err := url.Parse(str) + return err == nil && u.Scheme != "" && u.Host != "" +} + +func extractGRPCAddress(agentURL string, agentCard *types.AgentCard) string { + // Try to extract from agent card first + if agentCard != nil { + for _, iface := range agentCard.AdditionalInterfaces { + if iface.Transport == "grpc" { + return iface.URL + } + } + } + + // Default: assume gRPC is on port 9090 + if u, err := url.Parse(agentURL); err == nil { + u.Scheme = "grpc" + if u.Port() == "" { + u.Host += ":9090" + } + return u.Host + } + + return "" +} + +func encodeBasicAuth(username, password string) string { + // This is a placeholder - in real implementation, use base64 encoding + return fmt.Sprintf("%s:%s", username, password) // Should be base64 encoded +} diff --git a/a2a/pkg/a2a/middleware.go b/a2a/pkg/a2a/middleware.go new file mode 100644 index 000000000..f5a587130 --- /dev/null +++ b/a2a/pkg/a2a/middleware.go @@ -0,0 +1,487 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package a2a + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/auth/jws" + "seata-go-ai-a2a/pkg/types" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// AuthMiddleware provides authentication middleware for HTTP handlers +type AuthMiddleware struct { + authManager *AuthManager + jwsValidator *jws.Validator + optional bool +} + +// NewAuthMiddleware creates a new authentication middleware +func NewAuthMiddleware(authManager *AuthManager, jwsValidator *jws.Validator, optional bool) *AuthMiddleware { + return &AuthMiddleware{ + authManager: authManager, + jwsValidator: jwsValidator, + optional: optional, + } +} + +// HTTPMiddleware returns an HTTP middleware function +func (am *AuthMiddleware) HTTPMiddleware() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Convert HTTP headers to gRPC metadata + md := metadata.New(nil) + for name, values := range r.Header { + for _, value := range values { + md.Append(strings.ToLower(name), value) + } + } + + // Attempt authentication + user, err := am.authManager.Authenticate(r.Context(), md) + if err != nil && !am.optional { + am.writeAuthError(w, err) + return + } + + // Add user to context + ctx := context.WithValue(r.Context(), "user", user) + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } +} + +// GRPCUnaryInterceptor returns a gRPC unary server interceptor for authentication +func (am *AuthMiddleware) GRPCUnaryInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + // Extract metadata from context + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + md = metadata.New(nil) + } + + // Attempt authentication + user, err := am.authManager.Authenticate(ctx, md) + if err != nil && !am.optional { + return nil, am.convertAuthError(err) + } + + // Add user to context + ctx = context.WithValue(ctx, "user", user) + + return handler(ctx, req) + } +} + +// GRPCStreamInterceptor returns a gRPC stream server interceptor for authentication +func (am *AuthMiddleware) GRPCStreamInterceptor() grpc.StreamServerInterceptor { + return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + // Extract metadata from context + md, ok := metadata.FromIncomingContext(ss.Context()) + if !ok { + md = metadata.New(nil) + } + + // Attempt authentication + user, err := am.authManager.Authenticate(ss.Context(), md) + if err != nil && !am.optional { + return am.convertAuthError(err) + } + + // Add user to context + ctx := context.WithValue(ss.Context(), "user", user) + wrappedStream := &wrappedServerStream{ss, ctx} + + return handler(srv, wrappedStream) + } +} + +// writeAuthError writes an authentication error as HTTP response +func (am *AuthMiddleware) writeAuthError(w http.ResponseWriter, err error) { + if authErr, ok := err.(*auth.AuthenticationError); ok { + statusCode := http.StatusUnauthorized + + switch authErr.Code { + case auth.ErrCodeMissingCredentials: + statusCode = http.StatusUnauthorized + case auth.ErrCodeInvalidCredentials: + statusCode = http.StatusUnauthorized + case auth.ErrCodeInvalidToken: + statusCode = http.StatusUnauthorized + case auth.ErrCodeExpiredToken: + statusCode = http.StatusUnauthorized + case auth.ErrCodeRateLimitExceeded: + statusCode = http.StatusTooManyRequests + default: + statusCode = http.StatusInternalServerError + } + + types.WriteErrorResponse(w, statusCode, string(authErr.Code), authErr.Message, authErr.Details) + } else { + types.WriteErrorResponse(w, http.StatusInternalServerError, "INTERNAL_ERROR", "Authentication failed", err.Error()) + } +} + +// convertAuthError converts authentication errors to gRPC status errors +func (am *AuthMiddleware) convertAuthError(err error) error { + if authErr, ok := err.(*auth.AuthenticationError); ok { + code := codes.Unauthenticated + + switch authErr.Code { + case auth.ErrCodeMissingCredentials: + code = codes.Unauthenticated + case auth.ErrCodeInvalidCredentials: + code = codes.Unauthenticated + case auth.ErrCodeInvalidToken: + code = codes.Unauthenticated + case auth.ErrCodeExpiredToken: + code = codes.Unauthenticated + case auth.ErrCodeRateLimitExceeded: + code = codes.ResourceExhausted + default: + code = codes.Internal + } + + return status.Error(code, authErr.Message) + } + + return status.Error(codes.Internal, "Authentication failed") +} + +// wrappedServerStream wraps a ServerStream to override context +type wrappedServerStream struct { + grpc.ServerStream + ctx context.Context +} + +func (w *wrappedServerStream) Context() context.Context { + return w.ctx +} + +// JWSMiddleware provides JWS validation middleware for Agent Cards +type JWSMiddleware struct { + validator *jws.Validator + trustedJWKSURLs []string + required bool +} + +// NewJWSMiddleware creates a new JWS validation middleware +func NewJWSMiddleware(validator *jws.Validator, trustedJWKSURLs []string, required bool) *JWSMiddleware { + return &JWSMiddleware{ + validator: validator, + trustedJWKSURLs: trustedJWKSURLs, + required: required, + } +} + +// ValidateAgentCard validates the JWS signature of an agent card +func (jm *JWSMiddleware) ValidateAgentCard(agentCard *types.AgentCard) error { + if agentCard == nil { + return fmt.Errorf("agent card is nil") + } + + if len(agentCard.Signatures) == 0 { + if jm.required { + return fmt.Errorf("agent card signature is required") + } + return nil // Optional validation, no signature present + } + + // Create a copy without signatures for validation + cardCopy := *agentCard + cardCopy.Signatures = nil + + payload, err := json.Marshal(cardCopy) + if err != nil { + return fmt.Errorf("failed to marshal agent card for validation: %w", err) + } + + // Validate each signature + for i, signature := range agentCard.Signatures { + // If we have trusted JWKS URLs, validate against them + if len(jm.trustedJWKSURLs) > 0 { + if err := jm.validateSignatureWithTrustedKeys(payload, signature); err != nil { + return fmt.Errorf("signature %d validation failed: %w", i, err) + } + } else { + // Standard JWS validation (fetches keys from JKU header) + if err := jm.validator.ValidateSignature(context.Background(), payload, signature); err != nil { + return fmt.Errorf("signature %d validation failed: %w", i, err) + } + } + } + + return nil +} + +// validateSignatureWithTrustedKeys validates a signature against trusted JWKS URLs only +func (jm *JWSMiddleware) validateSignatureWithTrustedKeys(payload []byte, signature *types.AgentCardSignature) error { + // Decode protected header to get the JKU + protectedBytes, err := json.RawMessage(signature.Protected).MarshalJSON() + if err != nil { + return fmt.Errorf("failed to decode protected header: %w", err) + } + + var header struct { + JKU string `json:"jku"` + } + if err := json.Unmarshal(protectedBytes, &header); err != nil { + return fmt.Errorf("failed to parse protected header: %w", err) + } + + // Check if the JKU is in our trusted list + trusted := false + for _, trustedURL := range jm.trustedJWKSURLs { + if header.JKU == trustedURL { + trusted = true + break + } + } + + if !trusted { + return fmt.Errorf("JKU %s is not in the trusted JWKS URLs list", header.JKU) + } + + // Proceed with standard validation + return jm.validator.ValidateSignature(context.Background(), payload, signature) +} + +// RateLimitMiddleware provides rate limiting middleware +type RateLimitMiddleware struct { + limiter auth.RateLimiter +} + +// NewRateLimitMiddleware creates a new rate limiting middleware +func NewRateLimitMiddleware(limiter auth.RateLimiter) *RateLimitMiddleware { + return &RateLimitMiddleware{ + limiter: limiter, + } +} + +// HTTPMiddleware returns an HTTP middleware function for rate limiting +func (rl *RateLimitMiddleware) HTTPMiddleware(keyFunc func(*http.Request) string) func(http.Handler) http.Handler { + if keyFunc == nil { + keyFunc = func(r *http.Request) string { + // Default: use IP address as key + return r.RemoteAddr + } + } + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + key := keyFunc(r) + + if !rl.limiter.Allow(r.Context(), key) { + types.WriteErrorResponse(w, http.StatusTooManyRequests, + string(auth.ErrCodeRateLimitExceeded), + "Rate limit exceeded", + "Too many requests") + return + } + + next.ServeHTTP(w, r) + }) + } +} + +// GRPCUnaryInterceptor returns a gRPC unary server interceptor for rate limiting +func (rl *RateLimitMiddleware) GRPCUnaryInterceptor(keyFunc func(context.Context) string) grpc.UnaryServerInterceptor { + if keyFunc == nil { + keyFunc = func(ctx context.Context) string { + // Default: try to get peer address + if peer, ok := metadata.FromIncomingContext(ctx); ok { + if values := peer.Get("x-forwarded-for"); len(values) > 0 { + return values[0] + } + } + return "unknown" + } + } + + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + key := keyFunc(ctx) + + if !rl.limiter.Allow(ctx, key) { + return nil, status.Error(codes.ResourceExhausted, "Rate limit exceeded") + } + + return handler(ctx, req) + } +} + +// AuditMiddleware provides audit logging middleware +type AuditMiddleware struct { + logger auth.AuditLogger +} + +// NewAuditMiddleware creates a new audit logging middleware +func NewAuditMiddleware(logger auth.AuditLogger) *AuditMiddleware { + return &AuditMiddleware{ + logger: logger, + } +} + +// HTTPMiddleware returns an HTTP middleware function for audit logging +func (al *AuditMiddleware) HTTPMiddleware() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + // Create a response writer wrapper to capture status + wrapper := &responseWriterWrapper{ResponseWriter: w, statusCode: http.StatusOK} + + // Process request + next.ServeHTTP(wrapper, r) + + // Log the request + event := map[string]interface{}{ + "timestamp": start, + "method": r.Method, + "path": r.URL.Path, + "status_code": wrapper.statusCode, + "duration_ms": time.Since(start).Milliseconds(), + "remote_addr": r.RemoteAddr, + "user_agent": r.UserAgent(), + } + + // Add user info if available + if user := r.Context().Value("user"); user != nil { + if authUser, ok := user.(auth.User); ok { + event["user_id"] = authUser.GetUserID() + event["user_name"] = authUser.GetName() + } + } + + if wrapper.statusCode >= 400 { + al.logger.LogAuthFailure(r.Context(), event) + } else { + al.logger.LogAuthSuccess(r.Context(), event) + } + }) + } +} + +// responseWriterWrapper wraps http.ResponseWriter to capture status code +type responseWriterWrapper struct { + http.ResponseWriter + statusCode int +} + +func (rw *responseWriterWrapper) WriteHeader(code int) { + rw.statusCode = code + rw.ResponseWriter.WriteHeader(code) +} + +// MiddlewareChain provides a convenient way to chain multiple middlewares +type MiddlewareChain struct { + middlewares []func(http.Handler) http.Handler +} + +// NewMiddlewareChain creates a new middleware chain +func NewMiddlewareChain() *MiddlewareChain { + return &MiddlewareChain{ + middlewares: make([]func(http.Handler) http.Handler, 0), + } +} + +// Add adds a middleware to the chain +func (mc *MiddlewareChain) Add(middleware func(http.Handler) http.Handler) *MiddlewareChain { + mc.middlewares = append(mc.middlewares, middleware) + return mc +} + +// Build builds the middleware chain and returns the final handler +func (mc *MiddlewareChain) Build(handler http.Handler) http.Handler { + // Apply middlewares in reverse order so they execute in the correct order + for i := len(mc.middlewares) - 1; i >= 0; i-- { + handler = mc.middlewares[i](handler) + } + return handler +} + +// Helper functions for common middleware setups + +// DefaultSecurityMiddleware creates a default security middleware chain +func DefaultSecurityMiddleware(authManager *AuthManager, jwsValidator *jws.Validator, rateLimiter auth.RateLimiter, auditLogger auth.AuditLogger) *MiddlewareChain { + chain := NewMiddlewareChain() + + // CORS middleware (allow all origins by default) + chain.Add(CORSMiddleware(nil)) + + // Rate limiting + if rateLimiter != nil { + chain.Add(NewRateLimitMiddleware(rateLimiter).HTTPMiddleware(nil)) + } + + // Authentication + if authManager != nil { + chain.Add(NewAuthMiddleware(authManager, jwsValidator, false).HTTPMiddleware()) + } + + // Audit logging + if auditLogger != nil { + chain.Add(NewAuditMiddleware(auditLogger).HTTPMiddleware()) + } + + return chain +} + +// OptionalSecurityMiddleware creates an optional security middleware chain (for public endpoints) +func OptionalSecurityMiddleware(authManager *AuthManager, jwsValidator *jws.Validator, auditLogger auth.AuditLogger) *MiddlewareChain { + chain := NewMiddlewareChain() + + // CORS middleware + chain.Add(CORSMiddleware(nil)) + + // Optional authentication (doesn't fail if no auth provided) + if authManager != nil { + chain.Add(NewAuthMiddleware(authManager, jwsValidator, true).HTTPMiddleware()) + } + + // Audit logging + if auditLogger != nil { + chain.Add(NewAuditMiddleware(auditLogger).HTTPMiddleware()) + } + + return chain +} + +// GetUserFromContext extracts the authenticated user from the request context +func GetUserFromContext(ctx context.Context) (auth.User, bool) { + user := ctx.Value("user") + if user == nil { + return &auth.UnauthenticatedUser{}, false + } + + if authUser, ok := user.(auth.User); ok { + return authUser, authUser.IsAuthenticated() + } + + return &auth.UnauthenticatedUser{}, false +} diff --git a/a2a/pkg/a2a/server.go b/a2a/pkg/a2a/server.go new file mode 100644 index 000000000..f36099478 --- /dev/null +++ b/a2a/pkg/a2a/server.go @@ -0,0 +1,589 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package a2a + +import ( + "context" + "crypto" + "fmt" + "log" + "net/http" + "sync" + "time" + + "golang.org/x/crypto/bcrypt" + + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/auth/authenticator" + "seata-go-ai-a2a/pkg/auth/jws" + "seata-go-ai-a2a/pkg/handler" + "seata-go-ai-a2a/pkg/task" + "seata-go-ai-a2a/pkg/task/store" + "seata-go-ai-a2a/pkg/transport/grpc" + "seata-go-ai-a2a/pkg/transport/jsonrpc" + "seata-go-ai-a2a/pkg/transport/rest" + "seata-go-ai-a2a/pkg/transport/sse" + "seata-go-ai-a2a/pkg/types" +) + +// ServerConfig represents the configuration for the A2A server +type ServerConfig struct { + // Basic Configuration + AgentCard *types.AgentCard `json:"agentCard"` + ListenAddr string `json:"listenAddr,omitempty"` // Default: ":8080" + TLSCertPath string `json:"tlsCertPath,omitempty"` // Optional TLS + TLSKeyPath string `json:"tlsKeyPath,omitempty"` // Optional TLS + + // Transport Configuration + EnableGRPC bool `json:"enableGRPC,omitempty"` // Default: true + EnableREST bool `json:"enableREST,omitempty"` // Default: true + EnableJSONRPC bool `json:"enableJSONRPC,omitempty"` // Default: true + EnableSSE bool `json:"enableSSE,omitempty"` // Default: true + + // gRPC specific + GRPCAddr string `json:"grpcAddr,omitempty"` // Default: ":9090" + + // CORS Configuration + CORSOrigins []string `json:"corsOrigins,omitempty"` // Default: allow all + + // Authentication Configuration + Authenticators []*AuthenticatorConfig `json:"authenticators,omitempty"` + + // JWS Signing Configuration (for AgentCard signing) + JWSPrivateKey crypto.PrivateKey `json:"-"` // Runtime only + JWSKeyID string `json:"jwsKeyId,omitempty"` + JWKSURL string `json:"jwksUrl,omitempty"` + + // Task Configuration + TaskStore string `json:"taskStore,omitempty"` // "memory" or "redis" + RedisURL string `json:"redisUrl,omitempty"` // For redis task store + + // Security & Rate Limiting + EnableRateLimit bool `json:"enableRateLimit,omitempty"` + RateLimit int `json:"rateLimit,omitempty"` // requests per minute + EnableAuditLog bool `json:"enableAuditLog,omitempty"` + + // Handler Interface Configuration + MessageHandler handler.MessageHandler `json:"-"` // User-defined message processing handler + AgentCardProvider handler.AgentCardProvider `json:"-"` // User-defined agent card provider + + // Lifecycle hooks + OnTaskCreated func(task *types.Task) `json:"-"` + OnTaskCompleted func(task *types.Task) `json:"-"` + OnTaskFailed func(task *types.Task) `json:"-"` +} + +// AuthenticatorConfig represents authenticator configuration +type AuthenticatorConfig struct { + Type string `json:"type"` // "jwt", "apikey", "basic", "mtls" + Name string `json:"name"` + Config map[string]interface{} `json:"config"` +} + +// Server represents the unified A2A server +type Server struct { + config *ServerConfig + agentCard *types.AgentCard + taskManager *task.TaskManager + authManager *AuthManager + jwsSigner *jws.Signer + jwsValidator *jws.Validator + + // Transport servers + grpcServer *grpc.Server + restServer *rest.Server + jsonrpcServer *jsonrpc.Server + sseServer *sse.Server + httpServer *http.Server + + // Lifecycle + mu sync.RWMutex + started bool + stopped bool +} + +// NewServer creates a new A2A server with the given configuration +func NewServer(config *ServerConfig) (*Server, error) { + if err := validateConfig(config); err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + + setConfigDefaults(config) + + server := &Server{ + config: config, + agentCard: config.AgentCard, + jwsSigner: jws.NewSigner(), + jwsValidator: jws.NewValidator(), + } + + // Initialize components + if err := server.initTaskManager(); err != nil { + return nil, fmt.Errorf("failed to initialize task manager: %w", err) + } + + if err := server.initAuthManager(); err != nil { + return nil, fmt.Errorf("failed to initialize auth manager: %w", err) + } + + if err := server.initTransports(); err != nil { + return nil, fmt.Errorf("failed to initialize transports: %w", err) + } + + if err := server.signAgentCard(); err != nil { + return nil, fmt.Errorf("failed to sign agent card: %w", err) + } + + return server, nil +} + +// NewServerWithHandlers creates a new A2A server with handler interfaces +func NewServerWithHandlers(agentCard *types.AgentCard, messageHandler handler.MessageHandler, agentCardProvider handler.AgentCardProvider) (*Server, error) { + config := &ServerConfig{ + AgentCard: agentCard, + ListenAddr: ":8080", + GRPCAddr: ":9090", + EnableGRPC: true, + EnableREST: true, + EnableJSONRPC: true, + EnableSSE: true, + TaskStore: "memory", + MessageHandler: messageHandler, + AgentCardProvider: agentCardProvider, + } + + return NewServer(config) +} + +// Start starts the A2A server +func (s *Server) Start() error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.started { + return fmt.Errorf("server already started") + } + + // Start transport servers + if s.config.EnableGRPC && s.grpcServer != nil { + if err := s.grpcServer.Start(); err != nil { + return fmt.Errorf("failed to start gRPC server: %w", err) + } + log.Printf("A2A gRPC server started on %s", s.config.GRPCAddr) + } + + // Create HTTP server for REST/JSON-RPC/SSE + if s.config.EnableREST || s.config.EnableJSONRPC || s.config.EnableSSE { + mux := http.NewServeMux() + + if s.config.EnableREST && s.restServer != nil { + mux.Handle("/api/v1/", s.restServer) + log.Println("A2A REST API enabled on /api/v1/") + } + + if s.config.EnableJSONRPC && s.jsonrpcServer != nil { + mux.Handle("/jsonrpc", s.jsonrpcServer) + log.Println("A2A JSON-RPC enabled on /jsonrpc") + } + + if s.config.EnableSSE && s.sseServer != nil { + mux.Handle("/events", s.sseServer) + log.Println("A2A SSE enabled on /events") + } + + // AgentCard endpoint + mux.HandleFunc("/agentCard", s.handleAgentCard) + + s.httpServer = &http.Server{ + Addr: s.config.ListenAddr, + Handler: mux, + } + + go func() { + var err error + if s.config.TLSCertPath != "" && s.config.TLSKeyPath != "" { + err = s.httpServer.ListenAndServeTLS(s.config.TLSCertPath, s.config.TLSKeyPath) + log.Printf("A2A HTTPS server started on %s", s.config.ListenAddr) + } else { + err = s.httpServer.ListenAndServe() + log.Printf("A2A HTTP server started on %s", s.config.ListenAddr) + } + if err != nil && err != http.ErrServerClosed { + log.Printf("HTTP server error: %v", err) + } + }() + } + + s.started = true + log.Printf("A2A Server started successfully") + + return nil +} + +// Stop gracefully stops the A2A server +func (s *Server) Stop() error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.stopped { + return nil + } + + // Stop HTTP server + if s.httpServer != nil { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := s.httpServer.Shutdown(ctx); err != nil { + log.Printf("HTTP server shutdown error: %v", err) + } + } + + // Stop gRPC server + if s.grpcServer != nil { + if err := s.grpcServer.Stop(); err != nil { + log.Printf("gRPC server stop error: %v", err) + } + } + + // Close validators + if s.jwsValidator != nil { + s.jwsValidator.Close() + } + + s.stopped = true + log.Printf("A2A Server stopped") + + return nil +} + +// GetAgentCard returns the agent card +func (s *Server) GetAgentCard() *types.AgentCard { + return s.agentCard +} + +// GetTaskManager returns the task manager +func (s *Server) GetTaskManager() *task.TaskManager { + return s.taskManager +} + +// initTaskManager initializes the task manager +func (s *Server) initTaskManager() error { + var taskStore store.TaskStore + + switch s.config.TaskStore { + case "redis": + if s.config.RedisURL == "" { + return fmt.Errorf("redis URL required for redis task store") + } + // For now, return error as Redis needs proper client configuration + return fmt.Errorf("redis task store not implemented in this simplified version") + case "memory": + fallthrough + default: + taskStore = store.NewMemoryTaskStore() + } + + config := task.DefaultConfig() + + // Check if handler interfaces are provided + if s.config.MessageHandler != nil && s.config.AgentCardProvider != nil { + // Create task manager with handler interfaces + s.taskManager = task.NewTaskManagerWithHandlers(taskStore, config, s.config.MessageHandler, s.config.AgentCardProvider) + log.Println("Task manager initialized with handler interfaces") + } else { + // Create standard task manager + s.taskManager = task.NewTaskManager(taskStore, config) + log.Println("Task manager initialized without handler interfaces") + } + + // Task manager lifecycle hooks not available in current implementation + // These would need to be implemented separately if needed + + return nil +} + +// initAuthManager initializes the authentication manager +func (s *Server) initAuthManager() error { + s.authManager = NewAuthManager() + + // Initialize authenticators based on config + for _, authConfig := range s.config.Authenticators { + authenticator, err := s.createAuthenticator(authConfig) + if err != nil { + return fmt.Errorf("failed to create authenticator %s: %w", authConfig.Name, err) + } + s.authManager.AddAuthenticator(authenticator) + } + + return nil +} + +// createAuthenticator creates an authenticator based on configuration +func (s *Server) createAuthenticator(config *AuthenticatorConfig) (auth.Authenticator, error) { + switch config.Type { + case "jwt": + jwtConfig := &authenticator.JWTAuthenticatorConfig{ + Name: config.Name, + } + + // Extract JWT-specific config + if jwksURL, ok := config.Config["jwksUrl"].(string); ok { + jwtConfig.JWKSURL = jwksURL + } + if audience, ok := config.Config["audience"].(string); ok { + jwtConfig.Audience = audience + } + if issuer, ok := config.Config["issuer"].(string); ok { + jwtConfig.Issuer = issuer + } + if clockSkew, ok := config.Config["clockSkew"].(string); ok { + if duration, err := time.ParseDuration(clockSkew); err == nil { + jwtConfig.ClockSkew = duration + } + } + + return authenticator.NewJWTAuthenticator(jwtConfig), nil + + case "apikey": + apiKeyConfig := &authenticator.APIKeyAuthenticatorConfig{ + Name: config.Name, + Keys: make(map[string]*authenticator.APIKeyInfo), + } + + // Extract API key-specific config + if keys, ok := config.Config["apiKeys"].(map[string]interface{}); ok { + for k, v := range keys { + if keyValue, ok := v.(string); ok { + apiKeyConfig.Keys[k] = &authenticator.APIKeyInfo{ + UserID: keyValue, + Name: keyValue, + Scopes: []string{"read", "write"}, + } + } + } + } + + // Set default schemes if not provided + if apiKeyConfig.Schemes == nil { + apiKeyConfig.Schemes = []*types.APIKeySecurityScheme{ + { + Description: "API Key Authentication", + Location: "header", + Name: "X-API-Key", + }, + } + } + + return authenticator.NewAPIKeyAuthenticator(apiKeyConfig), nil + + case "basic": + basicConfig := &authenticator.BasicAuthenticatorConfig{ + Name: config.Name, + Users: make(map[string]*authenticator.BasicUserInfo), + } + + // Extract Basic auth-specific config + if users, ok := config.Config["users"].(map[string]interface{}); ok { + for username, password := range users { + if passwordStr, ok := password.(string); ok { + // Hash password with bcrypt + hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(passwordStr), bcrypt.DefaultCost) + basicConfig.Users[username] = &authenticator.BasicUserInfo{ + UserID: username, + Username: username, + PasswordHash: string(hashedPassword), + Name: username, + Scopes: []string{"read", "write"}, + CreatedAt: time.Now(), + IsActive: true, + } + } + } + } + + return authenticator.NewBasicAuthenticator(basicConfig), nil + + case "mtls": + mtlsConfig := &authenticator.MTLSAuthenticatorConfig{ + Name: config.Name, + TrustedCAs: []string{}, + ClientCertificates: make(map[string]*authenticator.ClientCertInfo), + RequireClientCert: true, + VerifyHostname: true, + } + + // Extract mTLS-specific config + if caCerts, ok := config.Config["trustedCAs"].([]interface{}); ok { + for _, cert := range caCerts { + if certStr, ok := cert.(string); ok { + mtlsConfig.TrustedCAs = append(mtlsConfig.TrustedCAs, certStr) + } + } + } + + authenticator, err := authenticator.NewMTLSAuthenticator(mtlsConfig) + if err != nil { + return nil, err + } + return authenticator, nil + + default: + return nil, fmt.Errorf("unknown authenticator type: %s", config.Type) + } +} + +// initTransports initializes all transport servers +func (s *Server) initTransports() error { + // Initialize gRPC transport + if s.config.EnableGRPC { + grpcConfig := &grpc.ServerConfig{ + Address: s.config.GRPCAddr, + AgentCard: s.agentCard, + TaskManager: s.taskManager, + } + + var err error + s.grpcServer, err = grpc.NewServer(grpcConfig) + if err != nil { + return fmt.Errorf("failed to create gRPC server: %w", err) + } + } + + // Initialize REST transport + if s.config.EnableREST { + s.restServer = rest.NewServer() + s.restServer.RegisterA2AHandlers(s.agentCard, s.taskManager) + } + + // Initialize JSON-RPC transport + if s.config.EnableJSONRPC { + s.jsonrpcServer = jsonrpc.NewServer() + s.jsonrpcServer.RegisterA2AHandlers(s.agentCard, s.taskManager) + s.jsonrpcServer.SetCORSOrigins(s.config.CORSOrigins) + } + + // Initialize SSE transport + if s.config.EnableSSE { + s.sseServer = sse.NewServer(s.taskManager) + // SSE server doesn't have RegisterA2AHandlers method - it handles requests directly + } + + return nil +} + +// signAgentCard signs the agent card if JWS configuration is provided +func (s *Server) signAgentCard() error { + if s.config.JWSPrivateKey == nil || s.config.JWSKeyID == "" || s.config.JWKSURL == "" { + log.Println("JWS signing configuration not provided, agent card will not be signed") + return nil + } + + signature, err := s.jwsSigner.SignAgentCard( + context.Background(), + s.agentCard, + s.config.JWSPrivateKey, + s.config.JWSKeyID, + s.config.JWKSURL, + ) + if err != nil { + return fmt.Errorf("failed to sign agent card: %w", err) + } + + s.agentCard.Signatures = []*types.AgentCardSignature{signature} + log.Printf("Agent card signed successfully with key ID: %s", s.config.JWSKeyID) + + return nil +} + +// handleAgentCard handles AgentCard requests +func (s *Server) handleAgentCard(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + w.Header().Set("Content-Type", "application/json") + + // Return the agent card directly as JSON + if err := types.WriteJSONResponse(w, http.StatusOK, s.agentCard); err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + } +} + +// validateConfig validates the server configuration +func validateConfig(config *ServerConfig) error { + if config == nil { + return fmt.Errorf("config is required") + } + + if config.AgentCard == nil { + return fmt.Errorf("agent card is required") + } + + if config.AgentCard.Name == "" { + return fmt.Errorf("agent card name is required") + } + + if config.AgentCard.ProtocolVersion == "" { + return fmt.Errorf("agent card protocol version is required") + } + + // Validate JWS config consistency + jwsConfigProvided := (config.JWSPrivateKey != nil) || (config.JWSKeyID != "") || (config.JWKSURL != "") + if jwsConfigProvided { + if config.JWSPrivateKey == nil { + return fmt.Errorf("JWS private key is required when JWS configuration is provided") + } + if config.JWSKeyID == "" { + return fmt.Errorf("JWS key ID is required when JWS configuration is provided") + } + if config.JWKSURL == "" { + return fmt.Errorf("JWKS URL is required when JWS configuration is provided") + } + } + + return nil +} + +// setConfigDefaults sets default values for configuration +func setConfigDefaults(config *ServerConfig) { + if config.ListenAddr == "" { + config.ListenAddr = ":8080" + } + + if config.GRPCAddr == "" { + config.GRPCAddr = ":9090" + } + + // Enable all transports by default + if !config.EnableGRPC && !config.EnableREST && !config.EnableJSONRPC && !config.EnableSSE { + config.EnableGRPC = true + config.EnableREST = true + config.EnableJSONRPC = true + config.EnableSSE = true + } + + if config.TaskStore == "" { + config.TaskStore = "memory" + } + + if config.RateLimit <= 0 { + config.RateLimit = 1000 // 1000 requests per minute default + } +} diff --git a/a2a/pkg/a2a/simple.go b/a2a/pkg/a2a/simple.go new file mode 100644 index 000000000..363771658 --- /dev/null +++ b/a2a/pkg/a2a/simple.go @@ -0,0 +1,468 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package a2a + +import ( + "crypto" + "fmt" + "log" + "time" + + "seata-go-ai-a2a/pkg/types" +) + +// SimpleServerBuilder provides a fluent API for building A2A servers with minimal configuration +type SimpleServerBuilder struct { + config *ServerConfig +} + +// NewSimpleServer creates a new simple server builder +func NewSimpleServer(name, description string) *SimpleServerBuilder { + agentCard := &types.AgentCard{ + ProtocolVersion: "1.0", + Name: name, + Description: description, + PreferredTransport: "rest", + Version: "1.0.0", + Capabilities: &types.AgentCapabilities{ + Streaming: false, + PushNotifications: false, + StateTransitionHistory: true, + }, + } + + config := &ServerConfig{ + AgentCard: agentCard, + ListenAddr: ":8080", + GRPCAddr: ":9090", + EnableGRPC: true, + EnableREST: true, + EnableJSONRPC: true, + EnableSSE: false, + TaskStore: "memory", + EnableRateLimit: false, + EnableAuditLog: false, + Authenticators: []*AuthenticatorConfig{}, + } + + return &SimpleServerBuilder{config: config} +} + +// WithURL sets the agent URL +func (b *SimpleServerBuilder) WithURL(url string) *SimpleServerBuilder { + b.config.AgentCard.URL = url + return b +} + +// WithPort sets the HTTP listening port +func (b *SimpleServerBuilder) WithPort(port int) *SimpleServerBuilder { + b.config.ListenAddr = fmt.Sprintf(":%d", port) + return b +} + +// WithGRPCPort sets the gRPC listening port +func (b *SimpleServerBuilder) WithGRPCPort(port int) *SimpleServerBuilder { + b.config.GRPCAddr = fmt.Sprintf(":%d", port) + return b +} + +// WithTLS enables TLS with the provided certificate and key files +func (b *SimpleServerBuilder) WithTLS(certPath, keyPath string) *SimpleServerBuilder { + b.config.TLSCertPath = certPath + b.config.TLSKeyPath = keyPath + return b +} + +// WithAPIKeyAuth adds API key authentication +func (b *SimpleServerBuilder) WithAPIKeyAuth(apiKeys map[string]string) *SimpleServerBuilder { + authConfig := &AuthenticatorConfig{ + Type: "apikey", + Name: "apikey-auth", + Config: map[string]interface{}{ + "location": "header", + "keyName": "X-API-Key", + "apiKeys": apiKeys, + }, + } + b.config.Authenticators = append(b.config.Authenticators, authConfig) + return b +} + +// WithJWTAuth adds JWT authentication +func (b *SimpleServerBuilder) WithJWTAuth(jwksURL, audience, issuer string) *SimpleServerBuilder { + authConfig := &AuthenticatorConfig{ + Type: "jwt", + Name: "jwt-auth", + Config: map[string]interface{}{ + "jwksUrl": jwksURL, + "audience": audience, + "issuer": issuer, + }, + } + b.config.Authenticators = append(b.config.Authenticators, authConfig) + return b +} + +// WithBasicAuth adds basic authentication +func (b *SimpleServerBuilder) WithBasicAuth(users map[string]string) *SimpleServerBuilder { + authConfig := &AuthenticatorConfig{ + Type: "basic", + Name: "basic-auth", + Config: map[string]interface{}{ + "users": users, + }, + } + b.config.Authenticators = append(b.config.Authenticators, authConfig) + return b +} + +// WithJWSSigning enables JWS signing for the agent card +func (b *SimpleServerBuilder) WithJWSSigning(privateKey crypto.PrivateKey, keyID, jwksURL string) *SimpleServerBuilder { + b.config.JWSPrivateKey = privateKey + b.config.JWSKeyID = keyID + b.config.JWKSURL = jwksURL + return b +} + +// WithRedisStore configures Redis as the task store +func (b *SimpleServerBuilder) WithRedisStore(redisURL string) *SimpleServerBuilder { + b.config.TaskStore = "redis" + b.config.RedisURL = redisURL + return b +} + +// WithStreamingSupport enables streaming capabilities +func (b *SimpleServerBuilder) WithStreamingSupport() *SimpleServerBuilder { + b.config.AgentCard.Capabilities.Streaming = true + b.config.EnableSSE = true + return b +} + +// WithPushNotifications enables push notification support +func (b *SimpleServerBuilder) WithPushNotifications() *SimpleServerBuilder { + b.config.AgentCard.Capabilities.PushNotifications = true + return b +} + +// WithCORS sets allowed CORS origins +func (b *SimpleServerBuilder) WithCORS(origins []string) *SimpleServerBuilder { + b.config.CORSOrigins = origins + return b +} + +// WithRateLimit enables rate limiting +func (b *SimpleServerBuilder) WithRateLimit(requestsPerMinute int) *SimpleServerBuilder { + b.config.EnableRateLimit = true + b.config.RateLimit = requestsPerMinute + return b +} + +// OnTaskCreated sets a callback for task creation events +func (b *SimpleServerBuilder) OnTaskCreated(callback func(task *types.Task)) *SimpleServerBuilder { + b.config.OnTaskCreated = callback + return b +} + +// OnTaskCompleted sets a callback for task completion events +func (b *SimpleServerBuilder) OnTaskCompleted(callback func(task *types.Task)) *SimpleServerBuilder { + b.config.OnTaskCompleted = callback + return b +} + +// OnTaskFailed sets a callback for task failure events +func (b *SimpleServerBuilder) OnTaskFailed(callback func(task *types.Task)) *SimpleServerBuilder { + b.config.OnTaskFailed = callback + return b +} + +// Build creates and returns the configured A2A server +func (b *SimpleServerBuilder) Build() (*Server, error) { + return NewServer(b.config) +} + +// BuildAndStart creates, starts, and returns the A2A server +func (b *SimpleServerBuilder) BuildAndStart() (*Server, error) { + server, err := b.Build() + if err != nil { + return nil, err + } + + if err := server.Start(); err != nil { + return nil, err + } + + return server, nil +} + +// SimpleClientBuilder provides a fluent API for building A2A clients with minimal configuration +type SimpleClientBuilder struct { + config *ClientConfig +} + +// NewSimpleClient creates a new simple client builder +func NewSimpleClient(agentURL string) *SimpleClientBuilder { + config := &ClientConfig{ + AgentURL: agentURL, + RequestTimeout: 30 * time.Second, + ConnectTimeout: 10 * time.Second, + MaxRetries: 3, + RetryDelay: time.Second, + ValidateJWS: false, + SkipAgentCardFetch: false, + } + + return &SimpleClientBuilder{config: config} +} + +// WithTransport sets the preferred transport +func (b *SimpleClientBuilder) WithTransport(transport string) *SimpleClientBuilder { + b.config.PreferredTransport = transport + return b +} + +// WithTimeout sets request timeout +func (b *SimpleClientBuilder) WithTimeout(timeout time.Duration) *SimpleClientBuilder { + b.config.RequestTimeout = timeout + return b +} + +// WithAPIKeyAuth adds API key authentication +func (b *SimpleClientBuilder) WithAPIKeyAuth(apiKey string) *SimpleClientBuilder { + b.config.AuthConfig = &ClientAuthConfig{ + Type: "apikey", + Credentials: map[string]string{ + "key": apiKey, + "location": "header", + "name": "X-API-Key", + }, + } + return b +} + +// WithBearerToken adds bearer token authentication +func (b *SimpleClientBuilder) WithBearerToken(token string) *SimpleClientBuilder { + b.config.AuthConfig = &ClientAuthConfig{ + Type: "bearer", + Credentials: map[string]string{ + "token": token, + }, + } + return b +} + +// WithBasicAuth adds basic authentication +func (b *SimpleClientBuilder) WithBasicAuth(username, password string) *SimpleClientBuilder { + b.config.AuthConfig = &ClientAuthConfig{ + Type: "basic", + Credentials: map[string]string{ + "username": username, + "password": password, + }, + } + return b +} + +// WithJWSValidation enables JWS validation for agent cards +func (b *SimpleClientBuilder) WithJWSValidation(trustedJWKSURLs []string) *SimpleClientBuilder { + b.config.ValidateJWS = true + b.config.TrustedJWKSURLs = trustedJWKSURLs + return b +} + +// WithStreaming enables streaming support +func (b *SimpleClientBuilder) WithStreaming() *SimpleClientBuilder { + b.config.Streaming = true + return b +} + +// WithRetries configures retry behavior +func (b *SimpleClientBuilder) WithRetries(maxRetries int, delay time.Duration) *SimpleClientBuilder { + b.config.MaxRetries = maxRetries + b.config.RetryDelay = delay + return b +} + +// Build creates and returns the configured A2A client +func (b *SimpleClientBuilder) Build() (*Client, error) { + return NewClient(b.config) +} + +// Convenience functions for quick setup + +// QuickServer creates and starts a simple A2A server with minimal configuration +func QuickServer(name, description string, port int) (*Server, error) { + return NewSimpleServer(name, description). + WithPort(port). + BuildAndStart() +} + +// QuickServerWithAuth creates and starts a simple A2A server with API key authentication +func QuickServerWithAuth(name, description string, port int, apiKeys map[string]string) (*Server, error) { + return NewSimpleServer(name, description). + WithPort(port). + WithAPIKeyAuth(apiKeys). + BuildAndStart() +} + +// QuickClient creates a simple A2A client +func QuickClient(agentURL string) (*Client, error) { + return NewSimpleClient(agentURL).Build() +} + +// QuickClientWithAuth creates a simple A2A client with API key authentication +func QuickClientWithAuth(agentURL, apiKey string) (*Client, error) { + return NewSimpleClient(agentURL). + WithAPIKeyAuth(apiKey). + Build() +} + +// Message helpers for easy message creation + +// TextMessage creates a simple text message +func TextMessage(text string) *types.Message { + return &types.Message{ + MessageID: fmt.Sprintf("msg_%d", time.Now().UnixNano()), + Role: types.RoleUser, + Parts: []types.Part{ + &types.TextPart{Text: text}, + }, + Kind: "request", + } +} + +// TextMessageWithContext creates a text message with context ID +func TextMessageWithContext(text, contextID string) *types.Message { + msg := TextMessage(text) + msg.ContextID = contextID + return msg +} + +// FileMessage creates a message with file content +func FileMessage(filename, mimeType string, content []byte) *types.Message { + return &types.Message{ + MessageID: fmt.Sprintf("msg_%d", time.Now().UnixNano()), + Role: types.RoleUser, + Parts: []types.Part{ + &types.FilePart{ + Name: filename, + MimeType: mimeType, + Content: &types.FileWithBytes{Bytes: content}, + }, + }, + Kind: "request", + } +} + +// DataMessage creates a message with structured data +func DataMessage(data map[string]any) *types.Message { + return &types.Message{ + MessageID: fmt.Sprintf("msg_%d", time.Now().UnixNano()), + Role: types.RoleUser, + Parts: []types.Part{ + &types.DataPart{Data: data}, + }, + Kind: "request", + } +} + +// Usage examples and documentation + +// Example usage: +// +// // Simple server setup +// server, err := QuickServer("My Agent", "A helpful AI agent", 8080) +// if err != nil { +// log.Fatal(err) +// } +// defer server.Stop() +// +// // Server with authentication +// apiKeys := map[string]string{"user1": "secret123", "user2": "secret456"} +// server, err := QuickServerWithAuth("My Agent", "A helpful AI agent", 8080, apiKeys) +// +// // Advanced server setup +// server, err := NewSimpleServer("My Agent", "A helpful AI agent"). +// WithPort(8080). +// WithJWTAuth("https://auth.example.com/.well-known/jwks.json", "my-app", "auth.example.com"). +// WithStreamingSupport(). +// WithPushNotifications(). +// WithRedisStore("redis://localhost:6379"). +// OnTaskCreated(func(task *types.Task) { +// log.Printf("Task created: %s", task.ID) +// }). +// BuildAndStart() +// +// // Simple client setup +// client, err := QuickClient("http://localhost:8080") +// if err != nil { +// log.Fatal(err) +// } +// defer client.Close() +// +// // Client with authentication +// client, err := QuickClientWithAuth("http://localhost:8080", "secret123") +// +// // Advanced client setup +// client, err := NewSimpleClient("http://localhost:8080"). +// WithTransport("grpc"). +// WithBearerToken("jwt-token-here"). +// WithJWSValidation([]string{"https://trusted.example.com/.well-known/jwks.json"}). +// WithStreaming(). +// Build() +// +// // Send a message +// message := TextMessage("Hello, how can you help me?") +// task, err := client.SendMessage(context.Background(), message, nil) +// if err != nil { +// log.Fatal(err) +// } +// +// // Wait for completion and get result +// for { +// updatedTask, err := client.GetTask(context.Background(), task.ID) +// if err != nil { +// log.Fatal(err) +// } +// +// if updatedTask.Status.State.IsTerminal() { +// if updatedTask.Status.State == types.TaskStateCompleted { +// log.Printf("Task completed: %+v", updatedTask.Artifacts) +// } else { +// log.Printf("Task failed: %s", updatedTask.Status.Update) +// } +// break +// } +// +// time.Sleep(time.Second) +// } + +// LoggingMiddleware provides simple logging for development +func LoggingMiddleware() func(*types.Task) { + return func(task *types.Task) { + log.Printf("[A2A] Task %s: %s", task.ID, task.Status.State.String()) + } +} + +// DebugMiddleware provides detailed logging for debugging +func DebugMiddleware() func(*types.Task) { + return func(task *types.Task) { + log.Printf("[A2A DEBUG] Task %s (%s): State=%s, Messages=%d, Artifacts=%d", + task.ID, task.ContextID, task.Status.State.String(), + len(task.History), len(task.Artifacts)) + } +} diff --git a/a2a/pkg/a2a/utils.go b/a2a/pkg/a2a/utils.go new file mode 100644 index 000000000..44a758b4f --- /dev/null +++ b/a2a/pkg/a2a/utils.go @@ -0,0 +1,552 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package a2a + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "seata-go-ai-a2a/pkg/types" +) + +// AgentCardBuilder provides a fluent API for building agent cards +type AgentCardBuilder struct { + card *types.AgentCard +} + +// NewAgentCard creates a new agent card builder +func NewAgentCard(name, description string) *AgentCardBuilder { + card := &types.AgentCard{ + ProtocolVersion: "1.0", + Name: name, + Description: description, + Version: "1.0.0", + Capabilities: &types.AgentCapabilities{ + Streaming: false, + PushNotifications: false, + StateTransitionHistory: true, + }, + SecuritySchemes: make(map[string]types.SecurityScheme), + Security: []*types.Security{}, + Skills: []*types.AgentSkill{}, + } + + return &AgentCardBuilder{card: card} +} + +// WithURL sets the agent URL +func (b *AgentCardBuilder) WithURL(url string) *AgentCardBuilder { + b.card.URL = url + return b +} + +// WithVersion sets the agent version +func (b *AgentCardBuilder) WithVersion(version string) *AgentCardBuilder { + b.card.Version = version + return b +} + +// WithProvider sets the provider information +func (b *AgentCardBuilder) WithProvider(organization, url string) *AgentCardBuilder { + b.card.Provider = &types.AgentProvider{ + Organization: organization, + URL: url, + } + return b +} + +// WithDocumentationURL sets the documentation URL +func (b *AgentCardBuilder) WithDocumentationURL(url string) *AgentCardBuilder { + b.card.DocumentationURL = url + return b +} + +// WithIconURL sets the icon URL +func (b *AgentCardBuilder) WithIconURL(url string) *AgentCardBuilder { + b.card.IconURL = url + return b +} + +// WithPreferredTransport sets the preferred transport +func (b *AgentCardBuilder) WithPreferredTransport(transport string) *AgentCardBuilder { + b.card.PreferredTransport = transport + return b +} + +// AddInterface adds an additional interface +func (b *AgentCardBuilder) AddInterface(url, transport string) *AgentCardBuilder { + b.card.AdditionalInterfaces = append(b.card.AdditionalInterfaces, &types.AgentInterface{ + URL: url, + Transport: transport, + }) + return b +} + +// WithStreaming enables streaming capabilities +func (b *AgentCardBuilder) WithStreaming() *AgentCardBuilder { + b.card.Capabilities.Streaming = true + return b +} + +// WithPushNotifications enables push notification capabilities +func (b *AgentCardBuilder) WithPushNotifications() *AgentCardBuilder { + b.card.Capabilities.PushNotifications = true + return b +} + +// AddAPIKeyAuth adds API key authentication scheme +func (b *AgentCardBuilder) AddAPIKeyAuth(name, description, location, keyName string) *AgentCardBuilder { + scheme := &types.APIKeySecurityScheme{ + Description: description, + Location: location, // "query", "header", or "cookie" + Name: keyName, + } + + b.card.SecuritySchemes[name] = scheme + return b +} + +// AddBearerAuth adds Bearer token authentication scheme +func (b *AgentCardBuilder) AddBearerAuth(name, description, bearerFormat string) *AgentCardBuilder { + scheme := &types.HTTPAuthSecurityScheme{ + Description: description, + Scheme: "bearer", + BearerFormat: bearerFormat, + } + + b.card.SecuritySchemes[name] = scheme + return b +} + +// AddBasicAuth adds Basic authentication scheme +func (b *AgentCardBuilder) AddBasicAuth(name, description string) *AgentCardBuilder { + scheme := &types.HTTPAuthSecurityScheme{ + Description: description, + Scheme: "basic", + } + + b.card.SecuritySchemes[name] = scheme + return b +} + +// AddOAuth2Auth adds OAuth2 authentication scheme +func (b *AgentCardBuilder) AddOAuth2Auth(name, description, authURL, tokenURL string, scopes map[string]string) *AgentCardBuilder { + flow := &types.AuthorizationCodeOAuthFlow{ + AuthorizationURL: authURL, + TokenURL: tokenURL, + Scopes: scopes, + } + + scheme := &types.OAuth2SecurityScheme{ + Description: description, + Flows: flow, + } + + b.card.SecuritySchemes[name] = scheme + return b +} + +// AddMutualTLSAuth adds mutual TLS authentication scheme +func (b *AgentCardBuilder) AddMutualTLSAuth(name, description string) *AgentCardBuilder { + scheme := &types.MutualTLSSecurityScheme{ + Description: description, + } + + b.card.SecuritySchemes[name] = scheme + return b +} + +// RequireAuth adds a security requirement +func (b *AgentCardBuilder) RequireAuth(schemes map[string][]string) *AgentCardBuilder { + security := &types.Security{ + Schemes: schemes, + } + b.card.Security = append(b.card.Security, security) + return b +} + +// AddSkill adds a skill to the agent card +func (b *AgentCardBuilder) AddSkill(id, name, description string, tags, examples []string) *AgentCardBuilder { + skill := &types.AgentSkill{ + ID: id, + Name: name, + Description: description, + Tags: tags, + Examples: examples, + InputModes: []string{"text"}, + OutputModes: []string{"text"}, + } + + b.card.Skills = append(b.card.Skills, skill) + return b +} + +// AddExtension adds an extension to the capabilities +func (b *AgentCardBuilder) AddExtension(uri, description string, required bool, params map[string]any) *AgentCardBuilder { + extension := &types.AgentExtension{ + URI: uri, + Description: description, + Required: required, + Params: params, + } + + b.card.Capabilities.Extensions = append(b.card.Capabilities.Extensions, extension) + return b +} + +// Build returns the constructed agent card +func (b *AgentCardBuilder) Build() *types.AgentCard { + return b.card +} + +// TaskBuilder provides a fluent API for building tasks (useful for testing) +type TaskBuilder struct { + task *types.Task +} + +// NewTask creates a new task builder +func NewTask(id, contextID string) *TaskBuilder { + task := &types.Task{ + ID: id, + ContextID: contextID, + Kind: "task", + Status: &types.TaskStatus{ + State: types.TaskStateSubmitted, + Timestamp: time.Now(), + }, + History: []*types.Message{}, + Artifacts: []*types.Artifact{}, + Metadata: make(map[string]any), + } + + return &TaskBuilder{task: task} +} + +// WithState sets the task state +func (b *TaskBuilder) WithState(state types.TaskState) *TaskBuilder { + b.task.Status.State = state + b.task.Status.Timestamp = time.Now() + return b +} + +// AddMessage adds a message to the task history +func (b *TaskBuilder) AddMessage(message *types.Message) *TaskBuilder { + b.task.History = append(b.task.History, message) + return b +} + +// AddArtifact adds an artifact to the task +func (b *TaskBuilder) AddArtifact(artifact *types.Artifact) *TaskBuilder { + b.task.Artifacts = append(b.task.Artifacts, artifact) + return b +} + +// WithMetadata sets task metadata +func (b *TaskBuilder) WithMetadata(metadata map[string]any) *TaskBuilder { + b.task.Metadata = metadata + return b +} + +// Build returns the constructed task +func (b *TaskBuilder) Build() *types.Task { + return b.task +} + +// HTTP Response Utilities + +// WriteJSONResponse writes a JSON response with appropriate headers +func WriteJSONResponse(w http.ResponseWriter, statusCode int, data interface{}) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + return json.NewEncoder(w).Encode(data) +} + +// WriteErrorResponse writes a standardized error response +func WriteErrorResponse(w http.ResponseWriter, statusCode int, code, message, details string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + errorResponse := map[string]interface{}{ + "error": map[string]interface{}{ + "code": code, + "message": message, + }, + } + + if details != "" { + errorResponse["error"].(map[string]interface{})["details"] = details + } + + json.NewEncoder(w).Encode(errorResponse) +} + +// Auth Utilities + +// ExtractBearerToken extracts the bearer token from an Authorization header +func ExtractBearerToken(authHeader string) (string, error) { + if authHeader == "" { + return "", fmt.Errorf("missing authorization header") + } + + if !strings.HasPrefix(authHeader, "Bearer ") { + return "", fmt.Errorf("invalid authorization header format") + } + + token := strings.TrimPrefix(authHeader, "Bearer ") + if token == "" { + return "", fmt.Errorf("empty bearer token") + } + + return token, nil +} + +// ExtractBasicAuth extracts username and password from a Basic auth header +func ExtractBasicAuth(authHeader string) (string, string, error) { + if authHeader == "" { + return "", "", fmt.Errorf("missing authorization header") + } + + if !strings.HasPrefix(authHeader, "Basic ") { + return "", "", fmt.Errorf("invalid authorization header format") + } + + encoded := strings.TrimPrefix(authHeader, "Basic ") + decoded, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return "", "", fmt.Errorf("invalid base64 encoding") + } + + credentials := string(decoded) + parts := strings.SplitN(credentials, ":", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("invalid credentials format") + } + + return parts[0], parts[1], nil +} + +// EncodeBasicAuth encodes username and password for Basic authentication +func EncodeBasicAuth(username, password string) string { + credentials := username + ":" + password + encoded := base64.StdEncoding.EncodeToString([]byte(credentials)) + return "Basic " + encoded +} + +// URL Utilities + +// ParseTaskIDFromPath extracts task ID from a path like "/api/v1/tasks/{taskId}" +func ParseTaskIDFromPath(path string) string { + parts := strings.Split(strings.Trim(path, "/"), "/") + for i, part := range parts { + if part == "tasks" && i+1 < len(parts) { + return parts[i+1] + } + } + return "" +} + +// BuildTaskURL builds a task URL from base URL and task ID +func BuildTaskURL(baseURL, taskID string) string { + return fmt.Sprintf("%s/api/v1/tasks/%s", strings.TrimSuffix(baseURL, "/"), taskID) +} + +// Validation Utilities + +// ValidateAgentCard performs basic validation of an agent card +func ValidateAgentCard(card *types.AgentCard) error { + if card == nil { + return fmt.Errorf("agent card is nil") + } + + if card.Name == "" { + return fmt.Errorf("agent card name is required") + } + + if card.Description == "" { + return fmt.Errorf("agent card description is required") + } + + if card.ProtocolVersion == "" { + return fmt.Errorf("agent card protocol version is required") + } + + if card.URL == "" { + return fmt.Errorf("agent card URL is required") + } + + // Validate security schemes if present + for name, scheme := range card.SecuritySchemes { + if scheme == nil { + return fmt.Errorf("security scheme '%s' is nil", name) + } + } + + return nil +} + +// ValidateMessage performs basic validation of a message +func ValidateMessage(msg *types.Message) error { + if msg == nil { + return fmt.Errorf("message is nil") + } + + if msg.MessageID == "" { + return fmt.Errorf("message ID is required") + } + + if len(msg.Parts) == 0 { + return fmt.Errorf("message must have at least one part") + } + + // Validate parts + for i, part := range msg.Parts { + if part == nil { + return fmt.Errorf("message part %d is nil", i) + } + } + + return nil +} + +// Context Utilities + +// GenerateContextID generates a unique context ID +func GenerateContextID() string { + return fmt.Sprintf("ctx_%d", time.Now().UnixNano()) +} + +// GenerateMessageID generates a unique message ID +func GenerateMessageID() string { + return fmt.Sprintf("msg_%d", time.Now().UnixNano()) +} + +// GenerateTaskID generates a unique task ID +func GenerateTaskID() string { + return fmt.Sprintf("task_%d", time.Now().UnixNano()) +} + +// GenerateArtifactID generates a unique artifact ID +func GenerateArtifactID() string { + return fmt.Sprintf("artifact_%d", time.Now().UnixNano()) +} + +// Content Type Utilities + +// DetectContentType attempts to detect content type from filename or content +func DetectContentType(filename string, content []byte) string { + // First try to detect from content + if len(content) > 0 { + return http.DetectContentType(content) + } + + // Fall back to filename extension + ext := strings.ToLower(filename) + if strings.HasSuffix(ext, ".json") { + return "application/json" + } + if strings.HasSuffix(ext, ".xml") { + return "application/xml" + } + if strings.HasSuffix(ext, ".txt") { + return "text/plain" + } + if strings.HasSuffix(ext, ".html") { + return "text/html" + } + if strings.HasSuffix(ext, ".pdf") { + return "application/pdf" + } + if strings.HasSuffix(ext, ".png") { + return "image/png" + } + if strings.HasSuffix(ext, ".jpg") || strings.HasSuffix(ext, ".jpeg") { + return "image/jpeg" + } + + return "application/octet-stream" +} + +// Time Utilities + +// FormatTimestamp formats a timestamp for A2A protocol +func FormatTimestamp(t time.Time) string { + return t.UTC().Format(time.RFC3339) +} + +// ParseTimestamp parses a timestamp from A2A protocol format +func ParseTimestamp(s string) (time.Time, error) { + return time.Parse(time.RFC3339, s) +} + +// IsTaskStateTerminal checks if a task state is terminal +func IsTaskStateTerminal(state types.TaskState) bool { + return state.IsTerminal() +} + +// IsTaskStateInterrupted checks if a task state is interrupted +func IsTaskStateInterrupted(state types.TaskState) bool { + return state.IsInterrupted() +} + +// Middleware helper for common patterns + +// CORSMiddleware creates a CORS middleware function +func CORSMiddleware(origins []string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin := r.Header.Get("Origin") + + // Check if origin is allowed + allowed := false + if len(origins) == 0 { + // Allow all origins if none specified + allowed = true + } else { + for _, allowedOrigin := range origins { + if origin == allowedOrigin { + allowed = true + break + } + } + } + + if allowed { + if len(origins) == 0 { + w.Header().Set("Access-Control-Allow-Origin", "*") + } else { + w.Header().Set("Access-Control-Allow-Origin", origin) + w.Header().Set("Vary", "Origin") + } + } + + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Key") + + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusOK) + return + } + + next.ServeHTTP(w, r) + }) + } +} diff --git a/a2a/pkg/auth/authenticator/apikey.go b/a2a/pkg/auth/authenticator/apikey.go new file mode 100644 index 000000000..e8007a8c2 --- /dev/null +++ b/a2a/pkg/auth/authenticator/apikey.go @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package authenticator + +import ( + "context" + "strings" + + "google.golang.org/grpc/metadata" + + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/types" +) + +// APIKeyAuthenticator implements API Key-based authentication +type APIKeyAuthenticator struct { + name string + validKeys map[string]*APIKeyInfo // key -> user info mapping + schemes []*types.APIKeySecurityScheme +} + +// APIKeyInfo represents information about an API key +type APIKeyInfo struct { + UserID string `json:"userId"` + Name string `json:"name"` + Email string `json:"email"` + Scopes []string `json:"scopes"` + Metadata map[string]string `json:"metadata"` +} + +// APIKeyAuthenticatorConfig represents configuration for API Key authentication +type APIKeyAuthenticatorConfig struct { + Name string `json:"name"` + Keys map[string]*APIKeyInfo `json:"keys"` // key -> user info + Schemes []*types.APIKeySecurityScheme `json:"schemes"` +} + +// NewAPIKeyAuthenticator creates a new API Key authenticator +func NewAPIKeyAuthenticator(config *APIKeyAuthenticatorConfig) *APIKeyAuthenticator { + return &APIKeyAuthenticator{ + name: config.Name, + validKeys: config.Keys, + schemes: config.Schemes, + } +} + +// Name returns the name of the authenticator +func (a *APIKeyAuthenticator) Name() string { + return a.name +} + +// Authenticate attempts to authenticate a request using API keys +func (a *APIKeyAuthenticator) Authenticate(ctx context.Context, md metadata.MD) (auth.User, error) { + // Try each configured scheme + for _, scheme := range a.schemes { + if !a.SupportsScheme(scheme) { + continue + } + + apiKey, found := a.extractAPIKey(md, scheme) + if !found { + continue + } + + // Validate the API key + keyInfo, valid := a.validKeys[apiKey] + if !valid { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Invalid API key", + } + } + + // Convert metadata to map[string]interface{} + claims := make(map[string]interface{}) + for k, v := range keyInfo.Metadata { + claims[k] = v + } + claims["auth_method"] = "api_key" + claims["scheme_name"] = scheme.Name + + return auth.NewAuthenticatedUser( + keyInfo.UserID, + keyInfo.Name, + keyInfo.Email, + keyInfo.Scopes, + claims, + ), nil + } + + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeMissingCredentials, + Message: "No valid API key found", + } +} + +// SupportsScheme returns true if this authenticator supports API Key schemes +func (a *APIKeyAuthenticator) SupportsScheme(scheme types.SecurityScheme) bool { + return scheme.SecuritySchemeType() == types.SecuritySchemeTypeAPIKey +} + +// extractAPIKey extracts the API key from metadata based on the scheme configuration +func (a *APIKeyAuthenticator) extractAPIKey(md metadata.MD, scheme *types.APIKeySecurityScheme) (string, bool) { + switch scheme.Location { + case "header": + headerName := strings.ToLower(scheme.Name) + values := md.Get(headerName) + if len(values) > 0 && values[0] != "" { + return values[0], true + } + + // Also try the authorization header for common patterns + if headerName == "authorization" || headerName == "x-api-key" { + authHeaders := md.Get("authorization") + for _, authHeader := range authHeaders { + if strings.HasPrefix(authHeader, "ApiKey ") { + return strings.TrimPrefix(authHeader, "ApiKey "), true + } + if strings.HasPrefix(authHeader, "Bearer ") && headerName == "authorization" { + // Some APIs use Bearer tokens as API keys + return strings.TrimPrefix(authHeader, "Bearer "), true + } + } + } + + case "query": + // gRPC doesn't directly support query parameters, but they might be passed + // in a special metadata field + queryMD := md.Get("grpc-query-" + scheme.Name) + if len(queryMD) > 0 && queryMD[0] != "" { + return queryMD[0], true + } + + case "cookie": + // Extract from cookie header + cookieHeaders := md.Get("cookie") + for _, cookieHeader := range cookieHeaders { + cookies := parseCookies(cookieHeader) + if value, exists := cookies[scheme.Name]; exists { + return value, true + } + } + } + + return "", false +} + +// parseCookies parses a cookie header string and returns a map of cookie names to values +func parseCookies(cookieHeader string) map[string]string { + cookies := make(map[string]string) + pairs := strings.Split(cookieHeader, ";") + + for _, pair := range pairs { + pair = strings.TrimSpace(pair) + if pair == "" { + continue + } + + parts := strings.SplitN(pair, "=", 2) + if len(parts) == 2 { + name := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + cookies[name] = value + } + } + + return cookies +} + +// AddAPIKey adds a new API key to the authenticator +func (a *APIKeyAuthenticator) AddAPIKey(key string, info *APIKeyInfo) { + if a.validKeys == nil { + a.validKeys = make(map[string]*APIKeyInfo) + } + a.validKeys[key] = info +} + +// RemoveAPIKey removes an API key from the authenticator +func (a *APIKeyAuthenticator) RemoveAPIKey(key string) { + delete(a.validKeys, key) +} + +// ListAPIKeys returns all configured API keys (without the actual key values) +func (a *APIKeyAuthenticator) ListAPIKeys() []*APIKeyInfo { + var keys []*APIKeyInfo + for _, info := range a.validKeys { + keys = append(keys, info) + } + return keys +} + +// ValidateAPIKey checks if an API key is valid +func (a *APIKeyAuthenticator) ValidateAPIKey(key string) (*APIKeyInfo, bool) { + info, exists := a.validKeys[key] + return info, exists +} diff --git a/a2a/pkg/auth/authenticator/basic.go b/a2a/pkg/auth/authenticator/basic.go new file mode 100644 index 000000000..5abeb8f15 --- /dev/null +++ b/a2a/pkg/auth/authenticator/basic.go @@ -0,0 +1,478 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package authenticator + +import ( + "context" + "crypto/rand" + "encoding/base64" + "fmt" + "strings" + "time" + + "golang.org/x/crypto/bcrypt" + "google.golang.org/grpc/metadata" + + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/types" +) + +// BasicAuthenticator implements HTTP Basic authentication with secure password handling +type BasicAuthenticator struct { + name string + users map[string]*BasicUserInfo // username -> user info mapping + bcryptCost int // bcrypt cost parameter + maxFailures int // maximum failed attempts before locking + lockoutTime time.Duration // lockout duration after max failures + failureTracker map[string]*FailureInfo // track failed authentication attempts +} + +// BasicUserInfo represents information about a basic auth user +type BasicUserInfo struct { + UserID string `json:"userId"` + Username string `json:"username"` + PasswordHash string `json:"passwordHash"` // bcrypt hash + Salt string `json:"salt,omitempty"` // Additional salt if needed + Name string `json:"name"` + Email string `json:"email"` + Scopes []string `json:"scopes"` + Metadata map[string]string `json:"metadata"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + LastLoginAt *time.Time `json:"lastLoginAt,omitempty"` + IsActive bool `json:"isActive"` +} + +// FailureInfo tracks authentication failures for rate limiting +type FailureInfo struct { + Count int `json:"count"` + LastFailAt time.Time `json:"lastFailAt"` + LockedUntil *time.Time `json:"lockedUntil,omitempty"` +} + +// BasicAuthenticatorConfig represents configuration for Basic authentication +type BasicAuthenticatorConfig struct { + Name string `json:"name"` + Users map[string]*BasicUserInfo `json:"users"` // username -> user info + BcryptCost int `json:"bcryptCost,omitempty"` // Default: 12 + MaxFailures int `json:"maxFailures,omitempty"` // Default: 5 + LockoutTime time.Duration `json:"lockoutTime,omitempty"` // Default: 15 minutes +} + +// NewBasicAuthenticator creates a new Basic authenticator with security hardening +func NewBasicAuthenticator(config *BasicAuthenticatorConfig) *BasicAuthenticator { + bcryptCost := config.BcryptCost + if bcryptCost == 0 { + bcryptCost = 12 // Secure default + } + + maxFailures := config.MaxFailures + if maxFailures == 0 { + maxFailures = 5 // Default: 5 failures before lockout + } + + lockoutTime := config.LockoutTime + if lockoutTime == 0 { + lockoutTime = 15 * time.Minute // Default: 15 minutes lockout + } + + return &BasicAuthenticator{ + name: config.Name, + users: config.Users, + bcryptCost: bcryptCost, + maxFailures: maxFailures, + lockoutTime: lockoutTime, + failureTracker: make(map[string]*FailureInfo), + } +} + +// Name returns the name of the authenticator +func (b *BasicAuthenticator) Name() string { + return b.name +} + +// Authenticate attempts to authenticate a request using HTTP Basic authentication +func (b *BasicAuthenticator) Authenticate(ctx context.Context, md metadata.MD) (auth.User, error) { + // Extract authorization header + authHeaders := md.Get("authorization") + if len(authHeaders) == 0 { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeMissingCredentials, + Message: "Missing authorization header", + } + } + + authHeader := authHeaders[0] + if !strings.HasPrefix(authHeader, "Basic ") { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Invalid authorization header format, expected Basic credentials", + } + } + + // Decode base64 credentials + encodedCredentials := strings.TrimPrefix(authHeader, "Basic ") + decodedBytes, err := base64.StdEncoding.DecodeString(encodedCredentials) + if err != nil { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Invalid base64 encoding in Basic authentication", + Details: err.Error(), + } + } + + credentials := string(decodedBytes) + parts := strings.SplitN(credentials, ":", 2) + if len(parts) != 2 { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Invalid Basic authentication format, expected username:password", + } + } + + username := parts[0] + password := parts[1] + + // Validate input length to prevent DoS attacks + if len(username) > 255 || len(password) > 512 { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Username or password too long", + } + } + + // Check if account is locked due to failed attempts + if b.isAccountLocked(username) { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Account temporarily locked due to too many failed attempts", + } + } + + // Validate credentials + userInfo, exists := b.users[username] + if !exists || !userInfo.IsActive { + b.recordFailedAttempt(username) + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Invalid username or password", + } + } + + // Validate password using bcrypt + if !b.validatePassword(password, userInfo.PasswordHash) { + b.recordFailedAttempt(username) + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Invalid username or password", + } + } + + // Reset failure count on successful authentication + b.resetFailureCount(username) + + // Update last login time + now := time.Now() + userInfo.LastLoginAt = &now + userInfo.UpdatedAt = now + + // Convert metadata to map[string]interface{} + claims := make(map[string]interface{}) + for k, v := range userInfo.Metadata { + claims[k] = v + } + claims["auth_method"] = "basic" + claims["username"] = username + claims["last_login_at"] = now.Unix() + claims["user_created_at"] = userInfo.CreatedAt.Unix() + + return auth.NewAuthenticatedUser( + userInfo.UserID, + userInfo.Name, + userInfo.Email, + userInfo.Scopes, + claims, + ), nil +} + +// SupportsScheme returns true if this authenticator supports HTTP Basic schemes +func (b *BasicAuthenticator) SupportsScheme(scheme types.SecurityScheme) bool { + if scheme.SecuritySchemeType() == types.SecuritySchemeTypeHTTPAuth { + if httpScheme, ok := scheme.(*types.HTTPAuthSecurityScheme); ok { + return strings.ToLower(httpScheme.Scheme) == "basic" + } + } + return false +} + +// validatePassword validates a password against its bcrypt hash +func (b *BasicAuthenticator) validatePassword(password, passwordHash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(password)) + return err == nil +} + +// HashPassword creates a bcrypt hash of the given password +func (b *BasicAuthenticator) HashPassword(password string) (string, error) { + // Validate password strength + if err := b.validatePasswordStrength(password); err != nil { + return "", err + } + + hash, err := bcrypt.GenerateFromPassword([]byte(password), b.bcryptCost) + if err != nil { + return "", &auth.AuthenticationError{ + Code: ErrCodePasswordHashFailed, + Message: "Failed to hash password", + Details: err.Error(), + } + } + + return string(hash), nil +} + +// validatePasswordStrength validates password meets security requirements +func (b *BasicAuthenticator) validatePasswordStrength(password string) error { + if len(password) < 8 { + return &auth.AuthenticationError{ + Code: ErrCodeWeakPassword, + Message: "Password must be at least 8 characters long", + } + } + + if len(password) > 512 { + return &auth.AuthenticationError{ + Code: ErrCodeWeakPassword, + Message: "Password too long (max 512 characters)", + } + } + + // Check for basic complexity requirements + var hasUpper, hasLower, hasDigit bool + for _, char := range password { + switch { + case char >= 'A' && char <= 'Z': + hasUpper = true + case char >= 'a' && char <= 'z': + hasLower = true + case char >= '0' && char <= '9': + hasDigit = true + } + } + + if !hasUpper || !hasLower || !hasDigit { + return &auth.AuthenticationError{ + Code: ErrCodeWeakPassword, + Message: "Password must contain at least one uppercase letter, one lowercase letter, and one digit", + } + } + + return nil +} + +// isAccountLocked checks if an account is currently locked +func (b *BasicAuthenticator) isAccountLocked(username string) bool { + failInfo, exists := b.failureTracker[username] + if !exists { + return false + } + + if failInfo.LockedUntil != nil && time.Now().Before(*failInfo.LockedUntil) { + return true + } + + // Clear expired lockout + if failInfo.LockedUntil != nil && time.Now().After(*failInfo.LockedUntil) { + failInfo.LockedUntil = nil + failInfo.Count = 0 + } + + return false +} + +// recordFailedAttempt records a failed authentication attempt +func (b *BasicAuthenticator) recordFailedAttempt(username string) { + now := time.Now() + + failInfo, exists := b.failureTracker[username] + if !exists { + failInfo = &FailureInfo{} + b.failureTracker[username] = failInfo + } + + failInfo.Count++ + failInfo.LastFailAt = now + + // Lock account if max failures reached + if failInfo.Count >= b.maxFailures { + lockUntil := now.Add(b.lockoutTime) + failInfo.LockedUntil = &lockUntil + } +} + +// resetFailureCount resets the failure count for a user +func (b *BasicAuthenticator) resetFailureCount(username string) { + delete(b.failureTracker, username) +} + +// AddUser adds a new user to the authenticator with secure password hashing +func (b *BasicAuthenticator) AddUser(username, password string, info *BasicUserInfo) error { + if b.users == nil { + b.users = make(map[string]*BasicUserInfo) + } + + // Hash the password + passwordHash, err := b.HashPassword(password) + if err != nil { + return err + } + + // Generate user ID if not provided + if info.UserID == "" { + info.UserID = b.generateUserID() + } + + // Set timestamps + now := time.Now() + info.Username = username + info.PasswordHash = passwordHash + info.CreatedAt = now + info.UpdatedAt = now + info.IsActive = true + + b.users[username] = info + return nil +} + +// UpdateUserPassword updates a user's password with proper hashing +func (b *BasicAuthenticator) UpdateUserPassword(username, newPassword string) error { + userInfo, exists := b.users[username] + if !exists { + return &auth.AuthenticationError{ + Code: ErrCodeUserNotFound, + Message: "User not found", + } + } + + passwordHash, err := b.HashPassword(newPassword) + if err != nil { + return err + } + + userInfo.PasswordHash = passwordHash + userInfo.UpdatedAt = time.Now() + + // Reset failure count on password change + b.resetFailureCount(username) + + return nil +} + +// RemoveUser removes a user from the authenticator +func (b *BasicAuthenticator) RemoveUser(username string) { + delete(b.users, username) + delete(b.failureTracker, username) +} + +// DeactivateUser deactivates a user account without removing it +func (b *BasicAuthenticator) DeactivateUser(username string) error { + userInfo, exists := b.users[username] + if !exists { + return &auth.AuthenticationError{ + Code: ErrCodeUserNotFound, + Message: "User not found", + } + } + + userInfo.IsActive = false + userInfo.UpdatedAt = time.Now() + b.resetFailureCount(username) + + return nil +} + +// ActivateUser activates a user account +func (b *BasicAuthenticator) ActivateUser(username string) error { + userInfo, exists := b.users[username] + if !exists { + return &auth.AuthenticationError{ + Code: ErrCodeUserNotFound, + Message: "User not found", + } + } + + userInfo.IsActive = true + userInfo.UpdatedAt = time.Now() + b.resetFailureCount(username) + + return nil +} + +// ListUsers returns all configured users (without sensitive information) +func (b *BasicAuthenticator) ListUsers() []*BasicUserInfo { + var users []*BasicUserInfo + for _, info := range b.users { + // Create a copy without sensitive information + userCopy := *info + userCopy.PasswordHash = "" // Never expose password hash + userCopy.Salt = "" // Never expose salt + users = append(users, &userCopy) + } + return users +} + +// GetUser returns user information (without sensitive data) +func (b *BasicAuthenticator) GetUser(username string) (*BasicUserInfo, bool) { + info, exists := b.users[username] + if !exists { + return nil, false + } + + // Return copy without sensitive information + userCopy := *info + userCopy.PasswordHash = "" + userCopy.Salt = "" + return &userCopy, true +} + +// GetFailureInfo returns failure tracking information for a user +func (b *BasicAuthenticator) GetFailureInfo(username string) (*FailureInfo, bool) { + info, exists := b.failureTracker[username] + return info, exists +} + +// UnlockUser manually unlocks a user account +func (b *BasicAuthenticator) UnlockUser(username string) { + b.resetFailureCount(username) +} + +// generateUserID generates a secure random user ID +func (b *BasicAuthenticator) generateUserID() string { + bytes := make([]byte, 16) + if _, err := rand.Read(bytes); err != nil { + // Fallback to timestamp-based ID if random fails + return fmt.Sprintf("user_%d", time.Now().UnixNano()) + } + return fmt.Sprintf("user_%x", bytes) +} + +// Additional error codes for password management +const ( + ErrCodePasswordHashFailed = "PASSWORD_HASH_FAILED" + ErrCodeWeakPassword = "WEAK_PASSWORD" + ErrCodeUserNotFound = "USER_NOT_FOUND" +) diff --git a/a2a/pkg/auth/authenticator/jwt.go b/a2a/pkg/auth/authenticator/jwt.go new file mode 100644 index 000000000..60c82461c --- /dev/null +++ b/a2a/pkg/auth/authenticator/jwt.go @@ -0,0 +1,395 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package authenticator + +import ( + "context" + "strings" + "sync" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/lestrrat-go/jwx/v2/jwk" + "google.golang.org/grpc/metadata" + + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/types" +) + +// JWTAuthenticator implements JWT-based authentication +type JWTAuthenticator struct { + name string + jwksURL string + audience string + issuer string + clockSkew time.Duration + keySet jwk.Set + validator auth.TokenValidator +} + +// JWTAuthenticatorConfig represents configuration for JWT authentication +type JWTAuthenticatorConfig struct { + Name string `json:"name"` + JWKSURL string `json:"jwksUrl"` + Audience string `json:"audience,omitempty"` + Issuer string `json:"issuer,omitempty"` + ClockSkew time.Duration `json:"clockSkew,omitempty"` + RequiredClaims []string `json:"requiredClaims,omitempty"` +} + +// NewJWTAuthenticator creates a new JWT authenticator +func NewJWTAuthenticator(config *JWTAuthenticatorConfig) *JWTAuthenticator { + clockSkew := config.ClockSkew + if clockSkew == 0 { + clockSkew = 30 * time.Second // Default clock skew + } + + return &JWTAuthenticator{ + name: config.Name, + jwksURL: config.JWKSURL, + audience: config.Audience, + issuer: config.Issuer, + clockSkew: clockSkew, + validator: NewDefaultTokenValidator(config.JWKSURL, config.Audience, config.Issuer), + } +} + +// Name returns the name of the authenticator +func (j *JWTAuthenticator) Name() string { + return j.name +} + +// Authenticate attempts to authenticate a request using JWT tokens +func (j *JWTAuthenticator) Authenticate(ctx context.Context, md metadata.MD) (auth.User, error) { + // Extract authorization header + authHeaders := md.Get("authorization") + if len(authHeaders) == 0 { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeMissingCredentials, + Message: "Missing authorization header", + } + } + + authHeader := authHeaders[0] + if !strings.HasPrefix(authHeader, "Bearer ") { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Invalid authorization header format, expected Bearer token", + } + } + + token := strings.TrimPrefix(authHeader, "Bearer ") + if token == "" { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Empty bearer token", + } + } + + // Validate the token + claims, err := j.validator.ValidateToken(ctx, token) + if err != nil { + return &auth.UnauthenticatedUser{}, err + } + + // Extract user information from claims + user, err := j.extractUserFromClaims(claims) + if err != nil { + return &auth.UnauthenticatedUser{}, err + } + + return user, nil +} + +// SupportsScheme returns true if this authenticator supports JWT-related schemes +func (j *JWTAuthenticator) SupportsScheme(scheme types.SecurityScheme) bool { + switch scheme.SecuritySchemeType() { + case types.SecuritySchemeTypeHTTPAuth: + if httpScheme, ok := scheme.(*types.HTTPAuthSecurityScheme); ok { + return strings.ToLower(httpScheme.Scheme) == "bearer" + } + return false + case types.SecuritySchemeTypeOAuth2: + return true + case types.SecuritySchemeTypeOpenIDConnect: + return true + default: + return false + } +} + +// extractUserFromClaims extracts user information from JWT claims +func (j *JWTAuthenticator) extractUserFromClaims(claims map[string]interface{}) (auth.User, error) { + // Extract standard claims + var userID, name, email string + var scopes []string + + if sub, ok := claims["sub"].(string); ok { + userID = sub + } + + if nameValue, ok := claims["name"].(string); ok { + name = nameValue + } else if preferredUsername, ok := claims["preferred_username"].(string); ok { + name = preferredUsername + } + + if emailValue, ok := claims["email"].(string); ok { + email = emailValue + } + + // Extract scopes + if scopeValue, ok := claims["scope"]; ok { + if scopeStr, ok := scopeValue.(string); ok { + scopes = strings.Fields(scopeStr) + } else if scopeSlice, ok := scopeValue.([]interface{}); ok { + for _, s := range scopeSlice { + if scopeStr, ok := s.(string); ok { + scopes = append(scopes, scopeStr) + } + } + } + } + + if userID == "" { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Missing subject (sub) claim in JWT", + } + } + + return auth.NewAuthenticatedUser(userID, name, email, scopes, claims), nil +} + +// DefaultTokenValidator implements JWT token validation +type DefaultTokenValidator struct { + jwksURL string + audience string + issuer string + keySet jwk.Set + mutex sync.RWMutex +} + +// NewDefaultTokenValidator creates a new token validator +func NewDefaultTokenValidator(jwksURL, audience, issuer string) *DefaultTokenValidator { + return &DefaultTokenValidator{ + jwksURL: jwksURL, + audience: audience, + issuer: issuer, + } +} + +// ValidateToken validates a JWT token and returns the claims +func (v *DefaultTokenValidator) ValidateToken(ctx context.Context, tokenString string) (map[string]interface{}, error) { + // Parse the token without verification first to get the kid + token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) + if err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Failed to parse JWT token", + Details: err.Error(), + } + } + + // Get the key ID from the header + kid, ok := token.Header["kid"].(string) + if !ok { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Missing key ID (kid) in JWT header", + } + } + + // Fetch the key set if not already cached (thread-safe) + v.mutex.RLock() + currentKeySet := v.keySet + v.mutex.RUnlock() + + if currentKeySet == nil { + // Double-check pattern to avoid race condition + v.mutex.Lock() + if v.keySet == nil { + keySet, err := jwk.Fetch(ctx, v.jwksURL) + if err != nil { + v.mutex.Unlock() + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeJWKSFetchFailed, + Message: "Failed to fetch JWKS", + Details: err.Error(), + } + } + v.keySet = keySet + } + currentKeySet = v.keySet + v.mutex.Unlock() + } + + // Find the key + key, ok := currentKeySet.LookupKeyID(kid) + if !ok { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Key not found in JWKS", + Details: "Key ID: " + kid, + } + } + + // Get the raw key + var rawKey interface{} + if err := key.Raw(&rawKey); err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Failed to extract raw key", + Details: err.Error(), + } + } + + // Parse and validate the token + parsedToken, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Validate signing method + switch token.Method.(type) { + case *jwt.SigningMethodRSA, *jwt.SigningMethodECDSA: + return rawKey, nil + default: + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Unsupported signing method", + Details: token.Header["alg"].(string), + } + } + }) + + if err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Token validation failed", + Details: err.Error(), + } + } + + claims, ok := parsedToken.Claims.(jwt.MapClaims) + if !ok || !parsedToken.Valid { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Invalid token claims", + } + } + + // Validate standard claims + if err := v.validateStandardClaims(claims); err != nil { + return nil, err + } + + // Convert to map[string]interface{} + result := make(map[string]interface{}) + for k, v := range claims { + result[k] = v + } + + return result, nil +} + +// ValidateWithKeySet validates a token using the provided key set +func (v *DefaultTokenValidator) ValidateWithKeySet(ctx context.Context, tokenString string, keySet jwk.Set) (map[string]interface{}, error) { + // Create a temporary validator to avoid modifying shared state + tempValidator := &DefaultTokenValidator{ + jwksURL: v.jwksURL, + audience: v.audience, + issuer: v.issuer, + keySet: keySet, + } + + return tempValidator.ValidateToken(ctx, tokenString) +} + +// validateStandardClaims validates standard JWT claims +func (v *DefaultTokenValidator) validateStandardClaims(claims jwt.MapClaims) error { + now := time.Now() + + // Validate audience + if v.audience != "" { + if aud, ok := claims["aud"]; ok { + audValid := false + switch audVal := aud.(type) { + case string: + audValid = audVal == v.audience + case []interface{}: + for _, a := range audVal { + if audStr, ok := a.(string); ok && audStr == v.audience { + audValid = true + break + } + } + } + if !audValid { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Invalid audience", + Details: "Expected: " + v.audience, + } + } + } else { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Missing audience claim", + Details: "Expected: " + v.audience, + } + } + } + + // Validate issuer + if v.issuer != "" { + if iss, ok := claims["iss"].(string); ok { + if iss != v.issuer { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Invalid issuer", + Details: "Expected: " + v.issuer, + } + } + } else { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Missing issuer claim", + Details: "Expected: " + v.issuer, + } + } + } + + // Validate expiration + if exp, ok := claims["exp"].(float64); ok { + if now.Unix() >= int64(exp) { + return &auth.AuthenticationError{ + Code: auth.ErrCodeExpiredToken, + Message: "Token has expired", + } + } + } + + // Validate not before + if nbf, ok := claims["nbf"].(float64); ok { + if now.Unix() < int64(nbf) { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidToken, + Message: "Token not yet valid", + } + } + } + + return nil +} diff --git a/a2a/pkg/auth/authenticator/mtls.go b/a2a/pkg/auth/authenticator/mtls.go new file mode 100644 index 000000000..9cf6ae826 --- /dev/null +++ b/a2a/pkg/auth/authenticator/mtls.go @@ -0,0 +1,1218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package authenticator + +import ( + "bytes" + "context" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "fmt" + "io" + "net" + "net/http" + "path/filepath" + "sync" + "time" + + "golang.org/x/crypto/ocsp" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" + + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/types" +) + +// MTLSAuthenticator implements mutual TLS (mTLS) authentication with complete certificate validation +type MTLSAuthenticator struct { + name string + trustedCAs *x509.CertPool + clientCertificates map[string]*ClientCertInfo // cert serial -> client info + requireClientCert bool + verifyHostname bool + allowedCNPatterns []string // Common Name patterns + allowedSANs []string // Subject Alternative Names + certificateValidator CertificateValidator + revocationChecker RevocationChecker + crlCache *CRLCache + ocspCache *OCSPCache + keyStrengthValidator KeyStrengthValidator + certificateChainDepth int + clockSkewTolerance time.Duration +} + +// ClientCertInfo represents comprehensive information about a client certificate +type ClientCertInfo struct { + UserID string `json:"userId"` + Name string `json:"name"` + Email string `json:"email"` + Organization string `json:"organization"` + Scopes []string `json:"scopes"` + Metadata map[string]string `json:"metadata"` + SerialNumber string `json:"serialNumber"` + Fingerprint string `json:"fingerprint"` // SHA-256 fingerprint + Subject pkix.Name `json:"subject"` + Issuer pkix.Name `json:"issuer"` + NotBefore time.Time `json:"notBefore"` + NotAfter time.Time `json:"notAfter"` + KeyUsage x509.KeyUsage `json:"keyUsage"` + ExtKeyUsage []x509.ExtKeyUsage `json:"extKeyUsage"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + LastUsedAt *time.Time `json:"lastUsedAt,omitempty"` +} + +// MTLSAuthenticatorConfig represents comprehensive configuration for mTLS authentication +type MTLSAuthenticatorConfig struct { + Name string `json:"name"` + TrustedCAs []string `json:"trustedCAs"` // PEM-encoded CA certificates + ClientCertificates map[string]*ClientCertInfo `json:"clientCertificates"` // serial -> client info + RequireClientCert bool `json:"requireClientCert"` + VerifyHostname bool `json:"verifyHostname"` + AllowedCNPatterns []string `json:"allowedCNPatterns"` + AllowedSANs []string `json:"allowedSANs"` + EnableCRLChecking bool `json:"enableCRLChecking"` + EnableOCSPChecking bool `json:"enableOCSPChecking"` + CRLURLs []string `json:"crlUrls,omitempty"` + OCSPURLs []string `json:"ocspUrls,omitempty"` + CRLCacheDuration time.Duration `json:"crlCacheDuration,omitempty"` // Default: 1 hour + OCSPCacheDuration time.Duration `json:"ocspCacheDuration,omitempty"` // Default: 1 hour + CertificateChainDepth int `json:"certificateChainDepth,omitempty"` // Default: 5 + ClockSkewTolerance time.Duration `json:"clockSkewTolerance,omitempty"` // Default: 5 minutes + MinRSAKeySize int `json:"minRSAKeySize,omitempty"` // Default: 2048 + MinECDSACurveSize int `json:"minECDSACurveSize,omitempty"` // Default: 256 + RequireKeyUsage []x509.KeyUsage `json:"requireKeyUsage,omitempty"` + RequireExtKeyUsage []x509.ExtKeyUsage `json:"requireExtKeyUsage,omitempty"` + HTTPTimeout time.Duration `json:"httpTimeout,omitempty"` // For CRL/OCSP fetching +} + +// CertificateValidator interface for comprehensive certificate validation +type CertificateValidator interface { + ValidateCertificate(cert *x509.Certificate, intermediates *x509.CertPool) error + ValidateCertificateChain(chain []*x509.Certificate) error +} + +// RevocationChecker interface for certificate revocation checking +type RevocationChecker interface { + IsRevoked(ctx context.Context, cert *x509.Certificate, issuer *x509.Certificate) (bool, error) + CheckCRL(ctx context.Context, cert *x509.Certificate, issuer *x509.Certificate) (bool, error) + CheckOCSP(ctx context.Context, cert *x509.Certificate, issuer *x509.Certificate) (bool, error) +} + +// KeyStrengthValidator validates cryptographic key strength +type KeyStrengthValidator interface { + ValidateKeyStrength(publicKey crypto.PublicKey) error +} + +// CRLCache manages Certificate Revocation List caching +type CRLCache struct { + cache map[string]*CRLEntry + mutex sync.RWMutex + duration time.Duration + httpClient *http.Client +} + +// CRLEntry represents a cached CRL entry +type CRLEntry struct { + CRL *x509.RevocationList + FetchedAt time.Time + URL string +} + +// OCSPCache manages OCSP response caching +type OCSPCache struct { + cache map[string]*OCSPEntry + mutex sync.RWMutex + duration time.Duration + httpClient *http.Client +} + +// OCSPEntry represents a cached OCSP response +type OCSPEntry struct { + Response *ocsp.Response + FetchedAt time.Time + URL string +} + +// NewMTLSAuthenticator creates a new comprehensive mTLS authenticator +func NewMTLSAuthenticator(config *MTLSAuthenticatorConfig) (*MTLSAuthenticator, error) { + // Parse trusted CA certificates + trustedCAs := x509.NewCertPool() + for _, caPEM := range config.TrustedCAs { + if !trustedCAs.AppendCertsFromPEM([]byte(caPEM)) { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Failed to parse trusted CA certificate", + } + } + } + + // Set defaults + crlCacheDuration := config.CRLCacheDuration + if crlCacheDuration == 0 { + crlCacheDuration = time.Hour + } + + ocspCacheDuration := config.OCSPCacheDuration + if ocspCacheDuration == 0 { + ocspCacheDuration = time.Hour + } + + certificateChainDepth := config.CertificateChainDepth + if certificateChainDepth == 0 { + certificateChainDepth = 5 + } + + clockSkewTolerance := config.ClockSkewTolerance + if clockSkewTolerance == 0 { + clockSkewTolerance = 5 * time.Minute + } + + httpTimeout := config.HTTPTimeout + if httpTimeout == 0 { + httpTimeout = 30 * time.Second + } + + httpClient := &http.Client{ + Timeout: httpTimeout, + Transport: &http.Transport{ + MaxIdleConns: 100, + MaxIdleConnsPerHost: 10, + IdleConnTimeout: 90 * time.Second, + }, + } + + authenticator := &MTLSAuthenticator{ + name: config.Name, + trustedCAs: trustedCAs, + clientCertificates: config.ClientCertificates, + requireClientCert: config.RequireClientCert, + verifyHostname: config.VerifyHostname, + allowedCNPatterns: config.AllowedCNPatterns, + allowedSANs: config.AllowedSANs, + certificateChainDepth: certificateChainDepth, + clockSkewTolerance: clockSkewTolerance, + } + + // Initialize CRL cache + if config.EnableCRLChecking { + authenticator.crlCache = &CRLCache{ + cache: make(map[string]*CRLEntry), + duration: crlCacheDuration, + httpClient: httpClient, + } + } + + // Initialize OCSP cache + if config.EnableOCSPChecking { + authenticator.ocspCache = &OCSPCache{ + cache: make(map[string]*OCSPEntry), + duration: ocspCacheDuration, + httpClient: httpClient, + } + } + + // Initialize certificate validator + authenticator.certificateValidator = &DefaultCertificateValidator{ + trustedCAs: trustedCAs, + minRSAKeySize: getOrDefault(config.MinRSAKeySize, 2048), + minECDSACurveSize: getOrDefault(config.MinECDSACurveSize, 256), + requireKeyUsage: config.RequireKeyUsage, + requireExtKeyUsage: config.RequireExtKeyUsage, + } + + // Initialize key strength validator + authenticator.keyStrengthValidator = &DefaultKeyStrengthValidator{ + minRSAKeySize: getOrDefault(config.MinRSAKeySize, 2048), + minECDSACurveSize: getOrDefault(config.MinECDSACurveSize, 256), + } + + // Initialize revocation checker + if config.EnableCRLChecking || config.EnableOCSPChecking { + authenticator.revocationChecker = &DefaultRevocationChecker{ + enableCRL: config.EnableCRLChecking, + enableOCSP: config.EnableOCSPChecking, + crlURLs: config.CRLURLs, + ocspURLs: config.OCSPURLs, + crlCache: authenticator.crlCache, + ocspCache: authenticator.ocspCache, + httpClient: httpClient, + } + } + + return authenticator, nil +} + +// Name returns the name of the authenticator +func (m *MTLSAuthenticator) Name() string { + return m.name +} + +// Authenticate attempts to authenticate a request using comprehensive mTLS validation +func (m *MTLSAuthenticator) Authenticate(ctx context.Context, md metadata.MD) (auth.User, error) { + // Get peer info from context + peerInfo, ok := peer.FromContext(ctx) + if !ok { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeMissingCredentials, + Message: "No peer information available", + } + } + + // Extract TLS info + tlsInfo, ok := peerInfo.AuthInfo.(credentials.TLSInfo) + if !ok { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeMissingCredentials, + Message: "No TLS authentication info available", + } + } + + // Check if client certificate is present + if len(tlsInfo.State.PeerCertificates) == 0 { + if m.requireClientCert { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeMissingCredentials, + Message: "Client certificate required but not provided", + } + } + return &auth.UnauthenticatedUser{}, nil + } + + // Validate certificate chain depth + if len(tlsInfo.State.PeerCertificates) > m.certificateChainDepth { + return &auth.UnauthenticatedUser{}, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Certificate chain too long", + Details: fmt.Sprintf("Chain length: %d, max allowed: %d", len(tlsInfo.State.PeerCertificates), m.certificateChainDepth), + } + } + + // Get the client certificate (first in chain) + clientCert := tlsInfo.State.PeerCertificates[0] + + // Build intermediate certificate pool + intermediates := x509.NewCertPool() + for i := 1; i < len(tlsInfo.State.PeerCertificates); i++ { + intermediates.AddCert(tlsInfo.State.PeerCertificates[i]) + } + + // Comprehensive certificate validation + if err := m.validateCertificateComprehensive(ctx, clientCert, intermediates, tlsInfo.State.PeerCertificates); err != nil { + return &auth.UnauthenticatedUser{}, err + } + + // Look up client information based on certificate + clientInfo, err := m.getClientInfoFromCertificate(clientCert) + if err != nil { + return &auth.UnauthenticatedUser{}, err + } + + // Update last used timestamp + now := time.Now() + clientInfo.LastUsedAt = &now + clientInfo.UpdatedAt = now + + // Build comprehensive claims from certificate + claims := m.buildComprehensiveClaimsFromCertificate(clientCert, peerInfo, &tlsInfo) + for k, v := range clientInfo.Metadata { + claims[k] = v + } + + return auth.NewAuthenticatedUser( + clientInfo.UserID, + clientInfo.Name, + clientInfo.Email, + clientInfo.Scopes, + claims, + ), nil +} + +// SupportsScheme returns true if this authenticator supports mTLS schemes +func (m *MTLSAuthenticator) SupportsScheme(scheme types.SecurityScheme) bool { + return scheme.SecuritySchemeType() == types.SecuritySchemeTypeMutualTLS +} + +// validateCertificateComprehensive performs comprehensive certificate validation +func (m *MTLSAuthenticator) validateCertificateComprehensive(ctx context.Context, cert *x509.Certificate, intermediates *x509.CertPool, chain []*x509.Certificate) error { + // Basic time validation with clock skew tolerance + now := time.Now() + notBefore := cert.NotBefore.Add(-m.clockSkewTolerance) + notAfter := cert.NotAfter.Add(m.clockSkewTolerance) + + if now.Before(notBefore) { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Client certificate is not yet valid", + Details: fmt.Sprintf("Not valid before: %s (with %s clock skew tolerance)", cert.NotBefore.Format(time.RFC3339), m.clockSkewTolerance), + } + } + + if now.After(notAfter) { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Client certificate has expired", + Details: fmt.Sprintf("Expired at: %s (with %s clock skew tolerance)", cert.NotAfter.Format(time.RFC3339), m.clockSkewTolerance), + } + } + + // Validate key strength + if err := m.keyStrengthValidator.ValidateKeyStrength(cert.PublicKey); err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Certificate key strength insufficient", + Details: err.Error(), + } + } + + // Verify certificate chain against trusted CAs + opts := x509.VerifyOptions{ + Roots: m.trustedCAs, + Intermediates: intermediates, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + CurrentTime: now, + } + + if _, err := cert.Verify(opts); err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Certificate chain verification failed", + Details: err.Error(), + } + } + + // Use certificate validator for additional checks + if m.certificateValidator != nil { + if err := m.certificateValidator.ValidateCertificate(cert, intermediates); err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Certificate validation failed", + Details: err.Error(), + } + } + + if err := m.certificateValidator.ValidateCertificateChain(chain); err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Certificate chain validation failed", + Details: err.Error(), + } + } + } + + // Check certificate revocation status + if m.revocationChecker != nil { + // Find the issuer certificate + var issuer *x509.Certificate + if len(chain) > 1 { + issuer = chain[1] // First intermediate or CA + } else { + // Try to find issuer in trusted CAs + for _, caCert := range m.trustedCAs.Subjects() { + if bytes.Equal(cert.RawIssuer, caCert) { + // This is a simplified approach - in production you'd need a more robust way to find the issuer + issuer = cert // Use self for root CA (this needs proper implementation) + break + } + } + } + + if issuer != nil { + revoked, err := m.revocationChecker.IsRevoked(ctx, cert, issuer) + if err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Failed to check certificate revocation status", + Details: err.Error(), + } + } + if revoked { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Client certificate has been revoked", + } + } + } + } + + // Validate certificate identity against allowed patterns + if err := m.validateCertificateIdentity(cert); err != nil { + return err + } + + return nil +} + +// validateCertificateIdentity validates certificate identity against allowed patterns with comprehensive matching +func (m *MTLSAuthenticator) validateCertificateIdentity(cert *x509.Certificate) error { + // Check Common Name patterns + if len(m.allowedCNPatterns) > 0 { + cnMatched := false + for _, pattern := range m.allowedCNPatterns { + if matched, err := matchWildcardPattern(pattern, cert.Subject.CommonName); err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Invalid CN pattern", + Details: err.Error(), + } + } else if matched { + cnMatched = true + break + } + } + if !cnMatched { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Certificate Common Name not allowed", + Details: fmt.Sprintf("CN: %s, Allowed patterns: %v", cert.Subject.CommonName, m.allowedCNPatterns), + } + } + } + + // Check Subject Alternative Names comprehensively + if len(m.allowedSANs) > 0 { + sanMatched := false + var allSANs []string + + // Collect all SANs + allSANs = append(allSANs, cert.DNSNames...) + allSANs = append(allSANs, cert.EmailAddresses...) + + for _, ip := range cert.IPAddresses { + allSANs = append(allSANs, ip.String()) + } + + for _, uri := range cert.URIs { + allSANs = append(allSANs, uri.String()) + } + + for _, allowedSAN := range m.allowedSANs { + for _, certSAN := range allSANs { + if matched, err := matchWildcardPattern(allowedSAN, certSAN); err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Invalid SAN pattern", + Details: err.Error(), + } + } else if matched { + sanMatched = true + break + } + } + if sanMatched { + break + } + } + + if !sanMatched { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Certificate Subject Alternative Names not allowed", + Details: fmt.Sprintf("SANs: %v, Allowed patterns: %v", allSANs, m.allowedSANs), + } + } + } + + return nil +} + +// getClientInfoFromCertificate retrieves comprehensive client information based on certificate +func (m *MTLSAuthenticator) getClientInfoFromCertificate(cert *x509.Certificate) (*ClientCertInfo, error) { + // Calculate certificate fingerprint + fingerprint := fmt.Sprintf("%x", cert.Raw) + + // Look up by serial number first + serialNumber := cert.SerialNumber.String() + if clientInfo, exists := m.clientCertificates[serialNumber]; exists { + if !clientInfo.IsActive { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Client certificate is deactivated", + Details: fmt.Sprintf("Serial: %s", serialNumber), + } + } + return clientInfo, nil + } + + // If not found by serial, look up by fingerprint + for _, clientInfo := range m.clientCertificates { + if clientInfo.Fingerprint == fingerprint { + if !clientInfo.IsActive { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidCredentials, + Message: "Client certificate is deactivated", + Details: fmt.Sprintf("Fingerprint: %s", fingerprint), + } + } + return clientInfo, nil + } + } + + // If not explicitly configured, create default client info from certificate + userID := fmt.Sprintf("cert_%s", serialNumber) + name := cert.Subject.CommonName + email := "" + organization := "" + + if len(cert.Subject.Organization) > 0 { + organization = cert.Subject.Organization[0] + } + + if len(cert.EmailAddresses) > 0 { + email = cert.EmailAddresses[0] + } + + // Extract scopes from certificate extensions or use defaults + scopes := []string{"read"} // Default minimal scope + + return &ClientCertInfo{ + UserID: userID, + Name: name, + Email: email, + Organization: organization, + Scopes: scopes, + Metadata: make(map[string]string), + SerialNumber: serialNumber, + Fingerprint: fingerprint, + Subject: cert.Subject, + Issuer: cert.Issuer, + NotBefore: cert.NotBefore, + NotAfter: cert.NotAfter, + KeyUsage: cert.KeyUsage, + ExtKeyUsage: cert.ExtKeyUsage, + IsActive: true, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, nil +} + +// buildComprehensiveClaimsFromCertificate builds comprehensive authentication claims from certificate +func (m *MTLSAuthenticator) buildComprehensiveClaimsFromCertificate(cert *x509.Certificate, peerInfo *peer.Peer, tlsInfo *credentials.TLSInfo) map[string]interface{} { + claims := make(map[string]interface{}) + + // Basic certificate information + claims["auth_method"] = "mtls" + claims["cert_serial"] = cert.SerialNumber.String() + claims["cert_fingerprint"] = fmt.Sprintf("%x", cert.Raw) + claims["cert_subject"] = cert.Subject.String() + claims["cert_issuer"] = cert.Issuer.String() + claims["cert_not_before"] = cert.NotBefore.Unix() + claims["cert_not_after"] = cert.NotAfter.Unix() + claims["cert_key_usage"] = int(cert.KeyUsage) + claims["cert_ext_key_usage"] = cert.ExtKeyUsage + claims["cert_version"] = cert.Version + claims["cert_is_ca"] = cert.IsCA + + // Public key information + switch pub := cert.PublicKey.(type) { + case *rsa.PublicKey: + claims["cert_key_type"] = "RSA" + claims["cert_key_size"] = pub.Size() * 8 + case *ecdsa.PublicKey: + claims["cert_key_type"] = "ECDSA" + claims["cert_key_size"] = pub.Curve.Params().BitSize + case *ed25519.PublicKey: + claims["cert_key_type"] = "Ed25519" + claims["cert_key_size"] = 256 + } + + // Subject Alternative Names + if len(cert.DNSNames) > 0 { + claims["cert_dns_names"] = cert.DNSNames + } + + if len(cert.EmailAddresses) > 0 { + claims["cert_emails"] = cert.EmailAddresses + } + + if len(cert.IPAddresses) > 0 { + var ips []string + for _, ip := range cert.IPAddresses { + ips = append(ips, ip.String()) + } + claims["cert_ip_addresses"] = ips + } + + if len(cert.URIs) > 0 { + var uris []string + for _, uri := range cert.URIs { + uris = append(uris, uri.String()) + } + claims["cert_uris"] = uris + } + + // TLS connection information + if tlsInfo != nil { + claims["tls_version"] = tlsInfo.State.Version + claims["tls_cipher_suite"] = tlsInfo.State.CipherSuite + claims["tls_server_name"] = tlsInfo.State.ServerName + claims["tls_negotiated_protocol"] = tlsInfo.State.NegotiatedProtocol + claims["tls_ocsp_response"] = len(tlsInfo.State.OCSPResponse) > 0 + claims["tls_scts"] = len(tlsInfo.State.SignedCertificateTimestamps) > 0 + } + + // Peer connection information + if peerInfo.Addr != nil { + claims["peer_address"] = peerInfo.Addr.String() + if tcpAddr, ok := peerInfo.Addr.(*net.TCPAddr); ok { + claims["peer_ip"] = tcpAddr.IP.String() + claims["peer_port"] = tcpAddr.Port + } + } + + claims["authenticated_at"] = time.Now().Unix() + + return claims +} + +// DefaultCertificateValidator provides comprehensive certificate validation +type DefaultCertificateValidator struct { + trustedCAs *x509.CertPool + minRSAKeySize int + minECDSACurveSize int + requireKeyUsage []x509.KeyUsage + requireExtKeyUsage []x509.ExtKeyUsage +} + +// DefaultKeyStrengthValidator validates cryptographic key strength +type DefaultKeyStrengthValidator struct { + minRSAKeySize int + minECDSACurveSize int +} + +// DefaultRevocationChecker provides comprehensive CRL and OCSP checking +type DefaultRevocationChecker struct { + enableCRL bool + enableOCSP bool + crlURLs []string + ocspURLs []string + crlCache *CRLCache + ocspCache *OCSPCache + httpClient *http.Client +} + +// ValidateCertificate implements comprehensive certificate validation +func (v *DefaultCertificateValidator) ValidateCertificate(cert *x509.Certificate, intermediates *x509.CertPool) error { + // Validate key strength + keyValidator := &DefaultKeyStrengthValidator{ + minRSAKeySize: v.minRSAKeySize, + minECDSACurveSize: v.minECDSACurveSize, + } + + if err := keyValidator.ValidateKeyStrength(cert.PublicKey); err != nil { + return fmt.Errorf("key strength validation failed: %w", err) + } + + // Validate required key usage + if len(v.requireKeyUsage) > 0 { + for _, requiredUsage := range v.requireKeyUsage { + if cert.KeyUsage&requiredUsage == 0 { + return fmt.Errorf("certificate missing required key usage: %v", requiredUsage) + } + } + } else { + // Default requirement: digital signature + if cert.KeyUsage&x509.KeyUsageDigitalSignature == 0 { + return fmt.Errorf("certificate does not have digital signature key usage") + } + } + + // Validate required extended key usage + if len(v.requireExtKeyUsage) > 0 { + for _, requiredExtUsage := range v.requireExtKeyUsage { + found := false + for _, extUsage := range cert.ExtKeyUsage { + if extUsage == requiredExtUsage { + found = true + break + } + } + if !found { + return fmt.Errorf("certificate missing required extended key usage: %v", requiredExtUsage) + } + } + } else { + // Default requirement: client authentication + hasClientAuth := false + for _, usage := range cert.ExtKeyUsage { + if usage == x509.ExtKeyUsageClientAuth { + hasClientAuth = true + break + } + } + if !hasClientAuth { + return fmt.Errorf("certificate does not have client authentication extended key usage") + } + } + + // Validate certificate extensions + if err := v.validateCertificateExtensions(cert); err != nil { + return fmt.Errorf("certificate extension validation failed: %w", err) + } + + // Validate certificate policies + if err := v.validateCertificatePolicies(cert); err != nil { + return fmt.Errorf("certificate policy validation failed: %w", err) + } + + return nil +} + +// ValidateCertificateChain validates the entire certificate chain +func (v *DefaultCertificateValidator) ValidateCertificateChain(chain []*x509.Certificate) error { + if len(chain) == 0 { + return fmt.Errorf("empty certificate chain") + } + + // Validate each certificate in the chain + for i, cert := range chain { + if i == 0 { + // Leaf certificate - already validated by ValidateCertificate + continue + } + + // Intermediate or root certificates + if err := v.validateIntermediateCertificate(cert, i); err != nil { + return fmt.Errorf("intermediate certificate validation failed at position %d: %w", i, err) + } + } + + // Validate chain relationships + for i := 0; i < len(chain)-1; i++ { + if err := v.validateCertificateRelationship(chain[i], chain[i+1]); err != nil { + return fmt.Errorf("certificate chain relationship validation failed between positions %d and %d: %w", i, i+1, err) + } + } + + return nil +} + +// validateCertificateExtensions validates critical certificate extensions +func (v *DefaultCertificateValidator) validateCertificateExtensions(cert *x509.Certificate) error { + // Check for critical extensions that we don't recognize + for _, ext := range cert.Extensions { + if ext.Critical { + // Check if this is a known critical extension + switch ext.Id.String() { + case "2.5.29.15": // Key Usage + case "2.5.29.17": // Subject Alternative Name + case "2.5.29.37": // Extended Key Usage + case "2.5.29.19": // Basic Constraints + case "2.5.29.32": // Certificate Policies + case "2.5.29.31": // CRL Distribution Points + case "1.3.6.1.5.5.7.1.1": // Authority Info Access + // Known extensions - OK + default: + return fmt.Errorf("unknown critical extension: %s", ext.Id.String()) + } + } + } + + // Validate Basic Constraints if present + if cert.BasicConstraintsValid { + if cert.IsCA && cert.MaxPathLen >= 0 { + // Validate path length constraints + if cert.MaxPathLenZero && cert.MaxPathLen != 0 { + return fmt.Errorf("invalid path length constraint") + } + } + } + + return nil +} + +// validateCertificatePolicies validates certificate policies +func (v *DefaultCertificateValidator) validateCertificatePolicies(cert *x509.Certificate) error { + // This is a simplified implementation + // In production, you would validate specific certificate policies based on your requirements + + for _, policy := range cert.PolicyIdentifiers { + // Validate against allowed policies + _ = policy // Placeholder for policy validation logic + } + + return nil +} + +// validateIntermediateCertificate validates intermediate certificates +func (v *DefaultCertificateValidator) validateIntermediateCertificate(cert *x509.Certificate, position int) error { + // Intermediate certificates should have CA=true in basic constraints + if !cert.IsCA { + return fmt.Errorf("intermediate certificate at position %d is not marked as CA", position) + } + + // Validate key usage for CA certificates + if cert.KeyUsage&x509.KeyUsageCertSign == 0 { + return fmt.Errorf("intermediate certificate at position %d does not have cert sign key usage", position) + } + + return nil +} + +// validateCertificateRelationship validates the relationship between two certificates in the chain +func (v *DefaultCertificateValidator) validateCertificateRelationship(child, parent *x509.Certificate) error { + // Check if parent issued child + if err := child.CheckSignatureFrom(parent); err != nil { + return fmt.Errorf("signature verification failed: %w", err) + } + + // Validate issuer/subject relationship + if child.Issuer.String() != parent.Subject.String() { + return fmt.Errorf("issuer/subject mismatch: child issuer %s != parent subject %s", + child.Issuer.String(), parent.Subject.String()) + } + + // Validate validity periods + if child.NotBefore.Before(parent.NotBefore) { + return fmt.Errorf("child certificate validity starts before parent") + } + + if child.NotAfter.After(parent.NotAfter) { + return fmt.Errorf("child certificate validity ends after parent") + } + + return nil +} + +// ValidateKeyStrength validates cryptographic key strength +func (v *DefaultKeyStrengthValidator) ValidateKeyStrength(publicKey crypto.PublicKey) error { + switch pub := publicKey.(type) { + case *rsa.PublicKey: + keySize := pub.Size() * 8 + if keySize < v.minRSAKeySize { + return fmt.Errorf("RSA key size %d is below minimum %d", keySize, v.minRSAKeySize) + } + + // Validate RSA public exponent + if pub.E < 3 || pub.E > 65537 { + return fmt.Errorf("RSA public exponent %d is not in acceptable range", pub.E) + } + + // Check for small public exponent vulnerability + if pub.E == 3 { + return fmt.Errorf("RSA public exponent 3 is not recommended") + } + + case *ecdsa.PublicKey: + curveSize := pub.Curve.Params().BitSize + if curveSize < v.minECDSACurveSize { + return fmt.Errorf("ECDSA curve size %d is below minimum %d", curveSize, v.minECDSACurveSize) + } + + // Validate specific curves + curveName := pub.Curve.Params().Name + switch curveName { + case "P-256", "P-384", "P-521": // NIST curves + // Acceptable + case "secp256k1": // Bitcoin curve + // May be acceptable depending on policy + default: + return fmt.Errorf("ECDSA curve %s is not approved", curveName) + } + + case ed25519.PublicKey: + // Ed25519 is always 256-bit and considered secure + + default: + return fmt.Errorf("unsupported public key type: %T", publicKey) + } + + return nil +} + +// IsRevoked implements comprehensive revocation checking +func (r *DefaultRevocationChecker) IsRevoked(ctx context.Context, cert *x509.Certificate, issuer *x509.Certificate) (bool, error) { + var revoked bool + var err error + + // Check CRL first (faster) + if r.enableCRL { + revoked, err = r.CheckCRL(ctx, cert, issuer) + if err != nil { + // Log error but continue to OCSP if available + } else if revoked { + return true, nil + } + } + + // Check OCSP + if r.enableOCSP { + revoked, err = r.CheckOCSP(ctx, cert, issuer) + if err != nil { + return false, fmt.Errorf("OCSP check failed: %w", err) + } + if revoked { + return true, nil + } + } + + return false, nil +} + +// CheckCRL checks certificate against Certificate Revocation Lists +func (r *DefaultRevocationChecker) CheckCRL(ctx context.Context, cert *x509.Certificate, issuer *x509.Certificate) (bool, error) { + // Extract CRL URLs from certificate + crlURLs := extractCRLURLs(cert) + crlURLs = append(crlURLs, r.crlURLs...) + + for _, crlURL := range crlURLs { + if crlURL == "" { + continue + } + + // Check cache first + crlEntry := r.crlCache.Get(crlURL) + if crlEntry == nil || time.Since(crlEntry.FetchedAt) > r.crlCache.duration { + // Fetch new CRL + crl, err := r.fetchCRL(ctx, crlURL) + if err != nil { + continue // Try next URL + } + + crlEntry = &CRLEntry{ + CRL: crl, + FetchedAt: time.Now(), + URL: crlURL, + } + r.crlCache.Set(crlURL, crlEntry) + } + + // Check if certificate is revoked + for _, revokedCert := range crlEntry.CRL.RevokedCertificates { + if revokedCert.SerialNumber.Cmp(cert.SerialNumber) == 0 { + return true, nil + } + } + } + + return false, nil +} + +// CheckOCSP checks certificate against OCSP responders +func (r *DefaultRevocationChecker) CheckOCSP(ctx context.Context, cert *x509.Certificate, issuer *x509.Certificate) (bool, error) { + // Extract OCSP URLs from certificate + ocspURLs := extractOCSPURLs(cert) + ocspURLs = append(ocspURLs, r.ocspURLs...) + + for _, ocspURL := range ocspURLs { + if ocspURL == "" { + continue + } + + // Create OCSP request + ocspReq, err := ocsp.CreateRequest(cert, issuer, nil) + if err != nil { + continue + } + + // Check cache first + reqHash := fmt.Sprintf("%x", sha1.Sum(ocspReq)) + cacheKey := fmt.Sprintf("%s_%s", ocspURL, reqHash) + + ocspEntry := r.ocspCache.Get(cacheKey) + if ocspEntry == nil || time.Since(ocspEntry.FetchedAt) > r.ocspCache.duration { + // Fetch new OCSP response + resp, err := r.fetchOCSP(ctx, ocspURL, ocspReq) + if err != nil { + continue // Try next URL + } + + ocspEntry = &OCSPEntry{ + Response: resp, + FetchedAt: time.Now(), + URL: ocspURL, + } + r.ocspCache.Set(cacheKey, ocspEntry) + } + + // Check OCSP response + switch ocspEntry.Response.Status { + case ocsp.Good: + return false, nil + case ocsp.Revoked: + return true, nil + case ocsp.Unknown: + continue // Try next responder + } + } + + return false, nil +} + +// fetchCRL fetches a CRL from the given URL +func (r *DefaultRevocationChecker) fetchCRL(ctx context.Context, crlURL string) (*x509.RevocationList, error) { + req, err := http.NewRequestWithContext(ctx, "GET", crlURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create CRL request: %w", err) + } + + resp, err := r.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to fetch CRL: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("CRL fetch failed with status: %d", resp.StatusCode) + } + + crlData, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read CRL data: %w", err) + } + + // Parse CRL + crl, err := x509.ParseRevocationList(crlData) + if err != nil { + return nil, fmt.Errorf("failed to parse CRL: %w", err) + } + + return crl, nil +} + +// fetchOCSP fetches an OCSP response +func (r *DefaultRevocationChecker) fetchOCSP(ctx context.Context, ocspURL string, req []byte) (*ocsp.Response, error) { + httpReq, err := http.NewRequestWithContext(ctx, "POST", ocspURL, bytes.NewReader(req)) + if err != nil { + return nil, fmt.Errorf("failed to create OCSP request: %w", err) + } + + httpReq.Header.Set("Content-Type", "application/ocsp-request") + httpReq.Header.Set("Accept", "application/ocsp-response") + + resp, err := r.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("failed to fetch OCSP response: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("OCSP fetch failed with status: %d", resp.StatusCode) + } + + respData, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read OCSP response: %w", err) + } + + // Parse OCSP response + ocspResp, err := ocsp.ParseResponse(respData, nil) + if err != nil { + return nil, fmt.Errorf("failed to parse OCSP response: %w", err) + } + + return ocspResp, nil +} + +// extractCRLURLs extracts CRL distribution point URLs from a certificate +func extractCRLURLs(cert *x509.Certificate) []string { + var urls []string + + for _, ext := range cert.Extensions { + if ext.Id.Equal(asn1.ObjectIdentifier{2, 5, 29, 31}) { // CRL Distribution Points + var cdp []distributionPoint + if _, err := asn1.Unmarshal(ext.Value, &cdp); err == nil { + for _, dp := range cdp { + if dp.DistributionPoint.FullName != nil { + for _, name := range dp.DistributionPoint.FullName { + if name.Tag == 6 { // URI + urls = append(urls, string(name.Bytes)) + } + } + } + } + } + } + } + + return urls +} + +// extractOCSPURLs extracts OCSP responder URLs from a certificate +func extractOCSPURLs(cert *x509.Certificate) []string { + return cert.OCSPServer +} + +// CRL distribution point structure for ASN.1 parsing +type distributionPoint struct { + DistributionPoint distributionPointName `asn1:"optional,tag:0"` + Reasons asn1.BitString `asn1:"optional,tag:1"` + CRLIssuer []asn1.RawValue `asn1:"optional,tag:2"` +} + +type distributionPointName struct { + FullName []asn1.RawValue `asn1:"optional,tag:0"` + NameRelative []asn1.RawValue `asn1:"optional,tag:1"` +} + +// CRLCache methods +func (c *CRLCache) Get(url string) *CRLEntry { + c.mutex.RLock() + defer c.mutex.RUnlock() + return c.cache[url] +} + +func (c *CRLCache) Set(url string, entry *CRLEntry) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.cache[url] = entry +} + +// OCSPCache methods +func (c *OCSPCache) Get(key string) *OCSPEntry { + c.mutex.RLock() + defer c.mutex.RUnlock() + return c.cache[key] +} + +func (c *OCSPCache) Set(key string, entry *OCSPEntry) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.cache[key] = entry +} + +// matchWildcardPattern performs comprehensive wildcard pattern matching +func matchWildcardPattern(pattern, value string) (bool, error) { + if pattern == "*" { + return true, nil + } + + // Use filepath.Match for shell-style pattern matching + matched, err := filepath.Match(pattern, value) + if err != nil { + return false, err + } + + return matched, nil +} + +// getOrDefault returns the value if non-zero, otherwise returns the default +func getOrDefault(value, defaultValue int) int { + if value == 0 { + return defaultValue + } + return value +} diff --git a/a2a/pkg/auth/interceptor.go b/a2a/pkg/auth/interceptor.go new file mode 100644 index 000000000..dec10bc99 --- /dev/null +++ b/a2a/pkg/auth/interceptor.go @@ -0,0 +1,533 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package auth + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "seata-go-ai-a2a/pkg/auth/internal" + "seata-go-ai-a2a/pkg/types" +) + +// AuthenticationInterceptor provides comprehensive gRPC authentication +type AuthenticationInterceptor struct { + authenticators []Authenticator + exemptMethods []string // Methods that don't require authentication + methodAuthenticators map[string][]string // Method -> authenticator names mapping + rateLimiter RateLimiter // Optional rate limiting + auditLogger AuditLogger // Optional audit logging + config *InterceptorConfig +} + +// InterceptorConfig configures the authentication interceptor +type InterceptorConfig struct { + RequireAuthentication bool `json:"requireAuthentication"` // Default: true + ExemptMethods []string `json:"exemptMethods"` // Methods that don't require auth + MethodAuthenticators map[string][]string `json:"methodAuthenticators"` // Method-specific authenticators + EnableRateLimiting bool `json:"enableRateLimiting"` // Default: false + EnableAuditLogging bool `json:"enableAuditLogging"` // Default: false + MaxRequestsPerSecond int `json:"maxRequestsPerSecond"` // Default: 100 + MaxRequestsPerMinute int `json:"maxRequestsPerMinute"` // Default: 1000 + RateLimitWindow time.Duration `json:"rateLimitWindow"` // Default: 1 minute + FailureThreshold int `json:"failureThreshold"` // Default: 10 + FailureWindow time.Duration `json:"failureWindow"` // Default: 5 minutes + BanDuration time.Duration `json:"banDuration"` // Default: 15 minutes +} + +// AuthenticationResult represents the result of authentication +type AuthenticationResult struct { + User User `json:"user"` + Authenticator string `json:"authenticator"` + AuthTime time.Time `json:"authTime"` + Metadata map[string]interface{} `json:"metadata"` +} + +// NewAuthenticationInterceptor creates a new authentication interceptor +func NewAuthenticationInterceptor(config *InterceptorConfig, authenticators ...Authenticator) *AuthenticationInterceptor { + if config == nil { + config = &InterceptorConfig{ + RequireAuthentication: true, + MaxRequestsPerSecond: 100, + MaxRequestsPerMinute: 1000, + RateLimitWindow: time.Minute, + FailureThreshold: 10, + FailureWindow: 5 * time.Minute, + BanDuration: 15 * time.Minute, + } + } + + interceptor := &AuthenticationInterceptor{ + authenticators: authenticators, + exemptMethods: config.ExemptMethods, + methodAuthenticators: config.MethodAuthenticators, + config: config, + } + + // Initialize rate limiter if enabled + if config.EnableRateLimiting { + rateLimiterConfig := &internal.RateLimiterConfig{ + MaxRequestsPerSecond: config.MaxRequestsPerSecond, + MaxRequestsPerMinute: config.MaxRequestsPerMinute, + Window: config.RateLimitWindow, + FailureThreshold: config.FailureThreshold, + FailureWindow: config.FailureWindow, + BanDuration: config.BanDuration, + } + interceptor.rateLimiter = internal.NewDefaultRateLimiter(rateLimiterConfig) + } + + // Initialize audit logger if enabled + if config.EnableAuditLogging { + auditLoggerConfig := &internal.AuditLoggerConfig{ + Enabled: true, + BufferSize: 1000, + FlushInterval: 10 * time.Second, + } + interceptor.auditLogger = internal.NewDefaultAuditLogger(&types.DefaultLogger{}, auditLoggerConfig) + } + + return interceptor +} + +// UnaryServerInterceptor returns a gRPC unary server interceptor for authentication +func (a *AuthenticationInterceptor) UnaryServerInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + // Check if method is exempt from authentication + if a.isMethodExempt(info.FullMethod) { + return handler(ctx, req) + } + + // Extract client identifier for rate limiting + clientID := a.extractClientIdentifier(ctx) + + // Check rate limiting + if a.rateLimiter != nil { + allowed := a.rateLimiter.Allow(ctx, clientID) + if !allowed { + if a.auditLogger != nil { + event := &internal.AuthFailureEvent{ + AuthAttemptEvent: internal.AuthAttemptEvent{ + Timestamp: time.Now(), + Method: "rate_limit", + RemoteAddr: clientID, + }, + Reason: "rate limit exceeded", + } + a.auditLogger.LogAuthFailure(ctx, event) + } + return nil, status.Error(codes.ResourceExhausted, "Rate limit exceeded") + } + } + + // Perform authentication + authResult, err := a.authenticate(ctx, info.FullMethod) + if err != nil { + // Record authentication failure for rate limiting + if a.rateLimiter != nil { + a.rateLimiter.RecordFailure(ctx, clientID, time.Since(time.Now())) + } + + // Log authentication failure + if a.auditLogger != nil { + var authErr *AuthenticationError + if authError, ok := err.(*AuthenticationError); ok { + authErr = authError + } else { + authErr = &AuthenticationError{ + Code: ErrCodeInvalidCredentials, + Message: err.Error(), + } + } + + event := &internal.AuthFailureEvent{ + AuthAttemptEvent: internal.AuthAttemptEvent{ + Timestamp: time.Now(), + Method: info.FullMethod, + RemoteAddr: clientID, + }, + Reason: authErr.Code.String(), + Error: authErr.Message, + } + a.auditLogger.LogAuthFailure(ctx, event) + } + + return nil, a.convertToGRPCError(err) + } + + // Add authenticated user to context + ctx = context.WithValue(ctx, UserContextKey, authResult.User) + ctx = context.WithValue(ctx, AuthResultContextKey, authResult) + + // Log successful authentication + if a.auditLogger != nil { + event := &internal.AuthSuccessEvent{ + AuthAttemptEvent: internal.AuthAttemptEvent{ + Timestamp: time.Now(), + Method: info.FullMethod, + UserID: authResult.User.GetUserID(), + RemoteAddr: clientID, + }, + Scopes: authResult.User.GetScopes(), + } + a.auditLogger.LogAuthSuccess(ctx, event) + } + + return handler(ctx, req) + } +} + +// StreamServerInterceptor returns a gRPC stream server interceptor for authentication +func (a *AuthenticationInterceptor) StreamServerInterceptor() grpc.StreamServerInterceptor { + return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + // Check if method is exempt from authentication + if a.isMethodExempt(info.FullMethod) { + return handler(srv, ss) + } + + ctx := ss.Context() + clientID := a.extractClientIdentifier(ctx) + + // Check rate limiting + if a.rateLimiter != nil { + allowed := a.rateLimiter.Allow(ctx, clientID) + if !allowed { + if a.auditLogger != nil { + event := &internal.AuthFailureEvent{ + AuthAttemptEvent: internal.AuthAttemptEvent{ + Timestamp: time.Now(), + Method: "rate_limit", + RemoteAddr: clientID, + }, + Reason: "rate limit exceeded", + } + a.auditLogger.LogAuthFailure(ctx, event) + } + return status.Error(codes.ResourceExhausted, "Rate limit exceeded") + } + } + + // Perform authentication + authResult, err := a.authenticate(ctx, info.FullMethod) + if err != nil { + // Record authentication failure for rate limiting + if a.rateLimiter != nil { + a.rateLimiter.RecordFailure(ctx, clientID, time.Since(time.Now())) + } + + // Log authentication failure + if a.auditLogger != nil { + var authErr *AuthenticationError + if authError, ok := err.(*AuthenticationError); ok { + authErr = authError + } else { + authErr = &AuthenticationError{ + Code: ErrCodeInvalidCredentials, + Message: err.Error(), + } + } + + event := &internal.AuthFailureEvent{ + AuthAttemptEvent: internal.AuthAttemptEvent{ + Timestamp: time.Now(), + Method: info.FullMethod, + RemoteAddr: clientID, + }, + Reason: authErr.Code.String(), + Error: authErr.Message, + } + a.auditLogger.LogAuthFailure(ctx, event) + } + + return a.convertToGRPCError(err) + } + + // Create wrapped stream with authenticated context + ctx = context.WithValue(ctx, UserContextKey, authResult.User) + ctx = context.WithValue(ctx, AuthResultContextKey, authResult) + + wrappedStream := &authenticatedServerStream{ + ServerStream: ss, + ctx: ctx, + } + + // Log successful authentication + if a.auditLogger != nil { + event := &internal.AuthSuccessEvent{ + AuthAttemptEvent: internal.AuthAttemptEvent{ + Timestamp: time.Now(), + Method: info.FullMethod, + UserID: authResult.User.GetUserID(), + RemoteAddr: clientID, + }, + Scopes: authResult.User.GetScopes(), + } + a.auditLogger.LogAuthSuccess(ctx, event) + } + + return handler(srv, wrappedStream) + } +} + +// authenticate performs authentication using configured authenticators +func (a *AuthenticationInterceptor) authenticate(ctx context.Context, method string) (*AuthenticationResult, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, &AuthenticationError{ + Code: ErrCodeMissingCredentials, + Message: "Missing gRPC metadata", + } + } + + // Determine which authenticators to try for this method + authenticatorsToTry := a.getAuthenticatorsForMethod(method) + if len(authenticatorsToTry) == 0 { + authenticatorsToTry = a.authenticators + } + + var lastError error + + // Try each authenticator + for _, authenticator := range authenticatorsToTry { + user, err := authenticator.Authenticate(ctx, md) + if err != nil { + lastError = err + continue + } + + if user.IsAuthenticated() { + return &AuthenticationResult{ + User: user, + Authenticator: authenticator.Name(), + AuthTime: time.Now(), + Metadata: map[string]interface{}{ + "authenticator_name": authenticator.Name(), + "method": method, + }, + }, nil + } + } + + if lastError != nil { + return nil, lastError + } + + return nil, &AuthenticationError{ + Code: ErrCodeInvalidCredentials, + Message: "Authentication failed with all configured authenticators", + } +} + +// isMethodExempt checks if a method is exempt from authentication +func (a *AuthenticationInterceptor) isMethodExempt(method string) bool { + if !a.config.RequireAuthentication { + return true + } + + for _, exemptMethod := range a.exemptMethods { + if exemptMethod == method || strings.HasPrefix(method, exemptMethod) { + return true + } + } + return false +} + +// getAuthenticatorsForMethod returns authenticators configured for a specific method +func (a *AuthenticationInterceptor) getAuthenticatorsForMethod(method string) []Authenticator { + authenticatorNames, exists := a.methodAuthenticators[method] + if !exists { + return nil + } + + var result []Authenticator + for _, name := range authenticatorNames { + for _, auth := range a.authenticators { + if auth.Name() == name { + result = append(result, auth) + break + } + } + } + return result +} + +// extractClientIdentifier extracts a client identifier for rate limiting +func (a *AuthenticationInterceptor) extractClientIdentifier(ctx context.Context) string { + // Try to extract from peer info first + if peer, ok := ctx.Value("peer").(interface { + Addr() interface{ String() string } + }); ok { + return peer.Addr().String() + } + + // Fallback to extracting from metadata + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return "unknown" + } + + // Try common identification headers + if values := md.Get("x-forwarded-for"); len(values) > 0 { + return values[0] + } + + if values := md.Get("x-real-ip"); len(values) > 0 { + return values[0] + } + + if values := md.Get("user-agent"); len(values) > 0 { + return fmt.Sprintf("ua:%s", values[0]) + } + + return "anonymous" +} + +// convertToGRPCError converts authentication errors to gRPC status codes +func (a *AuthenticationInterceptor) convertToGRPCError(err error) error { + var authErr *AuthenticationError + if errors.As(err, &authErr) { + switch authErr.Code { + case ErrCodeMissingCredentials: + return status.Error(codes.Unauthenticated, authErr.Message) + case ErrCodeInvalidCredentials, ErrCodeInvalidToken: + return status.Error(codes.Unauthenticated, authErr.Message) + case ErrCodeExpiredToken: + return status.Error(codes.Unauthenticated, "Token expired") + case ErrCodeJWKSFetchFailed: + return status.Error(codes.Unavailable, "Authentication service unavailable") + default: + return status.Error(codes.Internal, "Authentication error") + } + } + return status.Error(codes.Internal, "Internal authentication error") +} + +// authenticatedServerStream wraps grpc.ServerStream with authenticated context +type authenticatedServerStream struct { + grpc.ServerStream + ctx context.Context +} + +func (s *authenticatedServerStream) Context() context.Context { + return s.ctx +} + +// Context keys for storing authentication information +type contextKey string + +const ( + UserContextKey contextKey = "authenticated_user" + AuthResultContextKey contextKey = "auth_result" +) + +// GetUserFromContext extracts the authenticated user from context +func GetUserFromContext(ctx context.Context) (User, bool) { + if user, ok := ctx.Value(UserContextKey).(User); ok { + return user, true + } + return &UnauthenticatedUser{}, false +} + +// GetAuthResultFromContext extracts the authentication result from context +func GetAuthResultFromContext(ctx context.Context) (*AuthenticationResult, bool) { + if result, ok := ctx.Value(AuthResultContextKey).(*AuthenticationResult); ok { + return result, true + } + return nil, false +} + +// RequireUser is a helper function to get authenticated user or return error +func RequireUser(ctx context.Context) (User, error) { + user, ok := GetUserFromContext(ctx) + if !ok || !user.IsAuthenticated() { + return nil, status.Error(codes.Unauthenticated, "Authentication required") + } + return user, nil +} + +// RequireScope checks if the authenticated user has the required scope +func RequireScope(ctx context.Context, requiredScope string) error { + user, err := RequireUser(ctx) + if err != nil { + return err + } + + for _, scope := range user.GetScopes() { + if scope == requiredScope { + return nil + } + } + + return status.Errorf(codes.PermissionDenied, "Required scope '%s' not found", requiredScope) +} + +// RequireAnyScope checks if the authenticated user has at least one of the required scopes +func RequireAnyScope(ctx context.Context, requiredScopes ...string) error { + user, err := RequireUser(ctx) + if err != nil { + return err + } + + userScopes := user.GetScopes() + for _, requiredScope := range requiredScopes { + for _, userScope := range userScopes { + if userScope == requiredScope { + return nil + } + } + } + + return status.Errorf(codes.PermissionDenied, "None of the required scopes found: %v", requiredScopes) +} + +// RequireAllScopes checks if the authenticated user has all the required scopes +func RequireAllScopes(ctx context.Context, requiredScopes ...string) error { + user, err := RequireUser(ctx) + if err != nil { + return err + } + + userScopes := user.GetScopes() + for _, requiredScope := range requiredScopes { + found := false + for _, userScope := range userScopes { + if userScope == requiredScope { + found = true + break + } + } + if !found { + return status.Errorf(codes.PermissionDenied, "Required scope '%s' not found", requiredScope) + } + } + + return nil +} + +// Add String method to ErrorCode for audit logging +func (e ErrorCode) String() string { + return string(e) +} diff --git a/a2a/pkg/auth/interfaces.go b/a2a/pkg/auth/interfaces.go new file mode 100644 index 000000000..8a355463f --- /dev/null +++ b/a2a/pkg/auth/interfaces.go @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package auth + +import ( + "context" + "crypto" + "time" + + "seata-go-ai-a2a/pkg/types" + + "google.golang.org/grpc/metadata" +) + +// User represents an authenticated user +type User interface { + // GetUserID returns the unique user ID + GetUserID() string + + // GetName returns the user's display name + GetName() string + + // GetEmail returns the user's email address + GetEmail() string + + // GetScopes returns the user's authorized scopes + GetScopes() []string + + // GetClaims returns additional user claims + GetClaims() map[string]interface{} + + // IsAuthenticated returns true if the user is authenticated + IsAuthenticated() bool +} + +// Authenticator defines the interface for authentication providers +type Authenticator interface { + // Name returns the name of this authenticator + Name() string + + // Authenticate attempts to authenticate a request + Authenticate(ctx context.Context, md metadata.MD) (User, error) + + // SupportsScheme returns true if this authenticator supports the given security scheme + SupportsScheme(scheme types.SecurityScheme) bool +} + +// JWSValidator validates JSON Web Signatures for Agent Cards +type JWSValidator interface { + // ValidateSignature validates a JWS signature for the given payload + ValidateSignature(ctx context.Context, payload []byte, signature *types.AgentCardSignature) error +} + +// JWSSigner signs Agent Cards using JWS +type JWSSigner interface { + // SignAgentCard signs an Agent Card and returns the signature + SignAgentCard(ctx context.Context, agentCard *types.AgentCard, privateKey crypto.PrivateKey, keyID, jwksURL string) (*types.AgentCardSignature, error) +} + +// TokenValidator validates JWT tokens +type TokenValidator interface { + // ValidateToken validates a JWT token and returns the claims + ValidateToken(ctx context.Context, tokenString string) (map[string]interface{}, error) +} + +// RateLimiter defines rate limiting interface +type RateLimiter interface { + // Allow returns true if the request is allowed + Allow(ctx context.Context, key string) bool + + // RecordFailure records a failed request + RecordFailure(ctx context.Context, key string, duration time.Duration) + + // Reset resets the rate limiter for a specific key + Reset(ctx context.Context, key string) error + + // Close cleans up resources + Close() error +} + +// AuditLogger defines audit logging interface +type AuditLogger interface { + // LogAuthAttempt logs an authentication attempt + LogAuthAttempt(ctx context.Context, event interface{}) + + // LogAuthFailure logs an authentication failure + LogAuthFailure(ctx context.Context, event interface{}) + + // LogAuthSuccess logs a successful authentication + LogAuthSuccess(ctx context.Context, event interface{}) + + // Close cleans up resources + Close() error +} + +// ErrorCode represents authentication error codes +type ErrorCode string + +// Authentication error codes +const ( + ErrCodeInvalidCredentials = "INVALID_CREDENTIALS" + ErrCodeMissingCredentials = "MISSING_CREDENTIALS" + ErrCodeInvalidToken = "INVALID_TOKEN" + ErrCodeExpiredToken = "EXPIRED_TOKEN" + ErrCodeInvalidSignature = "INVALID_SIGNATURE" + ErrCodeJWKSFetchFailed = "JWKS_FETCH_FAILED" + ErrCodeRateLimitExceeded = "RATE_LIMIT_EXCEEDED" + ErrCodeInternalError = "INTERNAL_ERROR" +) + +// AuthenticationError represents an authentication error +type AuthenticationError struct { + Code ErrorCode `json:"code"` + Message string `json:"message"` + Details string `json:"details,omitempty"` +} + +func (e *AuthenticationError) Error() string { + if e.Details != "" { + return e.Message + ": " + e.Details + } + return e.Message +} diff --git a/a2a/pkg/auth/internal/audit_logger.go b/a2a/pkg/auth/internal/audit_logger.go new file mode 100644 index 000000000..2004575eb --- /dev/null +++ b/a2a/pkg/auth/internal/audit_logger.go @@ -0,0 +1,399 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package internal + +import ( + "context" + "encoding/json" + "os" + "sync" + "time" + + "seata-go-ai-a2a/pkg/types" +) + +// AuthAttemptEvent represents an authentication attempt +type AuthAttemptEvent struct { + Timestamp time.Time `json:"timestamp"` + Method string `json:"method"` + UserID string `json:"user_id,omitempty"` + RemoteAddr string `json:"remote_addr"` + UserAgent string `json:"user_agent,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +// AuthFailureEvent represents a failed authentication +type AuthFailureEvent struct { + AuthAttemptEvent + Reason string `json:"reason"` + Error string `json:"error,omitempty"` +} + +// AuthSuccessEvent represents a successful authentication +type AuthSuccessEvent struct { + AuthAttemptEvent + Scopes []string `json:"scopes,omitempty"` +} + +// DefaultAuditLogger provides a comprehensive audit logging implementation +type DefaultAuditLogger struct { + logger types.Logger + config *AuditLoggerConfig + eventChan chan *AuditLogEntry + stopChan chan struct{} + wg sync.WaitGroup +} + +// AuditLoggerConfig configures the audit logger +type AuditLoggerConfig struct { + Enabled bool `json:"enabled"` + BufferSize int `json:"buffer_size"` + FlushInterval time.Duration `json:"flush_interval"` + LogLevel string `json:"log_level"` + IncludeMetadata bool `json:"include_metadata"` + IncludeStackTrace bool `json:"include_stack_trace"` +} + +// AuditLogEntry represents a complete audit log entry +type AuditLogEntry struct { + ID string `json:"id"` + Timestamp time.Time `json:"timestamp"` + Type string `json:"type"` // "attempt", "success", "failure" + Event interface{} `json:"event"` + Context map[string]interface{} `json:"context,omitempty"` + StackTrace string `json:"stack_trace,omitempty"` +} + +// DefaultAuditLoggerConfig returns a default configuration +func DefaultAuditLoggerConfig() *AuditLoggerConfig { + return &AuditLoggerConfig{ + Enabled: true, + BufferSize: 1000, + FlushInterval: time.Second * 10, + LogLevel: "INFO", + IncludeMetadata: true, + IncludeStackTrace: false, + } +} + +// NewDefaultAuditLogger creates a new audit logger +func NewDefaultAuditLogger(logger types.Logger, config *AuditLoggerConfig) *DefaultAuditLogger { + if config == nil { + config = DefaultAuditLoggerConfig() + } + if logger == nil { + logger = &types.DefaultLogger{} + } + + al := &DefaultAuditLogger{ + logger: logger, + config: config, + eventChan: make(chan *AuditLogEntry, config.BufferSize), + stopChan: make(chan struct{}), + } + + // Start background logging goroutine + if config.Enabled { + al.wg.Add(1) + go al.processEvents() + } + + return al +} + +// LogAuthAttempt logs an authentication attempt +func (al *DefaultAuditLogger) LogAuthAttempt(ctx context.Context, event interface{}) { + if !al.config.Enabled { + return + } + + entry := &AuditLogEntry{ + ID: generateEventID(), + Timestamp: time.Now(), + Type: "attempt", + Event: event, + } + + if al.config.IncludeMetadata { + entry.Context = al.extractContextInfo(ctx) + } + + select { + case al.eventChan <- entry: + default: + // Channel is full, log directly to avoid blocking + al.logEntry(entry) + } +} + +// LogAuthFailure logs an authentication failure +func (al *DefaultAuditLogger) LogAuthFailure(ctx context.Context, event interface{}) { + if !al.config.Enabled { + return + } + + entry := &AuditLogEntry{ + ID: generateEventID(), + Timestamp: time.Now(), + Type: "failure", + Event: event, + } + + if al.config.IncludeMetadata { + entry.Context = al.extractContextInfo(ctx) + } + + if al.config.IncludeStackTrace { + entry.StackTrace = getStackTrace() + } + + select { + case al.eventChan <- entry: + default: + // Channel is full, log directly to avoid blocking + al.logEntry(entry) + } +} + +// LogAuthSuccess logs a successful authentication +func (al *DefaultAuditLogger) LogAuthSuccess(ctx context.Context, event interface{}) { + if !al.config.Enabled { + return + } + + entry := &AuditLogEntry{ + ID: generateEventID(), + Timestamp: time.Now(), + Type: "success", + Event: event, + } + + if al.config.IncludeMetadata { + entry.Context = al.extractContextInfo(ctx) + } + + select { + case al.eventChan <- entry: + default: + // Channel is full, log directly to avoid blocking + al.logEntry(entry) + } +} + +// Close cleans up resources +func (al *DefaultAuditLogger) Close() error { + if !al.config.Enabled { + return nil + } + + close(al.stopChan) + al.wg.Wait() + close(al.eventChan) + + // Process any remaining events + for entry := range al.eventChan { + al.logEntry(entry) + } + + return nil +} + +// processEvents processes audit log events in background +func (al *DefaultAuditLogger) processEvents() { + defer al.wg.Done() + + ticker := time.NewTicker(al.config.FlushInterval) + defer ticker.Stop() + + batch := make([]*AuditLogEntry, 0, 100) + + for { + select { + case entry, ok := <-al.eventChan: + if !ok { + // Channel closed, process remaining batch + if len(batch) > 0 { + al.processBatch(batch) + } + return + } + + batch = append(batch, entry) + + // Process batch if full + if len(batch) >= 100 { + al.processBatch(batch) + batch = batch[:0] // Reset slice + } + + case <-ticker.C: + // Flush on interval + if len(batch) > 0 { + al.processBatch(batch) + batch = batch[:0] // Reset slice + } + + case <-al.stopChan: + // Process remaining entries + if len(batch) > 0 { + al.processBatch(batch) + } + return + } + } +} + +// processBatch processes a batch of audit log entries +func (al *DefaultAuditLogger) processBatch(entries []*AuditLogEntry) { + for _, entry := range entries { + al.logEntry(entry) + } +} + +// logEntry logs a single audit log entry +func (al *DefaultAuditLogger) logEntry(entry *AuditLogEntry) { + // Convert entry to JSON for structured logging + entryJSON, err := json.Marshal(entry) + if err != nil { + al.logger.Error(context.Background(), "Failed to marshal audit log entry", "error", err) + return + } + + // Log based on type + switch entry.Type { + case "failure": + al.logger.Warn(context.Background(), "Authentication failure", "audit_entry", string(entryJSON)) + case "success": + al.logger.Info(context.Background(), "Authentication success", "audit_entry", string(entryJSON)) + case "attempt": + al.logger.Debug(context.Background(), "Authentication attempt", "audit_entry", string(entryJSON)) + default: + al.logger.Info(context.Background(), "Authentication event", "audit_entry", string(entryJSON)) + } +} + +// extractContextInfo extracts relevant information from context +func (al *DefaultAuditLogger) extractContextInfo(ctx context.Context) map[string]interface{} { + info := make(map[string]interface{}) + + // Extract request ID if available + if requestID := ctx.Value("request_id"); requestID != nil { + info["request_id"] = requestID + } + + // Extract trace ID if available + if traceID := ctx.Value("trace_id"); traceID != nil { + info["trace_id"] = traceID + } + + // Extract user agent if available + if userAgent := ctx.Value("user_agent"); userAgent != nil { + info["user_agent"] = userAgent + } + + // Extract IP address if available + if remoteAddr := ctx.Value("remote_addr"); remoteAddr != nil { + info["remote_addr"] = remoteAddr + } + + return info +} + +// generateEventID generates a unique event ID +func generateEventID() string { + // Simple timestamp-based ID - in production you might want UUIDs + return time.Now().Format("20060102150405.000000") +} + +// getStackTrace returns the current stack trace +func getStackTrace() string { + // Placeholder - in production you might use runtime.Stack() or similar + return "stack trace not implemented" +} + +// FileAuditLogger provides file-based audit logging +type FileAuditLogger struct { + file *os.File + encoder *json.Encoder + mu sync.Mutex + filePath string +} + +// NewFileAuditLogger creates a new file-based audit logger +func NewFileAuditLogger(filePath string) (*FileAuditLogger, error) { + file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if err != nil { + return nil, err + } + + return &FileAuditLogger{ + file: file, + encoder: json.NewEncoder(file), + filePath: filePath, + }, nil +} + +// LogAuthAttempt logs an authentication attempt to file +func (fal *FileAuditLogger) LogAuthAttempt(ctx context.Context, event interface{}) { + fal.mu.Lock() + defer fal.mu.Unlock() + + entry := map[string]interface{}{ + "timestamp": time.Now(), + "type": "attempt", + "event": event, + } + + fal.encoder.Encode(entry) +} + +// LogAuthFailure logs an authentication failure to file +func (fal *FileAuditLogger) LogAuthFailure(ctx context.Context, event interface{}) { + fal.mu.Lock() + defer fal.mu.Unlock() + + entry := map[string]interface{}{ + "timestamp": time.Now(), + "type": "failure", + "event": event, + } + + fal.encoder.Encode(entry) +} + +// LogAuthSuccess logs a successful authentication to file +func (fal *FileAuditLogger) LogAuthSuccess(ctx context.Context, event interface{}) { + fal.mu.Lock() + defer fal.mu.Unlock() + + entry := map[string]interface{}{ + "timestamp": time.Now(), + "type": "success", + "event": event, + } + + fal.encoder.Encode(entry) +} + +// Close closes the file +func (fal *FileAuditLogger) Close() error { + fal.mu.Lock() + defer fal.mu.Unlock() + + return fal.file.Close() +} diff --git a/a2a/pkg/auth/internal/rate_limiter.go b/a2a/pkg/auth/internal/rate_limiter.go new file mode 100644 index 000000000..6ecd25a92 --- /dev/null +++ b/a2a/pkg/auth/internal/rate_limiter.go @@ -0,0 +1,513 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package internal + +import ( + "context" + "sync" + "time" +) + +// DefaultRateLimiter provides a comprehensive in-memory rate limiter implementation +type DefaultRateLimiter struct { + config *RateLimiterConfig + clients map[string]*ClientRateLimitState + globalRequests []RequestInfo + mu sync.RWMutex + cleanupInterval time.Duration + stopChan chan struct{} + statistics *RateLimiterStatistics +} + +// RateLimiterConfig configures the comprehensive rate limiter +type RateLimiterConfig struct { + // Request rate limits + MaxRequestsPerSecond int `json:"max_requests_per_second"` + MaxRequestsPerMinute int `json:"max_requests_per_minute"` + MaxRequestsPerHour int `json:"max_requests_per_hour"` + MaxRequestsPerDay int `json:"max_requests_per_day"` + BurstSize int `json:"burst_size"` // Token bucket burst size + Window time.Duration `json:"window"` // Sliding window duration + + // Memory management + MaxRequestHistory int `json:"max_request_history"` // Maximum requests to keep in memory per client + MaxGlobalHistory int `json:"max_global_history"` // Maximum global requests to keep in memory + + // Failure tracking and banning + FailureThreshold int `json:"failure_threshold"` // Max failures before ban + FailureWindow time.Duration `json:"failure_window"` // Window for counting failures + BanDuration time.Duration `json:"ban_duration"` // Duration of ban + ProgressiveBanEnabled bool `json:"progressive_ban_enabled"` // Enable progressive ban durations + MaxBanDuration time.Duration `json:"max_ban_duration"` // Maximum ban duration + + // Advanced features + WhitelistedClients []string `json:"whitelisted_clients"` // Never rate limit these clients + BlacklistedClients []string `json:"blacklisted_clients"` // Always block these clients + GlobalRateLimit int `json:"global_rate_limit"` // Global requests per second + ClientQuotaEnabled bool `json:"client_quota_enabled"` // Enable per-client quotas + QuotaResetInterval time.Duration `json:"quota_reset_interval"` // How often to reset quotas + + // Adaptive rate limiting + AdaptiveEnabled bool `json:"adaptive_enabled"` // Enable adaptive rate limiting + AdaptiveThreshold float64 `json:"adaptive_threshold"` // Threshold for adaptive adjustment + AdaptiveAdjustment float64 `json:"adaptive_adjustment"` // How much to adjust rates +} + +// ClientRateLimitState tracks the rate limit state for a specific client +type ClientRateLimitState struct { + Requests []RequestInfo `json:"requests"` + Failures []time.Time `json:"failures"` + BannedUntil *time.Time `json:"banned_until,omitempty"` + BanCount int `json:"ban_count"` + QuotaUsed int `json:"quota_used"` + QuotaResetAt time.Time `json:"quota_reset_at"` + LastRequestTime time.Time `json:"last_request_time"` + TokenBucket *TokenBucket `json:"token_bucket"` + AdaptiveRate float64 `json:"adaptive_rate"` // Current adaptive rate multiplier +} + +// RequestInfo contains information about a request for rate limiting +type RequestInfo struct { + Timestamp time.Time `json:"timestamp"` + Success bool `json:"success"` + Duration int64 `json:"duration"` // nanoseconds +} + +// TokenBucket implements token bucket algorithm for burst control +type TokenBucket struct { + Tokens float64 `json:"tokens"` + LastRefill time.Time `json:"last_refill"` + RefillRate float64 `json:"refill_rate"` // tokens per second + BucketSize float64 `json:"bucket_size"` +} + +// RateLimiterStatistics provides comprehensive statistics +type RateLimiterStatistics struct { + TotalRequests int64 `json:"total_requests"` + AllowedRequests int64 `json:"allowed_requests"` + DeniedRequests int64 `json:"denied_requests"` + ActiveClients int `json:"active_clients"` + BannedClients int `json:"banned_clients"` + GlobalRequestsPerSec float64 `json:"global_requests_per_sec"` + AverageResponseTime float64 `json:"average_response_time"` + PeakRequestsPerSec float64 `json:"peak_requests_per_sec"` + LastResetTime time.Time `json:"last_reset_time"` +} + +// DefaultRateLimiterConfig returns a default configuration +func DefaultRateLimiterConfig() *RateLimiterConfig { + return &RateLimiterConfig{ + MaxRequestsPerSecond: 100, + MaxRequestsPerMinute: 6000, + MaxRequestsPerHour: 360000, + MaxRequestsPerDay: 8640000, + BurstSize: 10, + Window: time.Minute, + MaxRequestHistory: 1000, + MaxGlobalHistory: 10000, + FailureThreshold: 5, + FailureWindow: time.Minute * 5, + BanDuration: time.Minute * 15, + ProgressiveBanEnabled: true, + MaxBanDuration: time.Hour * 24, + GlobalRateLimit: 1000, + ClientQuotaEnabled: false, + QuotaResetInterval: time.Hour, + AdaptiveEnabled: false, + AdaptiveThreshold: 0.8, + AdaptiveAdjustment: 0.1, + } +} + +// NewDefaultRateLimiter creates a new comprehensive rate limiter +func NewDefaultRateLimiter(config *RateLimiterConfig) *DefaultRateLimiter { + if config == nil { + config = DefaultRateLimiterConfig() + } + + rl := &DefaultRateLimiter{ + config: config, + clients: make(map[string]*ClientRateLimitState), + globalRequests: make([]RequestInfo, 0), + cleanupInterval: time.Minute * 5, + stopChan: make(chan struct{}), + statistics: &RateLimiterStatistics{LastResetTime: time.Now()}, + } + + // Start cleanup goroutine + go rl.cleanup() + + return rl +} + +// Allow returns true if the request should be allowed +func (rl *DefaultRateLimiter) Allow(ctx context.Context, clientID string) bool { + rl.mu.Lock() + defer rl.mu.Unlock() + + now := time.Now() + + // Check blacklist + for _, blacklisted := range rl.config.BlacklistedClients { + if clientID == blacklisted { + rl.statistics.DeniedRequests++ + return false + } + } + + // Check whitelist + for _, whitelisted := range rl.config.WhitelistedClients { + if clientID == whitelisted { + rl.recordRequest(clientID, now, true, 0) + return true + } + } + + // Get or create client state + state, exists := rl.clients[clientID] + if !exists { + state = &ClientRateLimitState{ + Requests: make([]RequestInfo, 0), + Failures: make([]time.Time, 0), + TokenBucket: rl.createTokenBucket(), + QuotaResetAt: now.Add(rl.config.QuotaResetInterval), + AdaptiveRate: 1.0, + } + rl.clients[clientID] = state + } + + // Check if banned + if state.BannedUntil != nil && now.Before(*state.BannedUntil) { + rl.statistics.DeniedRequests++ + return false + } + + // Clear expired ban + if state.BannedUntil != nil && now.After(*state.BannedUntil) { + state.BannedUntil = nil + } + + // Check global rate limit + if !rl.checkGlobalRateLimit(now) { + rl.statistics.DeniedRequests++ + return false + } + + // Check per-client limits + if !rl.checkClientRateLimit(state, now) { + rl.statistics.DeniedRequests++ + return false + } + + // Check token bucket + if !rl.checkTokenBucket(state.TokenBucket, now) { + rl.statistics.DeniedRequests++ + return false + } + + // Check quota if enabled + if rl.config.ClientQuotaEnabled && !rl.checkQuota(state, now) { + rl.statistics.DeniedRequests++ + return false + } + + // Request is allowed + rl.recordRequest(clientID, now, true, 0) + rl.statistics.AllowedRequests++ + return true +} + +// Reset resets the rate limiter for a specific client +func (rl *DefaultRateLimiter) Reset(ctx context.Context, clientID string) error { + rl.mu.Lock() + defer rl.mu.Unlock() + + delete(rl.clients, clientID) + return nil +} + +// Close cleans up resources +func (rl *DefaultRateLimiter) Close() error { + close(rl.stopChan) + return nil +} + +// Helper methods... + +func (rl *DefaultRateLimiter) createTokenBucket() *TokenBucket { + return &TokenBucket{ + Tokens: float64(rl.config.BurstSize), + LastRefill: time.Now(), + RefillRate: float64(rl.config.MaxRequestsPerSecond), + BucketSize: float64(rl.config.BurstSize), + } +} + +func (rl *DefaultRateLimiter) checkGlobalRateLimit(now time.Time) bool { + // Clean old global requests + rl.cleanOldRequests(&rl.globalRequests, now, time.Second) + + // Apply memory limits + if len(rl.globalRequests) > rl.config.MaxGlobalHistory { + // Keep only the most recent requests + excess := len(rl.globalRequests) - rl.config.MaxGlobalHistory + rl.globalRequests = rl.globalRequests[excess:] + } + + return len(rl.globalRequests) < rl.config.GlobalRateLimit +} + +func (rl *DefaultRateLimiter) checkClientRateLimit(state *ClientRateLimitState, now time.Time) bool { + // Clean old requests + rl.cleanOldRequests(&state.Requests, now, rl.config.Window) + + // Apply memory limits + if len(state.Requests) > rl.config.MaxRequestHistory { + // Keep only the most recent requests + excess := len(state.Requests) - rl.config.MaxRequestHistory + state.Requests = state.Requests[excess:] + } + + // Count requests in different windows + requestsInSecond := rl.countRequestsInWindow(state.Requests, now, time.Second) + requestsInMinute := rl.countRequestsInWindow(state.Requests, now, time.Minute) + requestsInHour := rl.countRequestsInWindow(state.Requests, now, time.Hour) + requestsInDay := rl.countRequestsInWindow(state.Requests, now, time.Hour*24) + + // Apply adaptive rate limiting + adaptiveLimit := func(base int) int { + if rl.config.AdaptiveEnabled { + return int(float64(base) * state.AdaptiveRate) + } + return base + } + + return requestsInSecond < adaptiveLimit(rl.config.MaxRequestsPerSecond) && + requestsInMinute < adaptiveLimit(rl.config.MaxRequestsPerMinute) && + requestsInHour < adaptiveLimit(rl.config.MaxRequestsPerHour) && + requestsInDay < adaptiveLimit(rl.config.MaxRequestsPerDay) +} + +func (rl *DefaultRateLimiter) checkTokenBucket(bucket *TokenBucket, now time.Time) bool { + // Refill tokens + elapsed := now.Sub(bucket.LastRefill).Seconds() + tokensToAdd := elapsed * bucket.RefillRate + bucket.Tokens = min(bucket.BucketSize, bucket.Tokens+tokensToAdd) + bucket.LastRefill = now + + // Check if token available + if bucket.Tokens >= 1.0 { + bucket.Tokens -= 1.0 + return true + } + + return false +} + +func (rl *DefaultRateLimiter) checkQuota(state *ClientRateLimitState, now time.Time) bool { + // Reset quota if needed + if now.After(state.QuotaResetAt) { + state.QuotaUsed = 0 + state.QuotaResetAt = now.Add(rl.config.QuotaResetInterval) + } + + // Check quota (using daily limit as quota) + if state.QuotaUsed >= rl.config.MaxRequestsPerDay { + return false + } + + return true +} + +func (rl *DefaultRateLimiter) recordRequest(clientID string, timestamp time.Time, success bool, duration time.Duration) { + // Record global request + rl.globalRequests = append(rl.globalRequests, RequestInfo{ + Timestamp: timestamp, + Success: success, + Duration: duration.Nanoseconds(), + }) + + // Record client request + if state, exists := rl.clients[clientID]; exists { + state.Requests = append(state.Requests, RequestInfo{ + Timestamp: timestamp, + Success: success, + Duration: duration.Nanoseconds(), + }) + state.LastRequestTime = timestamp + + if rl.config.ClientQuotaEnabled { + state.QuotaUsed++ + } + + // Record failure if applicable + if !success { + state.Failures = append(state.Failures, timestamp) + rl.checkForBan(state, timestamp) + } + } + + // Update statistics + rl.statistics.TotalRequests++ +} + +func (rl *DefaultRateLimiter) checkForBan(state *ClientRateLimitState, now time.Time) { + // Clean old failures + cutoff := now.Add(-rl.config.FailureWindow) + newFailures := make([]time.Time, 0, len(state.Failures)) + for _, failure := range state.Failures { + if failure.After(cutoff) { + newFailures = append(newFailures, failure) + } + } + state.Failures = newFailures + + // Check if ban threshold reached + if len(state.Failures) >= rl.config.FailureThreshold { + banDuration := rl.config.BanDuration + + // Progressive ban duration + if rl.config.ProgressiveBanEnabled && state.BanCount > 0 { + multiplier := 1 << state.BanCount // Exponential backoff + banDuration = time.Duration(int64(banDuration) * int64(multiplier)) + if banDuration > rl.config.MaxBanDuration { + banDuration = rl.config.MaxBanDuration + } + } + + banUntil := now.Add(banDuration) + state.BannedUntil = &banUntil + state.BanCount++ + state.Failures = nil // Clear failures after ban + } +} + +func (rl *DefaultRateLimiter) cleanOldRequests(requests *[]RequestInfo, now time.Time, window time.Duration) { + cutoff := now.Add(-window) + newRequests := make([]RequestInfo, 0, len(*requests)) + for _, req := range *requests { + if req.Timestamp.After(cutoff) { + newRequests = append(newRequests, req) + } + } + *requests = newRequests +} + +func (rl *DefaultRateLimiter) countRequestsInWindow(requests []RequestInfo, now time.Time, window time.Duration) int { + cutoff := now.Add(-window) + count := 0 + for _, req := range requests { + if req.Timestamp.After(cutoff) { + count++ + } + } + return count +} + +func (rl *DefaultRateLimiter) cleanup() { + ticker := time.NewTicker(rl.cleanupInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + rl.performCleanup() + case <-rl.stopChan: + return + } + } +} + +func (rl *DefaultRateLimiter) performCleanup() { + rl.mu.Lock() + defer rl.mu.Unlock() + + now := time.Now() + + // Clean up expired client states + for clientID, state := range rl.clients { + // Remove clients that haven't made requests in a long time + if now.Sub(state.LastRequestTime) > time.Hour*24 { + delete(rl.clients, clientID) + continue + } + + // Clean old requests and failures + rl.cleanOldRequests(&state.Requests, now, rl.config.Window) + cutoff := now.Add(-rl.config.FailureWindow) + newFailures := make([]time.Time, 0, len(state.Failures)) + for _, failure := range state.Failures { + if failure.After(cutoff) { + newFailures = append(newFailures, failure) + } + } + state.Failures = newFailures + } + + // Clean global requests + rl.cleanOldRequests(&rl.globalRequests, now, time.Hour) + + // Update statistics + rl.updateStatistics(now) +} + +func (rl *DefaultRateLimiter) updateStatistics(now time.Time) { + rl.statistics.ActiveClients = len(rl.clients) + + bannedCount := 0 + for _, state := range rl.clients { + if state.BannedUntil != nil && now.Before(*state.BannedUntil) { + bannedCount++ + } + } + rl.statistics.BannedClients = bannedCount + + // Calculate requests per second + recentRequests := rl.countRequestsInWindow(rl.globalRequests, now, time.Second) + rl.statistics.GlobalRequestsPerSec = float64(recentRequests) + + if rl.statistics.GlobalRequestsPerSec > rl.statistics.PeakRequestsPerSec { + rl.statistics.PeakRequestsPerSec = rl.statistics.GlobalRequestsPerSec + } +} + +// GetStatistics returns current rate limiter statistics +func (rl *DefaultRateLimiter) GetStatistics() *RateLimiterStatistics { + rl.mu.RLock() + defer rl.mu.RUnlock() + + // Create a copy to avoid race conditions + stats := *rl.statistics + return &stats +} + +// RecordFailure records a failed request for the client +func (rl *DefaultRateLimiter) RecordFailure(ctx context.Context, clientID string, duration time.Duration) { + rl.mu.Lock() + defer rl.mu.Unlock() + + rl.recordRequest(clientID, time.Now(), false, duration) +} + +// min helper function +func min(a, b float64) float64 { + if a < b { + return a + } + return b +} diff --git a/a2a/pkg/auth/jws/agent_card.go b/a2a/pkg/auth/jws/agent_card.go new file mode 100644 index 000000000..506615ff1 --- /dev/null +++ b/a2a/pkg/auth/jws/agent_card.go @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package jws + +import ( + "context" + "crypto" + "encoding/json" + "fmt" + + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/types" +) + +// ValidateAgentCard validates all JWS signatures in an Agent Card +func ValidateAgentCard(ctx context.Context, agentCard *types.AgentCard, validator auth.JWSValidator) error { + if len(agentCard.Signatures) == 0 { + // No signatures to validate + return nil + } + + // Prepare the payload (agent card without signatures) + agentCardCopy := *agentCard + agentCardCopy.Signatures = nil + + payload, err := json.Marshal(agentCardCopy) + if err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to serialize agent card for validation", + Details: err.Error(), + } + } + + // Validate each signature + for i, signature := range agentCard.Signatures { + if err := validator.ValidateSignature(ctx, payload, signature); err != nil { + return fmt.Errorf("signature validation failed at index %d: %w", i, err) + } + } + + return nil +} + +// SignAgentCardInPlace signs an agent card and adds the signature to the card +func SignAgentCardInPlace(ctx context.Context, agentCard *types.AgentCard, signer auth.JWSSigner, privateKey crypto.PrivateKey, keyID, jwksURL string) error { + signature, err := signer.SignAgentCard(ctx, agentCard, privateKey, keyID, jwksURL) + if err != nil { + return err + } + + // Add signature to the agent card + agentCard.Signatures = append(agentCard.Signatures, signature) + + return nil +} diff --git a/a2a/pkg/auth/jws/cached_validator.go b/a2a/pkg/auth/jws/cached_validator.go new file mode 100644 index 000000000..f856a75ab --- /dev/null +++ b/a2a/pkg/auth/jws/cached_validator.go @@ -0,0 +1,273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package jws + +import ( + "context" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "time" + + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/lestrrat-go/jwx/v2/jws" + + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/cache" + "seata-go-ai-a2a/pkg/types" +) + +// CachedJWKSEntry represents a cached JWKS entry for serialization +type CachedJWKSEntry struct { + KeySetJSON []byte `json:"key_set_json"` + ExpiresAt time.Time `json:"expires_at"` +} + +// CachedValidator implements JWS validation with distributed caching support +type CachedValidator struct { + cache cache.Cache + cacheExpiry time.Duration + singleFlight *types.SingleFlight +} + +// NewCachedValidator creates a new JWS validator with cache support +func NewCachedValidator(cacheInstance cache.Cache) *CachedValidator { + return &CachedValidator{ + cache: cacheInstance, + cacheExpiry: 5 * time.Minute, // Cache JWKS for 5 minutes + singleFlight: types.NewSingleFlight(), + } +} + +// NewCachedValidatorWithExpiry creates a new JWS validator with custom cache expiry +func NewCachedValidatorWithExpiry(cacheInstance cache.Cache, expiry time.Duration) *CachedValidator { + return &CachedValidator{ + cache: cacheInstance, + cacheExpiry: expiry, + singleFlight: types.NewSingleFlight(), + } +} + +// ValidateSignature validates a JWS signature for the given payload +func (v *CachedValidator) ValidateSignature(ctx context.Context, payload []byte, signature *types.AgentCardSignature) error { + // Decode the protected header + protectedBytes, err := base64.RawURLEncoding.DecodeString(signature.Protected) + if err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to decode protected header", + Details: err.Error(), + } + } + + var header JWSHeader + if err := json.Unmarshal(protectedBytes, &header); err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to parse protected header", + Details: err.Error(), + } + } + + // Validate required fields + if header.Algorithm == "" { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Missing algorithm in JWS header", + } + } + + if header.KeyID == "" { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Missing key ID in JWS header", + } + } + + if header.JWKSURL == "" { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Missing JWKS URL in JWS header", + } + } + + // Get the key set from cache or fetch + keySet, err := v.getKeySet(ctx, header.JWKSURL) + if err != nil { + return err + } + + // Find the key + key, ok := keySet.LookupKeyID(header.KeyID) + if !ok { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Key not found in JWKS", + Details: fmt.Sprintf("Key ID: %s", header.KeyID), + } + } + + // Verify signature + return v.verifySignature(payload, signature, &header, key) +} + +// getKeySet retrieves the key set from cache or fetches it from URL +func (v *CachedValidator) getKeySet(ctx context.Context, jwksURL string) (jwk.Set, error) { + cacheKey := v.buildCacheKey(jwksURL) + + // Use single-flight to ensure only one fetch per URL + result, err := v.singleFlight.Do(jwksURL, func() (interface{}, error) { + // Try to get from cache first + if keySet, err := v.getFromCache(ctx, cacheKey); err == nil { + return keySet, nil + } + + // Fetch from URL + keySet, err := jwk.Fetch(ctx, jwksURL) + if err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeJWKSFetchFailed, + Message: "Failed to fetch JWKS", + Details: err.Error(), + } + } + + // Cache the key set + if cacheErr := v.setInCache(ctx, cacheKey, keySet); cacheErr != nil { + // Log cache error but don't fail the request + // In production, you'd want to use proper logging here + } + + return keySet, nil + }) + + if err != nil { + return nil, err + } + + return result.(jwk.Set), nil +} + +// buildCacheKey creates a cache key for JWKS URL +func (v *CachedValidator) buildCacheKey(jwksURL string) string { + // Hash the URL to create a consistent key + hash := sha256.Sum256([]byte(jwksURL)) + return fmt.Sprintf("jwks:%x", hash[:8]) // Use first 8 bytes of hash for shorter key +} + +// getFromCache retrieves a key set from cache +func (v *CachedValidator) getFromCache(ctx context.Context, cacheKey string) (jwk.Set, error) { + data, err := v.cache.Get(ctx, cacheKey) + if err != nil { + return nil, err + } + + var entry CachedJWKSEntry + if err := json.Unmarshal(data, &entry); err != nil { + return nil, err + } + + // Check if entry is expired + if time.Now().After(entry.ExpiresAt) { + return nil, cache.ErrCacheMiss + } + + // Parse the key set from JSON + keySet, err := jwk.Parse(entry.KeySetJSON) + if err != nil { + return nil, err + } + + return keySet, nil +} + +// setInCache stores a key set in cache +func (v *CachedValidator) setInCache(ctx context.Context, cacheKey string, keySet jwk.Set) error { + // Serialize the key set to JSON + keySetJSON, err := json.Marshal(keySet) + if err != nil { + return err + } + + entry := CachedJWKSEntry{ + KeySetJSON: keySetJSON, + ExpiresAt: time.Now().Add(v.cacheExpiry), + } + + entryJSON, err := json.Marshal(entry) + if err != nil { + return err + } + + return v.cache.Set(ctx, cacheKey, entryJSON, v.cacheExpiry) +} + +// verifySignature performs the actual JWS signature verification +func (v *CachedValidator) verifySignature(payload []byte, signature *types.AgentCardSignature, header *JWSHeader, key jwk.Key) error { + // Convert algorithm string to jwa.SignatureAlgorithm + alg := jwa.SignatureAlgorithm(header.Algorithm) + + // Construct the JWS token for verification + payloadEncoded := base64.RawURLEncoding.EncodeToString(payload) + jwsToken := fmt.Sprintf("%s.%s.%s", signature.Protected, payloadEncoded, signature.Signature) + + // Verify the signature + _, err := jws.Verify([]byte(jwsToken), jws.WithKey(alg, key)) + if err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "JWS signature verification failed", + Details: err.Error(), + } + } + + return nil +} + +// Close cleans up resources +func (v *CachedValidator) Close() error { + if v.cache != nil { + return v.cache.Close() + } + return nil +} + +// ClearCache removes all JWKS entries from cache +func (v *CachedValidator) ClearCache(ctx context.Context, jwksURL string) error { + cacheKey := v.buildCacheKey(jwksURL) + return v.cache.Delete(ctx, cacheKey) +} + +// GetCacheStats returns cache statistics (if supported by underlying cache) +func (v *CachedValidator) GetCacheStats(ctx context.Context, jwksURL string) (bool, time.Duration, error) { + cacheKey := v.buildCacheKey(jwksURL) + + exists, err := v.cache.Exists(ctx, cacheKey) + if err != nil { + return false, 0, err + } + + if !exists { + return false, 0, nil + } + + _, ttl, err := v.cache.GetWithTTL(ctx, cacheKey) + return exists, ttl, err +} diff --git a/a2a/pkg/auth/jws/signer.go b/a2a/pkg/auth/jws/signer.go new file mode 100644 index 000000000..b9f64e030 --- /dev/null +++ b/a2a/pkg/auth/jws/signer.go @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package jws + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/lestrrat-go/jwx/v2/jws" + + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/types" +) + +// Signer implements JWS signing for Agent Cards +type Signer struct{} + +// NewSigner creates a new JWS signer +func NewSigner() *Signer { + return &Signer{} +} + +// SignAgentCard signs an Agent Card and returns the signature +func (s *Signer) SignAgentCard(ctx context.Context, agentCard *types.AgentCard, privateKey crypto.PrivateKey, keyID, jwksURL string) (*types.AgentCardSignature, error) { + // Remove existing signatures from the agent card for signing + agentCardCopy := *agentCard + agentCardCopy.Signatures = nil + + // Serialize the agent card (canonical JSON) + payload, err := json.Marshal(agentCardCopy) + if err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to serialize agent card", + Details: err.Error(), + } + } + + // Create JWK from private key + jwkKey, err := jwk.FromRaw(privateKey) + if err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to create JWK from private key", + Details: err.Error(), + } + } + + // Set key metadata + if err := jwkKey.Set(jwk.KeyIDKey, keyID); err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to set key ID", + Details: err.Error(), + } + } + + // Determine the algorithm based on the actual key type (not interface) + algorithm, err := s.determineAlgorithm(privateKey) + if err != nil { + return nil, err + } + + if err := jwkKey.Set(jwk.AlgorithmKey, algorithm); err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to set algorithm", + Details: err.Error(), + } + } + + // Create JWS headers + headers := jws.NewHeaders() + if err := headers.Set(jws.AlgorithmKey, algorithm); err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to set algorithm header", + Details: err.Error(), + } + } + if err := headers.Set(jws.TypeKey, "JOSE"); err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to set type header", + Details: err.Error(), + } + } + if err := headers.Set(jws.KeyIDKey, keyID); err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to set key ID header", + Details: err.Error(), + } + } + if err := headers.Set("jku", jwksURL); err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to set JWKS URL header", + Details: err.Error(), + } + } + + // Sign the payload + signed, err := jws.Sign(payload, jws.WithKey(algorithm, jwkKey, jws.WithProtectedHeaders(headers))) + if err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to sign agent card", + Details: err.Error(), + } + } + + // Parse the JWS to extract components + parsedJWS, err := jws.Parse(signed) + if err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to parse signed JWS", + Details: err.Error(), + } + } + + // Extract the signature components + if len(parsedJWS.Signatures()) == 0 { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "No signatures found in JWS", + } + } + + signature := parsedJWS.Signatures()[0] + + // Marshal protected headers to get bytes + protectedBytes, err := json.Marshal(signature.ProtectedHeaders()) + if err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to marshal protected headers", + Details: err.Error(), + } + } + + // Convert public headers (unprotected) to map[string]any if they exist + headerMap := make(map[string]any) + if signature.PublicHeaders() != nil { + if publicHeaderMap, err := signature.PublicHeaders().AsMap(context.Background()); err == nil { + headerMap = publicHeaderMap + } + } + + return &types.AgentCardSignature{ + Protected: base64.RawURLEncoding.EncodeToString(protectedBytes), + Signature: base64.RawURLEncoding.EncodeToString(signature.Signature()), + Header: headerMap, + }, nil +} + +// determineAlgorithm determines the appropriate JWS algorithm for the given private key +func (s *Signer) determineAlgorithm(privateKey crypto.PrivateKey) (jwa.SignatureAlgorithm, error) { + switch key := privateKey.(type) { + case *ecdsa.PrivateKey: + // ECDSA keys + switch key.Curve.Params().BitSize { + case 256: + return jwa.ES256, nil + case 384: + return jwa.ES384, nil + case 521: + return jwa.ES512, nil + default: + return "", &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Unsupported ECDSA curve", + Details: fmt.Sprintf("Curve bit size: %d", key.Curve.Params().BitSize), + } + } + case *rsa.PrivateKey: + // RSA keys - use RS256 for most cases + return jwa.RS256, nil + case ed25519.PrivateKey: + // EdDSA keys + return jwa.EdDSA, nil + default: + return "", &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Unsupported private key type", + Details: fmt.Sprintf("Key type: %T", privateKey), + } + } +} diff --git a/a2a/pkg/auth/jws/validator.go b/a2a/pkg/auth/jws/validator.go new file mode 100644 index 000000000..d2b764ed5 --- /dev/null +++ b/a2a/pkg/auth/jws/validator.go @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package jws + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "sync" + "time" + + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/lestrrat-go/jwx/v2/jws" + + "seata-go-ai-a2a/pkg/auth" + "seata-go-ai-a2a/pkg/types" +) + +// JWSHeader represents JWS protected header +type JWSHeader struct { + Algorithm string `json:"alg"` + KeyID string `json:"kid"` + JWKSURL string `json:"jku"` + Type string `json:"typ,omitempty"` +} + +// JWKSCacheEntry represents a cached JWKS entry with expiration +type JWKSCacheEntry struct { + KeySet jwk.Set + ExpiresAt time.Time +} + +// Validator implements JWS validation for Agent Cards with in-memory caching +type Validator struct { + keySetCache map[string]*JWKSCacheEntry + cacheMutex sync.RWMutex + cacheExpiry time.Duration + stopChan chan struct{} + cleanupOnce sync.Once + singleFlight *types.SingleFlight +} + +// NewValidator creates a new JWS validator with in-memory caching +func NewValidator() *Validator { + validator := &Validator{ + keySetCache: make(map[string]*JWKSCacheEntry), + cacheExpiry: 5 * time.Minute, // Cache JWKS for 5 minutes + stopChan: make(chan struct{}), + singleFlight: types.NewSingleFlight(), + } + + // Start background cleanup routine + go validator.cleanupExpiredEntries() + + return validator +} + +// NewValidatorWithExpiry creates a new JWS validator with custom cache expiry +func NewValidatorWithExpiry(expiry time.Duration) *Validator { + validator := &Validator{ + keySetCache: make(map[string]*JWKSCacheEntry), + cacheExpiry: expiry, + stopChan: make(chan struct{}), + singleFlight: types.NewSingleFlight(), + } + + go validator.cleanupExpiredEntries() + return validator +} + +// ValidateSignature validates a JWS signature for the given payload +func (v *Validator) ValidateSignature(ctx context.Context, payload []byte, signature *types.AgentCardSignature) error { + // Decode the protected header + protectedBytes, err := base64.RawURLEncoding.DecodeString(signature.Protected) + if err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to decode protected header", + Details: err.Error(), + } + } + + var header JWSHeader + if err := json.Unmarshal(protectedBytes, &header); err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Failed to parse protected header", + Details: err.Error(), + } + } + + // Validate required fields + if header.Algorithm == "" { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Missing algorithm in JWS header", + } + } + + if header.KeyID == "" { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Missing key ID in JWS header", + } + } + + if header.JWKSURL == "" { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Missing JWKS URL in JWS header", + } + } + + // Get the key set + keySet, err := v.GetKeySet(ctx, header.JWKSURL) + if err != nil { + return err + } + + // Find the key + key, ok := keySet.LookupKeyID(header.KeyID) + if !ok { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "Key not found in JWKS", + Details: fmt.Sprintf("Key ID: %s", header.KeyID), + } + } + + // Construct the JWS token for verification + payloadEncoded := base64.RawURLEncoding.EncodeToString(payload) + jwsToken := fmt.Sprintf("%s.%s.%s", signature.Protected, payloadEncoded, signature.Signature) + + // Convert algorithm string to jwa.SignatureAlgorithm + alg := jwa.SignatureAlgorithm(header.Algorithm) + + // Verify the signature + _, err = jws.Verify([]byte(jwsToken), jws.WithKey(alg, key)) + if err != nil { + return &auth.AuthenticationError{ + Code: auth.ErrCodeInvalidSignature, + Message: "JWS signature verification failed", + Details: err.Error(), + } + } + + return nil +} + +// GetKeySet retrieves the key set from the given JWKS URL with caching and single-flight pattern +func (v *Validator) GetKeySet(ctx context.Context, jwksURL string) (jwk.Set, error) { + now := time.Now() + + // Check cache first + v.cacheMutex.RLock() + if entry, exists := v.keySetCache[jwksURL]; exists && now.Before(entry.ExpiresAt) { + keySet := entry.KeySet + v.cacheMutex.RUnlock() + return keySet, nil + } + v.cacheMutex.RUnlock() + + // Use single-flight to ensure only one fetch per URL + result, err := v.singleFlight.Do(jwksURL, func() (interface{}, error) { + // Double-check cache inside single-flight + v.cacheMutex.RLock() + if entry, exists := v.keySetCache[jwksURL]; exists && time.Now().Before(entry.ExpiresAt) { + keySet := entry.KeySet + v.cacheMutex.RUnlock() + return keySet, nil + } + v.cacheMutex.RUnlock() + + // Fetch from URL + keySet, err := jwk.Fetch(ctx, jwksURL) + if err != nil { + return nil, &auth.AuthenticationError{ + Code: auth.ErrCodeJWKSFetchFailed, + Message: "Failed to fetch JWKS", + Details: err.Error(), + } + } + + // Cache the key set with expiration + v.cacheMutex.Lock() + v.keySetCache[jwksURL] = &JWKSCacheEntry{ + KeySet: keySet, + ExpiresAt: time.Now().Add(v.cacheExpiry), + } + v.cacheMutex.Unlock() + + return keySet, nil + }) + + if err != nil { + return nil, err + } + + return result.(jwk.Set), nil +} + +// Close gracefully shuts down the validator and stops background routines +func (v *Validator) Close() { + v.cleanupOnce.Do(func() { + close(v.stopChan) + + // Clear cache + v.cacheMutex.Lock() + v.keySetCache = make(map[string]*JWKSCacheEntry) + v.cacheMutex.Unlock() + }) +} + +// cleanupExpiredEntries runs a background cleanup routine for expired cache entries +func (v *Validator) cleanupExpiredEntries() { + ticker := time.NewTicker(v.cacheExpiry / 2) // Clean up twice as often as expiry + defer ticker.Stop() + + for { + select { + case <-ticker.C: + v.performCleanup() + case <-v.stopChan: + return + } + } +} + +// performCleanup removes expired entries from the cache +func (v *Validator) performCleanup() { + now := time.Now() + + v.cacheMutex.Lock() + defer v.cacheMutex.Unlock() + + for url, entry := range v.keySetCache { + if now.After(entry.ExpiresAt) { + delete(v.keySetCache, url) + } + } +} diff --git a/a2a/pkg/auth/user.go b/a2a/pkg/auth/user.go new file mode 100644 index 000000000..b5e1875ec --- /dev/null +++ b/a2a/pkg/auth/user.go @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package auth + +// AuthenticatedUser represents an authenticated user +type AuthenticatedUser struct { + userID string + name string + email string + scopes []string + claims map[string]interface{} +} + +// NewAuthenticatedUser creates a new authenticated user +func NewAuthenticatedUser(userID, name, email string, scopes []string, claims map[string]interface{}) *AuthenticatedUser { + if claims == nil { + claims = make(map[string]interface{}) + } + + return &AuthenticatedUser{ + userID: userID, + name: name, + email: email, + scopes: scopes, + claims: claims, + } +} + +func (u *AuthenticatedUser) GetUserID() string { return u.userID } +func (u *AuthenticatedUser) GetName() string { return u.name } +func (u *AuthenticatedUser) GetEmail() string { return u.email } +func (u *AuthenticatedUser) GetScopes() []string { return u.scopes } +func (u *AuthenticatedUser) GetClaims() map[string]interface{} { return u.claims } +func (u *AuthenticatedUser) IsAuthenticated() bool { return true } + +// UnauthenticatedUser represents an unauthenticated user +type UnauthenticatedUser struct{} + +func (u *UnauthenticatedUser) GetUserID() string { return "" } +func (u *UnauthenticatedUser) GetName() string { return "" } +func (u *UnauthenticatedUser) GetEmail() string { return "" } +func (u *UnauthenticatedUser) GetScopes() []string { return nil } +func (u *UnauthenticatedUser) GetClaims() map[string]interface{} { return nil } +func (u *UnauthenticatedUser) IsAuthenticated() bool { return false } diff --git a/a2a/pkg/cache/cache.go b/a2a/pkg/cache/cache.go new file mode 100644 index 000000000..1dc484db4 --- /dev/null +++ b/a2a/pkg/cache/cache.go @@ -0,0 +1,399 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package cache + +import ( + "context" + "encoding/json" + "errors" + "sync" + "time" + + "github.com/redis/go-redis/v9" +) + +// Cache defines the interface for distributed caching +type Cache interface { + // Get retrieves a value from the cache + Get(ctx context.Context, key string) ([]byte, error) + + // Set stores a value in the cache with expiration + Set(ctx context.Context, key string, value []byte, expiration time.Duration) error + + // Delete removes a key from the cache + Delete(ctx context.Context, key string) error + + // Exists checks if a key exists in the cache + Exists(ctx context.Context, key string) (bool, error) + + // SetExpiration updates the expiration time for a key + SetExpiration(ctx context.Context, key string, expiration time.Duration) error + + // GetWithTTL retrieves a value and its remaining TTL + GetWithTTL(ctx context.Context, key string) ([]byte, time.Duration, error) + + // Close closes the cache connection + Close() error + + // Ping tests the cache connection + Ping(ctx context.Context) error +} + +// RedisCache implements Cache interface using Redis +type RedisCache struct { + client *redis.Client + prefix string +} + +// NewRedisCache creates a new Redis cache instance +func NewRedisCache(client *redis.Client, prefix string) *RedisCache { + if prefix == "" { + prefix = "a2a" + } + return &RedisCache{ + client: client, + prefix: prefix, + } +} + +// buildKey creates a prefixed cache key +func (r *RedisCache) buildKey(key string) string { + return r.prefix + ":" + key +} + +// Get retrieves a value from Redis +func (r *RedisCache) Get(ctx context.Context, key string) ([]byte, error) { + fullKey := r.buildKey(key) + result, err := r.client.Get(ctx, fullKey).Result() + if err != nil { + if err == redis.Nil { + return nil, ErrCacheMiss + } + return nil, err + } + return []byte(result), nil +} + +// Set stores a value in Redis with expiration +func (r *RedisCache) Set(ctx context.Context, key string, value []byte, expiration time.Duration) error { + fullKey := r.buildKey(key) + return r.client.Set(ctx, fullKey, value, expiration).Err() +} + +// Delete removes a key from Redis +func (r *RedisCache) Delete(ctx context.Context, key string) error { + fullKey := r.buildKey(key) + return r.client.Del(ctx, fullKey).Err() +} + +// Exists checks if a key exists in Redis +func (r *RedisCache) Exists(ctx context.Context, key string) (bool, error) { + fullKey := r.buildKey(key) + count, err := r.client.Exists(ctx, fullKey).Result() + return count > 0, err +} + +// SetExpiration updates the expiration time for a key +func (r *RedisCache) SetExpiration(ctx context.Context, key string, expiration time.Duration) error { + fullKey := r.buildKey(key) + return r.client.Expire(ctx, fullKey, expiration).Err() +} + +// GetWithTTL retrieves a value and its remaining TTL +func (r *RedisCache) GetWithTTL(ctx context.Context, key string) ([]byte, time.Duration, error) { + fullKey := r.buildKey(key) + + // Use pipeline for atomic operation + pipe := r.client.Pipeline() + getCmd := pipe.Get(ctx, fullKey) + ttlCmd := pipe.TTL(ctx, fullKey) + + _, err := pipe.Exec(ctx) + if err != nil { + if err == redis.Nil { + return nil, 0, ErrCacheMiss + } + return nil, 0, err + } + + value, err := getCmd.Result() + if err != nil { + if err == redis.Nil { + return nil, 0, ErrCacheMiss + } + return nil, 0, err + } + + ttl, err := ttlCmd.Result() + if err != nil { + return nil, 0, err + } + + return []byte(value), ttl, nil +} + +// Close closes the Redis connection +func (r *RedisCache) Close() error { + return r.client.Close() +} + +// Ping tests the Redis connection +func (r *RedisCache) Ping(ctx context.Context) error { + return r.client.Ping(ctx).Err() +} + +// MemoryCache implements Cache interface using in-memory storage +type MemoryCache struct { + data map[string]*memoryCacheEntry + mutex sync.RWMutex + stopCh chan struct{} + closed bool +} + +type memoryCacheEntry struct { + value []byte + expiresAt time.Time +} + +// NewMemoryCache creates a new in-memory cache instance +func NewMemoryCache() *MemoryCache { + mc := &MemoryCache{ + data: make(map[string]*memoryCacheEntry), + stopCh: make(chan struct{}), + } + + // Start cleanup goroutine + go mc.cleanupExpired() + + return mc +} + +// Get retrieves a value from memory cache +func (m *MemoryCache) Get(ctx context.Context, key string) ([]byte, error) { + m.mutex.RLock() + defer m.mutex.RUnlock() + + if m.closed { + return nil, ErrCacheClosed + } + + entry, exists := m.data[key] + if !exists { + return nil, ErrCacheMiss + } + + if time.Now().After(entry.expiresAt) { + // Entry expired, remove it + delete(m.data, key) + return nil, ErrCacheMiss + } + + return entry.value, nil +} + +// Set stores a value in memory cache with expiration +func (m *MemoryCache) Set(ctx context.Context, key string, value []byte, expiration time.Duration) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + if m.closed { + return ErrCacheClosed + } + + expiresAt := time.Now().Add(expiration) + m.data[key] = &memoryCacheEntry{ + value: value, + expiresAt: expiresAt, + } + + return nil +} + +// Delete removes a key from memory cache +func (m *MemoryCache) Delete(ctx context.Context, key string) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + if m.closed { + return ErrCacheClosed + } + + delete(m.data, key) + return nil +} + +// Exists checks if a key exists in memory cache +func (m *MemoryCache) Exists(ctx context.Context, key string) (bool, error) { + m.mutex.RLock() + defer m.mutex.RUnlock() + + if m.closed { + return false, ErrCacheClosed + } + + entry, exists := m.data[key] + if !exists { + return false, nil + } + + if time.Now().After(entry.expiresAt) { + return false, nil + } + + return true, nil +} + +// SetExpiration updates the expiration time for a key +func (m *MemoryCache) SetExpiration(ctx context.Context, key string, expiration time.Duration) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + if m.closed { + return ErrCacheClosed + } + + entry, exists := m.data[key] + if !exists { + return ErrCacheMiss + } + + entry.expiresAt = time.Now().Add(expiration) + return nil +} + +// GetWithTTL retrieves a value and its remaining TTL +func (m *MemoryCache) GetWithTTL(ctx context.Context, key string) ([]byte, time.Duration, error) { + m.mutex.RLock() + defer m.mutex.RUnlock() + + if m.closed { + return nil, 0, ErrCacheClosed + } + + entry, exists := m.data[key] + if !exists { + return nil, 0, ErrCacheMiss + } + + now := time.Now() + if now.After(entry.expiresAt) { + return nil, 0, ErrCacheMiss + } + + ttl := entry.expiresAt.Sub(now) + return entry.value, ttl, nil +} + +// Close closes the memory cache +func (m *MemoryCache) Close() error { + m.mutex.Lock() + defer m.mutex.Unlock() + + if !m.closed { + close(m.stopCh) + m.closed = true + m.data = nil + } + + return nil +} + +// Ping always returns nil for memory cache +func (m *MemoryCache) Ping(ctx context.Context) error { + m.mutex.RLock() + defer m.mutex.RUnlock() + + if m.closed { + return ErrCacheClosed + } + + return nil +} + +// cleanupExpired removes expired entries from memory cache +func (m *MemoryCache) cleanupExpired() { + ticker := time.NewTicker(1 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + m.performCleanup() + case <-m.stopCh: + return + } + } +} + +// performCleanup removes expired entries +func (m *MemoryCache) performCleanup() { + m.mutex.Lock() + defer m.mutex.Unlock() + + if m.closed { + return + } + + now := time.Now() + for key, entry := range m.data { + if now.After(entry.expiresAt) { + delete(m.data, key) + } + } +} + +// Cache errors +var ( + ErrCacheMiss = errors.New("cache miss") + ErrCacheClosed = errors.New("cache is closed") +) + +// JSONCache provides JSON serialization helpers +type JSONCache struct { + cache Cache +} + +// NewJSONCache wraps a cache with JSON serialization +func NewJSONCache(cache Cache) *JSONCache { + return &JSONCache{cache: cache} +} + +// GetJSON retrieves and unmarshals a JSON value +func (j *JSONCache) GetJSON(ctx context.Context, key string, dest interface{}) error { + data, err := j.cache.Get(ctx, key) + if err != nil { + return err + } + + return json.Unmarshal(data, dest) +} + +// SetJSON marshals and stores a JSON value +func (j *JSONCache) SetJSON(ctx context.Context, key string, value interface{}, expiration time.Duration) error { + data, err := json.Marshal(value) + if err != nil { + return err + } + + return j.cache.Set(ctx, key, data, expiration) +} + +// GetCache returns the underlying cache +func (j *JSONCache) GetCache() Cache { + return j.cache +} diff --git a/a2a/pkg/cache/cache_test.go b/a2a/pkg/cache/cache_test.go new file mode 100644 index 000000000..392ed0bf8 --- /dev/null +++ b/a2a/pkg/cache/cache_test.go @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package cache + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMemoryCache(t *testing.T) { + cache := NewMemoryCache() + defer cache.Close() + + ctx := context.Background() + + t.Run("Basic Set and Get", func(t *testing.T) { + key := "test-key" + value := []byte("test-value") + expiration := 1 * time.Hour + + err := cache.Set(ctx, key, value, expiration) + require.NoError(t, err) + + retrieved, err := cache.Get(ctx, key) + require.NoError(t, err) + assert.Equal(t, value, retrieved) + }) + + t.Run("Cache Miss", func(t *testing.T) { + _, err := cache.Get(ctx, "nonexistent-key") + assert.Equal(t, ErrCacheMiss, err) + }) + + t.Run("Expiration", func(t *testing.T) { + key := "expire-key" + value := []byte("expire-value") + expiration := 10 * time.Millisecond + + err := cache.Set(ctx, key, value, expiration) + require.NoError(t, err) + + // Should still be available immediately + retrieved, err := cache.Get(ctx, key) + require.NoError(t, err) + assert.Equal(t, value, retrieved) + + // Wait for expiration + time.Sleep(20 * time.Millisecond) + + _, err = cache.Get(ctx, key) + assert.Equal(t, ErrCacheMiss, err) + }) + + t.Run("Exists", func(t *testing.T) { + key := "exists-key" + value := []byte("exists-value") + expiration := 1 * time.Hour + + exists, err := cache.Exists(ctx, key) + require.NoError(t, err) + assert.False(t, exists) + + err = cache.Set(ctx, key, value, expiration) + require.NoError(t, err) + + exists, err = cache.Exists(ctx, key) + require.NoError(t, err) + assert.True(t, exists) + }) + + t.Run("Delete", func(t *testing.T) { + key := "delete-key" + value := []byte("delete-value") + expiration := 1 * time.Hour + + err := cache.Set(ctx, key, value, expiration) + require.NoError(t, err) + + err = cache.Delete(ctx, key) + require.NoError(t, err) + + _, err = cache.Get(ctx, key) + assert.Equal(t, ErrCacheMiss, err) + }) + + t.Run("GetWithTTL", func(t *testing.T) { + key := "ttl-key" + value := []byte("ttl-value") + expiration := 1 * time.Hour + + err := cache.Set(ctx, key, value, expiration) + require.NoError(t, err) + + retrieved, ttl, err := cache.GetWithTTL(ctx, key) + require.NoError(t, err) + assert.Equal(t, value, retrieved) + assert.Greater(t, ttl, 50*time.Minute) // Should be close to 1 hour + assert.LessOrEqual(t, ttl, expiration) + }) + + t.Run("SetExpiration", func(t *testing.T) { + key := "set-exp-key" + value := []byte("set-exp-value") + initialExpiration := 1 * time.Hour + newExpiration := 20 * time.Millisecond + + err := cache.Set(ctx, key, value, initialExpiration) + require.NoError(t, err) + + err = cache.SetExpiration(ctx, key, newExpiration) + require.NoError(t, err) + + // Wait for new expiration + time.Sleep(30 * time.Millisecond) + + _, err = cache.Get(ctx, key) + assert.Equal(t, ErrCacheMiss, err) + }) + + t.Run("Closed Cache", func(t *testing.T) { + closedCache := NewMemoryCache() + err := closedCache.Close() + require.NoError(t, err) + + err = closedCache.Set(ctx, "key", []byte("value"), time.Hour) + assert.Equal(t, ErrCacheClosed, err) + + _, err = closedCache.Get(ctx, "key") + assert.Equal(t, ErrCacheClosed, err) + }) + + t.Run("Ping", func(t *testing.T) { + err := cache.Ping(ctx) + assert.NoError(t, err) + + closedCache := NewMemoryCache() + err = closedCache.Close() + require.NoError(t, err) + + err = closedCache.Ping(ctx) + assert.Equal(t, ErrCacheClosed, err) + }) +} + +func TestJSONCache(t *testing.T) { + memCache := NewMemoryCache() + defer memCache.Close() + + jsonCache := NewJSONCache(memCache) + ctx := context.Background() + + t.Run("JSON Serialization", func(t *testing.T) { + type TestStruct struct { + Name string `json:"name"` + Value int `json:"value"` + } + + original := TestStruct{Name: "test", Value: 42} + key := "json-key" + expiration := 1 * time.Hour + + err := jsonCache.SetJSON(ctx, key, original, expiration) + require.NoError(t, err) + + var retrieved TestStruct + err = jsonCache.GetJSON(ctx, key, &retrieved) + require.NoError(t, err) + assert.Equal(t, original, retrieved) + }) + + t.Run("JSON Invalid Data", func(t *testing.T) { + key := "invalid-json-key" + expiration := 1 * time.Hour + + // Set invalid JSON directly through underlying cache + err := memCache.Set(ctx, key, []byte("invalid json"), expiration) + require.NoError(t, err) + + var result map[string]interface{} + err = jsonCache.GetJSON(ctx, key, &result) + assert.Error(t, err) + }) +} + +func TestCacheErrors(t *testing.T) { + t.Run("Error Types", func(t *testing.T) { + assert.Error(t, ErrCacheMiss) + assert.Error(t, ErrCacheClosed) + assert.Equal(t, "cache miss", ErrCacheMiss.Error()) + assert.Equal(t, "cache is closed", ErrCacheClosed.Error()) + }) +} + +// Benchmark tests +func BenchmarkMemoryCacheSet(b *testing.B) { + cache := NewMemoryCache() + defer cache.Close() + + ctx := context.Background() + value := []byte("benchmark-value") + expiration := 1 * time.Hour + + b.ResetTimer() + for i := 0; i < b.N; i++ { + key := "bench-key-" + string(rune(i)) + cache.Set(ctx, key, value, expiration) + } +} + +func BenchmarkMemoryCacheGet(b *testing.B) { + cache := NewMemoryCache() + defer cache.Close() + + ctx := context.Background() + value := []byte("benchmark-value") + expiration := 1 * time.Hour + + // Pre-populate cache + for i := 0; i < 1000; i++ { + key := "bench-key-" + string(rune(i)) + cache.Set(ctx, key, value, expiration) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + key := "bench-key-" + string(rune(i%1000)) + cache.Get(ctx, key) + } +} + +func BenchmarkJSONCacheSetJSON(b *testing.B) { + memCache := NewMemoryCache() + defer memCache.Close() + + jsonCache := NewJSONCache(memCache) + ctx := context.Background() + + type TestStruct struct { + Name string `json:"name"` + Value int `json:"value"` + } + + data := TestStruct{Name: "benchmark", Value: 42} + expiration := 1 * time.Hour + + b.ResetTimer() + for i := 0; i < b.N; i++ { + key := "json-bench-key-" + string(rune(i)) + jsonCache.SetJSON(ctx, key, data, expiration) + } +} diff --git a/a2a/pkg/discovery/discovery.go b/a2a/pkg/discovery/discovery.go new file mode 100644 index 000000000..eb45c901e --- /dev/null +++ b/a2a/pkg/discovery/discovery.go @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package discovery + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "seata-go-ai-a2a/pkg/types" +) + +// Service provides Agent Card discovery functionality +type Service struct { + agentCard *types.AgentCard +} + +// NewService creates a new discovery service with the given agent card +func NewService(agentCard *types.AgentCard) *Service { + return &Service{ + agentCard: agentCard, + } +} + +// RegisterWellKnownEndpoints registers the well-known Agent Card discovery endpoints +// This implements the A2A specification for Agent Card discovery +func (s *Service) RegisterWellKnownEndpoints(mux *http.ServeMux) { + // Register the well-known agent card endpoint + // As per A2A specification: https://{domain}/.well-known/agent-card.json + mux.HandleFunc("/.well-known/agent-card.json", s.handleAgentCardDiscovery) + + // Additional discovery endpoint as fallback + mux.HandleFunc("/.well-known/agent-card", s.handleAgentCardDiscovery) +} + +// handleAgentCardDiscovery handles requests for the agent card discovery endpoint +func (s *Service) handleAgentCardDiscovery(w http.ResponseWriter, r *http.Request) { + // Only allow GET requests + if r.Method != http.MethodGet { + w.Header().Set("Allow", "GET") + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Set appropriate headers + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "public, max-age=3600") // Cache for 1 hour + w.Header().Set("Access-Control-Allow-Origin", "*") // Allow CORS for discovery + + // Serialize and return the agent card + if err := json.NewEncoder(w).Encode(s.agentCard); err != nil { + http.Error(w, "Failed to encode agent card", http.StatusInternalServerError) + return + } +} + +// UpdateAgentCard updates the agent card served by the discovery service +func (s *Service) UpdateAgentCard(agentCard *types.AgentCard) { + s.agentCard = agentCard +} + +// GetAgentCard returns the current agent card +func (s *Service) GetAgentCard() *types.AgentCard { + return s.agentCard +} + +// DiscoverAgentCard discovers an agent card from a given domain +// This is a client-side function to discover remote agent cards +func DiscoverAgentCard(ctx context.Context, domain string) (*types.AgentCard, error) { + // Try the primary well-known URI first + url := fmt.Sprintf("https://%s/.well-known/agent-card.json", domain) + + agentCard, err := fetchAgentCard(ctx, url) + if err == nil { + return agentCard, nil + } + + // Try fallback without .json extension + url = fmt.Sprintf("https://%s/.well-known/agent-card", domain) + agentCard, err = fetchAgentCard(ctx, url) + if err != nil { + return nil, fmt.Errorf("failed to discover agent card for domain %s: %w", domain, err) + } + + return agentCard, nil +} + +// fetchAgentCard fetches an agent card from the given URL +func fetchAgentCard(ctx context.Context, url string) (*types.AgentCard, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "A2A-Discovery/1.0") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to fetch agent card: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var agentCard types.AgentCard + if err := json.NewDecoder(resp.Body).Decode(&agentCard); err != nil { + return nil, fmt.Errorf("failed to decode agent card: %w", err) + } + + return &agentCard, nil +} + +// ValidateAgentCard validates that an agent card conforms to A2A specification +func ValidateAgentCard(agentCard *types.AgentCard) error { + if agentCard == nil { + return fmt.Errorf("agent card cannot be nil") + } + + if agentCard.ProtocolVersion == "" { + return fmt.Errorf("protocol version is required") + } + + if agentCard.Name == "" { + return fmt.Errorf("name is required") + } + + if agentCard.Description == "" { + return fmt.Errorf("description is required") + } + + if agentCard.URL == "" { + return fmt.Errorf("URL is required") + } + + if agentCard.PreferredTransport == "" { + return fmt.Errorf("preferred transport is required") + } + + if agentCard.Version == "" { + return fmt.Errorf("version is required") + } + + // Validate preferred transport is one of the supported values + validTransports := map[string]bool{ + "grpc": true, + "jsonrpc": true, + "http+json": true, + "rest": true, + } + + if !validTransports[agentCard.PreferredTransport] { + return fmt.Errorf("invalid preferred transport: %s", agentCard.PreferredTransport) + } + + return nil +} diff --git a/a2a/pkg/handler/builder.go b/a2a/pkg/handler/builder.go new file mode 100644 index 000000000..e15c1a688 --- /dev/null +++ b/a2a/pkg/handler/builder.go @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package handler + +import ( + "fmt" + + "seata-go-ai-a2a/pkg/types" +) + +// AgentCardBuilder builder for creating AgentCard with fluent API +type AgentCardBuilder struct { + card *types.AgentCard +} + +// NewAgentCardBuilder creates a new AgentCard builder +func NewAgentCardBuilder() *AgentCardBuilder { + return &AgentCardBuilder{ + card: &types.AgentCard{ + ProtocolVersion: "2024-01-01", + Capabilities: &types.AgentCapabilities{}, + Skills: []*types.AgentSkill{}, + SecuritySchemes: make(map[string]types.SecurityScheme), + Security: []*types.Security{}, + }, + } +} + +// WithBasicInfo sets basic information for the agent +func (b *AgentCardBuilder) WithBasicInfo(name, description, version string) *AgentCardBuilder { + b.card.Name = name + b.card.Description = description + b.card.Version = version + return b +} + +// WithURL sets the agent URL +func (b *AgentCardBuilder) WithURL(url string) *AgentCardBuilder { + b.card.URL = url + return b +} + +// WithProvider sets the provider information +func (b *AgentCardBuilder) WithProvider(org, url string) *AgentCardBuilder { + b.card.Provider = &types.AgentProvider{ + Organization: org, + URL: url, + } + return b +} + +// WithCapabilities sets agent capabilities +func (b *AgentCardBuilder) WithCapabilities(streaming, pushNotifications bool) *AgentCardBuilder { + b.card.Capabilities.Streaming = streaming + b.card.Capabilities.PushNotifications = pushNotifications + return b +} + +// WithDefaultInputModes sets default input modes +func (b *AgentCardBuilder) WithDefaultInputModes(modes []string) *AgentCardBuilder { + b.card.DefaultInputModes = modes + return b +} + +// WithDefaultOutputModes sets default output modes +func (b *AgentCardBuilder) WithDefaultOutputModes(modes []string) *AgentCardBuilder { + b.card.DefaultOutputModes = modes + return b +} + +// WithSkill adds a skill to the agent +func (b *AgentCardBuilder) WithSkill(id, name, description string, inputModes, outputModes []string) *AgentCardBuilder { + skill := &types.AgentSkill{ + ID: id, + Name: name, + Description: description, + InputModes: inputModes, + OutputModes: outputModes, + } + + b.card.Skills = append(b.card.Skills, skill) + return b +} + +// WithDetailedSkill adds a skill with detailed configuration +func (b *AgentCardBuilder) WithDetailedSkill(skill *types.AgentSkill) *AgentCardBuilder { + b.card.Skills = append(b.card.Skills, skill) + return b +} + +// WithDocumentationURL sets documentation URL +func (b *AgentCardBuilder) WithDocumentationURL(url string) *AgentCardBuilder { + b.card.DocumentationURL = url + return b +} + +// WithIconURL sets icon URL +func (b *AgentCardBuilder) WithIconURL(url string) *AgentCardBuilder { + b.card.IconURL = url + return b +} + +// WithAPIKeySecurity adds API key security scheme +func (b *AgentCardBuilder) WithAPIKeySecurity(name, description, location, paramName string) *AgentCardBuilder { + scheme := &types.APIKeySecurityScheme{ + Description: description, + Location: location, + Name: paramName, + } + + b.card.SecuritySchemes[name] = scheme + return b +} + +// WithHTTPAuthSecurity adds HTTP authentication security scheme +func (b *AgentCardBuilder) WithHTTPAuthSecurity(name, description, scheme, bearerFormat string) *AgentCardBuilder { + securityScheme := &types.HTTPAuthSecurityScheme{ + Description: description, + Scheme: scheme, + BearerFormat: bearerFormat, + } + + b.card.SecuritySchemes[name] = securityScheme + return b +} + +// WithSecurityRequirement adds a security requirement +func (b *AgentCardBuilder) WithSecurityRequirement(schemeName string, scopes []string) *AgentCardBuilder { + security := &types.Security{ + Schemes: map[string][]string{ + schemeName: scopes, + }, + } + + b.card.Security = append(b.card.Security, security) + return b +} + +// WithExtension adds an agent extension +func (b *AgentCardBuilder) WithExtension(uri, description string, required bool, params map[string]any) *AgentCardBuilder { + extension := &types.AgentExtension{ + URI: uri, + Description: description, + Required: required, + Params: params, + } + + b.card.Capabilities.Extensions = append(b.card.Capabilities.Extensions, extension) + return b +} + +// WithPreferredTransport sets preferred transport +func (b *AgentCardBuilder) WithPreferredTransport(transport string) *AgentCardBuilder { + b.card.PreferredTransport = transport + return b +} + +// WithAdditionalInterface adds additional interface +func (b *AgentCardBuilder) WithAdditionalInterface(url, transport string) *AgentCardBuilder { + interface_ := &types.AgentInterface{ + URL: url, + Transport: transport, + } + + b.card.AdditionalInterfaces = append(b.card.AdditionalInterfaces, interface_) + return b +} + +// WithSignature adds a signature to the agent card +func (b *AgentCardBuilder) WithSignature(protected, signature string, header map[string]any) *AgentCardBuilder { + sig := &types.AgentCardSignature{ + Protected: protected, + Signature: signature, + Header: header, + } + + b.card.Signatures = append(b.card.Signatures, sig) + return b +} + +// Build creates the final AgentCard +func (b *AgentCardBuilder) Build() *types.AgentCard { + return b.card +} + +// BuildWithValidation creates the final AgentCard with validation +func (b *AgentCardBuilder) BuildWithValidation() (*types.AgentCard, error) { + if b.card.Name == "" { + return nil, fmt.Errorf("agent name is required") + } + + if b.card.ProtocolVersion == "" { + return nil, fmt.Errorf("protocol version is required") + } + + return b.card, nil +} diff --git a/a2a/pkg/handler/interfaces.go b/a2a/pkg/handler/interfaces.go new file mode 100644 index 000000000..1efd64291 --- /dev/null +++ b/a2a/pkg/handler/interfaces.go @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package handler + +import ( + "context" + "time" + + "seata-go-ai-a2a/pkg/types" +) + +// MessageHandler message processing interface +// Developers implement this interface to define agent's message processing logic +type MessageHandler interface { + // HandleMessage core method for processing messages + HandleMessage(ctx context.Context, req *MessageRequest) (*MessageResponse, error) + + // HandleCancel cancel task processing (optional implementation) + HandleCancel(ctx context.Context, taskID string) error +} + +// MessageRequest encapsulates all information for processing request +type MessageRequest struct { + // Original message + Message *types.Message + + // Task context information + TaskID string + ContextID string + + // Processing configuration + Configuration *MessageConfiguration + + // Historical messages + History []*types.Message + + // Request metadata + Metadata map[string]any + + // Task operations handler + TaskOperations TaskOperations +} + +// MessageConfiguration message processing configuration +type MessageConfiguration struct { + // Blocking mode - true: wait for completion; false: return immediately + Blocking bool + + // Streaming mode - true: return event stream; false: return single result + Streaming bool + + // Historical message length limit + HistoryLength int + + // Accepted output modes + AcceptedOutputModes []string + + // Push notification configuration + PushNotification *types.PushNotificationConfig + + // Request timeout + Timeout time.Duration +} + +// MessageResponse processing result +type MessageResponse struct { + // Processing mode + Mode ResponseMode + + // Direct result for non-streaming mode + Result *ResponseResult + + // Event stream for streaming mode + EventStream <-chan types.StreamResponse + + // Error information + Error error +} + +// ResponseMode response mode +type ResponseMode int + +const ( + ResponseModeMessage ResponseMode = iota // Direct message reply + ResponseModeTask // Task reply + ResponseModeStream // Streaming reply +) + +// ResponseResult response result +type ResponseResult struct { + // Result type - Message or Task + Data interface{} + + // Task state update (only when Data is Task) + TaskState types.TaskState + + // Output artifacts + Artifacts []*types.Artifact +} + +// TaskOperations task operations interface +// Provides task management capabilities for MessageHandler +type TaskOperations interface { + // UpdateTaskState update task state + UpdateTaskState(state types.TaskState, message *types.Message) error + + // AddArtifact add output artifact + AddArtifact(artifact *types.Artifact) error + + // GetTaskHistory get task history + GetTaskHistory() ([]*types.Message, error) + + // GetTaskMetadata get task metadata + GetTaskMetadata() (map[string]any, error) + + // SendEvent send event to stream (streaming mode) + SendEvent(event types.StreamResponse) error + + // GetTaskID get current task ID + GetTaskID() string + + // GetContextID get current context ID + GetContextID() string +} + +// AgentCardProvider agent card provider interface +// Developers implement this interface to provide agent's capability description +type AgentCardProvider interface { + // GetAgentCard get agent card + GetAgentCard(ctx context.Context) (*types.AgentCard, error) + + // GetAgentCardForUser get user-specific agent card (supports extended card after authentication) + GetAgentCardForUser(ctx context.Context, userInfo *UserInfo) (*types.AgentCard, error) +} + +// UserInfo user information +type UserInfo struct { + UserID string + Scopes []string + Metadata map[string]any +} diff --git a/a2a/pkg/handler/providers.go b/a2a/pkg/handler/providers.go new file mode 100644 index 000000000..29ac51ca3 --- /dev/null +++ b/a2a/pkg/handler/providers.go @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package handler + +import ( + "context" + + "seata-go-ai-a2a/pkg/types" +) + +// SimpleAgentCardProvider simple static card provider +type SimpleAgentCardProvider struct { + card *types.AgentCard +} + +// NewSimpleAgentCardProvider creates a new simple agent card provider with static card +func NewSimpleAgentCardProvider(card *types.AgentCard) *SimpleAgentCardProvider { + return &SimpleAgentCardProvider{card: card} +} + +// GetAgentCard get agent card +func (p *SimpleAgentCardProvider) GetAgentCard(ctx context.Context) (*types.AgentCard, error) { + return p.card, nil +} + +// GetAgentCardForUser get user-specific agent card (returns same card by default, can be overridden) +func (p *SimpleAgentCardProvider) GetAgentCardForUser(ctx context.Context, userInfo *UserInfo) (*types.AgentCard, error) { + return p.card, nil // Default returns same card, can be customized for different users +} + +// DynamicAgentCardProvider dynamic agent card provider that supports runtime customization +type DynamicAgentCardProvider struct { + baseCard *types.AgentCard + cardGenerator func(ctx context.Context, userInfo *UserInfo) (*types.AgentCard, error) + userCardCache map[string]*types.AgentCard +} + +// NewDynamicAgentCardProvider creates a new dynamic agent card provider +func NewDynamicAgentCardProvider(baseCard *types.AgentCard, generator func(ctx context.Context, userInfo *UserInfo) (*types.AgentCard, error)) *DynamicAgentCardProvider { + return &DynamicAgentCardProvider{ + baseCard: baseCard, + cardGenerator: generator, + userCardCache: make(map[string]*types.AgentCard), + } +} + +// GetAgentCard get base agent card +func (p *DynamicAgentCardProvider) GetAgentCard(ctx context.Context) (*types.AgentCard, error) { + return p.baseCard, nil +} + +// GetAgentCardForUser get user-specific agent card with customization +func (p *DynamicAgentCardProvider) GetAgentCardForUser(ctx context.Context, userInfo *UserInfo) (*types.AgentCard, error) { + if userInfo == nil { + return p.baseCard, nil + } + + // Check cache first + if cachedCard, exists := p.userCardCache[userInfo.UserID]; exists { + return cachedCard, nil + } + + // Generate customized card + if p.cardGenerator != nil { + customCard, err := p.cardGenerator(ctx, userInfo) + if err != nil { + return nil, err + } + + // Cache the result + p.userCardCache[userInfo.UserID] = customCard + return customCard, nil + } + + return p.baseCard, nil +} diff --git a/a2a/pkg/handler/task_operations.go b/a2a/pkg/handler/task_operations.go new file mode 100644 index 000000000..b89469452 --- /dev/null +++ b/a2a/pkg/handler/task_operations.go @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package handler + +import ( + "fmt" + "sync" + + "seata-go-ai-a2a/pkg/types" +) + +// taskOperationsImpl concrete implementation of TaskOperations interface +// It encapsulates interaction with TaskManager +type taskOperationsImpl struct { + taskManager types.TaskManagerInterface + taskID string + contextID string + + // Event channel for streaming mode + eventChan chan types.StreamResponse + mu sync.RWMutex +} + +// NewTaskOperations creates TaskOperations implementation +func NewTaskOperations(taskManager types.TaskManagerInterface, taskID, contextID string, eventChan chan types.StreamResponse) TaskOperations { + return &taskOperationsImpl{ + taskManager: taskManager, + taskID: taskID, + contextID: contextID, + eventChan: eventChan, + } +} + +// UpdateTaskState update task state +func (t *taskOperationsImpl) UpdateTaskState(state types.TaskState, message *types.Message) error { + if t.taskManager == nil { + return fmt.Errorf("task manager not initialized") + } + + // Update task state + err := t.taskManager.UpdateTaskStatus(nil, t.taskID, state, message) + if err != nil { + return fmt.Errorf("failed to update task state: %w", err) + } + + // If in streaming mode, send status update event + if t.eventChan != nil { + event := &types.TaskStatusUpdateEvent{ + TaskID: t.taskID, + ContextID: t.contextID, + Status: &types.TaskStatus{ + State: state, + Update: message, + }, + Final: isTerminalState(state), + } + + return t.SendEvent(&types.StatusUpdateStreamResponse{ + StatusUpdate: event, + }) + } + + return nil +} + +// AddArtifact add output artifact +func (t *taskOperationsImpl) AddArtifact(artifact *types.Artifact) error { + if t.taskManager == nil { + return fmt.Errorf("task manager not initialized") + } + + // Get task and add artifact + task, err := t.taskManager.GetTask(nil, t.taskID) + if err != nil { + return fmt.Errorf("failed to get task: %w", err) + } + + if task == nil { + return fmt.Errorf("task not found: %s", t.taskID) + } + + // Use AddArtifact method from TaskManagerInterface + err = t.taskManager.AddArtifact(nil, t.taskID, artifact, false, true) + if err != nil { + return fmt.Errorf("failed to add artifact: %w", err) + } + + // If in streaming mode, send artifact update event + if t.eventChan != nil { + event := &types.TaskArtifactUpdateEvent{ + TaskID: t.taskID, + ContextID: t.contextID, + Artifact: artifact, + Append: false, + LastChunk: true, + } + + return t.SendEvent(&types.ArtifactUpdateStreamResponse{ + ArtifactUpdate: event, + }) + } + + return nil +} + +// GetTaskHistory get task history +func (t *taskOperationsImpl) GetTaskHistory() ([]*types.Message, error) { + if t.taskManager == nil { + return nil, fmt.Errorf("task manager not initialized") + } + + // Get task details + task, err := t.taskManager.GetTask(nil, t.taskID) + if err != nil { + return nil, fmt.Errorf("failed to get task: %w", err) + } + + if task == nil { + return nil, fmt.Errorf("task not found: %s", t.taskID) + } + + return task.History, nil +} + +// GetTaskMetadata get task metadata +func (t *taskOperationsImpl) GetTaskMetadata() (map[string]any, error) { + if t.taskManager == nil { + return nil, fmt.Errorf("task manager not initialized") + } + + // Get task details + task, err := t.taskManager.GetTask(nil, t.taskID) + if err != nil { + return nil, fmt.Errorf("failed to get task: %w", err) + } + + if task == nil { + return nil, fmt.Errorf("task not found: %s", t.taskID) + } + + // Return metadata as map + if task.Metadata != nil { + return task.Metadata, nil + } + + return make(map[string]any), nil +} + +// SendEvent send event to stream (streaming mode) +func (t *taskOperationsImpl) SendEvent(event types.StreamResponse) error { + if t.eventChan == nil { + return fmt.Errorf("streaming not enabled") + } + + t.mu.RLock() + defer t.mu.RUnlock() + + select { + case t.eventChan <- event: + return nil + default: + return fmt.Errorf("event channel is full or closed") + } +} + +// GetTaskID get current task ID +func (t *taskOperationsImpl) GetTaskID() string { + return t.taskID +} + +// GetContextID get current context ID +func (t *taskOperationsImpl) GetContextID() string { + return t.contextID +} + +// isTerminalState check if state is terminal +func isTerminalState(state types.TaskState) bool { + switch state { + case types.TaskStateCompleted, + types.TaskStateFailed, + types.TaskStateCancelled, + types.TaskStateRejected: + return true + default: + return false + } +} + +// CloseEventChannel close event channel (used when streaming mode ends) +func (t *taskOperationsImpl) CloseEventChannel() { + t.mu.Lock() + defer t.mu.Unlock() + + if t.eventChan != nil { + close(t.eventChan) + t.eventChan = nil + } +} diff --git a/a2a/pkg/health/health.go b/a2a/pkg/health/health.go new file mode 100644 index 000000000..4be2a0be6 --- /dev/null +++ b/a2a/pkg/health/health.go @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package health + +import ( + "context" + "net/http" + "time" + + "seata-go-ai-a2a/pkg/metrics" + + "github.com/heptiolabs/healthcheck" +) + +// Manager wraps the healthcheck library and integrates with our metrics +type Manager struct { + handler healthcheck.Handler + metrics *metrics.PrometheusMetrics +} + +// NewManager creates a new health check manager using heptiolabs/healthcheck +func NewManager(metrics *metrics.PrometheusMetrics) *Manager { + handler := healthcheck.NewHandler() + + return &Manager{ + handler: handler, + metrics: metrics, + } +} + +// AddDatabaseCheck adds a database health check +func (m *Manager) AddDatabaseCheck(name string, db interface { + HealthCheck(ctx context.Context) error +}) { + m.handler.AddReadinessCheck(name, func() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err := db.HealthCheck(ctx) + + // Record metrics + if m.metrics != nil { + m.metrics.RecordHealthCheck(name, err == nil) + } + + return err + }) +} + +// AddLivenessCheck adds a custom liveness check +func (m *Manager) AddLivenessCheck(name string, check healthcheck.Check) { + m.handler.AddLivenessCheck(name, func() error { + err := check() + + // Record metrics + if m.metrics != nil { + m.metrics.RecordHealthCheck(name+"_liveness", err == nil) + } + + return err + }) +} + +// AddReadinessCheck adds a custom readiness check +func (m *Manager) AddReadinessCheck(name string, check healthcheck.Check) { + m.handler.AddReadinessCheck(name, func() error { + err := check() + + // Record metrics + if m.metrics != nil { + m.metrics.RecordHealthCheck(name+"_readiness", err == nil) + } + + return err + }) +} + +// GetHandler returns the HTTP handler for health checks +// Provides these endpoints: +// GET /live - liveness probe +// GET /ready - readiness probe +func (m *Manager) GetHandler() http.Handler { + return m.handler +} + +// GetHTTPHandler returns an extended HTTP handler with additional endpoints +func (m *Manager) GetHTTPHandler() http.Handler { + mux := http.NewServeMux() + + // Mount the healthcheck handler + mux.HandleFunc("/live", m.handler.LiveEndpoint) + mux.HandleFunc("/ready", m.handler.ReadyEndpoint) + + // Add a combined health endpoint + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status":"ok"}`)) + }) + + return mux +} diff --git a/a2a/pkg/metrics/prometheus.go b/a2a/pkg/metrics/prometheus.go new file mode 100644 index 000000000..a195325f3 --- /dev/null +++ b/a2a/pkg/metrics/prometheus.go @@ -0,0 +1,393 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package metrics + +import ( + "fmt" + "net/http" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// PrometheusMetrics wraps Prometheus metrics for A2A system +type PrometheusMetrics struct { + registry *prometheus.Registry + + // Task metrics + tasksCreatedTotal *prometheus.CounterVec + tasksCompletedTotal *prometheus.CounterVec + tasksFailedTotal *prometheus.CounterVec + tasksCancelledTotal *prometheus.CounterVec + tasksActiveGauge *prometheus.GaugeVec + taskDurationHistogram *prometheus.HistogramVec + + // Authentication metrics + authAttemptsTotal *prometheus.CounterVec + authFailuresTotal *prometheus.CounterVec + authDurationHist *prometheus.HistogramVec + + // JWKS metrics + jwksFetchTotal *prometheus.CounterVec + jwksCacheHitTotal *prometheus.CounterVec + jwksCacheMissTotal *prometheus.CounterVec + + // gRPC metrics + grpcRequestsTotal *prometheus.CounterVec + grpcRequestDuration *prometheus.HistogramVec + grpcActiveStreams *prometheus.GaugeVec + + // Health metrics + healthCheckStatus *prometheus.GaugeVec + healthCheckTotal *prometheus.CounterVec + + // System metrics + memoryUsageBytes *prometheus.GaugeVec + goroutineCount prometheus.Gauge +} + +// NewPrometheusMetrics creates a new Prometheus metrics instance +func NewPrometheusMetrics(subsystem string) *PrometheusMetrics { + if subsystem == "" { + subsystem = "a2a" + } + + registry := prometheus.NewRegistry() + + // Add Go runtime metrics + registry.MustRegister(prometheus.NewGoCollector()) + registry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})) + + pm := &PrometheusMetrics{ + registry: registry, + + // Task metrics + tasksCreatedTotal: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "tasks_created_total", + Help: "Total number of tasks created", + }, + []string{"context_id"}, + ), + + tasksCompletedTotal: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "tasks_completed_total", + Help: "Total number of tasks completed", + }, + []string{"context_id"}, + ), + + tasksFailedTotal: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "tasks_failed_total", + Help: "Total number of tasks failed", + }, + []string{"context_id", "reason"}, + ), + + tasksCancelledTotal: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "tasks_cancelled_total", + Help: "Total number of tasks cancelled", + }, + []string{"context_id"}, + ), + + tasksActiveGauge: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Subsystem: subsystem, + Name: "tasks_active", + Help: "Number of currently active tasks", + }, + []string{"state"}, + ), + + taskDurationHistogram: prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Subsystem: subsystem, + Name: "task_duration_seconds", + Help: "Task execution duration in seconds", + Buckets: prometheus.ExponentialBuckets(0.001, 2, 15), // 1ms to ~32s + }, + []string{"context_id", "state"}, + ), + + // Authentication metrics + authAttemptsTotal: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "auth_attempts_total", + Help: "Total number of authentication attempts", + }, + []string{"method", "result"}, + ), + + authFailuresTotal: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "auth_failures_total", + Help: "Total number of authentication failures", + }, + []string{"method", "reason"}, + ), + + authDurationHist: prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Subsystem: subsystem, + Name: "auth_duration_seconds", + Help: "Authentication duration in seconds", + Buckets: prometheus.DefBuckets, + }, + []string{"method"}, + ), + + // JWKS metrics + jwksFetchTotal: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "jwks_fetch_total", + Help: "Total number of JWKS fetch attempts", + }, + []string{"url", "result"}, + ), + + jwksCacheHitTotal: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "jwks_cache_hit_total", + Help: "Total number of JWKS cache hits", + }, + []string{"url"}, + ), + + jwksCacheMissTotal: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "jwks_cache_miss_total", + Help: "Total number of JWKS cache misses", + }, + []string{"url"}, + ), + + // gRPC metrics + grpcRequestsTotal: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "grpc_requests_total", + Help: "Total number of gRPC requests", + }, + []string{"method", "code"}, + ), + + grpcRequestDuration: prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Subsystem: subsystem, + Name: "grpc_request_duration_seconds", + Help: "gRPC request duration in seconds", + Buckets: prometheus.DefBuckets, + }, + []string{"method"}, + ), + + grpcActiveStreams: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Subsystem: subsystem, + Name: "grpc_active_streams", + Help: "Number of active gRPC streams", + }, + []string{"method"}, + ), + + // Health metrics + healthCheckStatus: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Subsystem: subsystem, + Name: "health_check_status", + Help: "Health check status (1 = healthy, 0 = unhealthy)", + }, + []string{"component"}, + ), + + healthCheckTotal: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "health_checks_total", + Help: "Total number of health checks performed", + }, + []string{"component", "result"}, + ), + + // System metrics + memoryUsageBytes: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Subsystem: subsystem, + Name: "memory_usage_bytes", + Help: "Memory usage in bytes", + }, + []string{"type"}, + ), + + goroutineCount: prometheus.NewGauge( + prometheus.GaugeOpts{ + Subsystem: subsystem, + Name: "goroutines", + Help: "Number of goroutines", + }, + ), + } + + // Register all metrics + registry.MustRegister( + pm.tasksCreatedTotal, + pm.tasksCompletedTotal, + pm.tasksFailedTotal, + pm.tasksCancelledTotal, + pm.tasksActiveGauge, + pm.taskDurationHistogram, + pm.authAttemptsTotal, + pm.authFailuresTotal, + pm.authDurationHist, + pm.jwksFetchTotal, + pm.jwksCacheHitTotal, + pm.jwksCacheMissTotal, + pm.grpcRequestsTotal, + pm.grpcRequestDuration, + pm.grpcActiveStreams, + pm.healthCheckStatus, + pm.healthCheckTotal, + pm.memoryUsageBytes, + pm.goroutineCount, + ) + + return pm +} + +// Task metrics methods +func (pm *PrometheusMetrics) RecordTaskCreated(contextID string) { + pm.tasksCreatedTotal.WithLabelValues(contextID).Inc() +} + +func (pm *PrometheusMetrics) RecordTaskCompleted(contextID string, duration time.Duration) { + pm.tasksCompletedTotal.WithLabelValues(contextID).Inc() + pm.taskDurationHistogram.WithLabelValues(contextID, "completed").Observe(duration.Seconds()) +} + +func (pm *PrometheusMetrics) RecordTaskFailed(contextID, reason string, duration time.Duration) { + pm.tasksFailedTotal.WithLabelValues(contextID, reason).Inc() + pm.taskDurationHistogram.WithLabelValues(contextID, "failed").Observe(duration.Seconds()) +} + +func (pm *PrometheusMetrics) RecordTaskCancelled(contextID string, duration time.Duration) { + pm.tasksCancelledTotal.WithLabelValues(contextID).Inc() + pm.taskDurationHistogram.WithLabelValues(contextID, "cancelled").Observe(duration.Seconds()) +} + +func (pm *PrometheusMetrics) SetActiveTasksCount(state string, count int) { + pm.tasksActiveGauge.WithLabelValues(state).Set(float64(count)) +} + +// Authentication metrics methods +func (pm *PrometheusMetrics) RecordAuthAttempt(method, result string, duration time.Duration) { + pm.authAttemptsTotal.WithLabelValues(method, result).Inc() + pm.authDurationHist.WithLabelValues(method).Observe(duration.Seconds()) +} + +func (pm *PrometheusMetrics) RecordAuthFailure(method, reason string) { + pm.authFailuresTotal.WithLabelValues(method, reason).Inc() +} + +// JWKS metrics methods +func (pm *PrometheusMetrics) RecordJWKSFetch(url, result string) { + pm.jwksFetchTotal.WithLabelValues(url, result).Inc() +} + +func (pm *PrometheusMetrics) RecordJWKSCacheHit(url string) { + pm.jwksCacheHitTotal.WithLabelValues(url).Inc() +} + +func (pm *PrometheusMetrics) RecordJWKSCacheMiss(url string) { + pm.jwksCacheMissTotal.WithLabelValues(url).Inc() +} + +// gRPC metrics methods +func (pm *PrometheusMetrics) RecordGRPCRequest(method, code string, duration time.Duration) { + pm.grpcRequestsTotal.WithLabelValues(method, code).Inc() + pm.grpcRequestDuration.WithLabelValues(method).Observe(duration.Seconds()) +} + +func (pm *PrometheusMetrics) SetActiveStreamsCount(method string, count int) { + pm.grpcActiveStreams.WithLabelValues(method).Set(float64(count)) +} + +// Health check methods +func (pm *PrometheusMetrics) RecordHealthCheck(component string, healthy bool) { + status := 0.0 + result := "unhealthy" + if healthy { + status = 1.0 + result = "healthy" + } + + pm.healthCheckStatus.WithLabelValues(component).Set(status) + pm.healthCheckTotal.WithLabelValues(component, result).Inc() +} + +// System metrics methods +func (pm *PrometheusMetrics) SetMemoryUsage(memType string, bytes int64) { + pm.memoryUsageBytes.WithLabelValues(memType).Set(float64(bytes)) +} + +func (pm *PrometheusMetrics) SetGoroutineCount(count int) { + pm.goroutineCount.Set(float64(count)) +} + +// GetRegistry returns the Prometheus registry +func (pm *PrometheusMetrics) GetRegistry() *prometheus.Registry { + return pm.registry +} + +// GetHandler returns an HTTP handler for metrics endpoint +func (pm *PrometheusMetrics) GetHandler() http.Handler { + return promhttp.HandlerFor(pm.registry, promhttp.HandlerOpts{ + EnableOpenMetrics: true, + }) +} + +// StartMetricsServer starts a dedicated HTTP server for metrics +func (pm *PrometheusMetrics) StartMetricsServer(port int) error { + mux := http.NewServeMux() + mux.Handle("/metrics", pm.GetHandler()) + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{"status":"ok","timestamp":"%s"}`, time.Now().Format(time.RFC3339)) + }) + + addr := fmt.Sprintf(":%d", port) + server := &http.Server{ + Addr: addr, + Handler: mux, + } + + return server.ListenAndServe() +} diff --git a/a2a/pkg/proto/v1/a2a.pb.go b/a2a/pkg/proto/v1/a2a.pb.go new file mode 100644 index 000000000..2bc2f2d9e --- /dev/null +++ b/a2a/pkg/proto/v1/a2a.pb.go @@ -0,0 +1,4225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +// Older protoc compilers don't understand edition yet. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.9 +// protoc v3.21.12 +// source: a2a.proto + +package v1 + +import ( + reflect "reflect" + sync "sync" + unsafe "unsafe" + + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + structpb "google.golang.org/protobuf/types/known/structpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// The set of states a Task can be in. +type TaskState int32 + +const ( + TaskState_TASK_STATE_UNSPECIFIED TaskState = 0 + // Represents the status that acknowledges a task is created + TaskState_TASK_STATE_SUBMITTED TaskState = 1 + // Represents the status that a task is actively being processed + TaskState_TASK_STATE_WORKING TaskState = 2 + // Represents the status a task is finished. This is a terminal state + TaskState_TASK_STATE_COMPLETED TaskState = 3 + // Represents the status a task is done but failed. This is a terminal state + TaskState_TASK_STATE_FAILED TaskState = 4 + // Represents the status a task was cancelled before it finished. + // This is a terminal state. + TaskState_TASK_STATE_CANCELLED TaskState = 5 + // Represents the status that the task requires information to complete. + // This is an interrupted state. + TaskState_TASK_STATE_INPUT_REQUIRED TaskState = 6 + // Represents the status that the agent has decided to not perform the task. + // This may be done during initial task creation or later once an agent + // has determined it can't or won't proceed. This is a terminal state. + TaskState_TASK_STATE_REJECTED TaskState = 7 + // Represents the state that some authentication is needed from the upstream + // client. Authentication is expected to come out-of-band thus this is not + // an interrupted or terminal state. + TaskState_TASK_STATE_AUTH_REQUIRED TaskState = 8 +) + +// Enum value maps for TaskState. +var ( + TaskState_name = map[int32]string{ + 0: "TASK_STATE_UNSPECIFIED", + 1: "TASK_STATE_SUBMITTED", + 2: "TASK_STATE_WORKING", + 3: "TASK_STATE_COMPLETED", + 4: "TASK_STATE_FAILED", + 5: "TASK_STATE_CANCELLED", + 6: "TASK_STATE_INPUT_REQUIRED", + 7: "TASK_STATE_REJECTED", + 8: "TASK_STATE_AUTH_REQUIRED", + } + TaskState_value = map[string]int32{ + "TASK_STATE_UNSPECIFIED": 0, + "TASK_STATE_SUBMITTED": 1, + "TASK_STATE_WORKING": 2, + "TASK_STATE_COMPLETED": 3, + "TASK_STATE_FAILED": 4, + "TASK_STATE_CANCELLED": 5, + "TASK_STATE_INPUT_REQUIRED": 6, + "TASK_STATE_REJECTED": 7, + "TASK_STATE_AUTH_REQUIRED": 8, + } +) + +func (x TaskState) Enum() *TaskState { + p := new(TaskState) + *p = x + return p +} + +func (x TaskState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TaskState) Descriptor() protoreflect.EnumDescriptor { + return file_a2a_proto_enumTypes[0].Descriptor() +} + +func (TaskState) Type() protoreflect.EnumType { + return &file_a2a_proto_enumTypes[0] +} + +func (x TaskState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TaskState.Descriptor instead. +func (TaskState) EnumDescriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{0} +} + +type Role int32 + +const ( + Role_ROLE_UNSPECIFIED Role = 0 + // USER role refers to communication from the client to the server. + Role_ROLE_USER Role = 1 + // AGENT role refers to communication from the server to the client. + Role_ROLE_AGENT Role = 2 +) + +// Enum value maps for Role. +var ( + Role_name = map[int32]string{ + 0: "ROLE_UNSPECIFIED", + 1: "ROLE_USER", + 2: "ROLE_AGENT", + } + Role_value = map[string]int32{ + "ROLE_UNSPECIFIED": 0, + "ROLE_USER": 1, + "ROLE_AGENT": 2, + } +) + +func (x Role) Enum() *Role { + p := new(Role) + *p = x + return p +} + +func (x Role) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Role) Descriptor() protoreflect.EnumDescriptor { + return file_a2a_proto_enumTypes[1].Descriptor() +} + +func (Role) Type() protoreflect.EnumType { + return &file_a2a_proto_enumTypes[1] +} + +func (x Role) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Role.Descriptor instead. +func (Role) EnumDescriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{1} +} + +// Configuration of a send message request. +type SendMessageConfiguration struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The output modes that the agent is expected to respond with. + AcceptedOutputModes []string `protobuf:"bytes,1,rep,name=accepted_output_modes,json=acceptedOutputModes,proto3" json:"accepted_output_modes,omitempty"` + // A configuration of a webhook that can be used to receive updates + PushNotification *PushNotificationConfig `protobuf:"bytes,2,opt,name=push_notification,json=pushNotification,proto3" json:"push_notification,omitempty"` + // The maximum number of messages to include in the history. if 0, the + // history will be unlimited. + HistoryLength int32 `protobuf:"varint,3,opt,name=history_length,json=historyLength,proto3" json:"history_length,omitempty"` + // If true, the message will be blocking until the task is completed. If + // false, the message will be non-blocking and the task will be returned + // immediately. It is the caller's responsibility to check for any task + // updates. + Blocking bool `protobuf:"varint,4,opt,name=blocking,proto3" json:"blocking,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendMessageConfiguration) Reset() { + *x = SendMessageConfiguration{} + mi := &file_a2a_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendMessageConfiguration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendMessageConfiguration) ProtoMessage() {} + +func (x *SendMessageConfiguration) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendMessageConfiguration.ProtoReflect.Descriptor instead. +func (*SendMessageConfiguration) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{0} +} + +func (x *SendMessageConfiguration) GetAcceptedOutputModes() []string { + if x != nil { + return x.AcceptedOutputModes + } + return nil +} + +func (x *SendMessageConfiguration) GetPushNotification() *PushNotificationConfig { + if x != nil { + return x.PushNotification + } + return nil +} + +func (x *SendMessageConfiguration) GetHistoryLength() int32 { + if x != nil { + return x.HistoryLength + } + return 0 +} + +func (x *SendMessageConfiguration) GetBlocking() bool { + if x != nil { + return x.Blocking + } + return false +} + +// Task is the core unit of action for A2A. It has a current status +// and when results are created for the task they are stored in the +// artifact. If there are multiple turns for a task, these are stored in +// history. +type Task struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Unique identifier (e.g. UUID) for the task, generated by the server for a + // new task. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Unique identifier (e.g. UUID) for the contextual collection of interactions + // (tasks and messages). Created by the A2A server. + ContextId string `protobuf:"bytes,2,opt,name=context_id,json=contextId,proto3" json:"context_id,omitempty"` + // The current status of a Task, including state and a message. + Status *TaskStatus `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + // A set of output artifacts for a Task. + Artifacts []*Artifact `protobuf:"bytes,4,rep,name=artifacts,proto3" json:"artifacts,omitempty"` + // protolint:disable REPEATED_FIELD_NAMES_PLURALIZED + // The history of interactions from a task. + History []*Message `protobuf:"bytes,5,rep,name=history,proto3" json:"history,omitempty"` + // protolint:enable REPEATED_FIELD_NAMES_PLURALIZED + // Type identifier for A2A protocol, should be "task" + Kind string `protobuf:"bytes,6,opt,name=kind,proto3" json:"kind,omitempty"` + // A key/value object to store custom metadata about a task. + Metadata *structpb.Struct `protobuf:"bytes,7,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Task) Reset() { + *x = Task{} + mi := &file_a2a_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Task) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Task) ProtoMessage() {} + +func (x *Task) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Task.ProtoReflect.Descriptor instead. +func (*Task) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{1} +} + +func (x *Task) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Task) GetContextId() string { + if x != nil { + return x.ContextId + } + return "" +} + +func (x *Task) GetStatus() *TaskStatus { + if x != nil { + return x.Status + } + return nil +} + +func (x *Task) GetArtifacts() []*Artifact { + if x != nil { + return x.Artifacts + } + return nil +} + +func (x *Task) GetHistory() []*Message { + if x != nil { + return x.History + } + return nil +} + +func (x *Task) GetKind() string { + if x != nil { + return x.Kind + } + return "" +} + +func (x *Task) GetMetadata() *structpb.Struct { + if x != nil { + return x.Metadata + } + return nil +} + +// A container for the status of a task +type TaskStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The current state of this task + State TaskState `protobuf:"varint,1,opt,name=state,proto3,enum=a2a.v1.TaskState" json:"state,omitempty"` + // A message associated with the status. + Update *Message `protobuf:"bytes,2,opt,name=update,json=message,proto3" json:"update,omitempty"` + // Timestamp when the status was recorded. + // Example: "2023-10-27T10:00:00Z" + Timestamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaskStatus) Reset() { + *x = TaskStatus{} + mi := &file_a2a_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaskStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaskStatus) ProtoMessage() {} + +func (x *TaskStatus) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaskStatus.ProtoReflect.Descriptor instead. +func (*TaskStatus) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{2} +} + +func (x *TaskStatus) GetState() TaskState { + if x != nil { + return x.State + } + return TaskState_TASK_STATE_UNSPECIFIED +} + +func (x *TaskStatus) GetUpdate() *Message { + if x != nil { + return x.Update + } + return nil +} + +func (x *TaskStatus) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +// Part represents a container for a section of communication content. +// Parts can be purely textual, some sort of file (image, video, etc) or +// a structured data blob (i.e. JSON). +type Part struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Part: + // + // *Part_Text + // *Part_File + // *Part_Data + Part isPart_Part `protobuf_oneof:"part"` + // Optional metadata associated with this part. + Metadata *structpb.Struct `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Part) Reset() { + *x = Part{} + mi := &file_a2a_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Part) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Part) ProtoMessage() {} + +func (x *Part) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Part.ProtoReflect.Descriptor instead. +func (*Part) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{3} +} + +func (x *Part) GetPart() isPart_Part { + if x != nil { + return x.Part + } + return nil +} + +func (x *Part) GetText() string { + if x != nil { + if x, ok := x.Part.(*Part_Text); ok { + return x.Text + } + } + return "" +} + +func (x *Part) GetFile() *FilePart { + if x != nil { + if x, ok := x.Part.(*Part_File); ok { + return x.File + } + } + return nil +} + +func (x *Part) GetData() *DataPart { + if x != nil { + if x, ok := x.Part.(*Part_Data); ok { + return x.Data + } + } + return nil +} + +func (x *Part) GetMetadata() *structpb.Struct { + if x != nil { + return x.Metadata + } + return nil +} + +type isPart_Part interface { + isPart_Part() +} + +type Part_Text struct { + Text string `protobuf:"bytes,1,opt,name=text,proto3,oneof"` +} + +type Part_File struct { + File *FilePart `protobuf:"bytes,2,opt,name=file,proto3,oneof"` +} + +type Part_Data struct { + Data *DataPart `protobuf:"bytes,3,opt,name=data,proto3,oneof"` +} + +func (*Part_Text) isPart_Part() {} + +func (*Part_File) isPart_Part() {} + +func (*Part_Data) isPart_Part() {} + +// FilePart represents the different ways files can be provided. If files are +// small, directly feeding the bytes is supported via file_with_bytes. If the +// file is large, the agent should read the content as appropriate directly +// from the file_with_uri source. +type FilePart struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to File: + // + // *FilePart_FileWithUri + // *FilePart_FileWithBytes + File isFilePart_File `protobuf_oneof:"file"` + MimeType string `protobuf:"bytes,3,opt,name=mime_type,json=mimeType,proto3" json:"mime_type,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FilePart) Reset() { + *x = FilePart{} + mi := &file_a2a_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FilePart) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FilePart) ProtoMessage() {} + +func (x *FilePart) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FilePart.ProtoReflect.Descriptor instead. +func (*FilePart) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{4} +} + +func (x *FilePart) GetFile() isFilePart_File { + if x != nil { + return x.File + } + return nil +} + +func (x *FilePart) GetFileWithUri() string { + if x != nil { + if x, ok := x.File.(*FilePart_FileWithUri); ok { + return x.FileWithUri + } + } + return "" +} + +func (x *FilePart) GetFileWithBytes() []byte { + if x != nil { + if x, ok := x.File.(*FilePart_FileWithBytes); ok { + return x.FileWithBytes + } + } + return nil +} + +func (x *FilePart) GetMimeType() string { + if x != nil { + return x.MimeType + } + return "" +} + +func (x *FilePart) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type isFilePart_File interface { + isFilePart_File() +} + +type FilePart_FileWithUri struct { + FileWithUri string `protobuf:"bytes,1,opt,name=file_with_uri,json=fileWithUri,proto3,oneof"` +} + +type FilePart_FileWithBytes struct { + FileWithBytes []byte `protobuf:"bytes,2,opt,name=file_with_bytes,json=fileWithBytes,proto3,oneof"` +} + +func (*FilePart_FileWithUri) isFilePart_File() {} + +func (*FilePart_FileWithBytes) isFilePart_File() {} + +// DataPart represents a structured blob. This is most commonly a JSON payload. +type DataPart struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data *structpb.Struct `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DataPart) Reset() { + *x = DataPart{} + mi := &file_a2a_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DataPart) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataPart) ProtoMessage() {} + +func (x *DataPart) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DataPart.ProtoReflect.Descriptor instead. +func (*DataPart) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{5} +} + +func (x *DataPart) GetData() *structpb.Struct { + if x != nil { + return x.Data + } + return nil +} + +// Message is one unit of communication between client and server. It is +// associated with a context and optionally a task. Since the server is +// responsible for the context definition, it must always provide a context_id +// in its messages. The client can optionally provide the context_id if it +// knows the context to associate the message to. Similarly for task_id, +// except the server decides if a task is created and whether to include the +// task_id. +type Message struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The unique identifier (e.g. UUID)of the message. This is required and + // created by the message creator. + MessageId string `protobuf:"bytes,1,opt,name=message_id,json=messageId,proto3" json:"message_id,omitempty"` + // The context id of the message. This is optional and if set, the message + // will be associated with the given context. + ContextId string `protobuf:"bytes,2,opt,name=context_id,json=contextId,proto3" json:"context_id,omitempty"` + // The task id of the message. This is optional and if set, the message + // will be associated with the given task. + TaskId string `protobuf:"bytes,3,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + // A role for the message. + Role Role `protobuf:"varint,4,opt,name=role,proto3,enum=a2a.v1.Role" json:"role,omitempty"` + // protolint:disable REPEATED_FIELD_NAMES_PLURALIZED + // Parts is the container of the message content. + Content []*Part `protobuf:"bytes,5,rep,name=content,proto3" json:"content,omitempty"` + // protolint:enable REPEATED_FIELD_NAMES_PLURALIZED + // Type identifier for A2A protocol, should be "message" + Kind string `protobuf:"bytes,6,opt,name=kind,proto3" json:"kind,omitempty"` + // Any optional metadata to provide along with the message. + Metadata *structpb.Struct `protobuf:"bytes,7,opt,name=metadata,proto3" json:"metadata,omitempty"` + // The URIs of extensions that are present or contributed to this Message. + Extensions []string `protobuf:"bytes,8,rep,name=extensions,proto3" json:"extensions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Message) Reset() { + *x = Message{} + mi := &file_a2a_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Message) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Message) ProtoMessage() {} + +func (x *Message) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Message.ProtoReflect.Descriptor instead. +func (*Message) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{6} +} + +func (x *Message) GetMessageId() string { + if x != nil { + return x.MessageId + } + return "" +} + +func (x *Message) GetContextId() string { + if x != nil { + return x.ContextId + } + return "" +} + +func (x *Message) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + +func (x *Message) GetRole() Role { + if x != nil { + return x.Role + } + return Role_ROLE_UNSPECIFIED +} + +func (x *Message) GetContent() []*Part { + if x != nil { + return x.Content + } + return nil +} + +func (x *Message) GetKind() string { + if x != nil { + return x.Kind + } + return "" +} + +func (x *Message) GetMetadata() *structpb.Struct { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *Message) GetExtensions() []string { + if x != nil { + return x.Extensions + } + return nil +} + +// Artifacts are the container for task completed results. These are similar +// to Messages but are intended to be the product of a task, as opposed to +// point-to-point communication. +type Artifact struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Unique identifier (e.g. UUID) for the artifact. It must be at least unique + // within a task. + ArtifactId string `protobuf:"bytes,1,opt,name=artifact_id,json=artifactId,proto3" json:"artifact_id,omitempty"` + // A human readable name for the artifact. + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + // A human readable description of the artifact, optional. + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + // The content of the artifact. + Parts []*Part `protobuf:"bytes,5,rep,name=parts,proto3" json:"parts,omitempty"` + // Optional metadata included with the artifact. + Metadata *structpb.Struct `protobuf:"bytes,6,opt,name=metadata,proto3" json:"metadata,omitempty"` + // The URIs of extensions that are present or contributed to this Artifact. + Extensions []string `protobuf:"bytes,7,rep,name=extensions,proto3" json:"extensions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Artifact) Reset() { + *x = Artifact{} + mi := &file_a2a_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Artifact) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Artifact) ProtoMessage() {} + +func (x *Artifact) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Artifact.ProtoReflect.Descriptor instead. +func (*Artifact) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{7} +} + +func (x *Artifact) GetArtifactId() string { + if x != nil { + return x.ArtifactId + } + return "" +} + +func (x *Artifact) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Artifact) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Artifact) GetParts() []*Part { + if x != nil { + return x.Parts + } + return nil +} + +func (x *Artifact) GetMetadata() *structpb.Struct { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *Artifact) GetExtensions() []string { + if x != nil { + return x.Extensions + } + return nil +} + +// TaskStatusUpdateEvent is a delta even on a task indicating that a task +// has changed. +type TaskStatusUpdateEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The id of the task that is changed + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + // The id of the context that the task belongs to + ContextId string `protobuf:"bytes,2,opt,name=context_id,json=contextId,proto3" json:"context_id,omitempty"` + // The new status of the task. + Status *TaskStatus `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + // Whether this is the last status update expected for this task. + Final bool `protobuf:"varint,4,opt,name=final,proto3" json:"final,omitempty"` + // Optional metadata to associate with the task update. + Metadata *structpb.Struct `protobuf:"bytes,5,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaskStatusUpdateEvent) Reset() { + *x = TaskStatusUpdateEvent{} + mi := &file_a2a_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaskStatusUpdateEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaskStatusUpdateEvent) ProtoMessage() {} + +func (x *TaskStatusUpdateEvent) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaskStatusUpdateEvent.ProtoReflect.Descriptor instead. +func (*TaskStatusUpdateEvent) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{8} +} + +func (x *TaskStatusUpdateEvent) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + +func (x *TaskStatusUpdateEvent) GetContextId() string { + if x != nil { + return x.ContextId + } + return "" +} + +func (x *TaskStatusUpdateEvent) GetStatus() *TaskStatus { + if x != nil { + return x.Status + } + return nil +} + +func (x *TaskStatusUpdateEvent) GetFinal() bool { + if x != nil { + return x.Final + } + return false +} + +func (x *TaskStatusUpdateEvent) GetMetadata() *structpb.Struct { + if x != nil { + return x.Metadata + } + return nil +} + +// TaskArtifactUpdateEvent represents a task delta where an artifact has +// been generated. +type TaskArtifactUpdateEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The id of the task for this artifact + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + // The id of the context that this task belongs too + ContextId string `protobuf:"bytes,2,opt,name=context_id,json=contextId,proto3" json:"context_id,omitempty"` + // The artifact itself + Artifact *Artifact `protobuf:"bytes,3,opt,name=artifact,proto3" json:"artifact,omitempty"` + // Whether this should be appended to a prior one produced + Append bool `protobuf:"varint,4,opt,name=append,proto3" json:"append,omitempty"` + // Whether this represents the last part of an artifact + LastChunk bool `protobuf:"varint,5,opt,name=last_chunk,json=lastChunk,proto3" json:"last_chunk,omitempty"` + // Optional metadata associated with the artifact update. + Metadata *structpb.Struct `protobuf:"bytes,6,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaskArtifactUpdateEvent) Reset() { + *x = TaskArtifactUpdateEvent{} + mi := &file_a2a_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaskArtifactUpdateEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaskArtifactUpdateEvent) ProtoMessage() {} + +func (x *TaskArtifactUpdateEvent) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaskArtifactUpdateEvent.ProtoReflect.Descriptor instead. +func (*TaskArtifactUpdateEvent) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{9} +} + +func (x *TaskArtifactUpdateEvent) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + +func (x *TaskArtifactUpdateEvent) GetContextId() string { + if x != nil { + return x.ContextId + } + return "" +} + +func (x *TaskArtifactUpdateEvent) GetArtifact() *Artifact { + if x != nil { + return x.Artifact + } + return nil +} + +func (x *TaskArtifactUpdateEvent) GetAppend() bool { + if x != nil { + return x.Append + } + return false +} + +func (x *TaskArtifactUpdateEvent) GetLastChunk() bool { + if x != nil { + return x.LastChunk + } + return false +} + +func (x *TaskArtifactUpdateEvent) GetMetadata() *structpb.Struct { + if x != nil { + return x.Metadata + } + return nil +} + +// Configuration for setting up push notifications for task updates. +type PushNotificationConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + // A unique identifier (e.g. UUID) for this push notification. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Url to send the notification too + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` + // Token unique for this task/session + Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"` + // Information about the authentication to sent with the notification + Authentication *AuthenticationInfo `protobuf:"bytes,4,opt,name=authentication,proto3" json:"authentication,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PushNotificationConfig) Reset() { + *x = PushNotificationConfig{} + mi := &file_a2a_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PushNotificationConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushNotificationConfig) ProtoMessage() {} + +func (x *PushNotificationConfig) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushNotificationConfig.ProtoReflect.Descriptor instead. +func (*PushNotificationConfig) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{10} +} + +func (x *PushNotificationConfig) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *PushNotificationConfig) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *PushNotificationConfig) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *PushNotificationConfig) GetAuthentication() *AuthenticationInfo { + if x != nil { + return x.Authentication + } + return nil +} + +// Defines authentication details, used for push notifications. +type AuthenticationInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Supported authentication schemes - e.g. Basic, Bearer, etc + Schemes []string `protobuf:"bytes,1,rep,name=schemes,proto3" json:"schemes,omitempty"` + // Optional credentials + Credentials string `protobuf:"bytes,2,opt,name=credentials,proto3" json:"credentials,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthenticationInfo) Reset() { + *x = AuthenticationInfo{} + mi := &file_a2a_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthenticationInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthenticationInfo) ProtoMessage() {} + +func (x *AuthenticationInfo) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthenticationInfo.ProtoReflect.Descriptor instead. +func (*AuthenticationInfo) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{11} +} + +func (x *AuthenticationInfo) GetSchemes() []string { + if x != nil { + return x.Schemes + } + return nil +} + +func (x *AuthenticationInfo) GetCredentials() string { + if x != nil { + return x.Credentials + } + return "" +} + +// Defines additional transport information for the agent. +type AgentInterface struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The url this interface is found at. + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + // The transport supported this url. This is an open form string, to be + // easily extended for many transport protocols. The core ones officially + // supported are JSONRPC, GRPC and HTTP+JSON. + Transport string `protobuf:"bytes,2,opt,name=transport,proto3" json:"transport,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentInterface) Reset() { + *x = AgentInterface{} + mi := &file_a2a_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentInterface) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentInterface) ProtoMessage() {} + +func (x *AgentInterface) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentInterface.ProtoReflect.Descriptor instead. +func (*AgentInterface) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{12} +} + +func (x *AgentInterface) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *AgentInterface) GetTransport() string { + if x != nil { + return x.Transport + } + return "" +} + +// AgentCard conveys key information: +// - Overall details (version, name, description, uses) +// - Skills; a set of actions/solutions the agent can perform +// - Default modalities/content types supported by the agent. +// - Authentication requirements +// Next ID: 19 +type AgentCard struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The version of the A2A protocol this agent supports. + ProtocolVersion string `protobuf:"bytes,16,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` + // A human readable name for the agent. + // Example: "Recipe Agent" + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // A description of the agent's domain of action/solution space. + // Example: "Agent that helps users with recipes and cooking." + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // A URL to the address the agent is hosted at. This represents the + // preferred endpoint as declared by the agent. + Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` + // The transport of the preferred endpoint. If empty, defaults to JSONRPC. + PreferredTransport string `protobuf:"bytes,14,opt,name=preferred_transport,json=preferredTransport,proto3" json:"preferred_transport,omitempty"` + // Announcement of additional supported transports. Client can use any of + // the supported transports. + AdditionalInterfaces []*AgentInterface `protobuf:"bytes,15,rep,name=additional_interfaces,json=additionalInterfaces,proto3" json:"additional_interfaces,omitempty"` + // The service provider of the agent. + Provider *AgentProvider `protobuf:"bytes,4,opt,name=provider,proto3" json:"provider,omitempty"` + // The version of the agent. + // Example: "1.0.0" + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` + // A url to provide additional documentation about the agent. + DocumentationUrl string `protobuf:"bytes,6,opt,name=documentation_url,json=documentationUrl,proto3" json:"documentation_url,omitempty"` + // A2A Capability set supported by the agent. + Capabilities *AgentCapabilities `protobuf:"bytes,7,opt,name=capabilities,proto3" json:"capabilities,omitempty"` + // The security scheme details used for authenticating with this agent. + SecuritySchemes map[string]*SecurityScheme `protobuf:"bytes,8,rep,name=security_schemes,json=securitySchemes,proto3" json:"security_schemes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // protolint:disable REPEATED_FIELD_NAMES_PLURALIZED + // Security requirements for contacting the agent. + // This list can be seen as an OR of ANDs. Each object in the list describes + // one possible set of security requirements that must be present on a + // request. This allows specifying, for example, "callers must either use + // OAuth OR an API Key AND mTLS." + // Example: + // + // security { + // schemes { key: "oauth" value { list: ["read"] } } + // } + // + // security { + // schemes { key: "api-key" } + // schemes { key: "mtls" } + // } + Security []*Security `protobuf:"bytes,9,rep,name=security,proto3" json:"security,omitempty"` + // protolint:enable REPEATED_FIELD_NAMES_PLURALIZED + // The set of interaction modes that the agent supports across all skills. + // This can be overridden per skill. Defined as mime types. + DefaultInputModes []string `protobuf:"bytes,10,rep,name=default_input_modes,json=defaultInputModes,proto3" json:"default_input_modes,omitempty"` + // The mime types supported as outputs from this agent. + DefaultOutputModes []string `protobuf:"bytes,11,rep,name=default_output_modes,json=defaultOutputModes,proto3" json:"default_output_modes,omitempty"` + // Skills represent a unit of ability an agent can perform. This may + // somewhat abstract but represents a more focused set of actions that the + // agent is highly likely to succeed at. + Skills []*AgentSkill `protobuf:"bytes,12,rep,name=skills,proto3" json:"skills,omitempty"` + // Whether the agent supports providing an extended agent card when + // the user is authenticated, i.e. is the card from .well-known + // different than the card from GetAgentCard. + SupportsAuthenticatedExtendedCard bool `protobuf:"varint,13,opt,name=supports_authenticated_extended_card,json=supportsAuthenticatedExtendedCard,proto3" json:"supports_authenticated_extended_card,omitempty"` + // JSON Web Signatures computed for this AgentCard. + Signatures []*AgentCardSignature `protobuf:"bytes,17,rep,name=signatures,proto3" json:"signatures,omitempty"` + // An optional URL to an icon for the agent. + IconUrl string `protobuf:"bytes,18,opt,name=icon_url,json=iconUrl,proto3" json:"icon_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentCard) Reset() { + *x = AgentCard{} + mi := &file_a2a_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentCard) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentCard) ProtoMessage() {} + +func (x *AgentCard) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentCard.ProtoReflect.Descriptor instead. +func (*AgentCard) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{13} +} + +func (x *AgentCard) GetProtocolVersion() string { + if x != nil { + return x.ProtocolVersion + } + return "" +} + +func (x *AgentCard) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *AgentCard) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *AgentCard) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *AgentCard) GetPreferredTransport() string { + if x != nil { + return x.PreferredTransport + } + return "" +} + +func (x *AgentCard) GetAdditionalInterfaces() []*AgentInterface { + if x != nil { + return x.AdditionalInterfaces + } + return nil +} + +func (x *AgentCard) GetProvider() *AgentProvider { + if x != nil { + return x.Provider + } + return nil +} + +func (x *AgentCard) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *AgentCard) GetDocumentationUrl() string { + if x != nil { + return x.DocumentationUrl + } + return "" +} + +func (x *AgentCard) GetCapabilities() *AgentCapabilities { + if x != nil { + return x.Capabilities + } + return nil +} + +func (x *AgentCard) GetSecuritySchemes() map[string]*SecurityScheme { + if x != nil { + return x.SecuritySchemes + } + return nil +} + +func (x *AgentCard) GetSecurity() []*Security { + if x != nil { + return x.Security + } + return nil +} + +func (x *AgentCard) GetDefaultInputModes() []string { + if x != nil { + return x.DefaultInputModes + } + return nil +} + +func (x *AgentCard) GetDefaultOutputModes() []string { + if x != nil { + return x.DefaultOutputModes + } + return nil +} + +func (x *AgentCard) GetSkills() []*AgentSkill { + if x != nil { + return x.Skills + } + return nil +} + +func (x *AgentCard) GetSupportsAuthenticatedExtendedCard() bool { + if x != nil { + return x.SupportsAuthenticatedExtendedCard + } + return false +} + +func (x *AgentCard) GetSignatures() []*AgentCardSignature { + if x != nil { + return x.Signatures + } + return nil +} + +func (x *AgentCard) GetIconUrl() string { + if x != nil { + return x.IconUrl + } + return "" +} + +// Represents information about the service provider of an agent. +type AgentProvider struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The providers reference url + // Example: "https://ai.google.dev" + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + // The providers organization name + // Example: "Google" + Organization string `protobuf:"bytes,2,opt,name=organization,proto3" json:"organization,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentProvider) Reset() { + *x = AgentProvider{} + mi := &file_a2a_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentProvider) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentProvider) ProtoMessage() {} + +func (x *AgentProvider) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentProvider.ProtoReflect.Descriptor instead. +func (*AgentProvider) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{14} +} + +func (x *AgentProvider) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *AgentProvider) GetOrganization() string { + if x != nil { + return x.Organization + } + return "" +} + +// Defines the A2A feature set supported by the agent +type AgentCapabilities struct { + state protoimpl.MessageState `protogen:"open.v1"` + // If the agent will support streaming responses + Streaming bool `protobuf:"varint,1,opt,name=streaming,proto3" json:"streaming,omitempty"` + // If the agent can send push notifications to the clients webhook + PushNotifications bool `protobuf:"varint,2,opt,name=push_notifications,json=pushNotifications,proto3" json:"push_notifications,omitempty"` + // If the agent supports state transition history tracking + StateTransitionHistory bool `protobuf:"varint,3,opt,name=state_transition_history,json=stateTransitionHistory,proto3" json:"state_transition_history,omitempty"` + // Extensions supported by this agent. + Extensions []*AgentExtension `protobuf:"bytes,4,rep,name=extensions,proto3" json:"extensions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentCapabilities) Reset() { + *x = AgentCapabilities{} + mi := &file_a2a_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentCapabilities) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentCapabilities) ProtoMessage() {} + +func (x *AgentCapabilities) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentCapabilities.ProtoReflect.Descriptor instead. +func (*AgentCapabilities) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{15} +} + +func (x *AgentCapabilities) GetStreaming() bool { + if x != nil { + return x.Streaming + } + return false +} + +func (x *AgentCapabilities) GetPushNotifications() bool { + if x != nil { + return x.PushNotifications + } + return false +} + +func (x *AgentCapabilities) GetStateTransitionHistory() bool { + if x != nil { + return x.StateTransitionHistory + } + return false +} + +func (x *AgentCapabilities) GetExtensions() []*AgentExtension { + if x != nil { + return x.Extensions + } + return nil +} + +// A declaration of an extension supported by an Agent. +type AgentExtension struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The URI of the extension. + // Example: "https://developers.google.com/identity/protocols/oauth2" + Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty"` + // A description of how this agent uses this extension. + // Example: "Google OAuth 2.0 authentication" + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // Whether the client must follow specific requirements of the extension. + // Example: false + Required bool `protobuf:"varint,3,opt,name=required,proto3" json:"required,omitempty"` + // Optional configuration for the extension. + Params *structpb.Struct `protobuf:"bytes,4,opt,name=params,proto3" json:"params,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentExtension) Reset() { + *x = AgentExtension{} + mi := &file_a2a_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentExtension) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentExtension) ProtoMessage() {} + +func (x *AgentExtension) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentExtension.ProtoReflect.Descriptor instead. +func (*AgentExtension) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{16} +} + +func (x *AgentExtension) GetUri() string { + if x != nil { + return x.Uri + } + return "" +} + +func (x *AgentExtension) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *AgentExtension) GetRequired() bool { + if x != nil { + return x.Required + } + return false +} + +func (x *AgentExtension) GetParams() *structpb.Struct { + if x != nil { + return x.Params + } + return nil +} + +// AgentSkill represents a unit of action/solution that the agent can perform. +// One can think of this as a type of highly reliable solution that an agent +// can be tasked to provide. Agents have the autonomy to choose how and when +// to use specific skills, but clients should have confidence that if the +// skill is defined that unit of action can be reliably performed. +type AgentSkill struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Unique identifier of the skill within this agent. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // A human readable name for the skill. + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // A human (or llm) readable description of the skill + // details and behaviors. + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + // A set of tags for the skill to enhance categorization/utilization. + // Example: ["cooking", "customer support", "billing"] + Tags []string `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty"` + // A set of example queries that this skill is designed to address. + // These examples should help the caller to understand how to craft requests + // to the agent to achieve specific goals. + // Example: ["I need a recipe for bread"] + Examples []string `protobuf:"bytes,5,rep,name=examples,proto3" json:"examples,omitempty"` + // Possible input modalities supported. + InputModes []string `protobuf:"bytes,6,rep,name=input_modes,json=inputModes,proto3" json:"input_modes,omitempty"` + // Possible output modalities produced + OutputModes []string `protobuf:"bytes,7,rep,name=output_modes,json=outputModes,proto3" json:"output_modes,omitempty"` + // protolint:disable REPEATED_FIELD_NAMES_PLURALIZED + // Security schemes necessary for the agent to leverage this skill. + // As in the overall AgentCard.security, this list represents a logical OR of + // security requirement objects. Each object is a set of security schemes + // that must be used together (a logical AND). + Security []*Security `protobuf:"bytes,8,rep,name=security,proto3" json:"security,omitempty"` // protolint:enable REPEATED_FIELD_NAMES_PLURALIZED + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentSkill) Reset() { + *x = AgentSkill{} + mi := &file_a2a_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentSkill) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentSkill) ProtoMessage() {} + +func (x *AgentSkill) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentSkill.ProtoReflect.Descriptor instead. +func (*AgentSkill) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{17} +} + +func (x *AgentSkill) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *AgentSkill) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *AgentSkill) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *AgentSkill) GetTags() []string { + if x != nil { + return x.Tags + } + return nil +} + +func (x *AgentSkill) GetExamples() []string { + if x != nil { + return x.Examples + } + return nil +} + +func (x *AgentSkill) GetInputModes() []string { + if x != nil { + return x.InputModes + } + return nil +} + +func (x *AgentSkill) GetOutputModes() []string { + if x != nil { + return x.OutputModes + } + return nil +} + +func (x *AgentSkill) GetSecurity() []*Security { + if x != nil { + return x.Security + } + return nil +} + +// AgentCardSignature represents a JWS signature of an AgentCard. +// This follows the JSON format of an RFC 7515 JSON Web Signature (JWS). +type AgentCardSignature struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The protected JWS header for the signature. This is always a + // base64url-encoded JSON object. Required. + Protected string `protobuf:"bytes,1,opt,name=protected,proto3" json:"protected,omitempty"` + // The computed signature, base64url-encoded. Required. + Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` + // The unprotected JWS header values. + Header *structpb.Struct `protobuf:"bytes,3,opt,name=header,proto3" json:"header,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentCardSignature) Reset() { + *x = AgentCardSignature{} + mi := &file_a2a_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentCardSignature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentCardSignature) ProtoMessage() {} + +func (x *AgentCardSignature) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentCardSignature.ProtoReflect.Descriptor instead. +func (*AgentCardSignature) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{18} +} + +func (x *AgentCardSignature) GetProtected() string { + if x != nil { + return x.Protected + } + return "" +} + +func (x *AgentCardSignature) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + +func (x *AgentCardSignature) GetHeader() *structpb.Struct { + if x != nil { + return x.Header + } + return nil +} + +type TaskPushNotificationConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The resource name of the config. + // Format: tasks/{task_id}/pushNotificationConfigs/{config_id} + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The push notification configuration details. + PushNotificationConfig *PushNotificationConfig `protobuf:"bytes,2,opt,name=push_notification_config,json=pushNotificationConfig,proto3" json:"push_notification_config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaskPushNotificationConfig) Reset() { + *x = TaskPushNotificationConfig{} + mi := &file_a2a_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaskPushNotificationConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaskPushNotificationConfig) ProtoMessage() {} + +func (x *TaskPushNotificationConfig) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaskPushNotificationConfig.ProtoReflect.Descriptor instead. +func (*TaskPushNotificationConfig) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{19} +} + +func (x *TaskPushNotificationConfig) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TaskPushNotificationConfig) GetPushNotificationConfig() *PushNotificationConfig { + if x != nil { + return x.PushNotificationConfig + } + return nil +} + +// protolint:disable REPEATED_FIELD_NAMES_PLURALIZED +type StringList struct { + state protoimpl.MessageState `protogen:"open.v1"` + List []string `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StringList) Reset() { + *x = StringList{} + mi := &file_a2a_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StringList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StringList) ProtoMessage() {} + +func (x *StringList) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StringList.ProtoReflect.Descriptor instead. +func (*StringList) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{20} +} + +func (x *StringList) GetList() []string { + if x != nil { + return x.List + } + return nil +} + +type Security struct { + state protoimpl.MessageState `protogen:"open.v1"` + Schemes map[string]*StringList `protobuf:"bytes,1,rep,name=schemes,proto3" json:"schemes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Security) Reset() { + *x = Security{} + mi := &file_a2a_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Security) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Security) ProtoMessage() {} + +func (x *Security) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Security.ProtoReflect.Descriptor instead. +func (*Security) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{21} +} + +func (x *Security) GetSchemes() map[string]*StringList { + if x != nil { + return x.Schemes + } + return nil +} + +type SecurityScheme struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Scheme: + // + // *SecurityScheme_ApiKeySecurityScheme + // *SecurityScheme_HttpAuthSecurityScheme + // *SecurityScheme_Oauth2SecurityScheme + // *SecurityScheme_OpenIdConnectSecurityScheme + // *SecurityScheme_MtlsSecurityScheme + Scheme isSecurityScheme_Scheme `protobuf_oneof:"scheme"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SecurityScheme) Reset() { + *x = SecurityScheme{} + mi := &file_a2a_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SecurityScheme) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SecurityScheme) ProtoMessage() {} + +func (x *SecurityScheme) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SecurityScheme.ProtoReflect.Descriptor instead. +func (*SecurityScheme) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{22} +} + +func (x *SecurityScheme) GetScheme() isSecurityScheme_Scheme { + if x != nil { + return x.Scheme + } + return nil +} + +func (x *SecurityScheme) GetApiKeySecurityScheme() *APIKeySecurityScheme { + if x != nil { + if x, ok := x.Scheme.(*SecurityScheme_ApiKeySecurityScheme); ok { + return x.ApiKeySecurityScheme + } + } + return nil +} + +func (x *SecurityScheme) GetHttpAuthSecurityScheme() *HTTPAuthSecurityScheme { + if x != nil { + if x, ok := x.Scheme.(*SecurityScheme_HttpAuthSecurityScheme); ok { + return x.HttpAuthSecurityScheme + } + } + return nil +} + +func (x *SecurityScheme) GetOauth2SecurityScheme() *OAuth2SecurityScheme { + if x != nil { + if x, ok := x.Scheme.(*SecurityScheme_Oauth2SecurityScheme); ok { + return x.Oauth2SecurityScheme + } + } + return nil +} + +func (x *SecurityScheme) GetOpenIdConnectSecurityScheme() *OpenIdConnectSecurityScheme { + if x != nil { + if x, ok := x.Scheme.(*SecurityScheme_OpenIdConnectSecurityScheme); ok { + return x.OpenIdConnectSecurityScheme + } + } + return nil +} + +func (x *SecurityScheme) GetMtlsSecurityScheme() *MutualTlsSecurityScheme { + if x != nil { + if x, ok := x.Scheme.(*SecurityScheme_MtlsSecurityScheme); ok { + return x.MtlsSecurityScheme + } + } + return nil +} + +type isSecurityScheme_Scheme interface { + isSecurityScheme_Scheme() +} + +type SecurityScheme_ApiKeySecurityScheme struct { + ApiKeySecurityScheme *APIKeySecurityScheme `protobuf:"bytes,1,opt,name=api_key_security_scheme,json=apiKeySecurityScheme,proto3,oneof"` +} + +type SecurityScheme_HttpAuthSecurityScheme struct { + HttpAuthSecurityScheme *HTTPAuthSecurityScheme `protobuf:"bytes,2,opt,name=http_auth_security_scheme,json=httpAuthSecurityScheme,proto3,oneof"` +} + +type SecurityScheme_Oauth2SecurityScheme struct { + Oauth2SecurityScheme *OAuth2SecurityScheme `protobuf:"bytes,3,opt,name=oauth2_security_scheme,json=oauth2SecurityScheme,proto3,oneof"` +} + +type SecurityScheme_OpenIdConnectSecurityScheme struct { + OpenIdConnectSecurityScheme *OpenIdConnectSecurityScheme `protobuf:"bytes,4,opt,name=open_id_connect_security_scheme,json=openIdConnectSecurityScheme,proto3,oneof"` +} + +type SecurityScheme_MtlsSecurityScheme struct { + MtlsSecurityScheme *MutualTlsSecurityScheme `protobuf:"bytes,5,opt,name=mtls_security_scheme,json=mtlsSecurityScheme,proto3,oneof"` +} + +func (*SecurityScheme_ApiKeySecurityScheme) isSecurityScheme_Scheme() {} + +func (*SecurityScheme_HttpAuthSecurityScheme) isSecurityScheme_Scheme() {} + +func (*SecurityScheme_Oauth2SecurityScheme) isSecurityScheme_Scheme() {} + +func (*SecurityScheme_OpenIdConnectSecurityScheme) isSecurityScheme_Scheme() {} + +func (*SecurityScheme_MtlsSecurityScheme) isSecurityScheme_Scheme() {} + +type APIKeySecurityScheme struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Description of this security scheme. + Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` + // Location of the API key, valid values are "query", "header", or "cookie" + Location string `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"` + // Name of the header, query or cookie parameter to be used. + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *APIKeySecurityScheme) Reset() { + *x = APIKeySecurityScheme{} + mi := &file_a2a_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *APIKeySecurityScheme) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*APIKeySecurityScheme) ProtoMessage() {} + +func (x *APIKeySecurityScheme) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use APIKeySecurityScheme.ProtoReflect.Descriptor instead. +func (*APIKeySecurityScheme) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{23} +} + +func (x *APIKeySecurityScheme) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *APIKeySecurityScheme) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *APIKeySecurityScheme) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type HTTPAuthSecurityScheme struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Description of this security scheme. + Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` + // The name of the HTTP Authentication scheme to be used in the + // Authorization header as defined in RFC7235. The values used SHOULD be + // registered in the IANA Authentication Scheme registry. + // The value is case-insensitive, as defined in RFC7235. + Scheme string `protobuf:"bytes,2,opt,name=scheme,proto3" json:"scheme,omitempty"` + // A hint to the client to identify how the bearer token is formatted. + // Bearer tokens are usually generated by an authorization server, so + // this information is primarily for documentation purposes. + BearerFormat string `protobuf:"bytes,3,opt,name=bearer_format,json=bearerFormat,proto3" json:"bearer_format,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HTTPAuthSecurityScheme) Reset() { + *x = HTTPAuthSecurityScheme{} + mi := &file_a2a_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HTTPAuthSecurityScheme) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HTTPAuthSecurityScheme) ProtoMessage() {} + +func (x *HTTPAuthSecurityScheme) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HTTPAuthSecurityScheme.ProtoReflect.Descriptor instead. +func (*HTTPAuthSecurityScheme) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{24} +} + +func (x *HTTPAuthSecurityScheme) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *HTTPAuthSecurityScheme) GetScheme() string { + if x != nil { + return x.Scheme + } + return "" +} + +func (x *HTTPAuthSecurityScheme) GetBearerFormat() string { + if x != nil { + return x.BearerFormat + } + return "" +} + +type OAuth2SecurityScheme struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Description of this security scheme. + Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` + // An object containing configuration information for the flow types supported + Flows *OAuthFlows `protobuf:"bytes,2,opt,name=flows,proto3" json:"flows,omitempty"` + // URL to the oauth2 authorization server metadata + // [RFC8414](https://datatracker.ietf.org/doc/html/rfc8414). TLS is required. + Oauth2MetadataUrl string `protobuf:"bytes,3,opt,name=oauth2_metadata_url,json=oauth2MetadataUrl,proto3" json:"oauth2_metadata_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OAuth2SecurityScheme) Reset() { + *x = OAuth2SecurityScheme{} + mi := &file_a2a_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OAuth2SecurityScheme) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OAuth2SecurityScheme) ProtoMessage() {} + +func (x *OAuth2SecurityScheme) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OAuth2SecurityScheme.ProtoReflect.Descriptor instead. +func (*OAuth2SecurityScheme) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{25} +} + +func (x *OAuth2SecurityScheme) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *OAuth2SecurityScheme) GetFlows() *OAuthFlows { + if x != nil { + return x.Flows + } + return nil +} + +func (x *OAuth2SecurityScheme) GetOauth2MetadataUrl() string { + if x != nil { + return x.Oauth2MetadataUrl + } + return "" +} + +type OpenIdConnectSecurityScheme struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Description of this security scheme. + Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` + // Well-known URL to discover the [[OpenID-Connect-Discovery]] provider + // metadata. + OpenIdConnectUrl string `protobuf:"bytes,2,opt,name=open_id_connect_url,json=openIdConnectUrl,proto3" json:"open_id_connect_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OpenIdConnectSecurityScheme) Reset() { + *x = OpenIdConnectSecurityScheme{} + mi := &file_a2a_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OpenIdConnectSecurityScheme) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OpenIdConnectSecurityScheme) ProtoMessage() {} + +func (x *OpenIdConnectSecurityScheme) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OpenIdConnectSecurityScheme.ProtoReflect.Descriptor instead. +func (*OpenIdConnectSecurityScheme) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{26} +} + +func (x *OpenIdConnectSecurityScheme) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *OpenIdConnectSecurityScheme) GetOpenIdConnectUrl() string { + if x != nil { + return x.OpenIdConnectUrl + } + return "" +} + +type MutualTlsSecurityScheme struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Description of this security scheme. + Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MutualTlsSecurityScheme) Reset() { + *x = MutualTlsSecurityScheme{} + mi := &file_a2a_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MutualTlsSecurityScheme) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MutualTlsSecurityScheme) ProtoMessage() {} + +func (x *MutualTlsSecurityScheme) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MutualTlsSecurityScheme.ProtoReflect.Descriptor instead. +func (*MutualTlsSecurityScheme) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{27} +} + +func (x *MutualTlsSecurityScheme) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +type OAuthFlows struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Flow: + // + // *OAuthFlows_AuthorizationCode + // *OAuthFlows_ClientCredentials + // *OAuthFlows_Implicit + // *OAuthFlows_Password + Flow isOAuthFlows_Flow `protobuf_oneof:"flow"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OAuthFlows) Reset() { + *x = OAuthFlows{} + mi := &file_a2a_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OAuthFlows) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OAuthFlows) ProtoMessage() {} + +func (x *OAuthFlows) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OAuthFlows.ProtoReflect.Descriptor instead. +func (*OAuthFlows) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{28} +} + +func (x *OAuthFlows) GetFlow() isOAuthFlows_Flow { + if x != nil { + return x.Flow + } + return nil +} + +func (x *OAuthFlows) GetAuthorizationCode() *AuthorizationCodeOAuthFlow { + if x != nil { + if x, ok := x.Flow.(*OAuthFlows_AuthorizationCode); ok { + return x.AuthorizationCode + } + } + return nil +} + +func (x *OAuthFlows) GetClientCredentials() *ClientCredentialsOAuthFlow { + if x != nil { + if x, ok := x.Flow.(*OAuthFlows_ClientCredentials); ok { + return x.ClientCredentials + } + } + return nil +} + +func (x *OAuthFlows) GetImplicit() *ImplicitOAuthFlow { + if x != nil { + if x, ok := x.Flow.(*OAuthFlows_Implicit); ok { + return x.Implicit + } + } + return nil +} + +func (x *OAuthFlows) GetPassword() *PasswordOAuthFlow { + if x != nil { + if x, ok := x.Flow.(*OAuthFlows_Password); ok { + return x.Password + } + } + return nil +} + +type isOAuthFlows_Flow interface { + isOAuthFlows_Flow() +} + +type OAuthFlows_AuthorizationCode struct { + AuthorizationCode *AuthorizationCodeOAuthFlow `protobuf:"bytes,1,opt,name=authorization_code,json=authorizationCode,proto3,oneof"` +} + +type OAuthFlows_ClientCredentials struct { + ClientCredentials *ClientCredentialsOAuthFlow `protobuf:"bytes,2,opt,name=client_credentials,json=clientCredentials,proto3,oneof"` +} + +type OAuthFlows_Implicit struct { + Implicit *ImplicitOAuthFlow `protobuf:"bytes,3,opt,name=implicit,proto3,oneof"` +} + +type OAuthFlows_Password struct { + Password *PasswordOAuthFlow `protobuf:"bytes,4,opt,name=password,proto3,oneof"` +} + +func (*OAuthFlows_AuthorizationCode) isOAuthFlows_Flow() {} + +func (*OAuthFlows_ClientCredentials) isOAuthFlows_Flow() {} + +func (*OAuthFlows_Implicit) isOAuthFlows_Flow() {} + +func (*OAuthFlows_Password) isOAuthFlows_Flow() {} + +type AuthorizationCodeOAuthFlow struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The authorization URL to be used for this flow. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS + AuthorizationUrl string `protobuf:"bytes,1,opt,name=authorization_url,json=authorizationUrl,proto3" json:"authorization_url,omitempty"` + // The token URL to be used for this flow. This MUST be in the form of a URL. + // The OAuth2 standard requires the use of TLS. + TokenUrl string `protobuf:"bytes,2,opt,name=token_url,json=tokenUrl,proto3" json:"token_url,omitempty"` + // The URL to be used for obtaining refresh tokens. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS. + RefreshUrl string `protobuf:"bytes,3,opt,name=refresh_url,json=refreshUrl,proto3" json:"refresh_url,omitempty"` + // The available scopes for the OAuth2 security scheme. A map between the + // scope name and a short description for it. The map MAY be empty. + Scopes map[string]string `protobuf:"bytes,4,rep,name=scopes,proto3" json:"scopes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthorizationCodeOAuthFlow) Reset() { + *x = AuthorizationCodeOAuthFlow{} + mi := &file_a2a_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthorizationCodeOAuthFlow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizationCodeOAuthFlow) ProtoMessage() {} + +func (x *AuthorizationCodeOAuthFlow) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizationCodeOAuthFlow.ProtoReflect.Descriptor instead. +func (*AuthorizationCodeOAuthFlow) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{29} +} + +func (x *AuthorizationCodeOAuthFlow) GetAuthorizationUrl() string { + if x != nil { + return x.AuthorizationUrl + } + return "" +} + +func (x *AuthorizationCodeOAuthFlow) GetTokenUrl() string { + if x != nil { + return x.TokenUrl + } + return "" +} + +func (x *AuthorizationCodeOAuthFlow) GetRefreshUrl() string { + if x != nil { + return x.RefreshUrl + } + return "" +} + +func (x *AuthorizationCodeOAuthFlow) GetScopes() map[string]string { + if x != nil { + return x.Scopes + } + return nil +} + +type ClientCredentialsOAuthFlow struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The token URL to be used for this flow. This MUST be in the form of a URL. + // The OAuth2 standard requires the use of TLS. + TokenUrl string `protobuf:"bytes,1,opt,name=token_url,json=tokenUrl,proto3" json:"token_url,omitempty"` + // The URL to be used for obtaining refresh tokens. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS. + RefreshUrl string `protobuf:"bytes,2,opt,name=refresh_url,json=refreshUrl,proto3" json:"refresh_url,omitempty"` + // The available scopes for the OAuth2 security scheme. A map between the + // scope name and a short description for it. The map MAY be empty. + Scopes map[string]string `protobuf:"bytes,3,rep,name=scopes,proto3" json:"scopes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientCredentialsOAuthFlow) Reset() { + *x = ClientCredentialsOAuthFlow{} + mi := &file_a2a_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientCredentialsOAuthFlow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientCredentialsOAuthFlow) ProtoMessage() {} + +func (x *ClientCredentialsOAuthFlow) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientCredentialsOAuthFlow.ProtoReflect.Descriptor instead. +func (*ClientCredentialsOAuthFlow) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{30} +} + +func (x *ClientCredentialsOAuthFlow) GetTokenUrl() string { + if x != nil { + return x.TokenUrl + } + return "" +} + +func (x *ClientCredentialsOAuthFlow) GetRefreshUrl() string { + if x != nil { + return x.RefreshUrl + } + return "" +} + +func (x *ClientCredentialsOAuthFlow) GetScopes() map[string]string { + if x != nil { + return x.Scopes + } + return nil +} + +type ImplicitOAuthFlow struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The authorization URL to be used for this flow. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS + AuthorizationUrl string `protobuf:"bytes,1,opt,name=authorization_url,json=authorizationUrl,proto3" json:"authorization_url,omitempty"` + // The URL to be used for obtaining refresh tokens. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS. + RefreshUrl string `protobuf:"bytes,2,opt,name=refresh_url,json=refreshUrl,proto3" json:"refresh_url,omitempty"` + // The available scopes for the OAuth2 security scheme. A map between the + // scope name and a short description for it. The map MAY be empty. + Scopes map[string]string `protobuf:"bytes,3,rep,name=scopes,proto3" json:"scopes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ImplicitOAuthFlow) Reset() { + *x = ImplicitOAuthFlow{} + mi := &file_a2a_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ImplicitOAuthFlow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImplicitOAuthFlow) ProtoMessage() {} + +func (x *ImplicitOAuthFlow) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImplicitOAuthFlow.ProtoReflect.Descriptor instead. +func (*ImplicitOAuthFlow) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{31} +} + +func (x *ImplicitOAuthFlow) GetAuthorizationUrl() string { + if x != nil { + return x.AuthorizationUrl + } + return "" +} + +func (x *ImplicitOAuthFlow) GetRefreshUrl() string { + if x != nil { + return x.RefreshUrl + } + return "" +} + +func (x *ImplicitOAuthFlow) GetScopes() map[string]string { + if x != nil { + return x.Scopes + } + return nil +} + +type PasswordOAuthFlow struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The token URL to be used for this flow. This MUST be in the form of a URL. + // The OAuth2 standard requires the use of TLS. + TokenUrl string `protobuf:"bytes,1,opt,name=token_url,json=tokenUrl,proto3" json:"token_url,omitempty"` + // The URL to be used for obtaining refresh tokens. This MUST be in the + // form of a URL. The OAuth2 standard requires the use of TLS. + RefreshUrl string `protobuf:"bytes,2,opt,name=refresh_url,json=refreshUrl,proto3" json:"refresh_url,omitempty"` + // The available scopes for the OAuth2 security scheme. A map between the + // scope name and a short description for it. The map MAY be empty. + Scopes map[string]string `protobuf:"bytes,3,rep,name=scopes,proto3" json:"scopes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PasswordOAuthFlow) Reset() { + *x = PasswordOAuthFlow{} + mi := &file_a2a_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PasswordOAuthFlow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PasswordOAuthFlow) ProtoMessage() {} + +func (x *PasswordOAuthFlow) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PasswordOAuthFlow.ProtoReflect.Descriptor instead. +func (*PasswordOAuthFlow) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{32} +} + +func (x *PasswordOAuthFlow) GetTokenUrl() string { + if x != nil { + return x.TokenUrl + } + return "" +} + +func (x *PasswordOAuthFlow) GetRefreshUrl() string { + if x != nil { + return x.RefreshUrl + } + return "" +} + +func (x *PasswordOAuthFlow) GetScopes() map[string]string { + if x != nil { + return x.Scopes + } + return nil +} + +// /////////// Request Messages /////////// +type SendMessageRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The message to send to the agent. + Request *Message `protobuf:"bytes,1,opt,name=request,json=message,proto3" json:"request,omitempty"` + // Configuration for the send request. + Configuration *SendMessageConfiguration `protobuf:"bytes,2,opt,name=configuration,proto3" json:"configuration,omitempty"` + // Optional metadata for the request. + Metadata *structpb.Struct `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendMessageRequest) Reset() { + *x = SendMessageRequest{} + mi := &file_a2a_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendMessageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendMessageRequest) ProtoMessage() {} + +func (x *SendMessageRequest) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[33] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendMessageRequest.ProtoReflect.Descriptor instead. +func (*SendMessageRequest) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{33} +} + +func (x *SendMessageRequest) GetRequest() *Message { + if x != nil { + return x.Request + } + return nil +} + +func (x *SendMessageRequest) GetConfiguration() *SendMessageConfiguration { + if x != nil { + return x.Configuration + } + return nil +} + +func (x *SendMessageRequest) GetMetadata() *structpb.Struct { + if x != nil { + return x.Metadata + } + return nil +} + +type GetTaskRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The resource name of the task. + // Format: tasks/{task_id} + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The number of most recent messages from the task's history to retrieve. + HistoryLength int32 `protobuf:"varint,2,opt,name=history_length,json=historyLength,proto3" json:"history_length,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetTaskRequest) Reset() { + *x = GetTaskRequest{} + mi := &file_a2a_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTaskRequest) ProtoMessage() {} + +func (x *GetTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[34] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTaskRequest.ProtoReflect.Descriptor instead. +func (*GetTaskRequest) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{34} +} + +func (x *GetTaskRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetTaskRequest) GetHistoryLength() int32 { + if x != nil { + return x.HistoryLength + } + return 0 +} + +type CancelTaskRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The resource name of the task to cancel. + // Format: tasks/{task_id} + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CancelTaskRequest) Reset() { + *x = CancelTaskRequest{} + mi := &file_a2a_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CancelTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CancelTaskRequest) ProtoMessage() {} + +func (x *CancelTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[35] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CancelTaskRequest.ProtoReflect.Descriptor instead. +func (*CancelTaskRequest) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{35} +} + +func (x *CancelTaskRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ListTasksRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Optional filter by context ID + ContextId string `protobuf:"bytes,1,opt,name=context_id,json=contextId,proto3" json:"context_id,omitempty"` + // Optional filter by task state + States []TaskState `protobuf:"varint,2,rep,packed,name=states,proto3,enum=a2a.v1.TaskState" json:"states,omitempty"` + // Maximum number of tasks to return + PageSize int32 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Token for pagination + PageToken string `protobuf:"bytes,4,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTasksRequest) Reset() { + *x = ListTasksRequest{} + mi := &file_a2a_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTasksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTasksRequest) ProtoMessage() {} + +func (x *ListTasksRequest) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTasksRequest.ProtoReflect.Descriptor instead. +func (*ListTasksRequest) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{36} +} + +func (x *ListTasksRequest) GetContextId() string { + if x != nil { + return x.ContextId + } + return "" +} + +func (x *ListTasksRequest) GetStates() []TaskState { + if x != nil { + return x.States + } + return nil +} + +func (x *ListTasksRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListTasksRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +type ListTasksResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The list of tasks + Tasks []*Task `protobuf:"bytes,1,rep,name=tasks,proto3" json:"tasks,omitempty"` + // Token for next page (if more tasks available) + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTasksResponse) Reset() { + *x = ListTasksResponse{} + mi := &file_a2a_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTasksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTasksResponse) ProtoMessage() {} + +func (x *ListTasksResponse) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[37] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTasksResponse.ProtoReflect.Descriptor instead. +func (*ListTasksResponse) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{37} +} + +func (x *ListTasksResponse) GetTasks() []*Task { + if x != nil { + return x.Tasks + } + return nil +} + +func (x *ListTasksResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +type GetTaskPushNotificationConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The resource name of the config to retrieve. + // Format: tasks/{task_id}/pushNotificationConfigs/{config_id} + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetTaskPushNotificationConfigRequest) Reset() { + *x = GetTaskPushNotificationConfigRequest{} + mi := &file_a2a_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetTaskPushNotificationConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTaskPushNotificationConfigRequest) ProtoMessage() {} + +func (x *GetTaskPushNotificationConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[38] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTaskPushNotificationConfigRequest.ProtoReflect.Descriptor instead. +func (*GetTaskPushNotificationConfigRequest) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{38} +} + +func (x *GetTaskPushNotificationConfigRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type DeleteTaskPushNotificationConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The resource name of the config to delete. + // Format: tasks/{task_id}/pushNotificationConfigs/{config_id} + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteTaskPushNotificationConfigRequest) Reset() { + *x = DeleteTaskPushNotificationConfigRequest{} + mi := &file_a2a_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteTaskPushNotificationConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTaskPushNotificationConfigRequest) ProtoMessage() {} + +func (x *DeleteTaskPushNotificationConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[39] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTaskPushNotificationConfigRequest.ProtoReflect.Descriptor instead. +func (*DeleteTaskPushNotificationConfigRequest) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{39} +} + +func (x *DeleteTaskPushNotificationConfigRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type CreateTaskPushNotificationConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The parent task resource for this config. + // Format: tasks/{task_id} + Parent string `protobuf:"bytes,1,opt,name=parent,proto3" json:"parent,omitempty"` + // The ID for the new config. + ConfigId string `protobuf:"bytes,2,opt,name=config_id,json=configId,proto3" json:"config_id,omitempty"` + // The configuration to create. + Config *TaskPushNotificationConfig `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateTaskPushNotificationConfigRequest) Reset() { + *x = CreateTaskPushNotificationConfigRequest{} + mi := &file_a2a_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateTaskPushNotificationConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTaskPushNotificationConfigRequest) ProtoMessage() {} + +func (x *CreateTaskPushNotificationConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[40] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTaskPushNotificationConfigRequest.ProtoReflect.Descriptor instead. +func (*CreateTaskPushNotificationConfigRequest) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{40} +} + +func (x *CreateTaskPushNotificationConfigRequest) GetParent() string { + if x != nil { + return x.Parent + } + return "" +} + +func (x *CreateTaskPushNotificationConfigRequest) GetConfigId() string { + if x != nil { + return x.ConfigId + } + return "" +} + +func (x *CreateTaskPushNotificationConfigRequest) GetConfig() *TaskPushNotificationConfig { + if x != nil { + return x.Config + } + return nil +} + +type TaskSubscriptionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The resource name of the task to subscribe to. + // Format: tasks/{task_id} + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaskSubscriptionRequest) Reset() { + *x = TaskSubscriptionRequest{} + mi := &file_a2a_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaskSubscriptionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaskSubscriptionRequest) ProtoMessage() {} + +func (x *TaskSubscriptionRequest) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[41] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaskSubscriptionRequest.ProtoReflect.Descriptor instead. +func (*TaskSubscriptionRequest) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{41} +} + +func (x *TaskSubscriptionRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ListTaskPushNotificationConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The parent task resource. + // Format: tasks/{task_id} + Parent string `protobuf:"bytes,1,opt,name=parent,proto3" json:"parent,omitempty"` + // For AIP-158 these fields are present. Usually not used/needed. + // The maximum number of configurations to return. + // If unspecified, all configs will be returned. + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // A page token received from a previous + // ListTaskPushNotificationConfigRequest call. + // Provide this to retrieve the subsequent page. + // When paginating, all other parameters provided to + // `ListTaskPushNotificationConfigRequest` must match the call that provided + // the page token. + PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTaskPushNotificationConfigRequest) Reset() { + *x = ListTaskPushNotificationConfigRequest{} + mi := &file_a2a_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTaskPushNotificationConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTaskPushNotificationConfigRequest) ProtoMessage() {} + +func (x *ListTaskPushNotificationConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[42] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTaskPushNotificationConfigRequest.ProtoReflect.Descriptor instead. +func (*ListTaskPushNotificationConfigRequest) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{42} +} + +func (x *ListTaskPushNotificationConfigRequest) GetParent() string { + if x != nil { + return x.Parent + } + return "" +} + +func (x *ListTaskPushNotificationConfigRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListTaskPushNotificationConfigRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +type GetAgentCardRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAgentCardRequest) Reset() { + *x = GetAgentCardRequest{} + mi := &file_a2a_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgentCardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgentCardRequest) ProtoMessage() {} + +func (x *GetAgentCardRequest) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[43] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAgentCardRequest.ProtoReflect.Descriptor instead. +func (*GetAgentCardRequest) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{43} +} + +// ////// Response Messages /////////// +type SendMessageResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *SendMessageResponse_Task + // *SendMessageResponse_Msg + Payload isSendMessageResponse_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendMessageResponse) Reset() { + *x = SendMessageResponse{} + mi := &file_a2a_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendMessageResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendMessageResponse) ProtoMessage() {} + +func (x *SendMessageResponse) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[44] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendMessageResponse.ProtoReflect.Descriptor instead. +func (*SendMessageResponse) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{44} +} + +func (x *SendMessageResponse) GetPayload() isSendMessageResponse_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *SendMessageResponse) GetTask() *Task { + if x != nil { + if x, ok := x.Payload.(*SendMessageResponse_Task); ok { + return x.Task + } + } + return nil +} + +func (x *SendMessageResponse) GetMsg() *Message { + if x != nil { + if x, ok := x.Payload.(*SendMessageResponse_Msg); ok { + return x.Msg + } + } + return nil +} + +type isSendMessageResponse_Payload interface { + isSendMessageResponse_Payload() +} + +type SendMessageResponse_Task struct { + Task *Task `protobuf:"bytes,1,opt,name=task,proto3,oneof"` +} + +type SendMessageResponse_Msg struct { + Msg *Message `protobuf:"bytes,2,opt,name=msg,json=message,proto3,oneof"` +} + +func (*SendMessageResponse_Task) isSendMessageResponse_Payload() {} + +func (*SendMessageResponse_Msg) isSendMessageResponse_Payload() {} + +// The stream response for a message. The stream should be one of the following +// sequences: +// If the response is a message, the stream should contain one, and only one, +// message and then close +// If the response is a task lifecycle, the first response should be a Task +// object followed by zero or more TaskStatusUpdateEvents and +// TaskArtifactUpdateEvents. The stream should complete when the Task +// if in an interrupted or terminal state. A stream that ends before these +// conditions are met are +type StreamResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Payload: + // + // *StreamResponse_Task + // *StreamResponse_Msg + // *StreamResponse_StatusUpdate + // *StreamResponse_ArtifactUpdate + Payload isStreamResponse_Payload `protobuf_oneof:"payload"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StreamResponse) Reset() { + *x = StreamResponse{} + mi := &file_a2a_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StreamResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StreamResponse) ProtoMessage() {} + +func (x *StreamResponse) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[45] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StreamResponse.ProtoReflect.Descriptor instead. +func (*StreamResponse) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{45} +} + +func (x *StreamResponse) GetPayload() isStreamResponse_Payload { + if x != nil { + return x.Payload + } + return nil +} + +func (x *StreamResponse) GetTask() *Task { + if x != nil { + if x, ok := x.Payload.(*StreamResponse_Task); ok { + return x.Task + } + } + return nil +} + +func (x *StreamResponse) GetMsg() *Message { + if x != nil { + if x, ok := x.Payload.(*StreamResponse_Msg); ok { + return x.Msg + } + } + return nil +} + +func (x *StreamResponse) GetStatusUpdate() *TaskStatusUpdateEvent { + if x != nil { + if x, ok := x.Payload.(*StreamResponse_StatusUpdate); ok { + return x.StatusUpdate + } + } + return nil +} + +func (x *StreamResponse) GetArtifactUpdate() *TaskArtifactUpdateEvent { + if x != nil { + if x, ok := x.Payload.(*StreamResponse_ArtifactUpdate); ok { + return x.ArtifactUpdate + } + } + return nil +} + +type isStreamResponse_Payload interface { + isStreamResponse_Payload() +} + +type StreamResponse_Task struct { + Task *Task `protobuf:"bytes,1,opt,name=task,proto3,oneof"` +} + +type StreamResponse_Msg struct { + Msg *Message `protobuf:"bytes,2,opt,name=msg,json=message,proto3,oneof"` +} + +type StreamResponse_StatusUpdate struct { + StatusUpdate *TaskStatusUpdateEvent `protobuf:"bytes,3,opt,name=status_update,json=statusUpdate,proto3,oneof"` +} + +type StreamResponse_ArtifactUpdate struct { + ArtifactUpdate *TaskArtifactUpdateEvent `protobuf:"bytes,4,opt,name=artifact_update,json=artifactUpdate,proto3,oneof"` +} + +func (*StreamResponse_Task) isStreamResponse_Payload() {} + +func (*StreamResponse_Msg) isStreamResponse_Payload() {} + +func (*StreamResponse_StatusUpdate) isStreamResponse_Payload() {} + +func (*StreamResponse_ArtifactUpdate) isStreamResponse_Payload() {} + +type ListTaskPushNotificationConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The list of push notification configurations. + Configs []*TaskPushNotificationConfig `protobuf:"bytes,1,rep,name=configs,proto3" json:"configs,omitempty"` + // A token, which can be sent as `page_token` to retrieve the next page. + // If this field is omitted, there are no subsequent pages. + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTaskPushNotificationConfigResponse) Reset() { + *x = ListTaskPushNotificationConfigResponse{} + mi := &file_a2a_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTaskPushNotificationConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTaskPushNotificationConfigResponse) ProtoMessage() {} + +func (x *ListTaskPushNotificationConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_a2a_proto_msgTypes[46] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTaskPushNotificationConfigResponse.ProtoReflect.Descriptor instead. +func (*ListTaskPushNotificationConfigResponse) Descriptor() ([]byte, []int) { + return file_a2a_proto_rawDescGZIP(), []int{46} +} + +func (x *ListTaskPushNotificationConfigResponse) GetConfigs() []*TaskPushNotificationConfig { + if x != nil { + return x.Configs + } + return nil +} + +func (x *ListTaskPushNotificationConfigResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +var File_a2a_proto protoreflect.FileDescriptor + +const file_a2a_proto_rawDesc = "" + + "\n" + + "\ta2a.proto\x12\x06a2a.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xde\x01\n" + + "\x18SendMessageConfiguration\x122\n" + + "\x15accepted_output_modes\x18\x01 \x03(\tR\x13acceptedOutputModes\x12K\n" + + "\x11push_notification\x18\x02 \x01(\v2\x1e.a2a.v1.PushNotificationConfigR\x10pushNotification\x12%\n" + + "\x0ehistory_length\x18\x03 \x01(\x05R\rhistoryLength\x12\x1a\n" + + "\bblocking\x18\x04 \x01(\bR\bblocking\"\x85\x02\n" + + "\x04Task\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n" + + "\n" + + "context_id\x18\x02 \x01(\tR\tcontextId\x12*\n" + + "\x06status\x18\x03 \x01(\v2\x12.a2a.v1.TaskStatusR\x06status\x12.\n" + + "\tartifacts\x18\x04 \x03(\v2\x10.a2a.v1.ArtifactR\tartifacts\x12)\n" + + "\ahistory\x18\x05 \x03(\v2\x0f.a2a.v1.MessageR\ahistory\x12\x12\n" + + "\x04kind\x18\x06 \x01(\tR\x04kind\x123\n" + + "\bmetadata\x18\a \x01(\v2\x17.google.protobuf.StructR\bmetadata\"\x99\x01\n" + + "\n" + + "TaskStatus\x12'\n" + + "\x05state\x18\x01 \x01(\x0e2\x11.a2a.v1.TaskStateR\x05state\x12(\n" + + "\x06update\x18\x02 \x01(\v2\x0f.a2a.v1.MessageR\amessage\x128\n" + + "\ttimestamp\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\"\xa9\x01\n" + + "\x04Part\x12\x14\n" + + "\x04text\x18\x01 \x01(\tH\x00R\x04text\x12&\n" + + "\x04file\x18\x02 \x01(\v2\x10.a2a.v1.FilePartH\x00R\x04file\x12&\n" + + "\x04data\x18\x03 \x01(\v2\x10.a2a.v1.DataPartH\x00R\x04data\x123\n" + + "\bmetadata\x18\x04 \x01(\v2\x17.google.protobuf.StructR\bmetadataB\x06\n" + + "\x04part\"\x93\x01\n" + + "\bFilePart\x12$\n" + + "\rfile_with_uri\x18\x01 \x01(\tH\x00R\vfileWithUri\x12(\n" + + "\x0ffile_with_bytes\x18\x02 \x01(\fH\x00R\rfileWithBytes\x12\x1b\n" + + "\tmime_type\x18\x03 \x01(\tR\bmimeType\x12\x12\n" + + "\x04name\x18\x04 \x01(\tR\x04nameB\x06\n" + + "\x04file\"7\n" + + "\bDataPart\x12+\n" + + "\x04data\x18\x01 \x01(\v2\x17.google.protobuf.StructR\x04data\"\x93\x02\n" + + "\aMessage\x12\x1d\n" + + "\n" + + "message_id\x18\x01 \x01(\tR\tmessageId\x12\x1d\n" + + "\n" + + "context_id\x18\x02 \x01(\tR\tcontextId\x12\x17\n" + + "\atask_id\x18\x03 \x01(\tR\x06taskId\x12 \n" + + "\x04role\x18\x04 \x01(\x0e2\f.a2a.v1.RoleR\x04role\x12&\n" + + "\acontent\x18\x05 \x03(\v2\f.a2a.v1.PartR\acontent\x12\x12\n" + + "\x04kind\x18\x06 \x01(\tR\x04kind\x123\n" + + "\bmetadata\x18\a \x01(\v2\x17.google.protobuf.StructR\bmetadata\x12\x1e\n" + + "\n" + + "extensions\x18\b \x03(\tR\n" + + "extensions\"\xda\x01\n" + + "\bArtifact\x12\x1f\n" + + "\vartifact_id\x18\x01 \x01(\tR\n" + + "artifactId\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12 \n" + + "\vdescription\x18\x04 \x01(\tR\vdescription\x12\"\n" + + "\x05parts\x18\x05 \x03(\v2\f.a2a.v1.PartR\x05parts\x123\n" + + "\bmetadata\x18\x06 \x01(\v2\x17.google.protobuf.StructR\bmetadata\x12\x1e\n" + + "\n" + + "extensions\x18\a \x03(\tR\n" + + "extensions\"\xc6\x01\n" + + "\x15TaskStatusUpdateEvent\x12\x17\n" + + "\atask_id\x18\x01 \x01(\tR\x06taskId\x12\x1d\n" + + "\n" + + "context_id\x18\x02 \x01(\tR\tcontextId\x12*\n" + + "\x06status\x18\x03 \x01(\v2\x12.a2a.v1.TaskStatusR\x06status\x12\x14\n" + + "\x05final\x18\x04 \x01(\bR\x05final\x123\n" + + "\bmetadata\x18\x05 \x01(\v2\x17.google.protobuf.StructR\bmetadata\"\xeb\x01\n" + + "\x17TaskArtifactUpdateEvent\x12\x17\n" + + "\atask_id\x18\x01 \x01(\tR\x06taskId\x12\x1d\n" + + "\n" + + "context_id\x18\x02 \x01(\tR\tcontextId\x12,\n" + + "\bartifact\x18\x03 \x01(\v2\x10.a2a.v1.ArtifactR\bartifact\x12\x16\n" + + "\x06append\x18\x04 \x01(\bR\x06append\x12\x1d\n" + + "\n" + + "last_chunk\x18\x05 \x01(\bR\tlastChunk\x123\n" + + "\bmetadata\x18\x06 \x01(\v2\x17.google.protobuf.StructR\bmetadata\"\x94\x01\n" + + "\x16PushNotificationConfig\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + + "\x03url\x18\x02 \x01(\tR\x03url\x12\x14\n" + + "\x05token\x18\x03 \x01(\tR\x05token\x12B\n" + + "\x0eauthentication\x18\x04 \x01(\v2\x1a.a2a.v1.AuthenticationInfoR\x0eauthentication\"P\n" + + "\x12AuthenticationInfo\x12\x18\n" + + "\aschemes\x18\x01 \x03(\tR\aschemes\x12 \n" + + "\vcredentials\x18\x02 \x01(\tR\vcredentials\"@\n" + + "\x0eAgentInterface\x12\x10\n" + + "\x03url\x18\x01 \x01(\tR\x03url\x12\x1c\n" + + "\ttransport\x18\x02 \x01(\tR\ttransport\"\xc8\a\n" + + "\tAgentCard\x12)\n" + + "\x10protocol_version\x18\x10 \x01(\tR\x0fprotocolVersion\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12 \n" + + "\vdescription\x18\x02 \x01(\tR\vdescription\x12\x10\n" + + "\x03url\x18\x03 \x01(\tR\x03url\x12/\n" + + "\x13preferred_transport\x18\x0e \x01(\tR\x12preferredTransport\x12K\n" + + "\x15additional_interfaces\x18\x0f \x03(\v2\x16.a2a.v1.AgentInterfaceR\x14additionalInterfaces\x121\n" + + "\bprovider\x18\x04 \x01(\v2\x15.a2a.v1.AgentProviderR\bprovider\x12\x18\n" + + "\aversion\x18\x05 \x01(\tR\aversion\x12+\n" + + "\x11documentation_url\x18\x06 \x01(\tR\x10documentationUrl\x12=\n" + + "\fcapabilities\x18\a \x01(\v2\x19.a2a.v1.AgentCapabilitiesR\fcapabilities\x12Q\n" + + "\x10security_schemes\x18\b \x03(\v2&.a2a.v1.AgentCard.SecuritySchemesEntryR\x0fsecuritySchemes\x12,\n" + + "\bsecurity\x18\t \x03(\v2\x10.a2a.v1.SecurityR\bsecurity\x12.\n" + + "\x13default_input_modes\x18\n" + + " \x03(\tR\x11defaultInputModes\x120\n" + + "\x14default_output_modes\x18\v \x03(\tR\x12defaultOutputModes\x12*\n" + + "\x06skills\x18\f \x03(\v2\x12.a2a.v1.AgentSkillR\x06skills\x12O\n" + + "$supports_authenticated_extended_card\x18\r \x01(\bR!supportsAuthenticatedExtendedCard\x12:\n" + + "\n" + + "signatures\x18\x11 \x03(\v2\x1a.a2a.v1.AgentCardSignatureR\n" + + "signatures\x12\x19\n" + + "\bicon_url\x18\x12 \x01(\tR\aiconUrl\x1aZ\n" + + "\x14SecuritySchemesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12,\n" + + "\x05value\x18\x02 \x01(\v2\x16.a2a.v1.SecuritySchemeR\x05value:\x028\x01\"E\n" + + "\rAgentProvider\x12\x10\n" + + "\x03url\x18\x01 \x01(\tR\x03url\x12\"\n" + + "\forganization\x18\x02 \x01(\tR\forganization\"\xd2\x01\n" + + "\x11AgentCapabilities\x12\x1c\n" + + "\tstreaming\x18\x01 \x01(\bR\tstreaming\x12-\n" + + "\x12push_notifications\x18\x02 \x01(\bR\x11pushNotifications\x128\n" + + "\x18state_transition_history\x18\x03 \x01(\bR\x16stateTransitionHistory\x126\n" + + "\n" + + "extensions\x18\x04 \x03(\v2\x16.a2a.v1.AgentExtensionR\n" + + "extensions\"\x91\x01\n" + + "\x0eAgentExtension\x12\x10\n" + + "\x03uri\x18\x01 \x01(\tR\x03uri\x12 \n" + + "\vdescription\x18\x02 \x01(\tR\vdescription\x12\x1a\n" + + "\brequired\x18\x03 \x01(\bR\brequired\x12/\n" + + "\x06params\x18\x04 \x01(\v2\x17.google.protobuf.StructR\x06params\"\xf4\x01\n" + + "\n" + + "AgentSkill\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + + "\vdescription\x18\x03 \x01(\tR\vdescription\x12\x12\n" + + "\x04tags\x18\x04 \x03(\tR\x04tags\x12\x1a\n" + + "\bexamples\x18\x05 \x03(\tR\bexamples\x12\x1f\n" + + "\vinput_modes\x18\x06 \x03(\tR\n" + + "inputModes\x12!\n" + + "\foutput_modes\x18\a \x03(\tR\voutputModes\x12,\n" + + "\bsecurity\x18\b \x03(\v2\x10.a2a.v1.SecurityR\bsecurity\"\x8b\x01\n" + + "\x12AgentCardSignature\x12!\n" + + "\tprotected\x18\x01 \x01(\tB\x03\xe0A\x02R\tprotected\x12!\n" + + "\tsignature\x18\x02 \x01(\tB\x03\xe0A\x02R\tsignature\x12/\n" + + "\x06header\x18\x03 \x01(\v2\x17.google.protobuf.StructR\x06header\"\x8a\x01\n" + + "\x1aTaskPushNotificationConfig\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12X\n" + + "\x18push_notification_config\x18\x02 \x01(\v2\x1e.a2a.v1.PushNotificationConfigR\x16pushNotificationConfig\" \n" + + "\n" + + "StringList\x12\x12\n" + + "\x04list\x18\x01 \x03(\tR\x04list\"\x93\x01\n" + + "\bSecurity\x127\n" + + "\aschemes\x18\x01 \x03(\v2\x1d.a2a.v1.Security.SchemesEntryR\aschemes\x1aN\n" + + "\fSchemesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12(\n" + + "\x05value\x18\x02 \x01(\v2\x12.a2a.v1.StringListR\x05value:\x028\x01\"\xe6\x03\n" + + "\x0eSecurityScheme\x12U\n" + + "\x17api_key_security_scheme\x18\x01 \x01(\v2\x1c.a2a.v1.APIKeySecuritySchemeH\x00R\x14apiKeySecurityScheme\x12[\n" + + "\x19http_auth_security_scheme\x18\x02 \x01(\v2\x1e.a2a.v1.HTTPAuthSecuritySchemeH\x00R\x16httpAuthSecurityScheme\x12T\n" + + "\x16oauth2_security_scheme\x18\x03 \x01(\v2\x1c.a2a.v1.OAuth2SecuritySchemeH\x00R\x14oauth2SecurityScheme\x12k\n" + + "\x1fopen_id_connect_security_scheme\x18\x04 \x01(\v2#.a2a.v1.OpenIdConnectSecuritySchemeH\x00R\x1bopenIdConnectSecurityScheme\x12S\n" + + "\x14mtls_security_scheme\x18\x05 \x01(\v2\x1f.a2a.v1.MutualTlsSecuritySchemeH\x00R\x12mtlsSecuritySchemeB\b\n" + + "\x06scheme\"h\n" + + "\x14APIKeySecurityScheme\x12 \n" + + "\vdescription\x18\x01 \x01(\tR\vdescription\x12\x1a\n" + + "\blocation\x18\x02 \x01(\tR\blocation\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\"w\n" + + "\x16HTTPAuthSecurityScheme\x12 \n" + + "\vdescription\x18\x01 \x01(\tR\vdescription\x12\x16\n" + + "\x06scheme\x18\x02 \x01(\tR\x06scheme\x12#\n" + + "\rbearer_format\x18\x03 \x01(\tR\fbearerFormat\"\x92\x01\n" + + "\x14OAuth2SecurityScheme\x12 \n" + + "\vdescription\x18\x01 \x01(\tR\vdescription\x12(\n" + + "\x05flows\x18\x02 \x01(\v2\x12.a2a.v1.OAuthFlowsR\x05flows\x12.\n" + + "\x13oauth2_metadata_url\x18\x03 \x01(\tR\x11oauth2MetadataUrl\"n\n" + + "\x1bOpenIdConnectSecurityScheme\x12 \n" + + "\vdescription\x18\x01 \x01(\tR\vdescription\x12-\n" + + "\x13open_id_connect_url\x18\x02 \x01(\tR\x10openIdConnectUrl\";\n" + + "\x17MutualTlsSecurityScheme\x12 \n" + + "\vdescription\x18\x01 \x01(\tR\vdescription\"\xb0\x02\n" + + "\n" + + "OAuthFlows\x12S\n" + + "\x12authorization_code\x18\x01 \x01(\v2\".a2a.v1.AuthorizationCodeOAuthFlowH\x00R\x11authorizationCode\x12S\n" + + "\x12client_credentials\x18\x02 \x01(\v2\".a2a.v1.ClientCredentialsOAuthFlowH\x00R\x11clientCredentials\x127\n" + + "\bimplicit\x18\x03 \x01(\v2\x19.a2a.v1.ImplicitOAuthFlowH\x00R\bimplicit\x127\n" + + "\bpassword\x18\x04 \x01(\v2\x19.a2a.v1.PasswordOAuthFlowH\x00R\bpasswordB\x06\n" + + "\x04flow\"\x8a\x02\n" + + "\x1aAuthorizationCodeOAuthFlow\x12+\n" + + "\x11authorization_url\x18\x01 \x01(\tR\x10authorizationUrl\x12\x1b\n" + + "\ttoken_url\x18\x02 \x01(\tR\btokenUrl\x12\x1f\n" + + "\vrefresh_url\x18\x03 \x01(\tR\n" + + "refreshUrl\x12F\n" + + "\x06scopes\x18\x04 \x03(\v2..a2a.v1.AuthorizationCodeOAuthFlow.ScopesEntryR\x06scopes\x1a9\n" + + "\vScopesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xdd\x01\n" + + "\x1aClientCredentialsOAuthFlow\x12\x1b\n" + + "\ttoken_url\x18\x01 \x01(\tR\btokenUrl\x12\x1f\n" + + "\vrefresh_url\x18\x02 \x01(\tR\n" + + "refreshUrl\x12F\n" + + "\x06scopes\x18\x03 \x03(\v2..a2a.v1.ClientCredentialsOAuthFlow.ScopesEntryR\x06scopes\x1a9\n" + + "\vScopesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xdb\x01\n" + + "\x11ImplicitOAuthFlow\x12+\n" + + "\x11authorization_url\x18\x01 \x01(\tR\x10authorizationUrl\x12\x1f\n" + + "\vrefresh_url\x18\x02 \x01(\tR\n" + + "refreshUrl\x12=\n" + + "\x06scopes\x18\x03 \x03(\v2%.a2a.v1.ImplicitOAuthFlow.ScopesEntryR\x06scopes\x1a9\n" + + "\vScopesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xcb\x01\n" + + "\x11PasswordOAuthFlow\x12\x1b\n" + + "\ttoken_url\x18\x01 \x01(\tR\btokenUrl\x12\x1f\n" + + "\vrefresh_url\x18\x02 \x01(\tR\n" + + "refreshUrl\x12=\n" + + "\x06scopes\x18\x03 \x03(\v2%.a2a.v1.PasswordOAuthFlow.ScopesEntryR\x06scopes\x1a9\n" + + "\vScopesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc1\x01\n" + + "\x12SendMessageRequest\x12.\n" + + "\arequest\x18\x01 \x01(\v2\x0f.a2a.v1.MessageB\x03\xe0A\x02R\amessage\x12F\n" + + "\rconfiguration\x18\x02 \x01(\v2 .a2a.v1.SendMessageConfigurationR\rconfiguration\x123\n" + + "\bmetadata\x18\x03 \x01(\v2\x17.google.protobuf.StructR\bmetadata\"P\n" + + "\x0eGetTaskRequest\x12\x17\n" + + "\x04name\x18\x01 \x01(\tB\x03\xe0A\x02R\x04name\x12%\n" + + "\x0ehistory_length\x18\x02 \x01(\x05R\rhistoryLength\"'\n" + + "\x11CancelTaskRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"\x98\x01\n" + + "\x10ListTasksRequest\x12\x1d\n" + + "\n" + + "context_id\x18\x01 \x01(\tR\tcontextId\x12)\n" + + "\x06states\x18\x02 \x03(\x0e2\x11.a2a.v1.TaskStateR\x06states\x12\x1b\n" + + "\tpage_size\x18\x03 \x01(\x05R\bpageSize\x12\x1d\n" + + "\n" + + "page_token\x18\x04 \x01(\tR\tpageToken\"_\n" + + "\x11ListTasksResponse\x12\"\n" + + "\x05tasks\x18\x01 \x03(\v2\f.a2a.v1.TaskR\x05tasks\x12&\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\":\n" + + "$GetTaskPushNotificationConfigRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"=\n" + + "'DeleteTaskPushNotificationConfigRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"\xa9\x01\n" + + "'CreateTaskPushNotificationConfigRequest\x12\x1b\n" + + "\x06parent\x18\x01 \x01(\tB\x03\xe0A\x02R\x06parent\x12 \n" + + "\tconfig_id\x18\x02 \x01(\tB\x03\xe0A\x02R\bconfigId\x12?\n" + + "\x06config\x18\x03 \x01(\v2\".a2a.v1.TaskPushNotificationConfigB\x03\xe0A\x02R\x06config\"-\n" + + "\x17TaskSubscriptionRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"{\n" + + "%ListTaskPushNotificationConfigRequest\x12\x16\n" + + "\x06parent\x18\x01 \x01(\tR\x06parent\x12\x1b\n" + + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x1d\n" + + "\n" + + "page_token\x18\x03 \x01(\tR\tpageToken\"\x15\n" + + "\x13GetAgentCardRequest\"m\n" + + "\x13SendMessageResponse\x12\"\n" + + "\x04task\x18\x01 \x01(\v2\f.a2a.v1.TaskH\x00R\x04task\x12'\n" + + "\x03msg\x18\x02 \x01(\v2\x0f.a2a.v1.MessageH\x00R\amessageB\t\n" + + "\apayload\"\xfa\x01\n" + + "\x0eStreamResponse\x12\"\n" + + "\x04task\x18\x01 \x01(\v2\f.a2a.v1.TaskH\x00R\x04task\x12'\n" + + "\x03msg\x18\x02 \x01(\v2\x0f.a2a.v1.MessageH\x00R\amessage\x12D\n" + + "\rstatus_update\x18\x03 \x01(\v2\x1d.a2a.v1.TaskStatusUpdateEventH\x00R\fstatusUpdate\x12J\n" + + "\x0fartifact_update\x18\x04 \x01(\v2\x1f.a2a.v1.TaskArtifactUpdateEventH\x00R\x0eartifactUpdateB\t\n" + + "\apayload\"\x8e\x01\n" + + "&ListTaskPushNotificationConfigResponse\x12<\n" + + "\aconfigs\x18\x01 \x03(\v2\".a2a.v1.TaskPushNotificationConfigR\aconfigs\x12&\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken*\xfa\x01\n" + + "\tTaskState\x12\x1a\n" + + "\x16TASK_STATE_UNSPECIFIED\x10\x00\x12\x18\n" + + "\x14TASK_STATE_SUBMITTED\x10\x01\x12\x16\n" + + "\x12TASK_STATE_WORKING\x10\x02\x12\x18\n" + + "\x14TASK_STATE_COMPLETED\x10\x03\x12\x15\n" + + "\x11TASK_STATE_FAILED\x10\x04\x12\x18\n" + + "\x14TASK_STATE_CANCELLED\x10\x05\x12\x1d\n" + + "\x19TASK_STATE_INPUT_REQUIRED\x10\x06\x12\x17\n" + + "\x13TASK_STATE_REJECTED\x10\a\x12\x1c\n" + + "\x18TASK_STATE_AUTH_REQUIRED\x10\b*;\n" + + "\x04Role\x12\x14\n" + + "\x10ROLE_UNSPECIFIED\x10\x00\x12\r\n" + + "\tROLE_USER\x10\x01\x12\x0e\n" + + "\n" + + "ROLE_AGENT\x10\x022\xf8\n" + + "\n" + + "\n" + + "A2AService\x12c\n" + + "\vSendMessage\x12\x1a.a2a.v1.SendMessageRequest\x1a\x1b.a2a.v1.SendMessageResponse\"\x1b\x82\xd3\xe4\x93\x02\x15:\x01*\"\x10/v1/message:send\x12k\n" + + "\x14SendStreamingMessage\x12\x1a.a2a.v1.SendMessageRequest\x1a\x16.a2a.v1.StreamResponse\"\x1d\x82\xd3\xe4\x93\x02\x17:\x01*\"\x12/v1/message:stream0\x01\x12R\n" + + "\aGetTask\x12\x16.a2a.v1.GetTaskRequest\x1a\f.a2a.v1.Task\"!\xdaA\x04name\x82\xd3\xe4\x93\x02\x14\x12\x12/v1/{name=tasks/*}\x12[\n" + + "\n" + + "CancelTask\x12\x19.a2a.v1.CancelTaskRequest\x1a\f.a2a.v1.Task\"$\x82\xd3\xe4\x93\x02\x1e:\x01*\"\x19/v1/{name=tasks/*}:cancel\x12s\n" + + "\x10TaskSubscription\x12\x1f.a2a.v1.TaskSubscriptionRequest\x1a\x16.a2a.v1.StreamResponse\"$\x82\xd3\xe4\x93\x02\x1e\x12\x1c/v1/{name=tasks/*}:subscribe0\x01\x12S\n" + + "\tListTasks\x12\x18.a2a.v1.ListTasksRequest\x1a\x19.a2a.v1.ListTasksResponse\"\x11\x82\xd3\xe4\x93\x02\v\x12\t/v1/tasks\x12\xbf\x01\n" + + "\x1aCreateTaskPushNotification\x12/.a2a.v1.CreateTaskPushNotificationConfigRequest\x1a\".a2a.v1.TaskPushNotificationConfig\"L\xdaA\rparent,config\x82\xd3\xe4\x93\x026:\x06config\",/v1/{parent=tasks/*/pushNotificationConfigs}\x12\xa8\x01\n" + + "\x17GetTaskPushNotification\x12,.a2a.v1.GetTaskPushNotificationConfigRequest\x1a\".a2a.v1.TaskPushNotificationConfig\";\xdaA\x04name\x82\xd3\xe4\x93\x02.\x12,/v1/{name=tasks/*/pushNotificationConfigs/*}\x12\xb8\x01\n" + + "\x18ListTaskPushNotification\x12-.a2a.v1.ListTaskPushNotificationConfigRequest\x1a..a2a.v1.ListTaskPushNotificationConfigResponse\"=\xdaA\x06parent\x82\xd3\xe4\x93\x02.\x12,/v1/{parent=tasks/*}/pushNotificationConfigs\x12P\n" + + "\fGetAgentCard\x12\x1b.a2a.v1.GetAgentCardRequest\x1a\x11.a2a.v1.AgentCard\"\x10\x82\xd3\xe4\x93\x02\n" + + "\x12\b/v1/card\x12\xa2\x01\n" + + "\x1aDeleteTaskPushNotification\x12/.a2a.v1.DeleteTaskPushNotificationConfigRequest\x1a\x16.google.protobuf.Empty\";\xdaA\x04name\x82\xd3\xe4\x93\x02.*,/v1/{name=tasks/*/pushNotificationConfigs/*}B=\n" + + "\x11com.google.a2a.v1B\x03A2AP\x01Z\x18google.golang.org/a2a/v1\xaa\x02\x06A2a.V1b\x06proto3" + +var ( + file_a2a_proto_rawDescOnce sync.Once + file_a2a_proto_rawDescData []byte +) + +func file_a2a_proto_rawDescGZIP() []byte { + file_a2a_proto_rawDescOnce.Do(func() { + file_a2a_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_a2a_proto_rawDesc), len(file_a2a_proto_rawDesc))) + }) + return file_a2a_proto_rawDescData +} + +var file_a2a_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_a2a_proto_msgTypes = make([]protoimpl.MessageInfo, 53) +var file_a2a_proto_goTypes = []any{ + (TaskState)(0), // 0: a2a.v1.TaskState + (Role)(0), // 1: a2a.v1.Role + (*SendMessageConfiguration)(nil), // 2: a2a.v1.SendMessageConfiguration + (*Task)(nil), // 3: a2a.v1.Task + (*TaskStatus)(nil), // 4: a2a.v1.TaskStatus + (*Part)(nil), // 5: a2a.v1.Part + (*FilePart)(nil), // 6: a2a.v1.FilePart + (*DataPart)(nil), // 7: a2a.v1.DataPart + (*Message)(nil), // 8: a2a.v1.Message + (*Artifact)(nil), // 9: a2a.v1.Artifact + (*TaskStatusUpdateEvent)(nil), // 10: a2a.v1.TaskStatusUpdateEvent + (*TaskArtifactUpdateEvent)(nil), // 11: a2a.v1.TaskArtifactUpdateEvent + (*PushNotificationConfig)(nil), // 12: a2a.v1.PushNotificationConfig + (*AuthenticationInfo)(nil), // 13: a2a.v1.AuthenticationInfo + (*AgentInterface)(nil), // 14: a2a.v1.AgentInterface + (*AgentCard)(nil), // 15: a2a.v1.AgentCard + (*AgentProvider)(nil), // 16: a2a.v1.AgentProvider + (*AgentCapabilities)(nil), // 17: a2a.v1.AgentCapabilities + (*AgentExtension)(nil), // 18: a2a.v1.AgentExtension + (*AgentSkill)(nil), // 19: a2a.v1.AgentSkill + (*AgentCardSignature)(nil), // 20: a2a.v1.AgentCardSignature + (*TaskPushNotificationConfig)(nil), // 21: a2a.v1.TaskPushNotificationConfig + (*StringList)(nil), // 22: a2a.v1.StringList + (*Security)(nil), // 23: a2a.v1.Security + (*SecurityScheme)(nil), // 24: a2a.v1.SecurityScheme + (*APIKeySecurityScheme)(nil), // 25: a2a.v1.APIKeySecurityScheme + (*HTTPAuthSecurityScheme)(nil), // 26: a2a.v1.HTTPAuthSecurityScheme + (*OAuth2SecurityScheme)(nil), // 27: a2a.v1.OAuth2SecurityScheme + (*OpenIdConnectSecurityScheme)(nil), // 28: a2a.v1.OpenIdConnectSecurityScheme + (*MutualTlsSecurityScheme)(nil), // 29: a2a.v1.MutualTlsSecurityScheme + (*OAuthFlows)(nil), // 30: a2a.v1.OAuthFlows + (*AuthorizationCodeOAuthFlow)(nil), // 31: a2a.v1.AuthorizationCodeOAuthFlow + (*ClientCredentialsOAuthFlow)(nil), // 32: a2a.v1.ClientCredentialsOAuthFlow + (*ImplicitOAuthFlow)(nil), // 33: a2a.v1.ImplicitOAuthFlow + (*PasswordOAuthFlow)(nil), // 34: a2a.v1.PasswordOAuthFlow + (*SendMessageRequest)(nil), // 35: a2a.v1.SendMessageRequest + (*GetTaskRequest)(nil), // 36: a2a.v1.GetTaskRequest + (*CancelTaskRequest)(nil), // 37: a2a.v1.CancelTaskRequest + (*ListTasksRequest)(nil), // 38: a2a.v1.ListTasksRequest + (*ListTasksResponse)(nil), // 39: a2a.v1.ListTasksResponse + (*GetTaskPushNotificationConfigRequest)(nil), // 40: a2a.v1.GetTaskPushNotificationConfigRequest + (*DeleteTaskPushNotificationConfigRequest)(nil), // 41: a2a.v1.DeleteTaskPushNotificationConfigRequest + (*CreateTaskPushNotificationConfigRequest)(nil), // 42: a2a.v1.CreateTaskPushNotificationConfigRequest + (*TaskSubscriptionRequest)(nil), // 43: a2a.v1.TaskSubscriptionRequest + (*ListTaskPushNotificationConfigRequest)(nil), // 44: a2a.v1.ListTaskPushNotificationConfigRequest + (*GetAgentCardRequest)(nil), // 45: a2a.v1.GetAgentCardRequest + (*SendMessageResponse)(nil), // 46: a2a.v1.SendMessageResponse + (*StreamResponse)(nil), // 47: a2a.v1.StreamResponse + (*ListTaskPushNotificationConfigResponse)(nil), // 48: a2a.v1.ListTaskPushNotificationConfigResponse + nil, // 49: a2a.v1.AgentCard.SecuritySchemesEntry + nil, // 50: a2a.v1.Security.SchemesEntry + nil, // 51: a2a.v1.AuthorizationCodeOAuthFlow.ScopesEntry + nil, // 52: a2a.v1.ClientCredentialsOAuthFlow.ScopesEntry + nil, // 53: a2a.v1.ImplicitOAuthFlow.ScopesEntry + nil, // 54: a2a.v1.PasswordOAuthFlow.ScopesEntry + (*structpb.Struct)(nil), // 55: google.protobuf.Struct + (*timestamppb.Timestamp)(nil), // 56: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 57: google.protobuf.Empty +} +var file_a2a_proto_depIdxs = []int32{ + 12, // 0: a2a.v1.SendMessageConfiguration.push_notification:type_name -> a2a.v1.PushNotificationConfig + 4, // 1: a2a.v1.Task.status:type_name -> a2a.v1.TaskStatus + 9, // 2: a2a.v1.Task.artifacts:type_name -> a2a.v1.Artifact + 8, // 3: a2a.v1.Task.history:type_name -> a2a.v1.Message + 55, // 4: a2a.v1.Task.metadata:type_name -> google.protobuf.Struct + 0, // 5: a2a.v1.TaskStatus.state:type_name -> a2a.v1.TaskState + 8, // 6: a2a.v1.TaskStatus.update:type_name -> a2a.v1.Message + 56, // 7: a2a.v1.TaskStatus.timestamp:type_name -> google.protobuf.Timestamp + 6, // 8: a2a.v1.Part.file:type_name -> a2a.v1.FilePart + 7, // 9: a2a.v1.Part.data:type_name -> a2a.v1.DataPart + 55, // 10: a2a.v1.Part.metadata:type_name -> google.protobuf.Struct + 55, // 11: a2a.v1.DataPart.data:type_name -> google.protobuf.Struct + 1, // 12: a2a.v1.Message.role:type_name -> a2a.v1.Role + 5, // 13: a2a.v1.Message.content:type_name -> a2a.v1.Part + 55, // 14: a2a.v1.Message.metadata:type_name -> google.protobuf.Struct + 5, // 15: a2a.v1.Artifact.parts:type_name -> a2a.v1.Part + 55, // 16: a2a.v1.Artifact.metadata:type_name -> google.protobuf.Struct + 4, // 17: a2a.v1.TaskStatusUpdateEvent.status:type_name -> a2a.v1.TaskStatus + 55, // 18: a2a.v1.TaskStatusUpdateEvent.metadata:type_name -> google.protobuf.Struct + 9, // 19: a2a.v1.TaskArtifactUpdateEvent.artifact:type_name -> a2a.v1.Artifact + 55, // 20: a2a.v1.TaskArtifactUpdateEvent.metadata:type_name -> google.protobuf.Struct + 13, // 21: a2a.v1.PushNotificationConfig.authentication:type_name -> a2a.v1.AuthenticationInfo + 14, // 22: a2a.v1.AgentCard.additional_interfaces:type_name -> a2a.v1.AgentInterface + 16, // 23: a2a.v1.AgentCard.provider:type_name -> a2a.v1.AgentProvider + 17, // 24: a2a.v1.AgentCard.capabilities:type_name -> a2a.v1.AgentCapabilities + 49, // 25: a2a.v1.AgentCard.security_schemes:type_name -> a2a.v1.AgentCard.SecuritySchemesEntry + 23, // 26: a2a.v1.AgentCard.security:type_name -> a2a.v1.Security + 19, // 27: a2a.v1.AgentCard.skills:type_name -> a2a.v1.AgentSkill + 20, // 28: a2a.v1.AgentCard.signatures:type_name -> a2a.v1.AgentCardSignature + 18, // 29: a2a.v1.AgentCapabilities.extensions:type_name -> a2a.v1.AgentExtension + 55, // 30: a2a.v1.AgentExtension.params:type_name -> google.protobuf.Struct + 23, // 31: a2a.v1.AgentSkill.security:type_name -> a2a.v1.Security + 55, // 32: a2a.v1.AgentCardSignature.header:type_name -> google.protobuf.Struct + 12, // 33: a2a.v1.TaskPushNotificationConfig.push_notification_config:type_name -> a2a.v1.PushNotificationConfig + 50, // 34: a2a.v1.Security.schemes:type_name -> a2a.v1.Security.SchemesEntry + 25, // 35: a2a.v1.SecurityScheme.api_key_security_scheme:type_name -> a2a.v1.APIKeySecurityScheme + 26, // 36: a2a.v1.SecurityScheme.http_auth_security_scheme:type_name -> a2a.v1.HTTPAuthSecurityScheme + 27, // 37: a2a.v1.SecurityScheme.oauth2_security_scheme:type_name -> a2a.v1.OAuth2SecurityScheme + 28, // 38: a2a.v1.SecurityScheme.open_id_connect_security_scheme:type_name -> a2a.v1.OpenIdConnectSecurityScheme + 29, // 39: a2a.v1.SecurityScheme.mtls_security_scheme:type_name -> a2a.v1.MutualTlsSecurityScheme + 30, // 40: a2a.v1.OAuth2SecurityScheme.flows:type_name -> a2a.v1.OAuthFlows + 31, // 41: a2a.v1.OAuthFlows.authorization_code:type_name -> a2a.v1.AuthorizationCodeOAuthFlow + 32, // 42: a2a.v1.OAuthFlows.client_credentials:type_name -> a2a.v1.ClientCredentialsOAuthFlow + 33, // 43: a2a.v1.OAuthFlows.implicit:type_name -> a2a.v1.ImplicitOAuthFlow + 34, // 44: a2a.v1.OAuthFlows.password:type_name -> a2a.v1.PasswordOAuthFlow + 51, // 45: a2a.v1.AuthorizationCodeOAuthFlow.scopes:type_name -> a2a.v1.AuthorizationCodeOAuthFlow.ScopesEntry + 52, // 46: a2a.v1.ClientCredentialsOAuthFlow.scopes:type_name -> a2a.v1.ClientCredentialsOAuthFlow.ScopesEntry + 53, // 47: a2a.v1.ImplicitOAuthFlow.scopes:type_name -> a2a.v1.ImplicitOAuthFlow.ScopesEntry + 54, // 48: a2a.v1.PasswordOAuthFlow.scopes:type_name -> a2a.v1.PasswordOAuthFlow.ScopesEntry + 8, // 49: a2a.v1.SendMessageRequest.request:type_name -> a2a.v1.Message + 2, // 50: a2a.v1.SendMessageRequest.configuration:type_name -> a2a.v1.SendMessageConfiguration + 55, // 51: a2a.v1.SendMessageRequest.metadata:type_name -> google.protobuf.Struct + 0, // 52: a2a.v1.ListTasksRequest.states:type_name -> a2a.v1.TaskState + 3, // 53: a2a.v1.ListTasksResponse.tasks:type_name -> a2a.v1.Task + 21, // 54: a2a.v1.CreateTaskPushNotificationConfigRequest.config:type_name -> a2a.v1.TaskPushNotificationConfig + 3, // 55: a2a.v1.SendMessageResponse.task:type_name -> a2a.v1.Task + 8, // 56: a2a.v1.SendMessageResponse.msg:type_name -> a2a.v1.Message + 3, // 57: a2a.v1.StreamResponse.task:type_name -> a2a.v1.Task + 8, // 58: a2a.v1.StreamResponse.msg:type_name -> a2a.v1.Message + 10, // 59: a2a.v1.StreamResponse.status_update:type_name -> a2a.v1.TaskStatusUpdateEvent + 11, // 60: a2a.v1.StreamResponse.artifact_update:type_name -> a2a.v1.TaskArtifactUpdateEvent + 21, // 61: a2a.v1.ListTaskPushNotificationConfigResponse.configs:type_name -> a2a.v1.TaskPushNotificationConfig + 24, // 62: a2a.v1.AgentCard.SecuritySchemesEntry.value:type_name -> a2a.v1.SecurityScheme + 22, // 63: a2a.v1.Security.SchemesEntry.value:type_name -> a2a.v1.StringList + 35, // 64: a2a.v1.A2AService.SendMessage:input_type -> a2a.v1.SendMessageRequest + 35, // 65: a2a.v1.A2AService.SendStreamingMessage:input_type -> a2a.v1.SendMessageRequest + 36, // 66: a2a.v1.A2AService.GetTask:input_type -> a2a.v1.GetTaskRequest + 37, // 67: a2a.v1.A2AService.CancelTask:input_type -> a2a.v1.CancelTaskRequest + 43, // 68: a2a.v1.A2AService.TaskSubscription:input_type -> a2a.v1.TaskSubscriptionRequest + 38, // 69: a2a.v1.A2AService.ListTasks:input_type -> a2a.v1.ListTasksRequest + 42, // 70: a2a.v1.A2AService.CreateTaskPushNotification:input_type -> a2a.v1.CreateTaskPushNotificationConfigRequest + 40, // 71: a2a.v1.A2AService.GetTaskPushNotification:input_type -> a2a.v1.GetTaskPushNotificationConfigRequest + 44, // 72: a2a.v1.A2AService.ListTaskPushNotification:input_type -> a2a.v1.ListTaskPushNotificationConfigRequest + 45, // 73: a2a.v1.A2AService.GetAgentCard:input_type -> a2a.v1.GetAgentCardRequest + 41, // 74: a2a.v1.A2AService.DeleteTaskPushNotification:input_type -> a2a.v1.DeleteTaskPushNotificationConfigRequest + 46, // 75: a2a.v1.A2AService.SendMessage:output_type -> a2a.v1.SendMessageResponse + 47, // 76: a2a.v1.A2AService.SendStreamingMessage:output_type -> a2a.v1.StreamResponse + 3, // 77: a2a.v1.A2AService.GetTask:output_type -> a2a.v1.Task + 3, // 78: a2a.v1.A2AService.CancelTask:output_type -> a2a.v1.Task + 47, // 79: a2a.v1.A2AService.TaskSubscription:output_type -> a2a.v1.StreamResponse + 39, // 80: a2a.v1.A2AService.ListTasks:output_type -> a2a.v1.ListTasksResponse + 21, // 81: a2a.v1.A2AService.CreateTaskPushNotification:output_type -> a2a.v1.TaskPushNotificationConfig + 21, // 82: a2a.v1.A2AService.GetTaskPushNotification:output_type -> a2a.v1.TaskPushNotificationConfig + 48, // 83: a2a.v1.A2AService.ListTaskPushNotification:output_type -> a2a.v1.ListTaskPushNotificationConfigResponse + 15, // 84: a2a.v1.A2AService.GetAgentCard:output_type -> a2a.v1.AgentCard + 57, // 85: a2a.v1.A2AService.DeleteTaskPushNotification:output_type -> google.protobuf.Empty + 75, // [75:86] is the sub-list for method output_type + 64, // [64:75] is the sub-list for method input_type + 64, // [64:64] is the sub-list for extension type_name + 64, // [64:64] is the sub-list for extension extendee + 0, // [0:64] is the sub-list for field type_name +} + +func init() { file_a2a_proto_init() } +func file_a2a_proto_init() { + if File_a2a_proto != nil { + return + } + file_a2a_proto_msgTypes[3].OneofWrappers = []any{ + (*Part_Text)(nil), + (*Part_File)(nil), + (*Part_Data)(nil), + } + file_a2a_proto_msgTypes[4].OneofWrappers = []any{ + (*FilePart_FileWithUri)(nil), + (*FilePart_FileWithBytes)(nil), + } + file_a2a_proto_msgTypes[22].OneofWrappers = []any{ + (*SecurityScheme_ApiKeySecurityScheme)(nil), + (*SecurityScheme_HttpAuthSecurityScheme)(nil), + (*SecurityScheme_Oauth2SecurityScheme)(nil), + (*SecurityScheme_OpenIdConnectSecurityScheme)(nil), + (*SecurityScheme_MtlsSecurityScheme)(nil), + } + file_a2a_proto_msgTypes[28].OneofWrappers = []any{ + (*OAuthFlows_AuthorizationCode)(nil), + (*OAuthFlows_ClientCredentials)(nil), + (*OAuthFlows_Implicit)(nil), + (*OAuthFlows_Password)(nil), + } + file_a2a_proto_msgTypes[44].OneofWrappers = []any{ + (*SendMessageResponse_Task)(nil), + (*SendMessageResponse_Msg)(nil), + } + file_a2a_proto_msgTypes[45].OneofWrappers = []any{ + (*StreamResponse_Task)(nil), + (*StreamResponse_Msg)(nil), + (*StreamResponse_StatusUpdate)(nil), + (*StreamResponse_ArtifactUpdate)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_a2a_proto_rawDesc), len(file_a2a_proto_rawDesc)), + NumEnums: 2, + NumMessages: 53, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_a2a_proto_goTypes, + DependencyIndexes: file_a2a_proto_depIdxs, + EnumInfos: file_a2a_proto_enumTypes, + MessageInfos: file_a2a_proto_msgTypes, + }.Build() + File_a2a_proto = out.File + file_a2a_proto_goTypes = nil + file_a2a_proto_depIdxs = nil +} diff --git a/a2a/pkg/proto/v1/a2a_grpc.pb.go b/a2a/pkg/proto/v1/a2a_grpc.pb.go new file mode 100644 index 000000000..2d47dc056 --- /dev/null +++ b/a2a/pkg/proto/v1/a2a_grpc.pb.go @@ -0,0 +1,585 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +// Older protoc compilers don't understand edition yet. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.21.12 +// source: a2a.proto + +package v1 + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + A2AService_SendMessage_FullMethodName = "/a2a.v1.A2AService/SendMessage" + A2AService_SendStreamingMessage_FullMethodName = "/a2a.v1.A2AService/SendStreamingMessage" + A2AService_GetTask_FullMethodName = "/a2a.v1.A2AService/GetTask" + A2AService_CancelTask_FullMethodName = "/a2a.v1.A2AService/CancelTask" + A2AService_TaskSubscription_FullMethodName = "/a2a.v1.A2AService/TaskSubscription" + A2AService_ListTasks_FullMethodName = "/a2a.v1.A2AService/ListTasks" + A2AService_CreateTaskPushNotification_FullMethodName = "/a2a.v1.A2AService/CreateTaskPushNotification" + A2AService_GetTaskPushNotification_FullMethodName = "/a2a.v1.A2AService/GetTaskPushNotification" + A2AService_ListTaskPushNotification_FullMethodName = "/a2a.v1.A2AService/ListTaskPushNotification" + A2AService_GetAgentCard_FullMethodName = "/a2a.v1.A2AService/GetAgentCard" + A2AService_DeleteTaskPushNotification_FullMethodName = "/a2a.v1.A2AService/DeleteTaskPushNotification" +) + +// A2AServiceClient is the client API for A2AService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// A2AService defines the gRPC version of the A2A protocol. This has a slightly +// different shape than the JSONRPC version to better conform to AIP-127, +// where appropriate. The nouns are AgentCard, Message, Task and +// TaskPushNotificationConfig. +// - Messages are not a standard resource so there is no get/delete/update/list +// interface, only a send and stream custom methods. +// - Tasks have a get interface and custom cancel and subscribe methods. +// - TaskPushNotificationConfig are a resource whose parent is a task. +// They have get, list and create methods. +// - AgentCard is a static resource with only a get method. +type A2AServiceClient interface { + // Send a message to the agent. This is a blocking call that will return the + // task once it is completed, or a LRO if requested. + SendMessage(ctx context.Context, in *SendMessageRequest, opts ...grpc.CallOption) (*SendMessageResponse, error) + // SendStreamingMessage is a streaming call that will return a stream of + // task update events until the Task is in an interrupted or terminal state. + SendStreamingMessage(ctx context.Context, in *SendMessageRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[StreamResponse], error) + // Get the current state of a task from the agent. + GetTask(ctx context.Context, in *GetTaskRequest, opts ...grpc.CallOption) (*Task, error) + // Cancel a task from the agent. If supported one should expect no + // more task updates for the task. + CancelTask(ctx context.Context, in *CancelTaskRequest, opts ...grpc.CallOption) (*Task, error) + // TaskSubscription is a streaming call that will return a stream of task + // update events. This attaches the stream to an existing in process task. + // If the task is complete the stream will return the completed task (like + // GetTask) and close the stream. + TaskSubscription(ctx context.Context, in *TaskSubscriptionRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[StreamResponse], error) + // List tasks with optional filtering + ListTasks(ctx context.Context, in *ListTasksRequest, opts ...grpc.CallOption) (*ListTasksResponse, error) + // Set a push notification config for a task. + CreateTaskPushNotification(ctx context.Context, in *CreateTaskPushNotificationConfigRequest, opts ...grpc.CallOption) (*TaskPushNotificationConfig, error) + // Get a push notification config for a task. + GetTaskPushNotification(ctx context.Context, in *GetTaskPushNotificationConfigRequest, opts ...grpc.CallOption) (*TaskPushNotificationConfig, error) + // Get a list of push notifications configured for a task. + ListTaskPushNotification(ctx context.Context, in *ListTaskPushNotificationConfigRequest, opts ...grpc.CallOption) (*ListTaskPushNotificationConfigResponse, error) + // GetAgentCard returns the agent card for the agent. + GetAgentCard(ctx context.Context, in *GetAgentCardRequest, opts ...grpc.CallOption) (*AgentCard, error) + // Delete a push notification config for a task. + DeleteTaskPushNotification(ctx context.Context, in *DeleteTaskPushNotificationConfigRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type a2AServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewA2AServiceClient(cc grpc.ClientConnInterface) A2AServiceClient { + return &a2AServiceClient{cc} +} + +func (c *a2AServiceClient) SendMessage(ctx context.Context, in *SendMessageRequest, opts ...grpc.CallOption) (*SendMessageResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SendMessageResponse) + err := c.cc.Invoke(ctx, A2AService_SendMessage_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *a2AServiceClient) SendStreamingMessage(ctx context.Context, in *SendMessageRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[StreamResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &A2AService_ServiceDesc.Streams[0], A2AService_SendStreamingMessage_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[SendMessageRequest, StreamResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type A2AService_SendStreamingMessageClient = grpc.ServerStreamingClient[StreamResponse] + +func (c *a2AServiceClient) GetTask(ctx context.Context, in *GetTaskRequest, opts ...grpc.CallOption) (*Task, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Task) + err := c.cc.Invoke(ctx, A2AService_GetTask_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *a2AServiceClient) CancelTask(ctx context.Context, in *CancelTaskRequest, opts ...grpc.CallOption) (*Task, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Task) + err := c.cc.Invoke(ctx, A2AService_CancelTask_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *a2AServiceClient) TaskSubscription(ctx context.Context, in *TaskSubscriptionRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[StreamResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &A2AService_ServiceDesc.Streams[1], A2AService_TaskSubscription_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[TaskSubscriptionRequest, StreamResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type A2AService_TaskSubscriptionClient = grpc.ServerStreamingClient[StreamResponse] + +func (c *a2AServiceClient) ListTasks(ctx context.Context, in *ListTasksRequest, opts ...grpc.CallOption) (*ListTasksResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListTasksResponse) + err := c.cc.Invoke(ctx, A2AService_ListTasks_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *a2AServiceClient) CreateTaskPushNotification(ctx context.Context, in *CreateTaskPushNotificationConfigRequest, opts ...grpc.CallOption) (*TaskPushNotificationConfig, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TaskPushNotificationConfig) + err := c.cc.Invoke(ctx, A2AService_CreateTaskPushNotification_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *a2AServiceClient) GetTaskPushNotification(ctx context.Context, in *GetTaskPushNotificationConfigRequest, opts ...grpc.CallOption) (*TaskPushNotificationConfig, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TaskPushNotificationConfig) + err := c.cc.Invoke(ctx, A2AService_GetTaskPushNotification_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *a2AServiceClient) ListTaskPushNotification(ctx context.Context, in *ListTaskPushNotificationConfigRequest, opts ...grpc.CallOption) (*ListTaskPushNotificationConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListTaskPushNotificationConfigResponse) + err := c.cc.Invoke(ctx, A2AService_ListTaskPushNotification_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *a2AServiceClient) GetAgentCard(ctx context.Context, in *GetAgentCardRequest, opts ...grpc.CallOption) (*AgentCard, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AgentCard) + err := c.cc.Invoke(ctx, A2AService_GetAgentCard_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *a2AServiceClient) DeleteTaskPushNotification(ctx context.Context, in *DeleteTaskPushNotificationConfigRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, A2AService_DeleteTaskPushNotification_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// A2AServiceServer is the server API for A2AService service. +// All implementations must embed UnimplementedA2AServiceServer +// for forward compatibility. +// +// A2AService defines the gRPC version of the A2A protocol. This has a slightly +// different shape than the JSONRPC version to better conform to AIP-127, +// where appropriate. The nouns are AgentCard, Message, Task and +// TaskPushNotificationConfig. +// - Messages are not a standard resource so there is no get/delete/update/list +// interface, only a send and stream custom methods. +// - Tasks have a get interface and custom cancel and subscribe methods. +// - TaskPushNotificationConfig are a resource whose parent is a task. +// They have get, list and create methods. +// - AgentCard is a static resource with only a get method. +type A2AServiceServer interface { + // Send a message to the agent. This is a blocking call that will return the + // task once it is completed, or a LRO if requested. + SendMessage(context.Context, *SendMessageRequest) (*SendMessageResponse, error) + // SendStreamingMessage is a streaming call that will return a stream of + // task update events until the Task is in an interrupted or terminal state. + SendStreamingMessage(*SendMessageRequest, grpc.ServerStreamingServer[StreamResponse]) error + // Get the current state of a task from the agent. + GetTask(context.Context, *GetTaskRequest) (*Task, error) + // Cancel a task from the agent. If supported one should expect no + // more task updates for the task. + CancelTask(context.Context, *CancelTaskRequest) (*Task, error) + // TaskSubscription is a streaming call that will return a stream of task + // update events. This attaches the stream to an existing in process task. + // If the task is complete the stream will return the completed task (like + // GetTask) and close the stream. + TaskSubscription(*TaskSubscriptionRequest, grpc.ServerStreamingServer[StreamResponse]) error + // List tasks with optional filtering + ListTasks(context.Context, *ListTasksRequest) (*ListTasksResponse, error) + // Set a push notification config for a task. + CreateTaskPushNotification(context.Context, *CreateTaskPushNotificationConfigRequest) (*TaskPushNotificationConfig, error) + // Get a push notification config for a task. + GetTaskPushNotification(context.Context, *GetTaskPushNotificationConfigRequest) (*TaskPushNotificationConfig, error) + // Get a list of push notifications configured for a task. + ListTaskPushNotification(context.Context, *ListTaskPushNotificationConfigRequest) (*ListTaskPushNotificationConfigResponse, error) + // GetAgentCard returns the agent card for the agent. + GetAgentCard(context.Context, *GetAgentCardRequest) (*AgentCard, error) + // Delete a push notification config for a task. + DeleteTaskPushNotification(context.Context, *DeleteTaskPushNotificationConfigRequest) (*emptypb.Empty, error) + mustEmbedUnimplementedA2AServiceServer() +} + +// UnimplementedA2AServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedA2AServiceServer struct{} + +func (UnimplementedA2AServiceServer) SendMessage(context.Context, *SendMessageRequest) (*SendMessageResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendMessage not implemented") +} +func (UnimplementedA2AServiceServer) SendStreamingMessage(*SendMessageRequest, grpc.ServerStreamingServer[StreamResponse]) error { + return status.Errorf(codes.Unimplemented, "method SendStreamingMessage not implemented") +} +func (UnimplementedA2AServiceServer) GetTask(context.Context, *GetTaskRequest) (*Task, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTask not implemented") +} +func (UnimplementedA2AServiceServer) CancelTask(context.Context, *CancelTaskRequest) (*Task, error) { + return nil, status.Errorf(codes.Unimplemented, "method CancelTask not implemented") +} +func (UnimplementedA2AServiceServer) TaskSubscription(*TaskSubscriptionRequest, grpc.ServerStreamingServer[StreamResponse]) error { + return status.Errorf(codes.Unimplemented, "method TaskSubscription not implemented") +} +func (UnimplementedA2AServiceServer) ListTasks(context.Context, *ListTasksRequest) (*ListTasksResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListTasks not implemented") +} +func (UnimplementedA2AServiceServer) CreateTaskPushNotification(context.Context, *CreateTaskPushNotificationConfigRequest) (*TaskPushNotificationConfig, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateTaskPushNotification not implemented") +} +func (UnimplementedA2AServiceServer) GetTaskPushNotification(context.Context, *GetTaskPushNotificationConfigRequest) (*TaskPushNotificationConfig, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTaskPushNotification not implemented") +} +func (UnimplementedA2AServiceServer) ListTaskPushNotification(context.Context, *ListTaskPushNotificationConfigRequest) (*ListTaskPushNotificationConfigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListTaskPushNotification not implemented") +} +func (UnimplementedA2AServiceServer) GetAgentCard(context.Context, *GetAgentCardRequest) (*AgentCard, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAgentCard not implemented") +} +func (UnimplementedA2AServiceServer) DeleteTaskPushNotification(context.Context, *DeleteTaskPushNotificationConfigRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteTaskPushNotification not implemented") +} +func (UnimplementedA2AServiceServer) mustEmbedUnimplementedA2AServiceServer() {} +func (UnimplementedA2AServiceServer) testEmbeddedByValue() {} + +// UnsafeA2AServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to A2AServiceServer will +// result in compilation errors. +type UnsafeA2AServiceServer interface { + mustEmbedUnimplementedA2AServiceServer() +} + +func RegisterA2AServiceServer(s grpc.ServiceRegistrar, srv A2AServiceServer) { + // If the following call pancis, it indicates UnimplementedA2AServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&A2AService_ServiceDesc, srv) +} + +func _A2AService_SendMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendMessageRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(A2AServiceServer).SendMessage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: A2AService_SendMessage_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(A2AServiceServer).SendMessage(ctx, req.(*SendMessageRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _A2AService_SendStreamingMessage_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SendMessageRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(A2AServiceServer).SendStreamingMessage(m, &grpc.GenericServerStream[SendMessageRequest, StreamResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type A2AService_SendStreamingMessageServer = grpc.ServerStreamingServer[StreamResponse] + +func _A2AService_GetTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTaskRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(A2AServiceServer).GetTask(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: A2AService_GetTask_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(A2AServiceServer).GetTask(ctx, req.(*GetTaskRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _A2AService_CancelTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CancelTaskRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(A2AServiceServer).CancelTask(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: A2AService_CancelTask_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(A2AServiceServer).CancelTask(ctx, req.(*CancelTaskRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _A2AService_TaskSubscription_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(TaskSubscriptionRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(A2AServiceServer).TaskSubscription(m, &grpc.GenericServerStream[TaskSubscriptionRequest, StreamResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type A2AService_TaskSubscriptionServer = grpc.ServerStreamingServer[StreamResponse] + +func _A2AService_ListTasks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListTasksRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(A2AServiceServer).ListTasks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: A2AService_ListTasks_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(A2AServiceServer).ListTasks(ctx, req.(*ListTasksRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _A2AService_CreateTaskPushNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateTaskPushNotificationConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(A2AServiceServer).CreateTaskPushNotification(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: A2AService_CreateTaskPushNotification_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(A2AServiceServer).CreateTaskPushNotification(ctx, req.(*CreateTaskPushNotificationConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _A2AService_GetTaskPushNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTaskPushNotificationConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(A2AServiceServer).GetTaskPushNotification(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: A2AService_GetTaskPushNotification_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(A2AServiceServer).GetTaskPushNotification(ctx, req.(*GetTaskPushNotificationConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _A2AService_ListTaskPushNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListTaskPushNotificationConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(A2AServiceServer).ListTaskPushNotification(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: A2AService_ListTaskPushNotification_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(A2AServiceServer).ListTaskPushNotification(ctx, req.(*ListTaskPushNotificationConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _A2AService_GetAgentCard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAgentCardRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(A2AServiceServer).GetAgentCard(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: A2AService_GetAgentCard_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(A2AServiceServer).GetAgentCard(ctx, req.(*GetAgentCardRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _A2AService_DeleteTaskPushNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteTaskPushNotificationConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(A2AServiceServer).DeleteTaskPushNotification(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: A2AService_DeleteTaskPushNotification_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(A2AServiceServer).DeleteTaskPushNotification(ctx, req.(*DeleteTaskPushNotificationConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// A2AService_ServiceDesc is the grpc.ServiceDesc for A2AService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var A2AService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "a2a.v1.A2AService", + HandlerType: (*A2AServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SendMessage", + Handler: _A2AService_SendMessage_Handler, + }, + { + MethodName: "GetTask", + Handler: _A2AService_GetTask_Handler, + }, + { + MethodName: "CancelTask", + Handler: _A2AService_CancelTask_Handler, + }, + { + MethodName: "ListTasks", + Handler: _A2AService_ListTasks_Handler, + }, + { + MethodName: "CreateTaskPushNotification", + Handler: _A2AService_CreateTaskPushNotification_Handler, + }, + { + MethodName: "GetTaskPushNotification", + Handler: _A2AService_GetTaskPushNotification_Handler, + }, + { + MethodName: "ListTaskPushNotification", + Handler: _A2AService_ListTaskPushNotification_Handler, + }, + { + MethodName: "GetAgentCard", + Handler: _A2AService_GetAgentCard_Handler, + }, + { + MethodName: "DeleteTaskPushNotification", + Handler: _A2AService_DeleteTaskPushNotification_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "SendStreamingMessage", + Handler: _A2AService_SendStreamingMessage_Handler, + ServerStreams: true, + }, + { + StreamName: "TaskSubscription", + Handler: _A2AService_TaskSubscription_Handler, + ServerStreams: true, + }, + }, + Metadata: "a2a.proto", +} diff --git a/a2a/pkg/task/eventbus.go b/a2a/pkg/task/eventbus.go new file mode 100644 index 000000000..d42086d0f --- /dev/null +++ b/a2a/pkg/task/eventbus.go @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package task + +import ( + "sync" + "time" + + "seata-go-ai-a2a/pkg/types" +) + +// EventBus manages task event subscriptions and publishing +type EventBus struct { + mu sync.RWMutex + subscriptions map[string][]*TaskSubscription + closing bool +} + +// NewEventBus creates a new event bus +func NewEventBus() *EventBus { + return &EventBus{ + subscriptions: make(map[string][]*TaskSubscription), + } +} + +// Subscribe creates a new subscription for a task +func (eb *EventBus) Subscribe(taskID string, subscription *TaskSubscription) { + eb.mu.Lock() + defer eb.mu.Unlock() + + if eb.closing { + subscription.Close() + return + } + + eb.subscriptions[taskID] = append(eb.subscriptions[taskID], subscription) +} + +// Unsubscribe removes a subscription for a task +func (eb *EventBus) Unsubscribe(taskID string, subscription *TaskSubscription) { + eb.mu.Lock() + defer eb.mu.Unlock() + + subs, exists := eb.subscriptions[taskID] + if !exists { + return + } + + // Remove the subscription from the slice + for i, sub := range subs { + if sub == subscription { + eb.subscriptions[taskID] = append(subs[:i], subs[i+1:]...) + break + } + } + + // Clean up empty subscription list + if len(eb.subscriptions[taskID]) == 0 { + delete(eb.subscriptions, taskID) + } +} + +// PublishToTask publishes an event to all subscribers of a task +func (eb *EventBus) PublishToTask(taskID string, response types.StreamResponse) { + eb.mu.RLock() + subs, exists := eb.subscriptions[taskID] + if !exists { + eb.mu.RUnlock() + return + } + + // Copy subscriptions to avoid holding lock during delivery + subscriptions := make([]*TaskSubscription, len(subs)) + copy(subscriptions, subs) + eb.mu.RUnlock() + + // Deliver to all subscriptions (non-blocking) + for _, sub := range subscriptions { + select { + case sub.events <- response: + case <-time.After(100 * time.Millisecond): + // Subscription is slow or closed, remove it + eb.Unsubscribe(taskID, sub) + sub.Close() + } + } +} + +// CleanupTask removes all subscriptions for a task +func (eb *EventBus) CleanupTask(taskID string) { + eb.mu.Lock() + defer eb.mu.Unlock() + + subs, exists := eb.subscriptions[taskID] + if !exists { + return + } + + // Close all subscriptions + for _, sub := range subs { + sub.Close() + } + + // Remove from map + delete(eb.subscriptions, taskID) +} + +// Close shuts down the event bus and closes all subscriptions +func (eb *EventBus) Close() { + eb.mu.Lock() + defer eb.mu.Unlock() + + eb.closing = true + + // Close all subscriptions + for taskID, subs := range eb.subscriptions { + for _, sub := range subs { + sub.Close() + } + delete(eb.subscriptions, taskID) + } +} + +// TaskSubscription represents a subscription to task events +type TaskSubscription struct { + taskID string + events chan types.StreamResponse + eventBus *EventBus + subscribedAt time.Time + closed bool + closeOnce sync.Once + mu sync.RWMutex +} + +// NewTaskSubscription creates a new task subscription +func NewTaskSubscription(taskID string, eventBus *EventBus) *TaskSubscription { + subscription := &TaskSubscription{ + taskID: taskID, + events: make(chan types.StreamResponse, 10), // Buffered channel + eventBus: eventBus, + subscribedAt: time.Now(), + } + + // Register with event bus + eventBus.Subscribe(taskID, subscription) + + return subscription +} + +// Events returns the channel for receiving stream responses +func (ts *TaskSubscription) Events() <-chan types.StreamResponse { + return ts.events +} + +// TaskID returns the task ID this subscription is for +func (ts *TaskSubscription) TaskID() string { + return ts.taskID +} + +// SubscribedAt returns when this subscription was created +func (ts *TaskSubscription) SubscribedAt() time.Time { + return ts.subscribedAt +} + +// IsClosed returns whether the subscription is closed +func (ts *TaskSubscription) IsClosed() bool { + ts.mu.RLock() + defer ts.mu.RUnlock() + return ts.closed +} + +// Close closes the subscription +func (ts *TaskSubscription) Close() { + ts.closeOnce.Do(func() { + ts.mu.Lock() + ts.closed = true + ts.mu.Unlock() + + // Unregister from event bus + ts.eventBus.Unsubscribe(ts.taskID, ts) + + // Close the channel + close(ts.events) + }) +} diff --git a/a2a/pkg/task/manager.go b/a2a/pkg/task/manager.go new file mode 100644 index 000000000..d9333818a --- /dev/null +++ b/a2a/pkg/task/manager.go @@ -0,0 +1,822 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package task + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/google/uuid" + + "seata-go-ai-a2a/pkg/handler" + "seata-go-ai-a2a/pkg/task/store" + "seata-go-ai-a2a/pkg/types" +) + +// TaskManager manages the complete lifecycle of A2A tasks +// It provides task creation, status updates, artifact management, and event streaming +type TaskManager struct { + store store.TaskStore + eventBus *EventBus + config *Config + logger types.Logger + mu sync.RWMutex + closing bool + closeOnce sync.Once + cleanupStopCh chan struct{} + cleanupDone chan struct{} + + // Handler interfaces (optional) + messageHandler handler.MessageHandler + agentCardProvider handler.AgentCardProvider +} + +// MessageRequest encapsulates all information for processing request +type MessageRequest struct { + // Original message + Message *types.Message + + // Task context information + TaskID string + ContextID string + + // Processing configuration + Configuration *MessageConfiguration + + // Historical messages + History []*types.Message + + // Request metadata + Metadata map[string]any + + // Task operations handler + TaskOperations TaskOperations +} + +// MessageConfiguration message processing configuration +type MessageConfiguration struct { + // Blocking mode - true: wait for completion; false: return immediately + Blocking bool + + // Streaming mode - true: return event stream; false: return single result + Streaming bool + + // Historical message length limit + HistoryLength int + + // Accepted output modes + AcceptedOutputModes []string + + // Push notification configuration + PushNotification *types.PushNotificationConfig + + // Request timeout + Timeout time.Duration +} + +// MessageResponse processing result +type MessageResponse struct { + // Processing mode + Mode ResponseMode + + // Direct result for non-streaming mode + Result *ResponseResult + + // Event stream for streaming mode + EventStream <-chan types.StreamResponse + + // Error information + Error error +} + +// ResponseMode response mode +type ResponseMode int + +const ( + ResponseModeMessage ResponseMode = iota // Direct message reply + ResponseModeTask // Task reply + ResponseModeStream // Streaming reply +) + +// ResponseResult response result +type ResponseResult struct { + // Result type - Message or Task + Data interface{} + + // Task state update (only when Data is Task) + TaskState types.TaskState + + // Output artifacts + Artifacts []*types.Artifact +} + +// TaskOperations task operations interface +type TaskOperations interface { + // UpdateTaskState update task state + UpdateTaskState(state types.TaskState, message *types.Message) error + + // AddArtifact add output artifact + AddArtifact(artifact *types.Artifact) error + + // GetTaskHistory get task history + GetTaskHistory() ([]*types.Message, error) + + // GetTaskMetadata get task metadata + GetTaskMetadata() (map[string]any, error) + + // SendEvent send event to stream (streaming mode) + SendEvent(event types.StreamResponse) error + + // GetTaskID get current task ID + GetTaskID() string + + // GetContextID get current context ID + GetContextID() string +} + +// UserInfo user information +type UserInfo struct { + UserID string + Scopes []string + Metadata map[string]any +} + +// Config configures the TaskManager +type Config struct { + // Maximum number of messages to keep in task history (0 = unlimited) + MaxHistoryLength int + + // Maximum number of artifacts per task (0 = unlimited) + MaxArtifacts int + + // Default timeout for operations + DefaultTimeout time.Duration + + // Cleanup interval for terminated tasks + CleanupInterval time.Duration + + // TTL for terminated tasks in memory + TerminatedTaskTTL time.Duration +} + +// DefaultConfig returns a default configuration +func DefaultConfig() *Config { + return &Config{ + MaxHistoryLength: 100, + MaxArtifacts: 50, + DefaultTimeout: 30 * time.Second, + CleanupInterval: 5 * time.Minute, + TerminatedTaskTTL: 1 * time.Hour, + } +} + +// NewTaskManager creates a new task manager with the given store and configuration +func NewTaskManager(taskStore store.TaskStore, config *Config) *TaskManager { + return NewTaskManagerWithLogger(taskStore, config, &types.DefaultLogger{}) +} + +// NewTaskManagerWithLogger creates a new task manager with a custom logger +func NewTaskManagerWithLogger(taskStore store.TaskStore, config *Config, logger types.Logger) *TaskManager { + if config == nil { + config = DefaultConfig() + } + if logger == nil { + logger = &types.DefaultLogger{} + } + + eventBus := NewEventBus() + + tm := &TaskManager{ + store: taskStore, + eventBus: eventBus, + config: config, + logger: logger, + cleanupStopCh: make(chan struct{}), + cleanupDone: make(chan struct{}), + } + + // Start cleanup routine + go tm.cleanupRoutine() + + return tm +} + +// NewTaskManagerWithHandlers creates a new task manager with handler interfaces +func NewTaskManagerWithHandlers(taskStore store.TaskStore, config *Config, messageHandler handler.MessageHandler, agentCardProvider handler.AgentCardProvider) *TaskManager { + return NewTaskManagerWithHandlersAndLogger(taskStore, config, messageHandler, agentCardProvider, &types.DefaultLogger{}) +} + +// NewTaskManagerWithHandlersAndLogger creates a new task manager with handler interfaces and custom logger +func NewTaskManagerWithHandlersAndLogger(taskStore store.TaskStore, config *Config, messageHandler handler.MessageHandler, agentCardProvider handler.AgentCardProvider, logger types.Logger) *TaskManager { + if config == nil { + config = DefaultConfig() + } + if logger == nil { + logger = &types.DefaultLogger{} + } + + eventBus := NewEventBus() + + tm := &TaskManager{ + store: taskStore, + eventBus: eventBus, + config: config, + logger: logger, + messageHandler: messageHandler, + agentCardProvider: agentCardProvider, + cleanupStopCh: make(chan struct{}), + cleanupDone: make(chan struct{}), + } + + // Start cleanup routine + go tm.cleanupRoutine() + + return tm +} + +// CreateTask creates a new task from an initial message +func (tm *TaskManager) CreateTask(ctx context.Context, contextID string, message *types.Message, metadata map[string]any) (*types.Task, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Generate task ID + taskID := uuid.New().String() + + // Create initial status + status := &types.TaskStatus{ + State: types.TaskStateSubmitted, + Update: message, + Timestamp: time.Now(), + } + + // Create task with initial message in history + task := &types.Task{ + ID: taskID, + ContextID: contextID, + Status: status, + History: []*types.Message{message}, + Kind: "task", + Metadata: metadata, + } + + // Store the task + if err := tm.store.CreateTask(ctx, task); err != nil { + return nil, fmt.Errorf("failed to create task: %w", err) + } + + // Publish task creation event + taskResponse := &types.TaskStreamResponse{Task: task} + tm.eventBus.PublishToTask(taskID, taskResponse) + + return task, nil +} + +// GetTask retrieves a task by ID +func (tm *TaskManager) GetTask(ctx context.Context, taskID string) (*types.Task, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + task, err := tm.store.GetTask(ctx, taskID) + if err != nil { + // Check if it's a "not found" error and convert to A2A error + if err.Error() == fmt.Sprintf("task not found: %s", taskID) || + err.Error() == fmt.Sprintf("task with ID %s not found", taskID) { + return nil, types.NewTaskNotFoundError(taskID) + } + return nil, err + } + return task, nil +} + +// UpdateTaskStatus updates the status of a task and publishes the event +func (tm *TaskManager) UpdateTaskStatus(ctx context.Context, taskID string, newState types.TaskState, updateMessage *types.Message) error { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Get current task + task, err := tm.store.GetTask(ctx, taskID) + if err != nil { + return fmt.Errorf("failed to get task: %w", err) + } + + // Validate state transition + if !tm.isValidStateTransition(task.Status.State, newState) { + return fmt.Errorf("invalid state transition from %s to %s", task.Status.State.String(), newState.String()) + } + + // Create new status + newStatus := &types.TaskStatus{ + State: newState, + Update: updateMessage, + Timestamp: time.Now(), + } + + // Update task status in store + if err := tm.store.UpdateTaskStatus(ctx, taskID, newStatus); err != nil { + return fmt.Errorf("failed to update task status: %w", err) + } + + // Add update message to history if provided + if updateMessage != nil { + if err := tm.AddMessage(ctx, taskID, updateMessage); err != nil { + // Log error but don't fail the status update + tm.logger.Warn(ctx, "Failed to add update message to history", + "task_id", taskID, + "error", err, + ) + } + } + + // Create and publish status update event + statusEvent := &types.TaskStatusUpdateEvent{ + TaskID: taskID, + ContextID: task.ContextID, + Status: newStatus, + Final: newState.IsTerminal(), + } + + statusResponse := &types.StatusUpdateStreamResponse{StatusUpdate: statusEvent} + tm.eventBus.PublishToTask(taskID, statusResponse) + + return nil +} + +// AddArtifact adds an artifact to a task and publishes the event +func (tm *TaskManager) AddArtifact(ctx context.Context, taskID string, artifact *types.Artifact, append, lastChunk bool) error { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Get task to validate it exists + task, err := tm.store.GetTask(ctx, taskID) + if err != nil { + return fmt.Errorf("failed to get task: %w", err) + } + + // Check artifact limit + if tm.config.MaxArtifacts > 0 && len(task.Artifacts) >= tm.config.MaxArtifacts { + return fmt.Errorf("maximum artifacts limit (%d) reached for task", tm.config.MaxArtifacts) + } + + // Add artifact to store + if err := tm.store.AddArtifact(ctx, taskID, artifact); err != nil { + return fmt.Errorf("failed to add artifact: %w", err) + } + + // Create and publish artifact update event + artifactEvent := &types.TaskArtifactUpdateEvent{ + TaskID: taskID, + ContextID: task.ContextID, + Artifact: artifact, + Append: append, + LastChunk: lastChunk, + } + + artifactResponse := &types.ArtifactUpdateStreamResponse{ArtifactUpdate: artifactEvent} + tm.eventBus.PublishToTask(taskID, artifactResponse) + + return nil +} + +// AddMessage adds a message to task history +func (tm *TaskManager) AddMessage(ctx context.Context, taskID string, message *types.Message) error { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Check history limit if configured + if tm.config.MaxHistoryLength > 0 { + history, err := tm.store.GetHistory(ctx, taskID, 0) // Get all history + if err != nil { + return fmt.Errorf("failed to check history length: %w", err) + } + + if len(history) >= tm.config.MaxHistoryLength { + return fmt.Errorf("maximum history length (%d) reached for task", tm.config.MaxHistoryLength) + } + } + + return tm.store.AddMessage(ctx, taskID, message) +} + +// CancelTask cancels a running task +func (tm *TaskManager) CancelTask(ctx context.Context, taskID string) (*types.Task, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Get current task + task, err := tm.store.GetTask(ctx, taskID) + if err != nil { + // Check if it's a "not found" error and convert to A2A error + if err.Error() == fmt.Sprintf("task not found: %s", taskID) || + err.Error() == fmt.Sprintf("task with ID %s not found", taskID) { + return nil, types.NewTaskNotFoundError(taskID) + } + return nil, fmt.Errorf("failed to get task: %w", err) + } + + // Check if task can be cancelled + if task.Status.State.IsTerminal() { + return nil, types.NewTaskNotCancelableError(taskID, task.Status.State) + } + + // Update status to cancelled + if err := tm.UpdateTaskStatus(ctx, taskID, types.TaskStateCancelled, nil); err != nil { + return nil, fmt.Errorf("failed to cancel task: %w", err) + } + + // Return updated task + return tm.store.GetTask(ctx, taskID) +} + +// SubscribeToTask creates a subscription to task events +func (tm *TaskManager) SubscribeToTask(ctx context.Context, taskID string) (*TaskSubscription, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Verify task exists + task, err := tm.store.GetTask(ctx, taskID) + if err != nil { + return nil, fmt.Errorf("failed to get task: %w", err) + } + + // Create subscription + subscription := NewTaskSubscription(taskID, tm.eventBus) + + // If task is already completed, send current state and close + if task.Status.State.IsTerminal() || task.Status.State.IsInterrupted() { + go func() { + defer subscription.Close() + + // Send current task state + taskResponse := &types.TaskStreamResponse{Task: task} + select { + case subscription.events <- taskResponse: + case <-time.After(5 * time.Second): + // Timeout sending to closed subscription + } + }() + } + + return subscription, nil +} + +// GetTaskHistory retrieves task history with optional limit +func (tm *TaskManager) GetTaskHistory(ctx context.Context, taskID string, limit int) ([]*types.Message, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + return tm.store.GetHistory(ctx, taskID, limit) +} + +// ListTasksByContext lists all tasks for a context +func (tm *TaskManager) ListTasksByContext(ctx context.Context, contextID string) ([]*types.Task, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + return tm.store.ListTasksByContext(ctx, contextID) +} + +// ListActiveTasks lists all non-terminal tasks +func (tm *TaskManager) ListActiveTasks(ctx context.Context) ([]*types.Task, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + activeStates := []types.TaskState{ + types.TaskStateSubmitted, + types.TaskStateWorking, + types.TaskStateInputRequired, + types.TaskStateAuthRequired, + } + + return tm.store.ListTasksByState(ctx, activeStates) +} + +// ListTasks lists tasks with optional filtering and pagination +func (tm *TaskManager) ListTasks(ctx context.Context, req *types.ListTasksRequest) (*types.ListTasksResponse, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + var tasks []*types.Task + var err error + + // If context ID is specified, filter by context + if req.ContextID != "" { + tasks, err = tm.store.ListTasksByContext(ctx, req.ContextID) + if err != nil { + return nil, fmt.Errorf("failed to list tasks by context: %w", err) + } + } else if len(req.States) > 0 { + // Filter by states if specified + tasks, err = tm.store.ListTasksByState(ctx, req.States) + if err != nil { + return nil, fmt.Errorf("failed to list tasks by state: %w", err) + } + } else { + // List all tasks (would need a new store method for this) + // For now, list active tasks as fallback + activeStates := []types.TaskState{ + types.TaskStateSubmitted, + types.TaskStateWorking, + types.TaskStateInputRequired, + types.TaskStateAuthRequired, + types.TaskStateCompleted, + types.TaskStateFailed, + types.TaskStateCancelled, + types.TaskStateRejected, + } + tasks, err = tm.store.ListTasksByState(ctx, activeStates) + if err != nil { + return nil, fmt.Errorf("failed to list all tasks: %w", err) + } + } + + // Apply pagination if requested + pageSize := int(req.PageSize) + if pageSize <= 0 { + pageSize = 50 // Default page size + } + + // Simple pagination implementation + // In a real implementation, you'd want proper cursor-based pagination + startIndex := 0 + if req.PageToken != "" { + // Parse page token (simplified - in practice you'd want proper encoding) + fmt.Sscanf(req.PageToken, "%d", &startIndex) + } + + endIndex := startIndex + pageSize + var nextPageToken string + + if endIndex < len(tasks) { + nextPageToken = fmt.Sprintf("%d", endIndex) + tasks = tasks[startIndex:endIndex] + } else if startIndex < len(tasks) { + tasks = tasks[startIndex:] + } else { + tasks = []*types.Task{} + } + + return &types.ListTasksResponse{ + Tasks: tasks, + NextPageToken: nextPageToken, + }, nil +} + +// Close shuts down the task manager +func (tm *TaskManager) Close(ctx context.Context) error { + var err error + tm.closeOnce.Do(func() { + tm.mu.Lock() + tm.closing = true + tm.mu.Unlock() + + // Stop cleanup routine gracefully + close(tm.cleanupStopCh) + + // Wait for cleanup routine to finish or timeout + select { + case <-tm.cleanupDone: + // Cleanup routine finished gracefully + case <-ctx.Done(): + // Context cancelled, proceed with cleanup + tm.logger.Warn(ctx, "Cleanup routine did not finish within context timeout") + case <-time.After(5 * time.Second): + // Fallback timeout + tm.logger.Warn(ctx, "Cleanup routine did not finish within timeout") + } + + // Close event bus + tm.eventBus.Close() + + // Close store + if closeErr := tm.store.Close(); closeErr != nil { + err = fmt.Errorf("failed to close store: %w", closeErr) + } + }) + + return err +} + +// HealthCheck checks the health of the task manager +func (tm *TaskManager) HealthCheck(ctx context.Context) error { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + return tm.store.HealthCheck(ctx) +} + +// isValidStateTransition validates task state transitions +func (tm *TaskManager) isValidStateTransition(from, to types.TaskState) bool { + // Allow any transition from unspecified + if from == types.TaskStateUnspecified { + return true + } + + // No transitions from terminal states (except cancellation handling) + if from.IsTerminal() && to != from { + return false + } + + // Define valid transitions + validTransitions := map[types.TaskState][]types.TaskState{ + types.TaskStateSubmitted: { + types.TaskStateWorking, + types.TaskStateRejected, + types.TaskStateCancelled, + types.TaskStateAuthRequired, + }, + types.TaskStateWorking: { + types.TaskStateCompleted, + types.TaskStateFailed, + types.TaskStateCancelled, + types.TaskStateInputRequired, + }, + types.TaskStateInputRequired: { + types.TaskStateWorking, + types.TaskStateCancelled, + types.TaskStateFailed, + }, + types.TaskStateAuthRequired: { + types.TaskStateSubmitted, + types.TaskStateWorking, + types.TaskStateCancelled, + types.TaskStateRejected, + }, + } + + allowedStates, exists := validTransitions[from] + if !exists { + return false + } + + for _, allowed := range allowedStates { + if to == allowed { + return true + } + } + + return false +} + +// cleanupRoutine periodically cleans up terminated tasks +func (tm *TaskManager) cleanupRoutine() { + defer close(tm.cleanupDone) + + ticker := time.NewTicker(tm.config.CleanupInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + tm.performCleanup() + case <-tm.cleanupStopCh: + // Perform final cleanup before stopping + tm.performCleanup() + return + } + } +} + +// performCleanup cleans up old terminated tasks +func (tm *TaskManager) performCleanup() { + ctx, cancel := context.WithTimeout(context.Background(), tm.config.DefaultTimeout) + defer cancel() + + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return + } + tm.mu.RUnlock() + + // Get all terminal tasks + terminalStates := []types.TaskState{ + types.TaskStateCompleted, + types.TaskStateFailed, + types.TaskStateCancelled, + types.TaskStateRejected, + } + + tasks, err := tm.store.ListTasksByState(ctx, terminalStates) + if err != nil { + tm.logger.Warn(ctx, "Failed to list terminal tasks for cleanup", + "error", err, + ) + return + } + + cutoff := time.Now().Add(-tm.config.TerminatedTaskTTL) + + for _, task := range tasks { + if task.Status != nil && task.Status.Timestamp.Before(cutoff) { + // Clean up old task subscriptions + tm.eventBus.CleanupTask(task.ID) + } + } +} + +// ProcessMessage processes a message through the handler interface +func (tm *TaskManager) ProcessMessage(ctx context.Context, req *handler.MessageRequest) (*handler.MessageResponse, error) { + tm.mu.RLock() + handler := tm.messageHandler + tm.mu.RUnlock() + + if handler == nil { + return nil, fmt.Errorf("no message handler configured") + } + + // Process through user handler + return handler.HandleMessage(ctx, req) +} + +// ProcessCancel processes a cancellation request through the handler interface +func (tm *TaskManager) ProcessCancel(ctx context.Context, taskID string) error { + tm.mu.RLock() + handler := tm.messageHandler + tm.mu.RUnlock() + + if handler == nil { + return fmt.Errorf("no message handler configured") + } + + // Process through user handler + return handler.HandleCancel(ctx, taskID) +} + +// GetAgentCard retrieves the agent card using the configured provider +func (tm *TaskManager) GetAgentCard(ctx context.Context) (*types.AgentCard, error) { + tm.mu.RLock() + provider := tm.agentCardProvider + tm.mu.RUnlock() + + if provider == nil { + return nil, fmt.Errorf("no agent card provider configured") + } + + // Get card through provider + return provider.GetAgentCard(ctx) +} diff --git a/a2a/pkg/task/manager.go.backup b/a2a/pkg/task/manager.go.backup new file mode 100644 index 000000000..0c1855319 --- /dev/null +++ b/a2a/pkg/task/manager.go.backup @@ -0,0 +1,806 @@ +package task + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/google/uuid" + + "grpc-a2a/pkg/handler" + "grpc-a2a/pkg/task/store" + "grpc-a2a/pkg/types" +) + +// TaskManager manages the complete lifecycle of A2A tasks +// It provides task creation, status updates, artifact management, and event streaming +type TaskManager struct { + store store.TaskStore + eventBus *EventBus + config *Config + logger types.Logger + mu sync.RWMutex + closing bool + closeOnce sync.Once + cleanupStopCh chan struct{} + cleanupDone chan struct{} + + // Handler interfaces (optional) + messageHandler handler.MessageHandler + agentCardProvider handler.AgentCardProvider +} + + +// MessageRequest encapsulates all information for processing request +type MessageRequest struct { + // Original message + Message *types.Message + + // Task context information + TaskID string + ContextID string + + // Processing configuration + Configuration *MessageConfiguration + + // Historical messages + History []*types.Message + + // Request metadata + Metadata map[string]any + + // Task operations handler + TaskOperations TaskOperations +} + +// MessageConfiguration message processing configuration +type MessageConfiguration struct { + // Blocking mode - true: wait for completion; false: return immediately + Blocking bool + + // Streaming mode - true: return event stream; false: return single result + Streaming bool + + // Historical message length limit + HistoryLength int + + // Accepted output modes + AcceptedOutputModes []string + + // Push notification configuration + PushNotification *types.PushNotificationConfig + + // Request timeout + Timeout time.Duration +} + +// MessageResponse processing result +type MessageResponse struct { + // Processing mode + Mode ResponseMode + + // Direct result for non-streaming mode + Result *ResponseResult + + // Event stream for streaming mode + EventStream <-chan types.StreamResponse + + // Error information + Error error +} + +// ResponseMode response mode +type ResponseMode int + +const ( + ResponseModeMessage ResponseMode = iota // Direct message reply + ResponseModeTask // Task reply + ResponseModeStream // Streaming reply +) + +// ResponseResult response result +type ResponseResult struct { + // Result type - Message or Task + Data interface{} + + // Task state update (only when Data is Task) + TaskState types.TaskState + + // Output artifacts + Artifacts []*types.Artifact +} + +// TaskOperations task operations interface +type TaskOperations interface { + // UpdateTaskState update task state + UpdateTaskState(state types.TaskState, message *types.Message) error + + // AddArtifact add output artifact + AddArtifact(artifact *types.Artifact) error + + // GetTaskHistory get task history + GetTaskHistory() ([]*types.Message, error) + + // GetTaskMetadata get task metadata + GetTaskMetadata() (map[string]any, error) + + // SendEvent send event to stream (streaming mode) + SendEvent(event types.StreamResponse) error + + // GetTaskID get current task ID + GetTaskID() string + + // GetContextID get current context ID + GetContextID() string +} + +// UserInfo user information +type UserInfo struct { + UserID string + Scopes []string + Metadata map[string]any +} + +// Config configures the TaskManager +type Config struct { + // Maximum number of messages to keep in task history (0 = unlimited) + MaxHistoryLength int + + // Maximum number of artifacts per task (0 = unlimited) + MaxArtifacts int + + // Default timeout for operations + DefaultTimeout time.Duration + + // Cleanup interval for terminated tasks + CleanupInterval time.Duration + + // TTL for terminated tasks in memory + TerminatedTaskTTL time.Duration +} + +// DefaultConfig returns a default configuration +func DefaultConfig() *Config { + return &Config{ + MaxHistoryLength: 100, + MaxArtifacts: 50, + DefaultTimeout: 30 * time.Second, + CleanupInterval: 5 * time.Minute, + TerminatedTaskTTL: 1 * time.Hour, + } +} + +// NewTaskManager creates a new task manager with the given store and configuration +func NewTaskManager(taskStore store.TaskStore, config *Config) *TaskManager { + return NewTaskManagerWithLogger(taskStore, config, &types.DefaultLogger{}) +} + +// NewTaskManagerWithLogger creates a new task manager with a custom logger +func NewTaskManagerWithLogger(taskStore store.TaskStore, config *Config, logger types.Logger) *TaskManager { + if config == nil { + config = DefaultConfig() + } + if logger == nil { + logger = &types.DefaultLogger{} + } + + eventBus := NewEventBus() + + tm := &TaskManager{ + store: taskStore, + eventBus: eventBus, + config: config, + logger: logger, + cleanupStopCh: make(chan struct{}), + cleanupDone: make(chan struct{}), + } + + // Start cleanup routine + go tm.cleanupRoutine() + + return tm +} + +// NewTaskManagerWithHandlers creates a new task manager with handler interfaces +func NewTaskManagerWithHandlers(taskStore store.TaskStore, config *Config, messageHandler handler.MessageHandler, agentCardProvider handler.AgentCardProvider) *TaskManager { + return NewTaskManagerWithHandlersAndLogger(taskStore, config, messageHandler, agentCardProvider, &types.DefaultLogger{}) +} + +// NewTaskManagerWithHandlersAndLogger creates a new task manager with handler interfaces and custom logger +func NewTaskManagerWithHandlersAndLogger(taskStore store.TaskStore, config *Config, messageHandler handler.MessageHandler, agentCardProvider handler.AgentCardProvider, logger types.Logger) *TaskManager { + if config == nil { + config = DefaultConfig() + } + if logger == nil { + logger = &types.DefaultLogger{} + } + + eventBus := NewEventBus() + + tm := &TaskManager{ + store: taskStore, + eventBus: eventBus, + config: config, + logger: logger, + messageHandler: messageHandler, + agentCardProvider: agentCardProvider, + cleanupStopCh: make(chan struct{}), + cleanupDone: make(chan struct{}), + } + + // Start cleanup routine + go tm.cleanupRoutine() + + return tm +} + +// CreateTask creates a new task from an initial message +func (tm *TaskManager) CreateTask(ctx context.Context, contextID string, message *types.Message, metadata map[string]any) (*types.Task, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Generate task ID + taskID := uuid.New().String() + + // Create initial status + status := &types.TaskStatus{ + State: types.TaskStateSubmitted, + Update: message, + Timestamp: time.Now(), + } + + // Create task with initial message in history + task := &types.Task{ + ID: taskID, + ContextID: contextID, + Status: status, + History: []*types.Message{message}, + Kind: "task", + Metadata: metadata, + } + + // Store the task + if err := tm.store.CreateTask(ctx, task); err != nil { + return nil, fmt.Errorf("failed to create task: %w", err) + } + + // Publish task creation event + taskResponse := &types.TaskStreamResponse{Task: task} + tm.eventBus.PublishToTask(taskID, taskResponse) + + return task, nil +} + +// GetTask retrieves a task by ID +func (tm *TaskManager) GetTask(ctx context.Context, taskID string) (*types.Task, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + task, err := tm.store.GetTask(ctx, taskID) + if err != nil { + // Check if it's a "not found" error and convert to A2A error + if err.Error() == fmt.Sprintf("task not found: %s", taskID) || + err.Error() == fmt.Sprintf("task with ID %s not found", taskID) { + return nil, types.NewTaskNotFoundError(taskID) + } + return nil, err + } + return task, nil +} + +// UpdateTaskStatus updates the status of a task and publishes the event +func (tm *TaskManager) UpdateTaskStatus(ctx context.Context, taskID string, newState types.TaskState, updateMessage *types.Message) error { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Get current task + task, err := tm.store.GetTask(ctx, taskID) + if err != nil { + return fmt.Errorf("failed to get task: %w", err) + } + + // Validate state transition + if !tm.isValidStateTransition(task.Status.State, newState) { + return fmt.Errorf("invalid state transition from %s to %s", task.Status.State.String(), newState.String()) + } + + // Create new status + newStatus := &types.TaskStatus{ + State: newState, + Update: updateMessage, + Timestamp: time.Now(), + } + + // Update task status in store + if err := tm.store.UpdateTaskStatus(ctx, taskID, newStatus); err != nil { + return fmt.Errorf("failed to update task status: %w", err) + } + + // Add update message to history if provided + if updateMessage != nil { + if err := tm.AddMessage(ctx, taskID, updateMessage); err != nil { + // Log error but don't fail the status update + tm.logger.Warn(ctx, "Failed to add update message to history", + "task_id", taskID, + "error", err, + ) + } + } + + // Create and publish status update event + statusEvent := &types.TaskStatusUpdateEvent{ + TaskID: taskID, + ContextID: task.ContextID, + Status: newStatus, + Final: newState.IsTerminal(), + } + + statusResponse := &types.StatusUpdateStreamResponse{StatusUpdate: statusEvent} + tm.eventBus.PublishToTask(taskID, statusResponse) + + return nil +} + +// AddArtifact adds an artifact to a task and publishes the event +func (tm *TaskManager) AddArtifact(ctx context.Context, taskID string, artifact *types.Artifact, append, lastChunk bool) error { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Get task to validate it exists + task, err := tm.store.GetTask(ctx, taskID) + if err != nil { + return fmt.Errorf("failed to get task: %w", err) + } + + // Check artifact limit + if tm.config.MaxArtifacts > 0 && len(task.Artifacts) >= tm.config.MaxArtifacts { + return fmt.Errorf("maximum artifacts limit (%d) reached for task", tm.config.MaxArtifacts) + } + + // Add artifact to store + if err := tm.store.AddArtifact(ctx, taskID, artifact); err != nil { + return fmt.Errorf("failed to add artifact: %w", err) + } + + // Create and publish artifact update event + artifactEvent := &types.TaskArtifactUpdateEvent{ + TaskID: taskID, + ContextID: task.ContextID, + Artifact: artifact, + Append: append, + LastChunk: lastChunk, + } + + artifactResponse := &types.ArtifactUpdateStreamResponse{ArtifactUpdate: artifactEvent} + tm.eventBus.PublishToTask(taskID, artifactResponse) + + return nil +} + +// AddMessage adds a message to task history +func (tm *TaskManager) AddMessage(ctx context.Context, taskID string, message *types.Message) error { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Check history limit if configured + if tm.config.MaxHistoryLength > 0 { + history, err := tm.store.GetHistory(ctx, taskID, 0) // Get all history + if err != nil { + return fmt.Errorf("failed to check history length: %w", err) + } + + if len(history) >= tm.config.MaxHistoryLength { + return fmt.Errorf("maximum history length (%d) reached for task", tm.config.MaxHistoryLength) + } + } + + return tm.store.AddMessage(ctx, taskID, message) +} + +// CancelTask cancels a running task +func (tm *TaskManager) CancelTask(ctx context.Context, taskID string) (*types.Task, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Get current task + task, err := tm.store.GetTask(ctx, taskID) + if err != nil { + // Check if it's a "not found" error and convert to A2A error + if err.Error() == fmt.Sprintf("task not found: %s", taskID) || + err.Error() == fmt.Sprintf("task with ID %s not found", taskID) { + return nil, types.NewTaskNotFoundError(taskID) + } + return nil, fmt.Errorf("failed to get task: %w", err) + } + + // Check if task can be cancelled + if task.Status.State.IsTerminal() { + return nil, types.NewTaskNotCancelableError(taskID, task.Status.State) + } + + // Update status to cancelled + if err := tm.UpdateTaskStatus(ctx, taskID, types.TaskStateCancelled, nil); err != nil { + return nil, fmt.Errorf("failed to cancel task: %w", err) + } + + // Return updated task + return tm.store.GetTask(ctx, taskID) +} + +// SubscribeToTask creates a subscription to task events +func (tm *TaskManager) SubscribeToTask(ctx context.Context, taskID string) (*TaskSubscription, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + // Verify task exists + task, err := tm.store.GetTask(ctx, taskID) + if err != nil { + return nil, fmt.Errorf("failed to get task: %w", err) + } + + // Create subscription + subscription := NewTaskSubscription(taskID, tm.eventBus) + + // If task is already completed, send current state and close + if task.Status.State.IsTerminal() || task.Status.State.IsInterrupted() { + go func() { + defer subscription.Close() + + // Send current task state + taskResponse := &types.TaskStreamResponse{Task: task} + select { + case subscription.events <- taskResponse: + case <-time.After(5 * time.Second): + // Timeout sending to closed subscription + } + }() + } + + return subscription, nil +} + +// GetTaskHistory retrieves task history with optional limit +func (tm *TaskManager) GetTaskHistory(ctx context.Context, taskID string, limit int) ([]*types.Message, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + return tm.store.GetHistory(ctx, taskID, limit) +} + +// ListTasksByContext lists all tasks for a context +func (tm *TaskManager) ListTasksByContext(ctx context.Context, contextID string) ([]*types.Task, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + return tm.store.ListTasksByContext(ctx, contextID) +} + +// ListActiveTasks lists all non-terminal tasks +func (tm *TaskManager) ListActiveTasks(ctx context.Context) ([]*types.Task, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + activeStates := []types.TaskState{ + types.TaskStateSubmitted, + types.TaskStateWorking, + types.TaskStateInputRequired, + types.TaskStateAuthRequired, + } + + return tm.store.ListTasksByState(ctx, activeStates) +} + +// ListTasks lists tasks with optional filtering and pagination +func (tm *TaskManager) ListTasks(ctx context.Context, req *types.ListTasksRequest) (*types.ListTasksResponse, error) { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return nil, fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + var tasks []*types.Task + var err error + + // If context ID is specified, filter by context + if req.ContextID != "" { + tasks, err = tm.store.ListTasksByContext(ctx, req.ContextID) + if err != nil { + return nil, fmt.Errorf("failed to list tasks by context: %w", err) + } + } else if len(req.States) > 0 { + // Filter by states if specified + tasks, err = tm.store.ListTasksByState(ctx, req.States) + if err != nil { + return nil, fmt.Errorf("failed to list tasks by state: %w", err) + } + } else { + // List all tasks (would need a new store method for this) + // For now, list active tasks as fallback + activeStates := []types.TaskState{ + types.TaskStateSubmitted, + types.TaskStateWorking, + types.TaskStateInputRequired, + types.TaskStateAuthRequired, + types.TaskStateCompleted, + types.TaskStateFailed, + types.TaskStateCancelled, + types.TaskStateRejected, + } + tasks, err = tm.store.ListTasksByState(ctx, activeStates) + if err != nil { + return nil, fmt.Errorf("failed to list all tasks: %w", err) + } + } + + // Apply pagination if requested + pageSize := int(req.PageSize) + if pageSize <= 0 { + pageSize = 50 // Default page size + } + + // Simple pagination implementation + // In a real implementation, you'd want proper cursor-based pagination + startIndex := 0 + if req.PageToken != "" { + // Parse page token (simplified - in practice you'd want proper encoding) + fmt.Sscanf(req.PageToken, "%d", &startIndex) + } + + endIndex := startIndex + pageSize + var nextPageToken string + + if endIndex < len(tasks) { + nextPageToken = fmt.Sprintf("%d", endIndex) + tasks = tasks[startIndex:endIndex] + } else if startIndex < len(tasks) { + tasks = tasks[startIndex:] + } else { + tasks = []*types.Task{} + } + + return &types.ListTasksResponse{ + Tasks: tasks, + NextPageToken: nextPageToken, + }, nil +} + +// Close shuts down the task manager +func (tm *TaskManager) Close(ctx context.Context) error { + var err error + tm.closeOnce.Do(func() { + tm.mu.Lock() + tm.closing = true + tm.mu.Unlock() + + // Stop cleanup routine gracefully + close(tm.cleanupStopCh) + + // Wait for cleanup routine to finish or timeout + select { + case <-tm.cleanupDone: + // Cleanup routine finished gracefully + case <-ctx.Done(): + // Context cancelled, proceed with cleanup + tm.logger.Warn(ctx, "Cleanup routine did not finish within context timeout") + case <-time.After(5 * time.Second): + // Fallback timeout + tm.logger.Warn(ctx, "Cleanup routine did not finish within timeout") + } + + // Close event bus + tm.eventBus.Close() + + // Close store + if closeErr := tm.store.Close(); closeErr != nil { + err = fmt.Errorf("failed to close store: %w", closeErr) + } + }) + + return err +} + +// HealthCheck checks the health of the task manager +func (tm *TaskManager) HealthCheck(ctx context.Context) error { + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return fmt.Errorf("task manager is closing") + } + tm.mu.RUnlock() + + return tm.store.HealthCheck(ctx) +} + +// isValidStateTransition validates task state transitions +func (tm *TaskManager) isValidStateTransition(from, to types.TaskState) bool { + // Allow any transition from unspecified + if from == types.TaskStateUnspecified { + return true + } + + // No transitions from terminal states (except cancellation handling) + if from.IsTerminal() && to != from { + return false + } + + // Define valid transitions + validTransitions := map[types.TaskState][]types.TaskState{ + types.TaskStateSubmitted: { + types.TaskStateWorking, + types.TaskStateRejected, + types.TaskStateCancelled, + types.TaskStateAuthRequired, + }, + types.TaskStateWorking: { + types.TaskStateCompleted, + types.TaskStateFailed, + types.TaskStateCancelled, + types.TaskStateInputRequired, + }, + types.TaskStateInputRequired: { + types.TaskStateWorking, + types.TaskStateCancelled, + types.TaskStateFailed, + }, + types.TaskStateAuthRequired: { + types.TaskStateSubmitted, + types.TaskStateWorking, + types.TaskStateCancelled, + types.TaskStateRejected, + }, + } + + allowedStates, exists := validTransitions[from] + if !exists { + return false + } + + for _, allowed := range allowedStates { + if to == allowed { + return true + } + } + + return false +} + +// cleanupRoutine periodically cleans up terminated tasks +func (tm *TaskManager) cleanupRoutine() { + defer close(tm.cleanupDone) + + ticker := time.NewTicker(tm.config.CleanupInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + tm.performCleanup() + case <-tm.cleanupStopCh: + // Perform final cleanup before stopping + tm.performCleanup() + return + } + } +} + +// performCleanup cleans up old terminated tasks +func (tm *TaskManager) performCleanup() { + ctx, cancel := context.WithTimeout(context.Background(), tm.config.DefaultTimeout) + defer cancel() + + tm.mu.RLock() + if tm.closing { + tm.mu.RUnlock() + return + } + tm.mu.RUnlock() + + // Get all terminal tasks + terminalStates := []types.TaskState{ + types.TaskStateCompleted, + types.TaskStateFailed, + types.TaskStateCancelled, + types.TaskStateRejected, + } + + tasks, err := tm.store.ListTasksByState(ctx, terminalStates) + if err != nil { + tm.logger.Warn(ctx, "Failed to list terminal tasks for cleanup", + "error", err, + ) + return + } + + cutoff := time.Now().Add(-tm.config.TerminatedTaskTTL) + + for _, task := range tasks { + if task.Status != nil && task.Status.Timestamp.Before(cutoff) { + // Clean up old task subscriptions + tm.eventBus.CleanupTask(task.ID) + } + } +} + +// ProcessMessage processes a message through the handler interface +func (tm *TaskManager) ProcessMessage(ctx context.Context, req *handler.MessageRequest) (*handler.MessageResponse, error) { + tm.mu.RLock() + handler := tm.messageHandler + tm.mu.RUnlock() + + if handler == nil { + return nil, fmt.Errorf("no message handler configured") + } + + // Process through user handler + return handler.HandleMessage(ctx, req) +} + +// ProcessCancel processes a cancellation request through the handler interface +func (tm *TaskManager) ProcessCancel(ctx context.Context, taskID string) error { + tm.mu.RLock() + handler := tm.messageHandler + tm.mu.RUnlock() + + if handler == nil { + return fmt.Errorf("no message handler configured") + } + + // Process through user handler + return handler.HandleCancel(ctx, taskID) +} + +// GetAgentCard retrieves the agent card using the configured provider +func (tm *TaskManager) GetAgentCard(ctx context.Context) (*types.AgentCard, error) { + tm.mu.RLock() + provider := tm.agentCardProvider + tm.mu.RUnlock() + + if provider == nil { + return nil, fmt.Errorf("no agent card provider configured") + } + + // Get card through provider + return provider.GetAgentCard(ctx) +} \ No newline at end of file diff --git a/a2a/pkg/task/store/memory.go b/a2a/pkg/task/store/memory.go new file mode 100644 index 000000000..e92bf734c --- /dev/null +++ b/a2a/pkg/task/store/memory.go @@ -0,0 +1,465 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package store + +import ( + "context" + "fmt" + "seata-go-ai-a2a/pkg/types" + "sync" +) + +// MemoryTaskStore implements TaskStore interface using in-memory storage +// This implementation is thread-safe and suitable for development and testing +type MemoryTaskStore struct { + mu sync.RWMutex + tasks map[string]*types.Task +} + +// NewMemoryTaskStore creates a new in-memory task store +func NewMemoryTaskStore() *MemoryTaskStore { + return &MemoryTaskStore{ + tasks: make(map[string]*types.Task), + } +} + +// CreateTask stores a new task in memory +func (m *MemoryTaskStore) CreateTask(ctx context.Context, task *types.Task) error { + if task == nil { + return fmt.Errorf("task cannot be nil") + } + if task.ID == "" { + return fmt.Errorf("task ID cannot be empty") + } + + m.mu.Lock() + defer m.mu.Unlock() + + if _, exists := m.tasks[task.ID]; exists { + return fmt.Errorf("task with ID %s already exists", task.ID) + } + + // Deep copy the task to avoid external mutations + taskCopy := m.copyTask(task) + m.tasks[task.ID] = taskCopy + + return nil +} + +// GetTask retrieves a task by ID +func (m *MemoryTaskStore) GetTask(ctx context.Context, taskID string) (*types.Task, error) { + if taskID == "" { + return nil, fmt.Errorf("task ID cannot be empty") + } + + m.mu.RLock() + defer m.mu.RUnlock() + + task, exists := m.tasks[taskID] + if !exists { + return nil, fmt.Errorf("task with ID %s not found", taskID) + } + + // Return a copy to prevent external mutations + return m.copyTask(task), nil +} + +// UpdateTask updates an existing task +func (m *MemoryTaskStore) UpdateTask(ctx context.Context, task *types.Task) error { + if task == nil { + return fmt.Errorf("task cannot be nil") + } + if task.ID == "" { + return fmt.Errorf("task ID cannot be empty") + } + + m.mu.Lock() + defer m.mu.Unlock() + + if _, exists := m.tasks[task.ID]; !exists { + return fmt.Errorf("task with ID %s not found", task.ID) + } + + // Deep copy the task to avoid external mutations + taskCopy := m.copyTask(task) + m.tasks[task.ID] = taskCopy + + return nil +} + +// DeleteTask removes a task from memory +func (m *MemoryTaskStore) DeleteTask(ctx context.Context, taskID string) error { + if taskID == "" { + return fmt.Errorf("task ID cannot be empty") + } + + m.mu.Lock() + defer m.mu.Unlock() + + if _, exists := m.tasks[taskID]; !exists { + return fmt.Errorf("task with ID %s not found", taskID) + } + + delete(m.tasks, taskID) + return nil +} + +// UpdateTaskStatus updates the status of an existing task +func (m *MemoryTaskStore) UpdateTaskStatus(ctx context.Context, taskID string, status *types.TaskStatus) error { + if taskID == "" { + return fmt.Errorf("task ID cannot be empty") + } + if status == nil { + return fmt.Errorf("status cannot be nil") + } + + m.mu.Lock() + defer m.mu.Unlock() + + task, exists := m.tasks[taskID] + if !exists { + return fmt.Errorf("task with ID %s not found", taskID) + } + + // Update the status with a copy + task.Status = m.copyTaskStatus(status) + return nil +} + +// AddArtifact adds an artifact to a task +func (m *MemoryTaskStore) AddArtifact(ctx context.Context, taskID string, artifact *types.Artifact) error { + if taskID == "" { + return fmt.Errorf("task ID cannot be empty") + } + if artifact == nil { + return fmt.Errorf("artifact cannot be nil") + } + + m.mu.Lock() + defer m.mu.Unlock() + + task, exists := m.tasks[taskID] + if !exists { + return fmt.Errorf("task with ID %s not found", taskID) + } + + // Add a copy of the artifact + artifactCopy := m.copyArtifact(artifact) + task.Artifacts = append(task.Artifacts, artifactCopy) + return nil +} + +// GetArtifacts retrieves all artifacts for a task +func (m *MemoryTaskStore) GetArtifacts(ctx context.Context, taskID string) ([]*types.Artifact, error) { + if taskID == "" { + return nil, fmt.Errorf("task ID cannot be empty") + } + + m.mu.RLock() + defer m.mu.RUnlock() + + task, exists := m.tasks[taskID] + if !exists { + return nil, fmt.Errorf("task with ID %s not found", taskID) + } + + // Return copies of artifacts + artifacts := make([]*types.Artifact, len(task.Artifacts)) + for i, artifact := range task.Artifacts { + artifacts[i] = m.copyArtifact(artifact) + } + + return artifacts, nil +} + +// AddMessage adds a message to task history +func (m *MemoryTaskStore) AddMessage(ctx context.Context, taskID string, message *types.Message) error { + if taskID == "" { + return fmt.Errorf("task ID cannot be empty") + } + if message == nil { + return fmt.Errorf("message cannot be nil") + } + + m.mu.Lock() + defer m.mu.Unlock() + + task, exists := m.tasks[taskID] + if !exists { + return fmt.Errorf("task with ID %s not found", taskID) + } + + // Add a copy of the message + messageCopy := m.copyMessage(message) + task.History = append(task.History, messageCopy) + return nil +} + +// GetHistory retrieves task history with optional limit +func (m *MemoryTaskStore) GetHistory(ctx context.Context, taskID string, limit int) ([]*types.Message, error) { + if taskID == "" { + return nil, fmt.Errorf("task ID cannot be empty") + } + + m.mu.RLock() + defer m.mu.RUnlock() + + task, exists := m.tasks[taskID] + if !exists { + return nil, fmt.Errorf("task with ID %s not found", taskID) + } + + history := task.History + if limit > 0 && len(history) > limit { + // Return the most recent messages + history = history[len(history)-limit:] + } + + // Return copies of messages + messages := make([]*types.Message, len(history)) + for i, message := range history { + messages[i] = m.copyMessage(message) + } + + return messages, nil +} + +// ListTasksByContext lists all tasks for a specific context +func (m *MemoryTaskStore) ListTasksByContext(ctx context.Context, contextID string) ([]*types.Task, error) { + if contextID == "" { + return nil, fmt.Errorf("context ID cannot be empty") + } + + m.mu.RLock() + defer m.mu.RUnlock() + + var tasks []*types.Task + for _, task := range m.tasks { + if task.ContextID == contextID { + tasks = append(tasks, m.copyTask(task)) + } + } + + return tasks, nil +} + +// ListTasksByState lists all tasks with specified states +func (m *MemoryTaskStore) ListTasksByState(ctx context.Context, states []types.TaskState) ([]*types.Task, error) { + if len(states) == 0 { + return nil, fmt.Errorf("at least one state must be specified") + } + + m.mu.RLock() + defer m.mu.RUnlock() + + stateSet := make(map[types.TaskState]bool) + for _, state := range states { + stateSet[state] = true + } + + var tasks []*types.Task + for _, task := range m.tasks { + if task.Status != nil && stateSet[task.Status.State] { + tasks = append(tasks, m.copyTask(task)) + } + } + + return tasks, nil +} + +// HealthCheck checks the health of the memory store +func (m *MemoryTaskStore) HealthCheck(ctx context.Context) error { + m.mu.RLock() + defer m.mu.RUnlock() + // Memory store is always healthy if accessible + return nil +} + +// Close cleans up the memory store +func (m *MemoryTaskStore) Close() error { + m.mu.Lock() + defer m.mu.Unlock() + + // Clear all tasks + m.tasks = make(map[string]*types.Task) + return nil +} + +// Helper methods for deep copying + +func (m *MemoryTaskStore) copyTask(task *types.Task) *types.Task { + if task == nil { + return nil + } + + copy := &types.Task{ + ID: task.ID, + ContextID: task.ContextID, + Status: m.copyTaskStatus(task.Status), + Metadata: m.copyMetadata(task.Metadata), + } + + // Copy artifacts + if task.Artifacts != nil { + copy.Artifacts = make([]*types.Artifact, len(task.Artifacts)) + for i, artifact := range task.Artifacts { + copy.Artifacts[i] = m.copyArtifact(artifact) + } + } + + // Copy history + if task.History != nil { + copy.History = make([]*types.Message, len(task.History)) + for i, message := range task.History { + copy.History[i] = m.copyMessage(message) + } + } + + return copy +} + +func (m *MemoryTaskStore) copyTaskStatus(status *types.TaskStatus) *types.TaskStatus { + if status == nil { + return nil + } + + return &types.TaskStatus{ + State: status.State, + Update: m.copyMessage(status.Update), + Timestamp: status.Timestamp, + } +} + +func (m *MemoryTaskStore) copyArtifact(artifact *types.Artifact) *types.Artifact { + if artifact == nil { + return nil + } + + copy := &types.Artifact{ + ArtifactID: artifact.ArtifactID, + Name: artifact.Name, + Description: artifact.Description, + Metadata: m.copyMetadata(artifact.Metadata), + Extensions: m.copyStringSlice(artifact.Extensions), + } + + // Copy parts + if artifact.Parts != nil { + copy.Parts = make([]types.Part, len(artifact.Parts)) + for i, part := range artifact.Parts { + copy.Parts[i] = m.copyPart(part) + } + } + + return copy +} + +func (m *MemoryTaskStore) copyMessage(message *types.Message) *types.Message { + if message == nil { + return nil + } + + copy := &types.Message{ + MessageID: message.MessageID, + ContextID: message.ContextID, + TaskID: message.TaskID, + Role: message.Role, + Metadata: m.copyMetadata(message.Metadata), + Extensions: m.copyStringSlice(message.Extensions), + } + + // Copy content parts + if message.Parts != nil { + copy.Parts = make([]types.Part, len(message.Parts)) + for i, part := range message.Parts { + copy.Parts[i] = m.copyPart(part) + } + } + + return copy +} + +func (m *MemoryTaskStore) copyPart(part types.Part) types.Part { + if part == nil { + return nil + } + + switch p := part.(type) { + case *types.TextPart: + return &types.TextPart{ + Text: p.Text, + Metadata: m.copyMetadata(p.Metadata), + } + case *types.FilePart: + return &types.FilePart{ + Content: m.copyFileContent(p.Content), + MimeType: p.MimeType, + Name: p.Name, + Metadata: m.copyMetadata(p.Metadata), + } + case *types.DataPart: + return &types.DataPart{ + Data: m.copyMetadata(p.Data), + Metadata: m.copyMetadata(p.Metadata), + } + default: + // Return the original part if type is unknown + return part + } +} + +func (m *MemoryTaskStore) copyFileContent(content types.FileContent) types.FileContent { + if content == nil { + return nil + } + + switch c := content.(type) { + case *types.FileWithURI: + return &types.FileWithURI{URI: c.URI} + case *types.FileWithBytes: + bytes := make([]byte, len(c.Bytes)) + copy(bytes, c.Bytes) + return &types.FileWithBytes{Bytes: bytes} + default: + return content + } +} + +func (m *MemoryTaskStore) copyMetadata(metadata map[string]any) map[string]any { + if metadata == nil { + return nil + } + + copy := make(map[string]any, len(metadata)) + for k, v := range metadata { + copy[k] = v + } + return copy +} + +func (m *MemoryTaskStore) copyStringSlice(slice []string) []string { + if slice == nil { + return nil + } + + copy := make([]string, len(slice)) + for i, s := range slice { + copy[i] = s + } + return copy +} diff --git a/a2a/pkg/task/store/redis.go b/a2a/pkg/task/store/redis.go new file mode 100644 index 000000000..ae6b3de14 --- /dev/null +++ b/a2a/pkg/task/store/redis.go @@ -0,0 +1,464 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package store + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/redis/go-redis/v9" + + "seata-go-ai-a2a/pkg/types" +) + +// RedisTaskStore implements TaskStore interface using Redis storage +// This implementation supports distributed deployments and persistence +type RedisTaskStore struct { + client redis.Cmdable + prefix string + ttl time.Duration +} + +// RedisTaskStoreConfig configures the Redis task store +type RedisTaskStoreConfig struct { + // Redis client (can be *redis.Client or *redis.ClusterClient) + Client redis.Cmdable + + // Key prefix for Redis keys (default: "a2a:tasks:") + KeyPrefix string + + // TTL for terminal tasks (default: 24 hours) + TerminalTaskTTL time.Duration +} + +// NewRedisTaskStore creates a new Redis-backed task store +func NewRedisTaskStore(config RedisTaskStoreConfig) *RedisTaskStore { + prefix := config.KeyPrefix + if prefix == "" { + prefix = "a2a:tasks:" + } + + ttl := config.TerminalTaskTTL + if ttl == 0 { + ttl = 24 * time.Hour + } + + return &RedisTaskStore{ + client: config.Client, + prefix: prefix, + ttl: ttl, + } +} + +// Redis key patterns +const ( + keyPatternTask = "%stask:%s" // a2a:tasks:task:{taskID} + keyPatternArtifacts = "%stask:%s:artifacts" // a2a:tasks:task:{taskID}:artifacts + keyPatternHistory = "%stask:%s:history" // a2a:tasks:task:{taskID}:history + keyPatternByContext = "%scontext:%s" // a2a:tasks:context:{contextID} + keyPatternByState = "%sstate:%s" // a2a:tasks:state:{state} +) + +// CreateTask stores a new task in Redis +func (r *RedisTaskStore) CreateTask(ctx context.Context, task *types.Task) error { + if task == nil { + return fmt.Errorf("task cannot be nil") + } + if task.ID == "" { + return fmt.Errorf("task ID cannot be empty") + } + + taskKey := fmt.Sprintf(keyPatternTask, r.prefix, task.ID) + + // Check if task already exists + exists, err := r.client.Exists(ctx, taskKey).Result() + if err != nil { + return fmt.Errorf("failed to check task existence: %w", err) + } + if exists > 0 { + return fmt.Errorf("task with ID %s already exists", task.ID) + } + + // Serialize task to JSON + taskData, err := json.Marshal(task) + if err != nil { + return fmt.Errorf("failed to marshal task: %w", err) + } + + // Use pipeline for atomic operations + pipe := r.client.Pipeline() + + // Store task data + pipe.Set(ctx, taskKey, taskData, 0) + + // Add to context index + if task.ContextID != "" { + contextKey := fmt.Sprintf(keyPatternByContext, r.prefix, task.ContextID) + pipe.SAdd(ctx, contextKey, task.ID) + } + + // Add to state index + if task.Status != nil { + stateKey := fmt.Sprintf(keyPatternByState, r.prefix, task.Status.State.String()) + pipe.SAdd(ctx, stateKey, task.ID) + } + + // Execute pipeline + _, err = pipe.Exec(ctx) + if err != nil { + return fmt.Errorf("failed to create task: %w", err) + } + + return nil +} + +// GetTask retrieves a task by ID from Redis +func (r *RedisTaskStore) GetTask(ctx context.Context, taskID string) (*types.Task, error) { + if taskID == "" { + return nil, fmt.Errorf("task ID cannot be empty") + } + + taskKey := fmt.Sprintf(keyPatternTask, r.prefix, taskID) + + taskData, err := r.client.Get(ctx, taskKey).Result() + if err != nil { + if err == redis.Nil { + return nil, fmt.Errorf("task with ID %s not found", taskID) + } + return nil, fmt.Errorf("failed to get task: %w", err) + } + + var task types.Task + if err := json.Unmarshal([]byte(taskData), &task); err != nil { + return nil, fmt.Errorf("failed to unmarshal task: %w", err) + } + + return &task, nil +} + +// UpdateTask updates an existing task in Redis +func (r *RedisTaskStore) UpdateTask(ctx context.Context, task *types.Task) error { + if task == nil { + return fmt.Errorf("task cannot be nil") + } + if task.ID == "" { + return fmt.Errorf("task ID cannot be empty") + } + + taskKey := fmt.Sprintf(keyPatternTask, r.prefix, task.ID) + + // Check if task exists + exists, err := r.client.Exists(ctx, taskKey).Result() + if err != nil { + return fmt.Errorf("failed to check task existence: %w", err) + } + if exists == 0 { + return fmt.Errorf("task with ID %s not found", task.ID) + } + + // Get current task to update indexes + currentTask, err := r.GetTask(ctx, task.ID) + if err != nil { + return fmt.Errorf("failed to get current task: %w", err) + } + + // Serialize updated task + taskData, err := json.Marshal(task) + if err != nil { + return fmt.Errorf("failed to marshal task: %w", err) + } + + pipe := r.client.Pipeline() + + // Update task data + var ttl time.Duration + if task.Status != nil && task.Status.State.IsTerminal() { + ttl = r.ttl + } + pipe.Set(ctx, taskKey, taskData, ttl) + + // Update state index if state changed + if currentTask.Status != nil && task.Status != nil && + currentTask.Status.State != task.Status.State { + + // Remove from old state index + oldStateKey := fmt.Sprintf(keyPatternByState, r.prefix, currentTask.Status.State.String()) + pipe.SRem(ctx, oldStateKey, task.ID) + + // Add to new state index + newStateKey := fmt.Sprintf(keyPatternByState, r.prefix, task.Status.State.String()) + pipe.SAdd(ctx, newStateKey, task.ID) + } + + // Execute pipeline + _, err = pipe.Exec(ctx) + if err != nil { + return fmt.Errorf("failed to update task: %w", err) + } + + return nil +} + +// DeleteTask removes a task from Redis +func (r *RedisTaskStore) DeleteTask(ctx context.Context, taskID string) error { + if taskID == "" { + return fmt.Errorf("task ID cannot be empty") + } + + // Get task first to clean up indexes + task, err := r.GetTask(ctx, taskID) + if err != nil { + return err + } + + pipe := r.client.Pipeline() + + // Delete main task key + taskKey := fmt.Sprintf(keyPatternTask, r.prefix, taskID) + pipe.Del(ctx, taskKey) + + // Delete artifacts + artifactsKey := fmt.Sprintf(keyPatternArtifacts, r.prefix, taskID) + pipe.Del(ctx, artifactsKey) + + // Delete history + historyKey := fmt.Sprintf(keyPatternHistory, r.prefix, taskID) + pipe.Del(ctx, historyKey) + + // Remove from context index + if task.ContextID != "" { + contextKey := fmt.Sprintf(keyPatternByContext, r.prefix, task.ContextID) + pipe.SRem(ctx, contextKey, taskID) + } + + // Remove from state index + if task.Status != nil { + stateKey := fmt.Sprintf(keyPatternByState, r.prefix, task.Status.State.String()) + pipe.SRem(ctx, stateKey, taskID) + } + + // Execute pipeline + _, err = pipe.Exec(ctx) + if err != nil { + return fmt.Errorf("failed to delete task: %w", err) + } + + return nil +} + +// UpdateTaskStatus updates the status of an existing task +func (r *RedisTaskStore) UpdateTaskStatus(ctx context.Context, taskID string, status *types.TaskStatus) error { + if taskID == "" { + return fmt.Errorf("task ID cannot be empty") + } + if status == nil { + return fmt.Errorf("status cannot be nil") + } + + // Get current task + task, err := r.GetTask(ctx, taskID) + if err != nil { + return err + } + + // Update status + task.Status = status + + // Update the task + return r.UpdateTask(ctx, task) +} + +// AddArtifact adds an artifact to a task +func (r *RedisTaskStore) AddArtifact(ctx context.Context, taskID string, artifact *types.Artifact) error { + if taskID == "" { + return fmt.Errorf("task ID cannot be empty") + } + if artifact == nil { + return fmt.Errorf("artifact cannot be nil") + } + + // Get current task + task, err := r.GetTask(ctx, taskID) + if err != nil { + return err + } + + // Add artifact to task + task.Artifacts = append(task.Artifacts, artifact) + + // Update the task + return r.UpdateTask(ctx, task) +} + +// GetArtifacts retrieves all artifacts for a task +func (r *RedisTaskStore) GetArtifacts(ctx context.Context, taskID string) ([]*types.Artifact, error) { + task, err := r.GetTask(ctx, taskID) + if err != nil { + return nil, err + } + + return task.Artifacts, nil +} + +// AddMessage adds a message to task history +func (r *RedisTaskStore) AddMessage(ctx context.Context, taskID string, message *types.Message) error { + if taskID == "" { + return fmt.Errorf("task ID cannot be empty") + } + if message == nil { + return fmt.Errorf("message cannot be nil") + } + + // Get current task + task, err := r.GetTask(ctx, taskID) + if err != nil { + return err + } + + // Add message to history + task.History = append(task.History, message) + + // Update the task + return r.UpdateTask(ctx, task) +} + +// GetHistory retrieves task history with optional limit +func (r *RedisTaskStore) GetHistory(ctx context.Context, taskID string, limit int) ([]*types.Message, error) { + task, err := r.GetTask(ctx, taskID) + if err != nil { + return nil, err + } + + history := task.History + if limit > 0 && len(history) > limit { + // Return the most recent messages + history = history[len(history)-limit:] + } + + return history, nil +} + +// ListTasksByContext lists all tasks for a specific context +func (r *RedisTaskStore) ListTasksByContext(ctx context.Context, contextID string) ([]*types.Task, error) { + if contextID == "" { + return nil, fmt.Errorf("context ID cannot be empty") + } + + contextKey := fmt.Sprintf(keyPatternByContext, r.prefix, contextID) + taskIDs, err := r.client.SMembers(ctx, contextKey).Result() + if err != nil { + return nil, fmt.Errorf("failed to get task IDs for context: %w", err) + } + + return r.getTasksByIDs(ctx, taskIDs) +} + +// ListTasksByState lists all tasks with specified states +func (r *RedisTaskStore) ListTasksByState(ctx context.Context, states []types.TaskState) ([]*types.Task, error) { + if len(states) == 0 { + return nil, fmt.Errorf("at least one state must be specified") + } + + var allTaskIDs []string + + for _, state := range states { + stateKey := fmt.Sprintf(keyPatternByState, r.prefix, state.String()) + taskIDs, err := r.client.SMembers(ctx, stateKey).Result() + if err != nil { + return nil, fmt.Errorf("failed to get task IDs for state %s: %w", state.String(), err) + } + allTaskIDs = append(allTaskIDs, taskIDs...) + } + + // Remove duplicates + taskIDSet := make(map[string]bool) + var uniqueTaskIDs []string + for _, taskID := range allTaskIDs { + if !taskIDSet[taskID] { + taskIDSet[taskID] = true + uniqueTaskIDs = append(uniqueTaskIDs, taskID) + } + } + + return r.getTasksByIDs(ctx, uniqueTaskIDs) +} + +// HealthCheck checks the health of Redis connection +func (r *RedisTaskStore) HealthCheck(ctx context.Context) error { + return r.client.Ping(ctx).Err() +} + +// Close cleans up Redis connections +func (r *RedisTaskStore) Close() error { + // If the client is a *redis.Client, close it + if client, ok := r.client.(*redis.Client); ok { + return client.Close() + } + // For cluster clients or other implementations, no-op + return nil +} + +// Helper method to get multiple tasks by IDs +func (r *RedisTaskStore) getTasksByIDs(ctx context.Context, taskIDs []string) ([]*types.Task, error) { + if len(taskIDs) == 0 { + return []*types.Task{}, nil + } + + // Build keys for pipeline + keys := make([]string, len(taskIDs)) + for i, taskID := range taskIDs { + keys[i] = fmt.Sprintf(keyPatternTask, r.prefix, taskID) + } + + // Use pipeline for batch get + pipe := r.client.Pipeline() + cmds := make([]*redis.StringCmd, len(keys)) + for i, key := range keys { + cmds[i] = pipe.Get(ctx, key) + } + + _, err := pipe.Exec(ctx) + if err != nil && !errors.Is(err, redis.Nil) { + return nil, fmt.Errorf("failed to get tasks: %w", err) + } + + // Parse results + var tasks []*types.Task + for i, cmd := range cmds { + result, err := cmd.Result() + if err != nil { + if err == redis.Nil { + // Task not found, skip + continue + } + return nil, fmt.Errorf("failed to get task %s: %w", taskIDs[i], err) + } + + var task types.Task + if err := json.Unmarshal([]byte(result), &task); err != nil { + return nil, fmt.Errorf("failed to unmarshal task %s: %w", taskIDs[i], err) + } + + tasks = append(tasks, &task) + } + + return tasks, nil +} diff --git a/a2a/pkg/task/store/store.go b/a2a/pkg/task/store/store.go new file mode 100644 index 000000000..878541313 --- /dev/null +++ b/a2a/pkg/task/store/store.go @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package store + +import ( + "context" + + "seata-go-ai-a2a/pkg/types" +) + +// TaskStore defines the interface for task persistence operations +// All implementations must be thread-safe and support concurrent access +type TaskStore interface { + // Task CRUD operations + CreateTask(ctx context.Context, task *types.Task) error + GetTask(ctx context.Context, taskID string) (*types.Task, error) + UpdateTask(ctx context.Context, task *types.Task) error + DeleteTask(ctx context.Context, taskID string) error + + // Task status operations + UpdateTaskStatus(ctx context.Context, taskID string, status *types.TaskStatus) error + + // Task artifact operations + AddArtifact(ctx context.Context, taskID string, artifact *types.Artifact) error + GetArtifacts(ctx context.Context, taskID string) ([]*types.Artifact, error) + + // Task history operations + AddMessage(ctx context.Context, taskID string, message *types.Message) error + GetHistory(ctx context.Context, taskID string, limit int) ([]*types.Message, error) + + // Query operations + ListTasksByContext(ctx context.Context, contextID string) ([]*types.Task, error) + ListTasksByState(ctx context.Context, states []types.TaskState) ([]*types.Task, error) + + // Health check and lifecycle + HealthCheck(ctx context.Context) error + Close() error +} diff --git a/a2a/pkg/transport/grpc/client.go b/a2a/pkg/transport/grpc/client.go new file mode 100644 index 000000000..68b6cdc97 --- /dev/null +++ b/a2a/pkg/transport/grpc/client.go @@ -0,0 +1,532 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package grpc + +import ( + "context" + "fmt" + "io" + "sync" + "time" + + "seata-go-ai-a2a/pkg/types" + + pb "seata-go-ai-a2a/pkg/proto/v1" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +// Client provides gRPC client implementation for A2A protocol +type Client struct { + conn *grpc.ClientConn + client pb.A2AServiceClient + + mu sync.RWMutex + closed bool +} + +// ClientConfig configures the gRPC client +type ClientConfig struct { + Address string + Timeout time.Duration + GRPCOptions []grpc.DialOption +} + +// NewClient creates a new gRPC client +func NewClient(config *ClientConfig) (*Client, error) { + if config.Address == "" { + return nil, fmt.Errorf("address is required") + } + + // Set default timeout + timeout := config.Timeout + if timeout == 0 { + timeout = 30 * time.Second + } + + // Set default gRPC dial options + grpcOptions := config.GRPCOptions + if grpcOptions == nil { + grpcOptions = []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), + grpc.WithTimeout(timeout), + } + } + + // Create connection + conn, err := grpc.Dial(config.Address, grpcOptions...) + if err != nil { + return nil, fmt.Errorf("failed to connect to %s: %w", config.Address, err) + } + + client := pb.NewA2AServiceClient(conn) + + return &Client{ + conn: conn, + client: client, + }, nil +} + +// Close closes the gRPC client connection +func (c *Client) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return nil + } + + c.closed = true + return c.conn.Close() +} + +// IsClosed returns true if the client is closed +func (c *Client) IsClosed() bool { + c.mu.RLock() + defer c.mu.RUnlock() + return c.closed +} + +// A2A Protocol Methods Implementation + +// SendMessage sends a message to the agent +func (c *Client) SendMessage(ctx context.Context, req *types.SendMessageRequest) (*types.Task, error) { + c.mu.RLock() + if c.closed { + c.mu.RUnlock() + return nil, fmt.Errorf("client is closed") + } + c.mu.RUnlock() + + // Convert to protobuf request + pbReq, err := types.SendMessageRequestToProto(req) + if err != nil { + return nil, fmt.Errorf("failed to convert request: %w", err) + } + + // Make gRPC call + pbResp, err := c.client.SendMessage(ctx, pbReq) + if err != nil { + return nil, fmt.Errorf("gRPC call failed: %w", err) + } + + // Extract task from response payload + taskResp, ok := pbResp.Payload.(*pb.SendMessageResponse_Task) + if !ok { + return nil, fmt.Errorf("unexpected response payload type: %T", pbResp.Payload) + } + + // Convert response from protobuf + task, err := types.TaskFromProto(taskResp.Task) + if err != nil { + return nil, fmt.Errorf("failed to convert response: %w", err) + } + + return task, nil +} + +// SendStreamingMessage sends a message and streams task updates +func (c *Client) SendStreamingMessage(ctx context.Context, req *types.SendMessageRequest) (*StreamConnection, error) { + c.mu.RLock() + if c.closed { + c.mu.RUnlock() + return nil, fmt.Errorf("client is closed") + } + c.mu.RUnlock() + + // Convert to protobuf request + pbReq, err := types.SendMessageRequestToProto(req) + if err != nil { + return nil, fmt.Errorf("failed to convert request: %w", err) + } + + // Create streaming call + stream, err := c.client.SendStreamingMessage(ctx, pbReq) + if err != nil { + return nil, fmt.Errorf("failed to create stream: %w", err) + } + + // Create stream connection + streamConn := &StreamConnection{ + stream: stream, + events: make(chan types.StreamResponse, 10), + errors: make(chan error, 5), + ctx: ctx, + } + + // Start receiving stream responses + go streamConn.receiveResponses() + + return streamConn, nil +} + +// GetTask retrieves a task by ID +func (c *Client) GetTask(ctx context.Context, req *types.GetTaskRequest) (*types.Task, error) { + c.mu.RLock() + if c.closed { + c.mu.RUnlock() + return nil, fmt.Errorf("client is closed") + } + c.mu.RUnlock() + + // Convert to protobuf request + pbReq := types.GetTaskRequestToProto(req) + + // Make gRPC call + pbTask, err := c.client.GetTask(ctx, pbReq) + if err != nil { + return nil, fmt.Errorf("gRPC call failed: %w", err) + } + + // Convert from protobuf + task, err := types.TaskFromProto(pbTask) + if err != nil { + return nil, fmt.Errorf("failed to convert response: %w", err) + } + + return task, nil +} + +// CancelTask cancels a task +func (c *Client) CancelTask(ctx context.Context, req *types.CancelTaskRequest) (*types.Task, error) { + c.mu.RLock() + if c.closed { + c.mu.RUnlock() + return nil, fmt.Errorf("client is closed") + } + c.mu.RUnlock() + + // Convert to protobuf request + pbReq := types.CancelTaskRequestToProto(req) + + // Make gRPC call + pbTask, err := c.client.CancelTask(ctx, pbReq) + if err != nil { + return nil, fmt.Errorf("gRPC call failed: %w", err) + } + + // Convert from protobuf + task, err := types.TaskFromProto(pbTask) + if err != nil { + return nil, fmt.Errorf("failed to convert response: %w", err) + } + + return task, nil +} + +// TaskSubscription subscribes to task updates +func (c *Client) TaskSubscription(ctx context.Context, req *types.TaskSubscriptionRequest) (*StreamConnection, error) { + c.mu.RLock() + if c.closed { + c.mu.RUnlock() + return nil, fmt.Errorf("client is closed") + } + c.mu.RUnlock() + + // Convert to protobuf request + pbReq := types.TaskSubscriptionRequestToProto(req) + + // Create streaming call + stream, err := c.client.TaskSubscription(ctx, pbReq) + if err != nil { + return nil, fmt.Errorf("failed to create stream: %w", err) + } + + // Create stream connection + streamConn := &StreamConnection{ + stream: stream, + events: make(chan types.StreamResponse, 10), + errors: make(chan error, 5), + ctx: ctx, + } + + // Start receiving stream responses + go streamConn.receiveResponses() + + return streamConn, nil +} + +// ListTasks lists tasks with optional filtering +func (c *Client) ListTasks(ctx context.Context, req *types.ListTasksRequest) (*types.ListTasksResponse, error) { + c.mu.RLock() + if c.closed { + c.mu.RUnlock() + return nil, fmt.Errorf("client is closed") + } + c.mu.RUnlock() + + // Convert to protobuf request + pbReq := types.ListTasksRequestToProto(req) + + // Make gRPC call + pbResp, err := c.client.ListTasks(ctx, pbReq) + if err != nil { + return nil, fmt.Errorf("gRPC call failed: %w", err) + } + + // Convert from protobuf + response, err := types.ListTasksResponseFromProto(pbResp) + if err != nil { + return nil, fmt.Errorf("failed to convert response: %w", err) + } + + return response, nil +} + +// GetAgentCard retrieves the agent card +func (c *Client) GetAgentCard(ctx context.Context) (*types.AgentCard, error) { + c.mu.RLock() + if c.closed { + c.mu.RUnlock() + return nil, fmt.Errorf("client is closed") + } + c.mu.RUnlock() + + // Create empty request + pbReq := &pb.GetAgentCardRequest{} + + // Make gRPC call + pbAgentCard, err := c.client.GetAgentCard(ctx, pbReq) + if err != nil { + return nil, fmt.Errorf("gRPC call failed: %w", err) + } + + // Convert from protobuf + agentCard, err := types.AgentCardFromProto(pbAgentCard) + if err != nil { + return nil, fmt.Errorf("failed to convert response: %w", err) + } + + return agentCard, nil +} + +// CreateTaskPushNotificationConfig creates a push notification config +func (c *Client) CreateTaskPushNotificationConfig(ctx context.Context, req *types.CreateTaskPushNotificationConfigRequest) (*types.TaskPushNotificationConfig, error) { + c.mu.RLock() + if c.closed { + c.mu.RUnlock() + return nil, fmt.Errorf("client is closed") + } + c.mu.RUnlock() + + // Convert to protobuf request + pbReq, err := types.CreateTaskPushNotificationConfigRequestToProto(req) + if err != nil { + return nil, fmt.Errorf("failed to convert request: %w", err) + } + + // Make gRPC call + pbConfig, err := c.client.CreateTaskPushNotification(ctx, pbReq) + if err != nil { + return nil, fmt.Errorf("gRPC call failed: %w", err) + } + + // Convert from protobuf + config, err := types.TaskPushNotificationConfigFromProto(pbConfig) + if err != nil { + return nil, fmt.Errorf("failed to convert response: %w", err) + } + + return config, nil +} + +// GetTaskPushNotificationConfig retrieves a push notification config +func (c *Client) GetTaskPushNotificationConfig(ctx context.Context, req *types.GetTaskPushNotificationConfigRequest) (*types.TaskPushNotificationConfig, error) { + c.mu.RLock() + if c.closed { + c.mu.RUnlock() + return nil, fmt.Errorf("client is closed") + } + c.mu.RUnlock() + + // Convert to protobuf request + pbReq := types.GetTaskPushNotificationConfigRequestToProto(req) + + // Make gRPC call + pbConfig, err := c.client.GetTaskPushNotification(ctx, pbReq) + if err != nil { + return nil, fmt.Errorf("gRPC call failed: %w", err) + } + + // Convert from protobuf + config, err := types.TaskPushNotificationConfigFromProto(pbConfig) + if err != nil { + return nil, fmt.Errorf("failed to convert response: %w", err) + } + + return config, nil +} + +// ListTaskPushNotificationConfigs lists push notification configs +func (c *Client) ListTaskPushNotificationConfigs(ctx context.Context, req *types.ListTaskPushNotificationConfigRequest) (*types.ListTaskPushNotificationConfigResponse, error) { + c.mu.RLock() + if c.closed { + c.mu.RUnlock() + return nil, fmt.Errorf("client is closed") + } + c.mu.RUnlock() + + // Convert to protobuf request + pbReq := types.ListTaskPushNotificationConfigRequestToProto(req) + + // Make gRPC call + pbResp, err := c.client.ListTaskPushNotification(ctx, pbReq) + if err != nil { + return nil, fmt.Errorf("gRPC call failed: %w", err) + } + + // Convert from protobuf + response, err := types.ListTaskPushNotificationConfigResponseFromProto(pbResp) + if err != nil { + return nil, fmt.Errorf("failed to convert response: %w", err) + } + + return response, nil +} + +// DeleteTaskPushNotificationConfig deletes a push notification config +func (c *Client) DeleteTaskPushNotificationConfig(ctx context.Context, req *types.DeleteTaskPushNotificationConfigRequest) error { + c.mu.RLock() + if c.closed { + c.mu.RUnlock() + return fmt.Errorf("client is closed") + } + c.mu.RUnlock() + + // Convert to protobuf request + pbReq := types.DeleteTaskPushNotificationConfigRequestToProto(req) + + // Make gRPC call + _, err := c.client.DeleteTaskPushNotification(ctx, pbReq) + if err != nil { + return fmt.Errorf("gRPC call failed: %w", err) + } + + return nil +} + +// StreamConnection represents a gRPC streaming connection +type StreamConnection struct { + stream pb.A2AService_SendStreamingMessageClient + events chan types.StreamResponse + errors chan error + ctx context.Context + + mu sync.Mutex + closed bool +} + +// Events returns the events channel for this stream +func (sc *StreamConnection) Events() <-chan types.StreamResponse { + return sc.events +} + +// Errors returns the errors channel for this stream +func (sc *StreamConnection) Errors() <-chan error { + return sc.errors +} + +// Close closes the stream connection +func (sc *StreamConnection) Close() { + sc.mu.Lock() + defer sc.mu.Unlock() + + if !sc.closed { + sc.closed = true + close(sc.events) + close(sc.errors) + } +} + +// IsClosed returns true if the connection is closed +func (sc *StreamConnection) IsClosed() bool { + sc.mu.Lock() + defer sc.mu.Unlock() + return sc.closed +} + +// receiveResponses receives responses from the gRPC stream +func (sc *StreamConnection) receiveResponses() { + defer sc.Close() + + for { + // Check if context is cancelled + select { + case <-sc.ctx.Done(): + select { + case sc.errors <- sc.ctx.Err(): + default: + } + return + default: + } + + // Receive from stream + pbResp, err := sc.stream.Recv() + if err != nil { + if err == io.EOF { + // Stream ended normally + return + } + + // Send error to error channel + select { + case sc.errors <- fmt.Errorf("stream receive error: %w", err): + case <-sc.ctx.Done(): + } + return + } + + // Convert protobuf response to types + streamResp, err := convertStreamResponseFromProto(pbResp) + if err != nil { + select { + case sc.errors <- fmt.Errorf("failed to convert stream response: %w", err): + case <-sc.ctx.Done(): + } + continue + } + + // Send to events channel + select { + case sc.events <- streamResp: + case <-sc.ctx.Done(): + return + } + } +} + +// GetConnectionInfo returns information about the gRPC connection +func (c *Client) GetConnectionInfo() (string, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.conn == nil { + return "", false + } + + return c.conn.Target(), !c.closed +} + +// Ping tests the connection to the server by calling GetAgentCard +func (c *Client) Ping(ctx context.Context) error { + _, err := c.GetAgentCard(ctx) + return err +} diff --git a/a2a/pkg/transport/grpc/server.go b/a2a/pkg/transport/grpc/server.go new file mode 100644 index 000000000..afbc0d0fe --- /dev/null +++ b/a2a/pkg/transport/grpc/server.go @@ -0,0 +1,696 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package grpc + +import ( + "context" + "fmt" + "net" + "strings" + "sync" + "time" + + "github.com/google/uuid" + + "seata-go-ai-a2a/pkg/handler" + "seata-go-ai-a2a/pkg/task" + "seata-go-ai-a2a/pkg/types" + + pb "seata-go-ai-a2a/pkg/proto/v1" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +// Server provides gRPC server implementation for A2A protocol +type Server struct { + pb.UnimplementedA2AServiceServer + + grpcServer *grpc.Server + listener net.Listener + agentCard *types.AgentCard + taskManager *task.TaskManager + + // Push notification config storage (in-memory for now) + pushNotificationConfigs map[string]*types.TaskPushNotificationConfig + pushConfigMu sync.RWMutex + + // Server lifecycle management + mu sync.RWMutex + started bool + stopped bool +} + +// ServerConfig configures the gRPC server +type ServerConfig struct { + Address string + AgentCard *types.AgentCard + TaskManager *task.TaskManager + GRPCOptions []grpc.ServerOption +} + +// NewServer creates a new gRPC server +func NewServer(config *ServerConfig) (*Server, error) { + if config.AgentCard == nil { + return nil, fmt.Errorf("agent card is required") + } + if config.TaskManager == nil { + return nil, fmt.Errorf("task manager is required") + } + + // Create listener + listener, err := net.Listen("tcp", config.Address) + if err != nil { + return nil, fmt.Errorf("failed to listen on %s: %w", config.Address, err) + } + + // Create gRPC server with default options + grpcOptions := config.GRPCOptions + if grpcOptions == nil { + grpcOptions = []grpc.ServerOption{ + grpc.MaxRecvMsgSize(4 * 1024 * 1024), // 4MB + grpc.MaxSendMsgSize(4 * 1024 * 1024), // 4MB + } + } + + grpcServer := grpc.NewServer(grpcOptions...) + + server := &Server{ + grpcServer: grpcServer, + listener: listener, + agentCard: config.AgentCard, + taskManager: config.TaskManager, + pushNotificationConfigs: make(map[string]*types.TaskPushNotificationConfig), + } + + // Register A2A service + pb.RegisterA2AServiceServer(grpcServer, server) + + return server, nil +} + +// Start starts the gRPC server +func (s *Server) Start() error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.started { + return fmt.Errorf("server already started") + } + if s.stopped { + return fmt.Errorf("server has been stopped") + } + + s.started = true + + // Start serving in a goroutine + go func() { + if err := s.grpcServer.Serve(s.listener); err != nil { + // Log error but don't panic + fmt.Printf("gRPC server error: %v\n", err) + } + }() + + return nil +} + +// Stop gracefully stops the gRPC server +func (s *Server) Stop() error { + s.mu.Lock() + defer s.mu.Unlock() + + if !s.started || s.stopped { + return nil + } + + s.stopped = true + s.grpcServer.GracefulStop() + return nil +} + +// GetAddress returns the server's listening address +func (s *Server) GetAddress() string { + if s.listener != nil { + return s.listener.Addr().String() + } + return "" +} + +// A2A Protocol Methods Implementation + +// SendMessage sends a message to the agent +func (s *Server) SendMessage(ctx context.Context, req *pb.SendMessageRequest) (*pb.SendMessageResponse, error) { + // Convert protobuf request to types + sendReq, err := types.SendMessageRequestFromProto(req) + if err != nil { + return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid request: %v", err)) + } + + // Check if task manager has handler interfaces + if handlerTaskManager, ok := interface{}(s.taskManager).(interface { + ProcessMessage(ctx context.Context, req *handler.MessageRequest) (*handler.MessageResponse, error) + }); ok { + // Use handler interface for processing + return s.sendMessageWithHandler(ctx, sendReq, handlerTaskManager) + } + + // Fallback to legacy direct task creation + task, err := s.taskManager.CreateTask(ctx, sendReq.Request.ContextID, sendReq.Request, sendReq.Metadata) + if err != nil { + // Map task manager errors to gRPC status codes + if isA2AError(err) { + return nil, mapA2AErrorToGRPCStatus(err) + } + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to create task: %v", err)) + } + + // Convert task to protobuf response + pbTask, err := types.TaskToProto(task) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert task: %v", err)) + } + + return &pb.SendMessageResponse{ + Payload: &pb.SendMessageResponse_Task{Task: pbTask}, + }, nil +} + +// SendStreamingMessage sends a message and streams task updates +func (s *Server) SendStreamingMessage(req *pb.SendMessageRequest, stream pb.A2AService_SendStreamingMessageServer) error { + ctx := stream.Context() + + // Convert protobuf request to types + sendReq, err := types.SendMessageRequestFromProto(req) + if err != nil { + return status.Error(codes.InvalidArgument, fmt.Sprintf("invalid request: %v", err)) + } + + // Create task using task manager + task, err := s.taskManager.CreateTask(ctx, sendReq.Request.ContextID, sendReq.Request, sendReq.Metadata) + if err != nil { + if isA2AError(err) { + return mapA2AErrorToGRPCStatus(err) + } + return status.Error(codes.Internal, fmt.Sprintf("failed to create task: %v", err)) + } + + // Subscribe to task updates + subscription, err := s.taskManager.SubscribeToTask(ctx, task.ID) + if err != nil { + return status.Error(codes.Internal, fmt.Sprintf("failed to subscribe to task: %v", err)) + } + defer subscription.Close() + + // Send initial task state + pbTask, err := types.TaskToProto(task) + if err != nil { + return status.Error(codes.Internal, fmt.Sprintf("failed to convert task: %v", err)) + } + + initialResponse := &pb.StreamResponse{ + Payload: &pb.StreamResponse_Task{Task: pbTask}, + } + + if err := stream.Send(initialResponse); err != nil { + return status.Error(codes.Internal, fmt.Sprintf("failed to send initial response: %v", err)) + } + + // Stream task updates + for { + select { + case update, ok := <-subscription.Events(): + if !ok { + // Subscription closed + return nil + } + + // Convert update to protobuf stream response + pbResponse, err := convertStreamResponseToProto(update) + if err != nil { + return status.Error(codes.Internal, fmt.Sprintf("failed to convert stream response: %v", err)) + } + + if err := stream.Send(pbResponse); err != nil { + return status.Error(codes.Internal, fmt.Sprintf("failed to send stream response: %v", err)) + } + + case <-ctx.Done(): + return status.Error(codes.Canceled, "stream cancelled by client") + } + } +} + +// GetTask retrieves a task by ID +func (s *Server) GetTask(ctx context.Context, req *pb.GetTaskRequest) (*pb.Task, error) { + // Convert protobuf request to types + getReq := types.GetTaskRequestFromProto(req) + + // Extract task ID from name (format: tasks/{task_id}) + taskID := extractTaskIDFromName(getReq.Name) + if taskID == "" { + return nil, status.Error(codes.InvalidArgument, "invalid task name format") + } + + // Get task from task manager + task, err := s.taskManager.GetTask(ctx, taskID) + if err != nil { + if isA2AError(err) { + return nil, mapA2AErrorToGRPCStatus(err) + } + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to get task: %v", err)) + } + + // Apply history length limit if specified + if req.HistoryLength > 0 && int(req.HistoryLength) < len(task.History) { + // Create a copy with limited history + taskCopy := *task + taskCopy.History = task.History[:req.HistoryLength] + task = &taskCopy + } + + // Convert to protobuf + pbTask, err := types.TaskToProto(task) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert task: %v", err)) + } + + return pbTask, nil +} + +// CancelTask cancels a task +func (s *Server) CancelTask(ctx context.Context, req *pb.CancelTaskRequest) (*pb.Task, error) { + // Convert protobuf request to types + cancelReq := types.CancelTaskRequestFromProto(req) + + // Extract task ID from name + taskID := extractTaskIDFromName(cancelReq.Name) + if taskID == "" { + return nil, status.Error(codes.InvalidArgument, "invalid task name format") + } + + // Cancel task using task manager + task, err := s.taskManager.CancelTask(ctx, taskID) + if err != nil { + if isA2AError(err) { + return nil, mapA2AErrorToGRPCStatus(err) + } + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to cancel task: %v", err)) + } + + // Convert to protobuf + pbTask, err := types.TaskToProto(task) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert task: %v", err)) + } + + return pbTask, nil +} + +// TaskSubscription subscribes to task updates +func (s *Server) TaskSubscription(req *pb.TaskSubscriptionRequest, stream pb.A2AService_TaskSubscriptionServer) error { + ctx := stream.Context() + + // Convert protobuf request to types + subReq := types.TaskSubscriptionRequestFromProto(req) + + // Extract task ID from name + taskID := extractTaskIDFromName(subReq.Name) + if taskID == "" { + return status.Error(codes.InvalidArgument, "invalid task name format") + } + + // Subscribe to task updates + subscription, err := s.taskManager.SubscribeToTask(ctx, taskID) + if err != nil { + if isA2AError(err) { + return mapA2AErrorToGRPCStatus(err) + } + return status.Error(codes.Internal, fmt.Sprintf("failed to subscribe to task: %v", err)) + } + defer subscription.Close() + + // Stream task updates + for { + select { + case update, ok := <-subscription.Events(): + if !ok { + // Subscription closed + return nil + } + + // Convert update to protobuf stream response + pbResponse, err := convertStreamResponseToProto(update) + if err != nil { + return status.Error(codes.Internal, fmt.Sprintf("failed to convert stream response: %v", err)) + } + + if err := stream.Send(pbResponse); err != nil { + return status.Error(codes.Internal, fmt.Sprintf("failed to send stream response: %v", err)) + } + + case <-ctx.Done(): + return status.Error(codes.Canceled, "stream cancelled by client") + } + } +} + +// ListTasks lists tasks with optional filtering +func (s *Server) ListTasks(ctx context.Context, req *pb.ListTasksRequest) (*pb.ListTasksResponse, error) { + // Convert protobuf request to types + listReq := types.ListTasksRequestFromProto(req) + + // List tasks using task manager + response, err := s.taskManager.ListTasks(ctx, listReq) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to list tasks: %v", err)) + } + + // Convert to protobuf + pbResponse, err := types.ListTasksResponseToProto(response) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert response: %v", err)) + } + + return pbResponse, nil +} + +// GetAgentCard retrieves the agent card +func (s *Server) GetAgentCard(ctx context.Context, req *pb.GetAgentCardRequest) (*pb.AgentCard, error) { + var agentCard *types.AgentCard + + // Check if task manager has agent card provider + if cardProvider, ok := interface{}(s.taskManager).(interface { + GetAgentCard(ctx context.Context) (*types.AgentCard, error) + }); ok { + // Use provider interface + card, err := cardProvider.GetAgentCard(ctx) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to get agent card from provider: %v", err)) + } + agentCard = card + } else { + // Fallback to static agent card + agentCard = s.agentCard + } + + if agentCard == nil { + return nil, status.Error(codes.Internal, "no agent card available") + } + + // Convert agent card to protobuf + pbAgentCard, err := types.AgentCardToProto(agentCard) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert agent card: %v", err)) + } + + return pbAgentCard, nil +} + +// CreateTaskPushNotification creates a push notification config +func (s *Server) CreateTaskPushNotification(ctx context.Context, req *pb.CreateTaskPushNotificationConfigRequest) (*pb.TaskPushNotificationConfig, error) { + // Parse task ID from parent (format: tasks/{task_id}) + taskID := parseTaskIDFromResourceName(req.Parent) + if taskID == "" { + return nil, status.Error(codes.InvalidArgument, "invalid parent format, expected tasks/{task_id}") + } + + // Verify task exists + _, err := s.taskManager.GetTask(ctx, taskID) + if err != nil { + if isA2ATaskNotFoundError(err) { + return nil, status.Error(codes.NotFound, "task not found") + } + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to verify task: %v", err)) + } + + // Convert config from proto + config, err := types.TaskPushNotificationConfigFromProto(req.Config) + if err != nil { + return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid config: %v", err)) + } + + // Generate config ID if not provided + configID := req.ConfigId + if configID == "" { + configID = uuid.New().String() + } + + // Generate full name for the config + configName := fmt.Sprintf("tasks/%s/pushNotificationConfigs/%s", taskID, configID) + config.Name = configName + + // Store the config + s.pushConfigMu.Lock() + s.pushNotificationConfigs[configName] = config + s.pushConfigMu.Unlock() + + // Convert back to proto and return + pbConfig, err := types.TaskPushNotificationConfigToProto(config) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert config: %v", err)) + } + + return pbConfig, nil +} + +// GetTaskPushNotification retrieves a push notification config +func (s *Server) GetTaskPushNotification(ctx context.Context, req *pb.GetTaskPushNotificationConfigRequest) (*pb.TaskPushNotificationConfig, error) { + // Get the config from storage + s.pushConfigMu.RLock() + config, exists := s.pushNotificationConfigs[req.Name] + s.pushConfigMu.RUnlock() + + if !exists { + return nil, status.Error(codes.NotFound, "push notification config not found") + } + + // Convert to proto and return + pbConfig, err := types.TaskPushNotificationConfigToProto(config) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert config: %v", err)) + } + + return pbConfig, nil +} + +// ListTaskPushNotification lists push notification configs +func (s *Server) ListTaskPushNotification(ctx context.Context, req *pb.ListTaskPushNotificationConfigRequest) (*pb.ListTaskPushNotificationConfigResponse, error) { + // Parse task ID from parent (format: tasks/{task_id}) + taskID := parseTaskIDFromResourceName(req.Parent) + if taskID == "" { + return nil, status.Error(codes.InvalidArgument, "invalid parent format, expected tasks/{task_id}") + } + + // Verify task exists + _, err := s.taskManager.GetTask(ctx, taskID) + if err != nil { + if isA2ATaskNotFoundError(err) { + return nil, status.Error(codes.NotFound, "task not found") + } + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to verify task: %v", err)) + } + + // Find all configs for this task + s.pushConfigMu.RLock() + var configs []*types.TaskPushNotificationConfig + prefix := fmt.Sprintf("tasks/%s/pushNotificationConfigs/", taskID) + + for name, config := range s.pushNotificationConfigs { + if strings.HasPrefix(name, prefix) { + configs = append(configs, config) + } + } + s.pushConfigMu.RUnlock() + + // Convert to proto response + response := &types.ListTaskPushNotificationConfigResponse{ + Configs: configs, + } + + pbResponse, err := types.ListTaskPushNotificationConfigResponseToProto(response) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert response: %v", err)) + } + + return pbResponse, nil +} + +// DeleteTaskPushNotification deletes a push notification config +func (s *Server) DeleteTaskPushNotification(ctx context.Context, req *pb.DeleteTaskPushNotificationConfigRequest) (*emptypb.Empty, error) { + // Check if config exists + s.pushConfigMu.Lock() + defer s.pushConfigMu.Unlock() + + _, exists := s.pushNotificationConfigs[req.Name] + if !exists { + return nil, status.Error(codes.NotFound, "push notification config not found") + } + + // Delete the config + delete(s.pushNotificationConfigs, req.Name) + + return &emptypb.Empty{}, nil +} + +// UpdateAgentCard updates the agent card served by the gRPC server +func (s *Server) UpdateAgentCard(agentCard *types.AgentCard) { + s.mu.Lock() + defer s.mu.Unlock() + s.agentCard = agentCard +} + +// GetCurrentAgentCard returns the current agent card +func (s *Server) GetCurrentAgentCard() *types.AgentCard { + s.mu.RLock() + defer s.mu.RUnlock() + return s.agentCard +} + +// SetTaskManager updates the task manager used by the server +func (s *Server) SetTaskManager(taskManager *task.TaskManager) { + s.mu.Lock() + defer s.mu.Unlock() + s.taskManager = taskManager +} + +// Validate validates the server configuration +func (s *Server) Validate() error { + s.mu.RLock() + defer s.mu.RUnlock() + + if s.agentCard == nil { + return fmt.Errorf("agent card is required") + } + if s.taskManager == nil { + return fmt.Errorf("task manager is required") + } + + return nil +} + +// parseTaskIDFromResourceName parses task ID from resource name (e.g., "tasks/{task_id}") +func parseTaskIDFromResourceName(name string) string { + const prefix = "tasks/" + if !strings.HasPrefix(name, prefix) { + return "" + } + // Find the next slash or take the rest if no slash + remaining := name[len(prefix):] + if idx := strings.Index(remaining, "/"); idx != -1 { + return remaining[:idx] + } + return remaining +} + +// isA2ATaskNotFoundError checks if an error is an A2A TaskNotFoundError +func isA2ATaskNotFoundError(err error) bool { + if a2aErr, ok := err.(*types.A2AError); ok { + return a2aErr.Code == types.TaskNotFoundError + } + return false +} + +// sendMessageWithHandler processes message through handler interface +func (s *Server) sendMessageWithHandler(ctx context.Context, sendReq *types.SendMessageRequest, handlerTaskManager interface { + ProcessMessage(ctx context.Context, req *handler.MessageRequest) (*handler.MessageResponse, error) +}) (*pb.SendMessageResponse, error) { + // Create task first to get task ID + task, err := s.taskManager.CreateTask(ctx, sendReq.Request.ContextID, sendReq.Request, sendReq.Metadata) + if err != nil { + // Map task manager errors to gRPC status codes + if isA2AError(err) { + return nil, mapA2AErrorToGRPCStatus(err) + } + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to create task: %v", err)) + } + + // Create task operations for handler + eventChan := make(chan types.StreamResponse, 100) + taskOps := handler.NewTaskOperations(s.taskManager, task.ID, sendReq.Request.ContextID, eventChan) + + // Build message request for handler + messageReq := &handler.MessageRequest{ + Message: sendReq.Request, + TaskID: task.ID, + ContextID: sendReq.Request.ContextID, + Configuration: &handler.MessageConfiguration{ + Blocking: true, // gRPC is blocking by default + Streaming: false, + Timeout: 30 * time.Second, + }, + History: task.History, + Metadata: sendReq.Metadata, + TaskOperations: taskOps, + } + + // Process through handler + response, err := handlerTaskManager.ProcessMessage(ctx, messageReq) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("handler processing failed: %v", err)) + } + + // Handle response based on mode + switch response.Mode { + case handler.ResponseModeTask: + // Return task response + if response.Result != nil && response.Result.Data != nil { + if taskData, ok := response.Result.Data.(*types.Task); ok { + pbTask, err := types.TaskToProto(taskData) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert task: %v", err)) + } + return &pb.SendMessageResponse{ + Payload: &pb.SendMessageResponse_Task{Task: pbTask}, + }, nil + } + } + case handler.ResponseModeMessage: + // Return message response + if response.Result != nil && response.Result.Data != nil { + if msgData, ok := response.Result.Data.(*types.Message); ok { + pbMsg, err := types.MessageToProto(msgData) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert message: %v", err)) + } + return &pb.SendMessageResponse{ + Payload: &pb.SendMessageResponse_Msg{Msg: pbMsg}, + }, nil + } + } + default: + // For other modes or errors, return the original task + pbTask, err := types.TaskToProto(task) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert task: %v", err)) + } + return &pb.SendMessageResponse{ + Payload: &pb.SendMessageResponse_Task{Task: pbTask}, + }, nil + } + + // Fallback - return original task + pbTask, err := types.TaskToProto(task) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("failed to convert task: %v", err)) + } + return &pb.SendMessageResponse{ + Payload: &pb.SendMessageResponse_Task{Task: pbTask}, + }, nil +} diff --git a/a2a/pkg/transport/grpc/utils.go b/a2a/pkg/transport/grpc/utils.go new file mode 100644 index 000000000..2b4db44ff --- /dev/null +++ b/a2a/pkg/transport/grpc/utils.go @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package grpc + +import ( + "errors" + "fmt" + "strings" + + "seata-go-ai-a2a/pkg/types" + + pb "seata-go-ai-a2a/pkg/proto/v1" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// extractTaskIDFromName extracts task ID from resource name format (tasks/{task_id}) +func extractTaskIDFromName(name string) string { + if !strings.HasPrefix(name, "tasks/") { + return "" + } + return strings.TrimPrefix(name, "tasks/") +} + +// extractConfigIDFromName extracts config ID from resource name format (tasks/{task_id}/pushNotificationConfigs/{config_id}) +func extractConfigIDFromName(name string) (taskID, configID string) { + parts := strings.Split(name, "/") + if len(parts) >= 4 && parts[0] == "tasks" && parts[2] == "pushNotificationConfigs" { + return parts[1], parts[3] + } + return "", "" +} + +// isA2AError checks if an error is an A2A-specific error +func isA2AError(err error) bool { + var a2AError *types.A2AError + switch { + case errors.As(err, &a2AError): + return true + default: + return false + } +} + +// mapA2AErrorToGRPCStatus maps A2A error codes to gRPC status codes +func mapA2AErrorToGRPCStatus(err error) error { + a2aErr, ok := err.(*types.A2AError) + if !ok { + return status.Error(codes.Internal, err.Error()) + } + + switch a2aErr.Code { + case types.TaskNotFoundError: + return status.Error(codes.NotFound, a2aErr.Message) + case types.TaskNotCancelableError: + return status.Error(codes.FailedPrecondition, a2aErr.Message) + case types.UnsupportedOperationError: + return status.Error(codes.Unimplemented, a2aErr.Message) + case types.ContentTypeNotSupportedError: + return status.Error(codes.InvalidArgument, a2aErr.Message) + case types.InvalidAgentResponseError: + return status.Error(codes.InvalidArgument, a2aErr.Message) + case types.AuthenticatedExtendedCardNotConfiguredError: + return status.Error(codes.FailedPrecondition, a2aErr.Message) + default: + return status.Error(codes.Internal, a2aErr.Message) + } +} + +// convertStreamResponseToProto converts a types.StreamResponse to pb.StreamResponse +func convertStreamResponseToProto(resp types.StreamResponse) (*pb.StreamResponse, error) { + switch r := resp.(type) { + case *types.TaskStreamResponse: + pbTask, err := types.TaskToProto(r.Task) + if err != nil { + return nil, fmt.Errorf("failed to convert task: %w", err) + } + return &pb.StreamResponse{ + Payload: &pb.StreamResponse_Task{Task: pbTask}, + }, nil + + case *types.MessageStreamResponse: + pbMessage, err := types.MessageToProto(r.Message) + if err != nil { + return nil, fmt.Errorf("failed to convert message: %w", err) + } + return &pb.StreamResponse{ + Payload: &pb.StreamResponse_Msg{Msg: pbMessage}, + }, nil + + case *types.StatusUpdateStreamResponse: + pbStatusUpdate, err := types.TaskStatusUpdateEventToProto(r.StatusUpdate) + if err != nil { + return nil, fmt.Errorf("failed to convert status update: %w", err) + } + return &pb.StreamResponse{ + Payload: &pb.StreamResponse_StatusUpdate{StatusUpdate: pbStatusUpdate}, + }, nil + + case *types.ArtifactUpdateStreamResponse: + pbArtifactUpdate, err := types.TaskArtifactUpdateEventToProto(r.ArtifactUpdate) + if err != nil { + return nil, fmt.Errorf("failed to convert artifact update: %w", err) + } + return &pb.StreamResponse{ + Payload: &pb.StreamResponse_ArtifactUpdate{ArtifactUpdate: pbArtifactUpdate}, + }, nil + + default: + return nil, fmt.Errorf("unknown stream response type: %T", resp) + } +} + +// convertStreamResponseFromProto converts a pb.StreamResponse to types.StreamResponse +func convertStreamResponseFromProto(pbResp *pb.StreamResponse) (types.StreamResponse, error) { + switch resp := pbResp.Payload.(type) { + case *pb.StreamResponse_Task: + task, err := types.TaskFromProto(resp.Task) + if err != nil { + return nil, fmt.Errorf("failed to convert task: %w", err) + } + return &types.TaskStreamResponse{Task: task}, nil + + case *pb.StreamResponse_Msg: + message, err := types.MessageFromProto(resp.Msg) + if err != nil { + return nil, fmt.Errorf("failed to convert message: %w", err) + } + return &types.MessageStreamResponse{Message: message}, nil + + case *pb.StreamResponse_StatusUpdate: + statusUpdate, err := types.TaskStatusUpdateEventFromProto(resp.StatusUpdate) + if err != nil { + return nil, fmt.Errorf("failed to convert status update: %w", err) + } + return &types.StatusUpdateStreamResponse{StatusUpdate: statusUpdate}, nil + + case *pb.StreamResponse_ArtifactUpdate: + artifactUpdate, err := types.TaskArtifactUpdateEventFromProto(resp.ArtifactUpdate) + if err != nil { + return nil, fmt.Errorf("failed to convert artifact update: %w", err) + } + return &types.ArtifactUpdateStreamResponse{ArtifactUpdate: artifactUpdate}, nil + + default: + return nil, fmt.Errorf("unknown protobuf stream response type: %T", resp) + } +} + +// validateGRPCRequest performs basic validation on gRPC requests +func validateGRPCRequest(req interface{}) error { + if req == nil { + return status.Error(codes.InvalidArgument, "request cannot be nil") + } + return nil +} + +// validateTaskName validates that a task name follows the expected format +func validateTaskName(name string) error { + if name == "" { + return status.Error(codes.InvalidArgument, "task name cannot be empty") + } + if !strings.HasPrefix(name, "tasks/") { + return status.Error(codes.InvalidArgument, "task name must follow format: tasks/{task_id}") + } + taskID := strings.TrimPrefix(name, "tasks/") + if taskID == "" { + return status.Error(codes.InvalidArgument, "task ID cannot be empty") + } + return nil +} + +// validatePushNotificationConfigName validates push notification config name format +func validatePushNotificationConfigName(name string) error { + if name == "" { + return status.Error(codes.InvalidArgument, "config name cannot be empty") + } + + parts := strings.Split(name, "/") + if len(parts) != 4 || parts[0] != "tasks" || parts[2] != "pushNotificationConfigs" { + return status.Error(codes.InvalidArgument, "config name must follow format: tasks/{task_id}/pushNotificationConfigs/{config_id}") + } + + if parts[1] == "" { + return status.Error(codes.InvalidArgument, "task ID cannot be empty") + } + if parts[3] == "" { + return status.Error(codes.InvalidArgument, "config ID cannot be empty") + } + + return nil +} + +// createGRPCStatusFromError creates a gRPC status from a standard error +func createGRPCStatusFromError(err error, defaultCode codes.Code) error { + if err == nil { + return nil + } + + if isA2AError(err) { + return mapA2AErrorToGRPCStatus(err) + } + + return status.Error(defaultCode, err.Error()) +} diff --git a/a2a/pkg/transport/jsonrpc/client.go b/a2a/pkg/transport/jsonrpc/client.go new file mode 100644 index 000000000..72aa2f470 --- /dev/null +++ b/a2a/pkg/transport/jsonrpc/client.go @@ -0,0 +1,290 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package jsonrpc + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "sync/atomic" + "time" + + "seata-go-ai-a2a/pkg/types" +) + +// Client represents a JSON-RPC 2.0 client for A2A protocol +type Client struct { + endpoint string + httpClient *http.Client + requestID int64 +} + +// ClientConfig configures the JSON-RPC client +type ClientConfig struct { + Endpoint string + Timeout time.Duration +} + +// NewClient creates a new JSON-RPC client +func NewClient(config *ClientConfig) *Client { + timeout := config.Timeout + if timeout == 0 { + timeout = 30 * time.Second + } + + return &Client{ + endpoint: config.Endpoint, + httpClient: &http.Client{ + Timeout: timeout, + }, + requestID: 0, + } +} + +// nextRequestID generates the next request ID +func (c *Client) nextRequestID() int64 { + return atomic.AddInt64(&c.requestID, 1) +} + +// Call makes a JSON-RPC call and returns the result +func (c *Client) Call(ctx context.Context, method string, params interface{}, result interface{}) error { + req := NewRequest(method, params, c.nextRequestID()) + + resp, err := c.doRequest(ctx, req) + if err != nil { + return fmt.Errorf("request failed: %w", err) + } + + if resp.IsError() { + return &JSONRPCError{ + Code: resp.Error.Code, + Message: resp.Error.Message, + Data: resp.Error.Data, + } + } + + // Unmarshal result if provided + if result != nil && resp.Result != nil { + resultJSON, err := json.Marshal(resp.Result) + if err != nil { + return fmt.Errorf("failed to marshal result: %w", err) + } + + if err := json.Unmarshal(resultJSON, result); err != nil { + return fmt.Errorf("failed to unmarshal result: %w", err) + } + } + + return nil +} + +// Notify makes a JSON-RPC notification (no response expected) +func (c *Client) Notify(ctx context.Context, method string, params interface{}) error { + req := &Request{ + JSONRPC: JSONRPCVersion, + Method: method, + Params: params, + // No ID for notifications + } + + _, err := c.doRequest(ctx, req) + return err +} + +// doRequest performs the actual HTTP request +func (c *Client) doRequest(ctx context.Context, req *Request) (*Response, error) { + // Marshal request to JSON + reqJSON, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + // Create HTTP request + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint, bytes.NewReader(reqJSON)) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %w", err) + } + + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("Accept", "application/json") + httpReq.Header.Set("User-Agent", "A2A-JSONRPC-Client/1.0") + + // Make HTTP request + httpResp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("HTTP request failed: %w", err) + } + defer httpResp.Body.Close() + + // Read response body + respBody, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + // For notifications, we don't expect a response + if req.IsNotification() { + return nil, nil + } + + // Parse JSON-RPC response + var resp Response + if err := json.Unmarshal(respBody, &resp); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", err) + } + + // Validate response + if err := ValidateResponse(&resp); err != nil { + return nil, fmt.Errorf("invalid response: %w", err) + } + + return &resp, nil +} + +// A2A Protocol Methods + +// SendMessage sends a message using the A2A protocol +func (c *Client) SendMessage(ctx context.Context, req *types.SendMessageRequest) (*types.SendMessageResponse, error) { + var resp types.SendMessageResponse + err := c.Call(ctx, "message/send", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// GetAgentCard retrieves the agent card +func (c *Client) GetAgentCard(ctx context.Context) (*types.AgentCard, error) { + var agentCard types.AgentCard + err := c.Call(ctx, "agentCard/get", nil, &agentCard) + if err != nil { + return nil, err + } + return &agentCard, nil +} + +// GetTask retrieves a task by name/ID +func (c *Client) GetTask(ctx context.Context, req *types.GetTaskRequest) (*types.Task, error) { + var task types.Task + err := c.Call(ctx, "tasks/get", req, &task) + if err != nil { + return nil, err + } + return &task, nil +} + +// CancelTask cancels a task +func (c *Client) CancelTask(ctx context.Context, req *types.CancelTaskRequest) (*types.Task, error) { + var task types.Task + err := c.Call(ctx, "tasks/cancel", req, &task) + if err != nil { + return nil, err + } + return &task, nil +} + +// ListTasks lists tasks with optional filtering +func (c *Client) ListTasks(ctx context.Context, req *types.ListTasksRequest) (*types.ListTasksResponse, error) { + var resp types.ListTasksResponse + err := c.Call(ctx, "tasks/list", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// CreateTaskPushNotificationConfig creates a push notification config +func (c *Client) CreateTaskPushNotificationConfig(ctx context.Context, req *types.CreateTaskPushNotificationConfigRequest) (*types.TaskPushNotificationConfig, error) { + var config types.TaskPushNotificationConfig + err := c.Call(ctx, "tasks/pushNotificationConfig/set", req, &config) + if err != nil { + return nil, err + } + return &config, nil +} + +// GetTaskPushNotificationConfig retrieves a push notification config +func (c *Client) GetTaskPushNotificationConfig(ctx context.Context, req *types.GetTaskPushNotificationConfigRequest) (*types.TaskPushNotificationConfig, error) { + var config types.TaskPushNotificationConfig + err := c.Call(ctx, "tasks/pushNotificationConfig/get", req, &config) + if err != nil { + return nil, err + } + return &config, nil +} + +// ListTaskPushNotificationConfig lists push notification configs +func (c *Client) ListTaskPushNotificationConfig(ctx context.Context, req *types.ListTaskPushNotificationConfigRequest) (*types.ListTaskPushNotificationConfigResponse, error) { + var resp types.ListTaskPushNotificationConfigResponse + err := c.Call(ctx, "tasks/pushNotificationConfig/list", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// DeleteTaskPushNotificationConfig deletes a push notification config +func (c *Client) DeleteTaskPushNotificationConfig(ctx context.Context, req *types.DeleteTaskPushNotificationConfigRequest) error { + return c.Call(ctx, "tasks/pushNotificationConfig/delete", req, nil) +} + +// JSONRPCError represents a JSON-RPC error returned by the client +type JSONRPCError struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +func (e *JSONRPCError) Error() string { + return fmt.Sprintf("JSON-RPC error %d: %s", e.Code, e.Message) +} + +// IsA2AError returns true if this is an A2A specific error code +func (e *JSONRPCError) IsA2AError() bool { + return e.Code >= -32007 && e.Code <= -32001 +} + +// IsTaskNotFound returns true if this is a TaskNotFoundError +func (e *JSONRPCError) IsTaskNotFound() bool { + return e.Code == TaskNotFoundError +} + +// IsTaskNotCancelable returns true if this is a TaskNotCancelableError +func (e *JSONRPCError) IsTaskNotCancelable() bool { + return e.Code == TaskNotCancelableError +} + +// Close closes the client and cleans up resources +func (c *Client) Close() error { + // Close HTTP client connections + c.httpClient.CloseIdleConnections() + return nil +} + +// SetTimeout sets the client timeout +func (c *Client) SetTimeout(timeout time.Duration) { + c.httpClient.Timeout = timeout +} + +// GetEndpoint returns the client endpoint +func (c *Client) GetEndpoint() string { + return c.endpoint +} diff --git a/a2a/pkg/transport/jsonrpc/server.go b/a2a/pkg/transport/jsonrpc/server.go new file mode 100644 index 000000000..eaa847b35 --- /dev/null +++ b/a2a/pkg/transport/jsonrpc/server.go @@ -0,0 +1,490 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package jsonrpc + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/google/uuid" + + "seata-go-ai-a2a/pkg/handler" + "seata-go-ai-a2a/pkg/types" +) + +// HandlerFunc represents a JSON-RPC method handler +type HandlerFunc func(ctx context.Context, params interface{}) (interface{}, error) + +// Server represents a JSON-RPC 2.0 server implementation for A2A protocol +type Server struct { + handlers map[string]HandlerFunc + corsOrigins []string + allowAllCORS bool +} + +// NewServer creates a new JSON-RPC server +func NewServer() *Server { + return &Server{ + handlers: make(map[string]HandlerFunc), + allowAllCORS: true, // Default to allowing all origins + } +} + +// RegisterHandler registers a handler for a specific JSON-RPC method +func (s *Server) RegisterHandler(method string, handler HandlerFunc) { + s.handlers[method] = handler +} + +// RegisterA2AHandlers registers handlers for A2A protocol methods +func (s *Server) RegisterA2AHandlers(agentCard *types.AgentCard, taskManager types.TaskManagerInterface) { + // Register message/send handler + s.RegisterHandler("message/send", func(ctx context.Context, params interface{}) (interface{}, error) { + // Parse SendMessageRequest from params + var req types.SendMessageRequest + if err := parseParams(params, &req); err != nil { + return nil, NewError(InvalidParams, "Invalid parameters for message/send", err.Error()) + } + + // Validate the request + if req.Request == nil { + return nil, NewError(InvalidParams, "Missing message in request", nil) + } + + // Extract context ID from message or generate a new one + contextID := req.Request.ContextID + if contextID == "" { + contextID = fmt.Sprintf("ctx_%s", generateUUID()) + } + + // Set context ID in the message if not already set + if req.Request.ContextID == "" { + req.Request.ContextID = contextID + } + + // Check if task manager has handler interfaces + if handlerTaskManager, ok := interface{}(taskManager).(interface { + ProcessMessage(ctx context.Context, req *handler.MessageRequest) (*handler.MessageResponse, error) + }); ok { + // Use handler interface for processing + return s.handleSendMessageWithHandler(ctx, &req, handlerTaskManager, taskManager) + } + + // Fallback to legacy direct task creation + task, err := taskManager.CreateTask(ctx, contextID, req.Request, req.Metadata) + if err != nil { + return nil, NewError(InternalError, "Failed to create task", err.Error()) + } + + // If blocking mode is requested and the task is still submitted, + // update it to working state for demonstration + if req.Configuration != nil && req.Configuration.Blocking { + err = taskManager.UpdateTaskStatus(ctx, task.ID, types.TaskStateWorking, nil) + if err != nil { + // Log error but don't fail the request + } + } + + return &types.TaskSendMessageResponse{Task: task}, nil + }) + + // Register agentCard/get handler + s.RegisterHandler("agentCard/get", func(ctx context.Context, params interface{}) (interface{}, error) { + // Check if task manager has agent card provider + if cardProvider, ok := interface{}(taskManager).(interface { + GetAgentCard(ctx context.Context) (*types.AgentCard, error) + }); ok { + // Use provider interface + card, err := cardProvider.GetAgentCard(ctx) + if err != nil { + return nil, NewError(InternalError, "Failed to get agent card from provider", err.Error()) + } + return card, nil + } + + // Fallback to static agent card + if agentCard == nil { + return nil, NewError(InternalError, "No agent card available", nil) + } + return agentCard, nil + }) + + // Register tasks/get handler + s.RegisterHandler("tasks/get", func(ctx context.Context, params interface{}) (interface{}, error) { + var req types.GetTaskRequest + if err := parseParams(params, &req); err != nil { + return nil, NewError(InvalidParams, "Invalid parameters for tasks/get", err.Error()) + } + + // Parse task ID from name (format: tasks/{task_id}) + taskID := parseTaskIDFromName(req.Name) + if taskID == "" { + return nil, NewError(InvalidParams, "Invalid task name format, expected tasks/{task_id}", req.Name) + } + + // Get task using the task manager + task, err := taskManager.GetTask(ctx, taskID) + if err != nil { + // Check if it's an A2A error + if a2aErr, ok := err.(*types.A2AError); ok { + return nil, ConvertA2AErrorToJSONRPCError(a2aErr) + } + return nil, NewError(InternalError, "Failed to get task", err.Error()) + } + + return task, nil + }) + + // Register tasks/cancel handler + s.RegisterHandler("tasks/cancel", func(ctx context.Context, params interface{}) (interface{}, error) { + var req types.CancelTaskRequest + if err := parseParams(params, &req); err != nil { + return nil, NewError(InvalidParams, "Invalid parameters for tasks/cancel", err.Error()) + } + + // Parse task ID from name (format: tasks/{task_id}) + taskID := parseTaskIDFromName(req.Name) + if taskID == "" { + return nil, NewError(InvalidParams, "Invalid task name format, expected tasks/{task_id}", req.Name) + } + + // Cancel task using the task manager + task, err := taskManager.CancelTask(ctx, taskID) + if err != nil { + // Check if it's an A2A error + if a2aErr, ok := err.(*types.A2AError); ok { + return nil, ConvertA2AErrorToJSONRPCError(a2aErr) + } + return nil, NewError(InternalError, "Failed to cancel task", err.Error()) + } + + return task, nil + }) + + // Register tasks/list handler + s.RegisterHandler("tasks/list", func(ctx context.Context, params interface{}) (interface{}, error) { + var req types.ListTasksRequest + if err := parseParams(params, &req); err != nil { + return nil, NewError(InvalidParams, "Invalid parameters for tasks/list", err.Error()) + } + + // List tasks using the task manager + response, err := taskManager.ListTasks(ctx, &req) + if err != nil { + return nil, NewError(InternalError, "Failed to list tasks", err.Error()) + } + + return response, nil + }) + + // Add more handlers as needed... +} + +// ServeHTTP implements http.Handler interface +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Set CORS headers + s.setCORSHeaders(w, r) + w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + + // Handle preflight OPTIONS requests + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusOK) + return + } + + // Only allow POST requests + if r.Method != http.MethodPost { + w.Header().Set("Allow", "POST") + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Set content type + w.Header().Set("Content-Type", "application/json") + + // Read request body + body, err := io.ReadAll(r.Body) + if err != nil { + s.writeErrorResponse(w, NewError(InternalError, "Failed to read request body", err.Error()), nil) + return + } + + // Parse JSON-RPC request + req, err := ParseRequest(body) + if err != nil { + if jsonErr, ok := err.(*Error); ok { + s.writeErrorResponse(w, jsonErr, nil) + } else { + s.writeErrorResponse(w, NewError(ParseError, "Parse error", err.Error()), nil) + } + return + } + + // Handle the request + s.handleRequest(w, r, req) +} + +// handleRequest handles a parsed JSON-RPC request +func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request, req *Request) { + // Check if handler exists + handler, exists := s.handlers[req.Method] + if !exists { + s.writeErrorResponse(w, NewError(MethodNotFound, "Method not found", req.Method), req.ID) + return + } + + // Call the handler + result, err := handler(r.Context(), req.Params) + if err != nil { + // Convert various error types to JSON-RPC errors + var jsonErr *Error + + switch e := err.(type) { + case *Error: + jsonErr = e + case *types.A2AError: + jsonErr = ConvertA2AErrorToJSONRPCError(e) + default: + jsonErr = NewError(InternalError, "Internal error", e.Error()) + } + + s.writeErrorResponse(w, jsonErr, req.ID) + return + } + + // Write success response + resp := NewSuccessResponse(result, req.ID) + s.writeResponse(w, resp) +} + +// writeResponse writes a JSON-RPC response +func (s *Server) writeResponse(w http.ResponseWriter, resp *Response) { + w.WriteHeader(http.StatusOK) + + if err := json.NewEncoder(w).Encode(resp); err != nil { + // Fallback error response if encoding fails + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(`{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error"},"id":null}`)) + } +} + +// writeErrorResponse writes a JSON-RPC error response +func (s *Server) writeErrorResponse(w http.ResponseWriter, err *Error, id interface{}) { + resp := NewErrorResponse(err, id) + + // Set appropriate HTTP status code based on JSON-RPC error code + switch err.Code { + case ParseError, InvalidRequest, InvalidParams: + w.WriteHeader(http.StatusBadRequest) + case MethodNotFound: + w.WriteHeader(http.StatusNotFound) + case TaskNotFoundError: + w.WriteHeader(http.StatusNotFound) + case UnsupportedOperationError: + w.WriteHeader(http.StatusNotImplemented) + default: + w.WriteHeader(http.StatusInternalServerError) + } + + json.NewEncoder(w).Encode(resp) +} + +// parseParams parses JSON-RPC params into a target struct +func parseParams(params interface{}, target interface{}) error { + if params == nil { + return nil + } + + // Convert params to JSON and then unmarshal into target + jsonData, err := json.Marshal(params) + if err != nil { + return fmt.Errorf("failed to marshal params: %w", err) + } + + if err := json.Unmarshal(jsonData, target); err != nil { + return fmt.Errorf("failed to unmarshal params: %w", err) + } + + return nil +} + +// GetRegisteredMethods returns a list of registered method names +func (s *Server) GetRegisteredMethods() []string { + methods := make([]string, 0, len(s.handlers)) + for method := range s.handlers { + methods = append(methods, method) + } + return methods +} + +// HasMethod checks if a method is registered +func (s *Server) HasMethod(method string) bool { + _, exists := s.handlers[method] + return exists +} + +// GetSupportedMethods returns the A2A methods supported by this server +func (s *Server) GetSupportedMethods() []string { + supported := make([]string, 0, len(MethodMapping)) + for a2aMethod, jsonRPCMethod := range MethodMapping { + if s.HasMethod(jsonRPCMethod) { + supported = append(supported, a2aMethod) + } + } + return supported +} + +// SetCORSOrigins sets allowed CORS origins +func (s *Server) SetCORSOrigins(origins []string) { + s.corsOrigins = make([]string, len(origins)) + copy(s.corsOrigins, origins) + s.allowAllCORS = len(origins) == 0 +} + +// Validate validates the server configuration +func (s *Server) Validate() error { + if len(s.handlers) == 0 { + return fmt.Errorf("no handlers registered") + } + + // Check that essential A2A methods are implemented + requiredMethods := []string{"agentCard/get"} + var missing []string + + for _, method := range requiredMethods { + if !s.HasMethod(method) { + missing = append(missing, method) + } + } + + if len(missing) > 0 { + return fmt.Errorf("missing required methods: %s", strings.Join(missing, ", ")) + } + + return nil +} + +// parseTaskIDFromName extracts task ID from resource name format (tasks/{task_id}) +func parseTaskIDFromName(name string) string { + const prefix = "tasks/" + if !strings.HasPrefix(name, prefix) { + return "" + } + return name[len(prefix):] +} + +// generateUUID generates a new UUID string +func generateUUID() string { + return uuid.New().String() +} + +// setCORSHeaders sets CORS headers based on configured origins +func (s *Server) setCORSHeaders(w http.ResponseWriter, r *http.Request) { + if s.allowAllCORS { + w.Header().Set("Access-Control-Allow-Origin", "*") + return + } + + origin := r.Header.Get("Origin") + if origin == "" { + return + } + + // Check if origin is in allowed list + for _, allowedOrigin := range s.corsOrigins { + if origin == allowedOrigin { + w.Header().Set("Access-Control-Allow-Origin", origin) + w.Header().Set("Vary", "Origin") + return + } + } + + // Origin not allowed, don't set CORS headers +} + +// handleSendMessageWithHandler processes message through handler interface for JSON-RPC +func (s *Server) handleSendMessageWithHandler(ctx context.Context, sendReq *types.SendMessageRequest, handlerTaskManager interface { + ProcessMessage(ctx context.Context, req *handler.MessageRequest) (*handler.MessageResponse, error) +}, taskManager types.TaskManagerInterface) (interface{}, error) { + // Create task first to get task ID + task, err := taskManager.CreateTask(ctx, sendReq.Request.ContextID, sendReq.Request, sendReq.Metadata) + if err != nil { + return nil, NewError(InternalError, "Failed to create task", err.Error()) + } + + // Create task operations for handler + eventChan := make(chan types.StreamResponse, 100) + taskOps := handler.NewTaskOperations(taskManager, task.ID, sendReq.Request.ContextID, eventChan) + + // Determine configuration from request + config := &handler.MessageConfiguration{ + Blocking: false, // JSON-RPC is non-blocking by default + Streaming: false, + Timeout: 30 * time.Second, + } + + if sendReq.Configuration != nil { + config.Blocking = sendReq.Configuration.Blocking + config.HistoryLength = int(sendReq.Configuration.HistoryLength) + config.AcceptedOutputModes = sendReq.Configuration.AcceptedOutputModes + config.PushNotification = sendReq.Configuration.PushNotification + } + + // Build message request for handler + messageReq := &handler.MessageRequest{ + Message: sendReq.Request, + TaskID: task.ID, + ContextID: sendReq.Request.ContextID, + Configuration: config, + History: task.History, + Metadata: sendReq.Metadata, + TaskOperations: taskOps, + } + + // Process through handler + response, err := handlerTaskManager.ProcessMessage(ctx, messageReq) + if err != nil { + return nil, NewError(InternalError, "Handler processing failed", err.Error()) + } + + // Handle response based on mode + switch response.Mode { + case handler.ResponseModeTask: + // Return task response + if response.Result != nil && response.Result.Data != nil { + if taskData, ok := response.Result.Data.(*types.Task); ok { + return &types.TaskSendMessageResponse{Task: taskData}, nil + } + } + case handler.ResponseModeMessage: + // Return message response + if response.Result != nil && response.Result.Data != nil { + if msgData, ok := response.Result.Data.(*types.Message); ok { + return &types.MessageSendMessageResponse{Message: msgData}, nil + } + } + } + + // Fallback - return original task + return &types.TaskSendMessageResponse{Task: task}, nil +} diff --git a/a2a/pkg/transport/jsonrpc/types.go b/a2a/pkg/transport/jsonrpc/types.go new file mode 100644 index 000000000..585d6e4bf --- /dev/null +++ b/a2a/pkg/transport/jsonrpc/types.go @@ -0,0 +1,223 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package jsonrpc + +import ( + "encoding/json" + "fmt" + + "seata-go-ai-a2a/pkg/types" +) + +// JSONRPCVersion represents the JSON-RPC version +const JSONRPCVersion = "2.0" + +// Request represents a JSON-RPC 2.0 request +type Request struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + Params interface{} `json:"params,omitempty"` + ID interface{} `json:"id"` +} + +// Response represents a JSON-RPC 2.0 response +type Response struct { + JSONRPC string `json:"jsonrpc"` + Result interface{} `json:"result,omitempty"` + Error *Error `json:"error,omitempty"` + ID interface{} `json:"id"` +} + +// Error represents a JSON-RPC 2.0 error +type Error struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +// Error implements the error interface +func (e *Error) Error() string { + return fmt.Sprintf("JSON-RPC error %d: %s", e.Code, e.Message) +} + +// Standard JSON-RPC error codes +const ( + // JSON-RPC 2.0 standard errors + ParseError = -32700 + InvalidRequest = -32600 + MethodNotFound = -32601 + InvalidParams = -32602 + InternalError = -32603 + + // A2A specific error codes (as defined in A2A specification) + TaskNotFoundError = -32001 + TaskNotCancelableError = -32002 + UnsupportedOperationError = -32004 + ContentTypeNotSupportedError = -32005 + InvalidAgentResponseError = -32006 + AuthenticatedExtendedCardNotConfiguredError = -32007 +) + +// MethodMapping maps A2A methods to JSON-RPC method names +var MethodMapping = map[string]string{ + "message/send": "message/send", + "message/stream": "message/stream", + "tasks/get": "tasks/get", + "tasks/list": "tasks/list", + "tasks/cancel": "tasks/cancel", + "tasks/resubscribe": "tasks/resubscribe", + "tasks/pushNotificationConfig/set": "tasks/pushNotificationConfig/set", + "tasks/pushNotificationConfig/get": "tasks/pushNotificationConfig/get", + "tasks/pushNotificationConfig/list": "tasks/pushNotificationConfig/list", + "tasks/pushNotificationConfig/delete": "tasks/pushNotificationConfig/delete", + "agentCard/get": "agentCard/get", +} + +// NewRequest creates a new JSON-RPC request +func NewRequest(method string, params interface{}, id interface{}) *Request { + return &Request{ + JSONRPC: JSONRPCVersion, + Method: method, + Params: params, + ID: id, + } +} + +// NewSuccessResponse creates a successful JSON-RPC response +func NewSuccessResponse(result interface{}, id interface{}) *Response { + return &Response{ + JSONRPC: JSONRPCVersion, + Result: result, + ID: id, + } +} + +// NewErrorResponse creates an error JSON-RPC response +func NewErrorResponse(err *Error, id interface{}) *Response { + return &Response{ + JSONRPC: JSONRPCVersion, + Error: err, + ID: id, + } +} + +// NewError creates a new JSON-RPC error +func NewError(code int, message string, data interface{}) *Error { + return &Error{ + Code: code, + Message: message, + Data: data, + } +} + +// ConvertA2AErrorToJSONRPCError converts an A2A error to JSON-RPC error format +func ConvertA2AErrorToJSONRPCError(a2aErr *types.A2AError) *Error { + return &Error{ + Code: a2aErr.Code, + Message: a2aErr.Message, + Data: a2aErr.Data, + } +} + +// ParseRequest parses a JSON-RPC request from JSON bytes +func ParseRequest(data []byte) (*Request, error) { + var req Request + if err := json.Unmarshal(data, &req); err != nil { + return nil, NewError(ParseError, "Parse error", err.Error()) + } + + if err := ValidateRequest(&req); err != nil { + return nil, err + } + + return &req, nil +} + +// ValidateRequest validates a JSON-RPC request +func ValidateRequest(req *Request) *Error { + if req.JSONRPC != JSONRPCVersion { + return NewError(InvalidRequest, "Invalid JSON-RPC version", fmt.Sprintf("expected %s, got %s", JSONRPCVersion, req.JSONRPC)) + } + + if req.Method == "" { + return NewError(InvalidRequest, "Missing method", nil) + } + + if req.ID == nil { + return NewError(InvalidRequest, "Missing id", nil) + } + + return nil +} + +// ValidateResponse validates a JSON-RPC response +func ValidateResponse(resp *Response) error { + if resp.JSONRPC != JSONRPCVersion { + return fmt.Errorf("invalid JSON-RPC version: expected %s, got %s", JSONRPCVersion, resp.JSONRPC) + } + + if resp.Result == nil && resp.Error == nil { + return fmt.Errorf("response must have either result or error") + } + + if resp.Result != nil && resp.Error != nil { + return fmt.Errorf("response cannot have both result and error") + } + + return nil +} + +// IsNotification returns true if the request is a notification (no ID) +func (r *Request) IsNotification() bool { + return r.ID == nil +} + +// GetMethodName returns the method name from the request +func (r *Request) GetMethodName() string { + return r.Method +} + +// GetParams returns the parameters from the request +func (r *Request) GetParams() interface{} { + return r.Params +} + +// GetID returns the request ID +func (r *Request) GetID() interface{} { + return r.ID +} + +// IsError returns true if the response contains an error +func (r *Response) IsError() bool { + return r.Error != nil +} + +// GetResult returns the result from the response +func (r *Response) GetResult() interface{} { + return r.Result +} + +// GetError returns the error from the response +func (r *Response) GetError() *Error { + return r.Error +} + +// GetID returns the response ID +func (r *Response) GetID() interface{} { + return r.ID +} diff --git a/a2a/pkg/transport/rest/client.go b/a2a/pkg/transport/rest/client.go new file mode 100644 index 000000000..415fa1aaa --- /dev/null +++ b/a2a/pkg/transport/rest/client.go @@ -0,0 +1,310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package rest + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "seata-go-ai-a2a/pkg/types" +) + +// Client represents a RESTful HTTP client for A2A protocol +type Client struct { + baseURL string + httpClient *http.Client +} + +// ClientConfig configures the REST client +type ClientConfig struct { + BaseURL string + Timeout time.Duration +} + +// NewClient creates a new REST client +func NewClient(config *ClientConfig) *Client { + timeout := config.Timeout + if timeout == 0 { + timeout = 30 * time.Second + } + + return &Client{ + baseURL: config.BaseURL, + httpClient: &http.Client{ + Timeout: timeout, + }, + } +} + +// doRequest performs an HTTP request and returns the response +func (c *Client) doRequest(ctx context.Context, method, path string, body interface{}, result interface{}) error { + var reqBody io.Reader + + // Marshal request body if provided + if body != nil { + jsonData, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("failed to marshal request body: %w", err) + } + reqBody = bytes.NewReader(jsonData) + } + + // Create HTTP request + url := fmt.Sprintf("%s%s", c.baseURL, path) + httpReq, err := http.NewRequestWithContext(ctx, method, url, reqBody) + if err != nil { + return fmt.Errorf("failed to create HTTP request: %w", err) + } + + // Set headers + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("Accept", "application/json") + httpReq.Header.Set("User-Agent", "A2A-REST-Client/1.0") + + // Make HTTP request + httpResp, err := c.httpClient.Do(httpReq) + if err != nil { + return fmt.Errorf("HTTP request failed: %w", err) + } + defer httpResp.Body.Close() + + // Read response body + respBody, err := io.ReadAll(httpResp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + // Handle non-2xx status codes + if httpResp.StatusCode >= 400 { + var errorResp map[string]interface{} + if err := json.Unmarshal(respBody, &errorResp); err == nil { + if errorData, ok := errorResp["error"].(map[string]interface{}); ok { + if message, ok := errorData["message"].(string); ok { + return &RESTError{ + StatusCode: httpResp.StatusCode, + Message: message, + Details: fmt.Sprintf("%v", errorData["details"]), + } + } + } + } + return &RESTError{ + StatusCode: httpResp.StatusCode, + Message: fmt.Sprintf("HTTP %d", httpResp.StatusCode), + Details: string(respBody), + } + } + + // Unmarshal result if provided + if result != nil && len(respBody) > 0 { + if err := json.Unmarshal(respBody, result); err != nil { + return fmt.Errorf("failed to unmarshal response: %w", err) + } + } + + return nil +} + +// A2A Protocol Methods + +// SendMessage sends a message using the A2A REST API +func (c *Client) SendMessage(ctx context.Context, req *types.SendMessageRequest) (*types.TaskSendMessageResponse, error) { + var resp types.TaskSendMessageResponse + err := c.doRequest(ctx, http.MethodPost, "/api/v1/messages", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// GetAgentCard retrieves the agent card via REST API +func (c *Client) GetAgentCard(ctx context.Context) (*types.AgentCard, error) { + var agentCard types.AgentCard + err := c.doRequest(ctx, http.MethodGet, "/api/v1/agent-card", nil, &agentCard) + if err != nil { + return nil, err + } + return &agentCard, nil +} + +// GetTask retrieves a task by ID via REST API +func (c *Client) GetTask(ctx context.Context, req *types.GetTaskRequest) (*types.Task, error) { + // Extract task ID from Name field (format: tasks/{task_id}) + taskID := strings.TrimPrefix(req.Name, "tasks/") + path := fmt.Sprintf("/api/v1/tasks/%s", url.PathEscape(taskID)) + + var task types.Task + err := c.doRequest(ctx, http.MethodGet, path, nil, &task) + if err != nil { + return nil, err + } + return &task, nil +} + +// CancelTask cancels a task via REST API +func (c *Client) CancelTask(ctx context.Context, req *types.CancelTaskRequest) (*types.Task, error) { + // Extract task ID from Name field (format: tasks/{task_id}) + taskID := strings.TrimPrefix(req.Name, "tasks/") + path := fmt.Sprintf("/api/v1/tasks/%s", url.PathEscape(taskID)) + + var task types.Task + err := c.doRequest(ctx, http.MethodDelete, path, nil, &task) + if err != nil { + return nil, err + } + return &task, nil +} + +// ListTasks lists tasks with optional filtering via REST API +func (c *Client) ListTasks(ctx context.Context, req *types.ListTasksRequest) (*types.ListTasksResponse, error) { + path := "/api/v1/tasks" + + // Add query parameters for filtering + params := url.Values{} + if req.ContextID != "" { + params.Set("contextId", req.ContextID) + } + // Convert states to string representation + for _, state := range req.States { + params.Add("state", state.String()) + } + + if len(params) > 0 { + path += "?" + params.Encode() + } + + var resp types.ListTasksResponse + err := c.doRequest(ctx, http.MethodGet, path, nil, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// CreateTaskPushNotificationConfig creates a push notification config via REST API +func (c *Client) CreateTaskPushNotificationConfig(ctx context.Context, req *types.CreateTaskPushNotificationConfigRequest) (*types.TaskPushNotificationConfig, error) { + var config types.TaskPushNotificationConfig + err := c.doRequest(ctx, http.MethodPost, "/api/v1/push-notification-configs", req, &config) + if err != nil { + return nil, err + } + return &config, nil +} + +// GetTaskPushNotificationConfig retrieves a push notification config via REST API +func (c *Client) GetTaskPushNotificationConfig(ctx context.Context, req *types.GetTaskPushNotificationConfigRequest) (*types.TaskPushNotificationConfig, error) { + // Extract config ID from Name field (format: tasks/{task_id}/pushNotificationConfigs/{config_id}) + parts := strings.Split(req.Name, "/") + var configID string + if len(parts) >= 4 { + configID = parts[3] + } + path := fmt.Sprintf("/api/v1/push-notification-configs/%s", url.PathEscape(configID)) + + var config types.TaskPushNotificationConfig + err := c.doRequest(ctx, http.MethodGet, path, nil, &config) + if err != nil { + return nil, err + } + return &config, nil +} + +// ListTaskPushNotificationConfig lists push notification configs via REST API +func (c *Client) ListTaskPushNotificationConfig(ctx context.Context, req *types.ListTaskPushNotificationConfigRequest) (*types.ListTaskPushNotificationConfigResponse, error) { + var resp types.ListTaskPushNotificationConfigResponse + err := c.doRequest(ctx, http.MethodGet, "/api/v1/push-notification-configs", req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// DeleteTaskPushNotificationConfig deletes a push notification config via REST API +func (c *Client) DeleteTaskPushNotificationConfig(ctx context.Context, req *types.DeleteTaskPushNotificationConfigRequest) error { + // Extract config ID from Name field (format: tasks/{task_id}/pushNotificationConfigs/{config_id}) + parts := strings.Split(req.Name, "/") + var configID string + if len(parts) >= 4 { + configID = parts[3] + } + path := fmt.Sprintf("/api/v1/push-notification-configs/%s", url.PathEscape(configID)) + return c.doRequest(ctx, http.MethodDelete, path, nil, nil) +} + +// RESTError represents an error returned by the REST API +type RESTError struct { + StatusCode int `json:"statusCode"` + Message string `json:"message"` + Details string `json:"details,omitempty"` +} + +func (e *RESTError) Error() string { + if e.Details != "" { + return fmt.Sprintf("REST API error %d: %s (%s)", e.StatusCode, e.Message, e.Details) + } + return fmt.Sprintf("REST API error %d: %s", e.StatusCode, e.Message) +} + +// IsNotFound returns true if this is a 404 error +func (e *RESTError) IsNotFound() bool { + return e.StatusCode == http.StatusNotFound +} + +// IsBadRequest returns true if this is a 400 error +func (e *RESTError) IsBadRequest() bool { + return e.StatusCode == http.StatusBadRequest +} + +// IsUnauthorized returns true if this is a 401 error +func (e *RESTError) IsUnauthorized() bool { + return e.StatusCode == http.StatusUnauthorized +} + +// IsForbidden returns true if this is a 403 error +func (e *RESTError) IsForbidden() bool { + return e.StatusCode == http.StatusForbidden +} + +// IsInternalServerError returns true if this is a 500 error +func (e *RESTError) IsInternalServerError() bool { + return e.StatusCode == http.StatusInternalServerError +} + +// Close closes the client and cleans up resources +func (c *Client) Close() error { + // Close HTTP client connections + c.httpClient.CloseIdleConnections() + return nil +} + +// SetTimeout sets the client timeout +func (c *Client) SetTimeout(timeout time.Duration) { + c.httpClient.Timeout = timeout +} + +// GetBaseURL returns the client base URL +func (c *Client) GetBaseURL() string { + return c.baseURL +} diff --git a/a2a/pkg/transport/rest/server.go b/a2a/pkg/transport/rest/server.go new file mode 100644 index 000000000..cd5b86fab --- /dev/null +++ b/a2a/pkg/transport/rest/server.go @@ -0,0 +1,636 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package rest + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "sync" + "time" + + "github.com/google/uuid" + + "seata-go-ai-a2a/pkg/handler" + "seata-go-ai-a2a/pkg/types" +) + +// HandlerFunc represents a REST API handler function +type HandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request) error + +// Server represents a RESTful HTTP server for A2A protocol +type Server struct { + mux *http.ServeMux + agentCard *types.AgentCard + taskManager types.TaskManagerInterface + pushNotificationConfigs map[string]*types.TaskPushNotificationConfig + pushConfigMu sync.RWMutex +} + +// NewServer creates a new REST server +func NewServer() *Server { + return &Server{ + mux: http.NewServeMux(), + pushNotificationConfigs: make(map[string]*types.TaskPushNotificationConfig), + } +} + +// RegisterA2AHandlers registers REST handlers for A2A protocol endpoints +func (s *Server) RegisterA2AHandlers(agentCard *types.AgentCard, taskManager types.TaskManagerInterface) { + s.agentCard = agentCard + s.taskManager = taskManager + + // Register A2A REST endpoints according to specification + s.mux.HandleFunc("/api/v1/messages", s.handleMessages) + s.mux.HandleFunc("/api/v1/tasks", s.handleTasks) + s.mux.HandleFunc("/api/v1/tasks/", s.handleTaskOperations) + s.mux.HandleFunc("/api/v1/agent-card", s.handleAgentCard) + s.mux.HandleFunc("/api/v1/push-notification-configs", s.handlePushNotificationConfigs) + s.mux.HandleFunc("/api/v1/push-notification-configs/", s.handlePushNotificationConfigOperations) +} + +// ServeHTTP implements http.Handler interface +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Set CORS headers + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + + // Handle preflight OPTIONS requests + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusOK) + return + } + + // Set common headers + w.Header().Set("Content-Type", "application/json") + + s.mux.ServeHTTP(w, r) +} + +// handleMessages handles /api/v1/messages endpoint +func (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPost: + s.handleSendMessage(w, r) + default: + s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "Only POST is supported for messages endpoint") + } +} + +// handleSendMessage handles POST /api/v1/messages (message/send) +func (s *Server) handleSendMessage(w http.ResponseWriter, r *http.Request) { + // Read request body + body, err := io.ReadAll(r.Body) + if err != nil { + s.writeErrorResponse(w, http.StatusBadRequest, "Invalid request body", err.Error()) + return + } + + // Parse SendMessageRequest + var req types.SendMessageRequest + if err := json.Unmarshal(body, &req); err != nil { + s.writeErrorResponse(w, http.StatusBadRequest, "Invalid JSON", err.Error()) + return + } + + // Validate the request + if req.Request == nil { + s.writeErrorResponse(w, http.StatusBadRequest, "Missing message in request", "Request must contain a message") + return + } + + // Extract context ID from message or generate a new one + contextID := req.Request.ContextID + if contextID == "" { + contextID = fmt.Sprintf("ctx_%s", uuid.New().String()) + } + + // Set context ID in the message if not already set + if req.Request.ContextID == "" { + req.Request.ContextID = contextID + } + + // Check if task manager has handler interfaces + if handlerTaskManager, ok := interface{}(s.taskManager).(interface { + ProcessMessage(ctx context.Context, req *handler.MessageRequest) (*handler.MessageResponse, error) + }); ok { + // Use handler interface for processing + s.handleSendMessageWithHandler(w, r, &req, handlerTaskManager) + return + } + + // Fallback to legacy direct task creation + task, err := s.taskManager.CreateTask(r.Context(), contextID, req.Request, req.Metadata) + if err != nil { + s.writeErrorResponse(w, http.StatusInternalServerError, "Failed to create task", err.Error()) + return + } + + // If blocking mode is requested and the task is still submitted, + // update it to working state for demonstration + if req.Configuration != nil && req.Configuration.Blocking { + err = s.taskManager.UpdateTaskStatus(r.Context(), task.ID, types.TaskStateWorking, nil) + if err != nil { + // Log error but don't fail the request + } + } + + response := &types.TaskSendMessageResponse{Task: task} + s.writeJSONResponse(w, http.StatusOK, response) +} + +// handleTasks handles /api/v1/tasks endpoint +func (s *Server) handleTasks(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + s.handleListTasks(w, r) + default: + s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "Only GET is supported for tasks endpoint") + } +} + +// handleListTasks handles GET /api/v1/tasks (tasks/list) +func (s *Server) handleListTasks(w http.ResponseWriter, r *http.Request) { + // Parse query parameters for filtering + contextID := r.URL.Query().Get("contextId") + states := r.URL.Query()["state"] + + // Convert state strings to TaskState enums + var stateEnums []types.TaskState + for _, stateStr := range states { + if state, err := types.ParseTaskState(stateStr); err == nil { + stateEnums = append(stateEnums, state) + } + } + + req := &types.ListTasksRequest{ + ContextID: contextID, + States: stateEnums, + } + + // List tasks using the task manager + response, err := s.taskManager.ListTasks(r.Context(), req) + if err != nil { + s.writeErrorResponse(w, http.StatusInternalServerError, "Failed to list tasks", err.Error()) + return + } + + s.writeJSONResponse(w, http.StatusOK, response) +} + +// handleTaskOperations handles /api/v1/tasks/{taskId} endpoint +func (s *Server) handleTaskOperations(w http.ResponseWriter, r *http.Request) { + // Extract task ID from path + path := strings.TrimPrefix(r.URL.Path, "/api/v1/tasks/") + taskID := strings.Split(path, "/")[0] + + if taskID == "" { + s.writeErrorResponse(w, http.StatusBadRequest, "Missing task ID", "Task ID is required in the URL path") + return + } + + switch r.Method { + case http.MethodGet: + s.handleGetTask(w, r, taskID) + case http.MethodDelete: + s.handleCancelTask(w, r, taskID) + default: + s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "Only GET and DELETE are supported for task operations") + } +} + +// handleGetTask handles GET /api/v1/tasks/{taskId} (tasks/get) +func (s *Server) handleGetTask(w http.ResponseWriter, r *http.Request, taskID string) { + // Get task using the task manager + task, err := s.taskManager.GetTask(r.Context(), taskID) + if err != nil { + // Check if it's an A2A error + if a2aErr, ok := err.(*types.A2AError); ok { + switch a2aErr.Code { + case types.TaskNotFoundError: + s.writeErrorResponse(w, http.StatusNotFound, "Task not found", a2aErr.Message) + default: + s.writeErrorResponse(w, http.StatusBadRequest, "Task error", a2aErr.Message) + } + return + } + s.writeErrorResponse(w, http.StatusInternalServerError, "Failed to get task", err.Error()) + return + } + + s.writeJSONResponse(w, http.StatusOK, task) +} + +// handleCancelTask handles DELETE /api/v1/tasks/{taskId} (tasks/cancel) +func (s *Server) handleCancelTask(w http.ResponseWriter, r *http.Request, taskID string) { + // Cancel task using the task manager + task, err := s.taskManager.CancelTask(r.Context(), taskID) + if err != nil { + // Check if it's an A2A error + if a2aErr, ok := err.(*types.A2AError); ok { + switch a2aErr.Code { + case types.TaskNotFoundError: + s.writeErrorResponse(w, http.StatusNotFound, "Task not found", a2aErr.Message) + case types.TaskNotCancelableError: + s.writeErrorResponse(w, http.StatusConflict, "Task not cancelable", a2aErr.Message) + default: + s.writeErrorResponse(w, http.StatusBadRequest, "Task error", a2aErr.Message) + } + return + } + s.writeErrorResponse(w, http.StatusInternalServerError, "Failed to cancel task", err.Error()) + return + } + + s.writeJSONResponse(w, http.StatusOK, task) +} + +// handleAgentCard handles /api/v1/agent-card endpoint +func (s *Server) handleAgentCard(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + var agentCard *types.AgentCard + + // Check if task manager has agent card provider + if cardProvider, ok := interface{}(s.taskManager).(interface { + GetAgentCard(ctx context.Context) (*types.AgentCard, error) + }); ok { + // Use provider interface + card, err := cardProvider.GetAgentCard(r.Context()) + if err != nil { + s.writeErrorResponse(w, http.StatusInternalServerError, "Failed to get agent card from provider", err.Error()) + return + } + agentCard = card + } else { + // Fallback to static agent card + agentCard = s.agentCard + } + + if agentCard == nil { + s.writeErrorResponse(w, http.StatusInternalServerError, "No agent card available", "Agent card is not configured") + return + } + + s.writeJSONResponse(w, http.StatusOK, agentCard) + default: + s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "Only GET is supported for agent-card endpoint") + } +} + +// handlePushNotificationConfigs handles /api/v1/push-notification-configs endpoint +func (s *Server) handlePushNotificationConfigs(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPost: + s.handleCreatePushNotificationConfig(w, r) + case http.MethodGet: + s.handleListPushNotificationConfigs(w, r) + default: + s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "Only POST and GET are supported for push-notification-configs endpoint") + } +} + +// handleCreatePushNotificationConfig handles POST /api/v1/push-notification-configs +func (s *Server) handleCreatePushNotificationConfig(w http.ResponseWriter, r *http.Request) { + // Read request body + body, err := io.ReadAll(r.Body) + if err != nil { + s.writeErrorResponse(w, http.StatusBadRequest, "Invalid request body", err.Error()) + return + } + + // Parse CreateTaskPushNotificationConfigRequest + var req types.CreateTaskPushNotificationConfigRequest + if err := json.Unmarshal(body, &req); err != nil { + s.writeErrorResponse(w, http.StatusBadRequest, "Invalid JSON", err.Error()) + return + } + + // Validate parent format (tasks/{task_id}) + if !strings.HasPrefix(req.Parent, "tasks/") { + s.writeErrorResponse(w, http.StatusBadRequest, "Invalid parent format", "Parent must be in format tasks/{task_id}") + return + } + + taskID := strings.TrimPrefix(req.Parent, "tasks/") + if taskID == "" { + s.writeErrorResponse(w, http.StatusBadRequest, "Missing task ID", "Task ID is required in parent") + return + } + + // Verify task exists + _, err = s.taskManager.GetTask(r.Context(), taskID) + if err != nil { + if a2aErr, ok := err.(*types.A2AError); ok && a2aErr.Code == types.TaskNotFoundError { + s.writeErrorResponse(w, http.StatusNotFound, "Task not found", "The specified task does not exist") + } else { + s.writeErrorResponse(w, http.StatusInternalServerError, "Failed to verify task", err.Error()) + } + return + } + + // Generate config ID if not provided + configID := req.ConfigID + if configID == "" { + configID = uuid.New().String() + } + + // Generate full name for the config + configName := fmt.Sprintf("tasks/%s/pushNotificationConfigs/%s", taskID, configID) + req.Config.Name = configName + + // Store the config + s.pushConfigMu.Lock() + s.pushNotificationConfigs[configName] = req.Config + s.pushConfigMu.Unlock() + + s.writeJSONResponse(w, http.StatusCreated, req.Config) +} + +// handleListPushNotificationConfigs handles GET /api/v1/push-notification-configs +func (s *Server) handleListPushNotificationConfigs(w http.ResponseWriter, r *http.Request) { + // Get parent (task ID) from query parameters + parent := r.URL.Query().Get("parent") + if parent == "" { + s.writeErrorResponse(w, http.StatusBadRequest, "Missing parent parameter", "Parent parameter is required") + return + } + + // Parse task ID from parent + if !strings.HasPrefix(parent, "tasks/") { + s.writeErrorResponse(w, http.StatusBadRequest, "Invalid parent format", "Parent must be in format tasks/{task_id}") + return + } + + taskID := strings.TrimPrefix(parent, "tasks/") + if taskID == "" { + s.writeErrorResponse(w, http.StatusBadRequest, "Missing task ID", "Task ID is required in parent") + return + } + + // Verify task exists + _, err := s.taskManager.GetTask(r.Context(), taskID) + if err != nil { + if a2aErr, ok := err.(*types.A2AError); ok && a2aErr.Code == types.TaskNotFoundError { + s.writeErrorResponse(w, http.StatusNotFound, "Task not found", "The specified task does not exist") + } else { + s.writeErrorResponse(w, http.StatusInternalServerError, "Failed to verify task", err.Error()) + } + return + } + + // Find all configs for this task + s.pushConfigMu.RLock() + var configs []*types.TaskPushNotificationConfig + prefix := fmt.Sprintf("tasks/%s/pushNotificationConfigs/", taskID) + + for name, config := range s.pushNotificationConfigs { + if strings.HasPrefix(name, prefix) { + configs = append(configs, config) + } + } + s.pushConfigMu.RUnlock() + + response := &types.ListTaskPushNotificationConfigResponse{ + Configs: configs, + } + + s.writeJSONResponse(w, http.StatusOK, response) +} + +// handlePushNotificationConfigOperations handles /api/v1/push-notification-configs/{configId} endpoint +func (s *Server) handlePushNotificationConfigOperations(w http.ResponseWriter, r *http.Request) { + // Extract config ID from path + path := strings.TrimPrefix(r.URL.Path, "/api/v1/push-notification-configs/") + configID := strings.Split(path, "/")[0] + + if configID == "" { + s.writeErrorResponse(w, http.StatusBadRequest, "Missing config ID", "Config ID is required in the URL path") + return + } + + switch r.Method { + case http.MethodGet: + s.handleGetPushNotificationConfig(w, r, configID) + case http.MethodDelete: + s.handleDeletePushNotificationConfig(w, r, configID) + default: + s.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed", "Only GET and DELETE are supported for push notification config operations") + } +} + +// handleGetPushNotificationConfig handles GET /api/v1/push-notification-configs/{configId} +func (s *Server) handleGetPushNotificationConfig(w http.ResponseWriter, r *http.Request, configID string) { + // Get task ID from query parameter (required since we need the full resource path) + taskID := r.URL.Query().Get("task_id") + if taskID == "" { + s.writeErrorResponse(w, http.StatusBadRequest, "Missing task_id parameter", "task_id query parameter is required") + return + } + + // Construct full config name + configName := fmt.Sprintf("tasks/%s/pushNotificationConfigs/%s", taskID, configID) + + // Get the config from storage + s.pushConfigMu.RLock() + config, exists := s.pushNotificationConfigs[configName] + s.pushConfigMu.RUnlock() + + if !exists { + s.writeErrorResponse(w, http.StatusNotFound, "Push notification config not found", "The specified config does not exist") + return + } + + s.writeJSONResponse(w, http.StatusOK, config) +} + +// handleDeletePushNotificationConfig handles DELETE /api/v1/push-notification-configs/{configId} +func (s *Server) handleDeletePushNotificationConfig(w http.ResponseWriter, r *http.Request, configID string) { + // Get task ID from query parameter (required since we need the full resource path) + taskID := r.URL.Query().Get("task_id") + if taskID == "" { + s.writeErrorResponse(w, http.StatusBadRequest, "Missing task_id parameter", "task_id query parameter is required") + return + } + + // Construct full config name + configName := fmt.Sprintf("tasks/%s/pushNotificationConfigs/%s", taskID, configID) + + // Check if config exists and delete it + s.pushConfigMu.Lock() + defer s.pushConfigMu.Unlock() + + _, exists := s.pushNotificationConfigs[configName] + if !exists { + s.writeErrorResponse(w, http.StatusNotFound, "Push notification config not found", "The specified config does not exist") + return + } + + // Delete the config + delete(s.pushNotificationConfigs, configName) + + // Return empty success response + w.WriteHeader(http.StatusNoContent) +} + +// writeJSONResponse writes a JSON response with the given status code +func (s *Server) writeJSONResponse(w http.ResponseWriter, statusCode int, data interface{}) { + w.WriteHeader(statusCode) + + if err := json.NewEncoder(w).Encode(data); err != nil { + // Fallback error response if encoding fails + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(`{"error":"Failed to encode response"}`)) + } +} + +// writeErrorResponse writes an error response in A2A error format +func (s *Server) writeErrorResponse(w http.ResponseWriter, statusCode int, message string, details string) { + errorResp := map[string]interface{}{ + "error": map[string]interface{}{ + "code": statusCode, + "message": message, + "details": details, + }, + "timestamp": time.Now().UTC().Format(time.RFC3339), + } + + w.WriteHeader(statusCode) + json.NewEncoder(w).Encode(errorResp) +} + +// Validate validates the server configuration +func (s *Server) Validate() error { + if s.agentCard == nil { + return fmt.Errorf("agent card is required") + } + + return nil +} + +// GetRegisteredEndpoints returns a list of registered REST endpoints +func (s *Server) GetRegisteredEndpoints() []string { + return []string{ + "GET /api/v1/agent-card", + "POST /api/v1/messages", + "GET /api/v1/tasks", + "GET /api/v1/tasks/{taskId}", + "DELETE /api/v1/tasks/{taskId}", + "POST /api/v1/push-notification-configs", + "GET /api/v1/push-notification-configs", + "GET /api/v1/push-notification-configs/{configId}", + "DELETE /api/v1/push-notification-configs/{configId}", + } +} + +// SetAgentCard updates the agent card served by the REST server +func (s *Server) SetAgentCard(agentCard *types.AgentCard) { + s.agentCard = agentCard +} + +// SetTaskManager sets the task manager for handling task operations +func (s *Server) SetTaskManager(taskManager types.TaskManagerInterface) { + s.taskManager = taskManager +} + +// handleSendMessageWithHandler processes message through handler interface +func (s *Server) handleSendMessageWithHandler(w http.ResponseWriter, r *http.Request, sendReq *types.SendMessageRequest, handlerTaskManager interface { + ProcessMessage(ctx context.Context, req *handler.MessageRequest) (*handler.MessageResponse, error) +}) { + // Create task first to get task ID + task, err := s.taskManager.CreateTask(r.Context(), sendReq.Request.ContextID, sendReq.Request, sendReq.Metadata) + if err != nil { + s.writeErrorResponse(w, http.StatusInternalServerError, "Failed to create task", err.Error()) + return + } + + // Create task operations for handler + eventChan := make(chan types.StreamResponse, 100) + taskOps := handler.NewTaskOperations(s.taskManager, task.ID, sendReq.Request.ContextID, eventChan) + + // Determine configuration from request + config := &handler.MessageConfiguration{ + Blocking: false, // REST is non-blocking by default + Streaming: false, + Timeout: 30 * time.Second, + } + + if sendReq.Configuration != nil { + config.Blocking = sendReq.Configuration.Blocking + config.HistoryLength = int(sendReq.Configuration.HistoryLength) + config.AcceptedOutputModes = sendReq.Configuration.AcceptedOutputModes + config.PushNotification = sendReq.Configuration.PushNotification + } + + // Build message request for handler + messageReq := &handler.MessageRequest{ + Message: sendReq.Request, + TaskID: task.ID, + ContextID: sendReq.Request.ContextID, + Configuration: config, + History: task.History, + Metadata: sendReq.Metadata, + TaskOperations: taskOps, + } + + // Process through handler + response, err := handlerTaskManager.ProcessMessage(r.Context(), messageReq) + if err != nil { + s.writeErrorResponse(w, http.StatusInternalServerError, "Handler processing failed", err.Error()) + return + } + + // Handle response based on mode + switch response.Mode { + case handler.ResponseModeTask: + // Return task response + if response.Result != nil && response.Result.Data != nil { + if taskData, ok := response.Result.Data.(*types.Task); ok { + taskResponse := &types.TaskSendMessageResponse{Task: taskData} + s.writeJSONResponse(w, http.StatusOK, taskResponse) + return + } + } + case handler.ResponseModeMessage: + // Return message response + if response.Result != nil && response.Result.Data != nil { + if msgData, ok := response.Result.Data.(*types.Message); ok { + msgResponse := &types.MessageSendMessageResponse{Message: msgData} + s.writeJSONResponse(w, http.StatusOK, msgResponse) + return + } + } + case handler.ResponseModeStream: + // For streaming mode, return the task and let client poll for updates + if response.Result != nil && response.Result.Data != nil { + if taskData, ok := response.Result.Data.(*types.Task); ok { + taskResponse := &types.TaskSendMessageResponse{Task: taskData} + s.writeJSONResponse(w, http.StatusOK, taskResponse) + return + } + } + } + + // Fallback - return original task + taskResponse := &types.TaskSendMessageResponse{Task: task} + s.writeJSONResponse(w, http.StatusOK, taskResponse) +} diff --git a/a2a/pkg/transport/sse/client.go b/a2a/pkg/transport/sse/client.go new file mode 100644 index 000000000..523899bb4 --- /dev/null +++ b/a2a/pkg/transport/sse/client.go @@ -0,0 +1,409 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package sse + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "seata-go-ai-a2a/pkg/types" +) + +// Client provides Server-Sent Events client functionality for A2A protocol +type Client struct { + baseURL string + httpClient *http.Client + mu sync.RWMutex + streams map[string]*StreamConnection +} + +// StreamConnection represents an active SSE stream connection +type StreamConnection struct { + id string + resp *http.Response + scanner *bufio.Scanner + events chan types.StreamResponse + errors chan error + ctx context.Context + cancel context.CancelFunc + mu sync.Mutex + closed bool +} + +// ClientConfig configures the SSE client +type ClientConfig struct { + BaseURL string + Timeout time.Duration +} + +// NewClient creates a new SSE client +func NewClient(config *ClientConfig) *Client { + timeout := config.Timeout + if timeout == 0 { + timeout = 0 // No timeout for streaming connections + } + + return &Client{ + baseURL: config.BaseURL, + httpClient: &http.Client{ + Timeout: timeout, + }, + streams: make(map[string]*StreamConnection), + } +} + +// StreamMessages subscribes to message stream for a task +func (c *Client) StreamMessages(ctx context.Context, taskID string) (*StreamConnection, error) { + url := fmt.Sprintf("%s/stream/message?taskId=%s", c.baseURL, taskID) + return c.createStream(ctx, url) +} + +// StreamTask subscribes to task updates for a task +func (c *Client) StreamTask(ctx context.Context, taskID string) (*StreamConnection, error) { + url := fmt.Sprintf("%s/stream/task?taskId=%s", c.baseURL, taskID) + return c.createStream(ctx, url) +} + +// createStream creates a new SSE stream connection +func (c *Client) createStream(ctx context.Context, url string) (*StreamConnection, error) { + // Create HTTP request + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + // Set SSE headers + req.Header.Set("Accept", "text/event-stream") + req.Header.Set("Cache-Control", "no-cache") + req.Header.Set("User-Agent", "A2A-SSE-Client/1.0") + + // Make request + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to connect to SSE endpoint: %w", err) + } + + if resp.StatusCode != http.StatusOK { + resp.Body.Close() + return nil, fmt.Errorf("SSE endpoint returned status %d", resp.StatusCode) + } + + // Check content type + contentType := resp.Header.Get("Content-Type") + if !strings.HasPrefix(contentType, "text/event-stream") { + resp.Body.Close() + return nil, fmt.Errorf("invalid content type: %s", contentType) + } + + // Create stream connection + streamCtx, cancel := context.WithCancel(ctx) + connectionID := fmt.Sprintf("stream_%d", time.Now().UnixNano()) + + conn := &StreamConnection{ + id: connectionID, + resp: resp, + scanner: bufio.NewScanner(resp.Body), + events: make(chan types.StreamResponse, 10), + errors: make(chan error, 5), + ctx: streamCtx, + cancel: cancel, + } + + // Register connection + c.mu.Lock() + c.streams[connectionID] = conn + c.mu.Unlock() + + // Start reading events + go conn.readEvents() + + return conn, nil +} + +// readEvents reads and parses SSE events from the connection +func (sc *StreamConnection) readEvents() { + defer func() { + sc.Close() + }() + + var event SSEEvent + var dataLines []string + + for sc.scanner.Scan() { + line := sc.scanner.Text() + + // Handle different SSE line types + if line == "" { + // Empty line indicates end of event + if event.Event != "" || len(dataLines) > 0 { + // Process complete event + event.Data = strings.Join(dataLines, "\n") + sc.processEvent(&event) + + // Reset for next event + event = SSEEvent{} + dataLines = nil + } + continue + } + + if strings.HasPrefix(line, "event: ") { + event.Event = strings.TrimPrefix(line, "event: ") + } else if strings.HasPrefix(line, "data: ") { + dataLines = append(dataLines, strings.TrimPrefix(line, "data: ")) + } else if strings.HasPrefix(line, "id: ") { + event.ID = strings.TrimPrefix(line, "id: ") + } else if strings.HasPrefix(line, "retry: ") { + // Handle retry directive (not implemented for now) + } else if strings.HasPrefix(line, ":") { + // Comment line, ignore + } + + // Check if context is cancelled + select { + case <-sc.ctx.Done(): + return + default: + } + } + + // Check for scanning errors + if err := sc.scanner.Err(); err != nil { + select { + case sc.errors <- fmt.Errorf("error reading SSE stream: %w", err): + case <-sc.ctx.Done(): + } + } +} + +// processEvent processes a complete SSE event +func (sc *StreamConnection) processEvent(event *SSEEvent) { + // Skip ping events + if event.Event == "ping" { + return + } + + // Handle error events + if event.Event == "error" { + var errorData map[string]interface{} + if err := json.Unmarshal([]byte(event.Data), &errorData); err == nil { + if errorInfo, ok := errorData["error"].(map[string]interface{}); ok { + if message, ok := errorInfo["message"].(string); ok { + select { + case sc.errors <- fmt.Errorf("SSE error: %s", message): + case <-sc.ctx.Done(): + } + return + } + } + } + return + } + + // Handle stream_closed events + if event.Event == "stream_closed" { + sc.Close() + return + } + + // Parse JSON-RPC response from event data + var jsonrpcResp map[string]interface{} + if err := json.Unmarshal([]byte(event.Data), &jsonrpcResp); err != nil { + select { + case sc.errors <- fmt.Errorf("failed to parse JSON-RPC response: %w", err): + case <-sc.ctx.Done(): + } + return + } + + // Extract result from JSON-RPC response + result, ok := jsonrpcResp["result"] + if !ok { + select { + case sc.errors <- fmt.Errorf("JSON-RPC response missing result field"): + case <-sc.ctx.Done(): + } + return + } + + // Convert to appropriate stream response type based on event type + var streamResp types.StreamResponse + + switch event.Event { + case "task": + var task types.Task + if err := unmarshalInterface(result, &task); err != nil { + select { + case sc.errors <- fmt.Errorf("failed to unmarshal task: %w", err): + case <-sc.ctx.Done(): + } + return + } + streamResp = &types.TaskStreamResponse{Task: &task} + + case "message": + var message types.Message + if err := unmarshalInterface(result, &message); err != nil { + select { + case sc.errors <- fmt.Errorf("failed to unmarshal message: %w", err): + case <-sc.ctx.Done(): + } + return + } + streamResp = &types.MessageStreamResponse{Message: &message} + + case "status_update": + var statusUpdate types.TaskStatusUpdateEvent + if err := unmarshalInterface(result, &statusUpdate); err != nil { + select { + case sc.errors <- fmt.Errorf("failed to unmarshal status update: %w", err): + case <-sc.ctx.Done(): + } + return + } + streamResp = &types.StatusUpdateStreamResponse{StatusUpdate: &statusUpdate} + + case "artifact_update": + var artifactUpdate types.TaskArtifactUpdateEvent + if err := unmarshalInterface(result, &artifactUpdate); err != nil { + select { + case sc.errors <- fmt.Errorf("failed to unmarshal artifact update: %w", err): + case <-sc.ctx.Done(): + } + return + } + streamResp = &types.ArtifactUpdateStreamResponse{ArtifactUpdate: &artifactUpdate} + + case "connected": + // Handle connection confirmation - could create a custom response type + return + + default: + select { + case sc.errors <- fmt.Errorf("unknown event type: %s", event.Event): + case <-sc.ctx.Done(): + } + return + } + + // Send stream response + select { + case sc.events <- streamResp: + case <-sc.ctx.Done(): + } +} + +// Events returns the events channel for this stream +func (sc *StreamConnection) Events() <-chan types.StreamResponse { + return sc.events +} + +// Errors returns the errors channel for this stream +func (sc *StreamConnection) Errors() <-chan error { + return sc.errors +} + +// Close closes the stream connection +func (sc *StreamConnection) Close() { + sc.mu.Lock() + defer sc.mu.Unlock() + + if !sc.closed { + sc.closed = true + sc.cancel() + sc.resp.Body.Close() + close(sc.events) + close(sc.errors) + } +} + +// IsClosed returns true if the connection is closed +func (sc *StreamConnection) IsClosed() bool { + sc.mu.Lock() + defer sc.mu.Unlock() + return sc.closed +} + +// GetID returns the connection ID +func (sc *StreamConnection) GetID() string { + return sc.id +} + +// Close closes the SSE client and all active streams +func (c *Client) Close() error { + c.mu.Lock() + streams := make([]*StreamConnection, 0, len(c.streams)) + for _, stream := range c.streams { + streams = append(streams, stream) + } + c.streams = make(map[string]*StreamConnection) + c.mu.Unlock() + + // Close all streams + for _, stream := range streams { + stream.Close() + } + + // Close HTTP client connections + c.httpClient.CloseIdleConnections() + return nil +} + +// GetActiveStreams returns the number of active streams +func (c *Client) GetActiveStreams() int { + c.mu.RLock() + defer c.mu.RUnlock() + return len(c.streams) +} + +// SSEEvent represents a parsed Server-Sent Event +type SSEEvent struct { + Event string + Data string + ID string +} + +// unmarshalInterface converts interface{} to a target struct +func unmarshalInterface(source interface{}, target interface{}) error { + jsonData, err := json.Marshal(source) + if err != nil { + return fmt.Errorf("failed to marshal source: %w", err) + } + + if err := json.Unmarshal(jsonData, target); err != nil { + return fmt.Errorf("failed to unmarshal to target: %w", err) + } + + return nil +} + +// SetTimeout sets the client timeout (note: this doesn't affect active streams) +func (c *Client) SetTimeout(timeout time.Duration) { + c.httpClient.Timeout = timeout +} + +// GetBaseURL returns the client base URL +func (c *Client) GetBaseURL() string { + return c.baseURL +} diff --git a/a2a/pkg/transport/sse/server.go b/a2a/pkg/transport/sse/server.go new file mode 100644 index 000000000..358fd7503 --- /dev/null +++ b/a2a/pkg/transport/sse/server.go @@ -0,0 +1,359 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package sse + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "sync" + "time" + + "seata-go-ai-a2a/pkg/task" + "seata-go-ai-a2a/pkg/types" +) + +// Server provides Server-Sent Events functionality for A2A protocol streaming +type Server struct { + mu sync.RWMutex + connections map[string]*Connection + taskManager *task.TaskManager + pingInterval time.Duration +} + +// Connection represents an active SSE connection +type Connection struct { + id string + writer http.ResponseWriter + flusher http.Flusher + ctx context.Context + cancel context.CancelFunc + mu sync.Mutex + closed bool +} + +// NewServer creates a new SSE server +func NewServer(taskManager *task.TaskManager) *Server { + return &Server{ + connections: make(map[string]*Connection), + taskManager: taskManager, + pingInterval: 30 * time.Second, + } +} + +// ServeHTTP handles SSE requests +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Only allow GET requests for SSE + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Check if client accepts text/event-stream + if r.Header.Get("Accept") != "text/event-stream" { + http.Error(w, "Accept header must be text/event-stream", http.StatusBadRequest) + return + } + + // Set SSE headers + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "Cache-Control") + + // Create flusher + flusher, ok := w.(http.Flusher) + if !ok { + http.Error(w, "Streaming unsupported", http.StatusInternalServerError) + return + } + + // Create connection + ctx, cancel := context.WithCancel(r.Context()) + conn := &Connection{ + id: generateConnectionID(), + writer: w, + flusher: flusher, + ctx: ctx, + cancel: cancel, + } + + // Register connection + s.mu.Lock() + s.connections[conn.id] = conn + s.mu.Unlock() + + // Handle connection cleanup + defer func() { + s.mu.Lock() + delete(s.connections, conn.id) + s.mu.Unlock() + conn.Close() + }() + + // Start ping routine for keep-alive + go s.pingConnection(conn) + + // Handle different streaming endpoints + switch r.URL.Path { + case "/stream/message": + s.handleMessageStream(conn, r) + case "/stream/task": + s.handleTaskStream(conn, r) + default: + s.sendError(conn, "Unknown stream endpoint", http.StatusNotFound) + } +} + +// handleMessageStream handles message/stream requests via SSE +func (s *Server) handleMessageStream(conn *Connection, r *http.Request) { + // Parse the JSON-RPC request from query parameters or request body + // For SSE, we typically get the initial request via query params or headers + + taskID := r.URL.Query().Get("taskId") + if taskID == "" { + s.sendError(conn, "taskId parameter required", http.StatusBadRequest) + return + } + + // Subscribe to task updates + subscription, err := s.taskManager.SubscribeToTask(r.Context(), taskID) + if err != nil { + s.sendError(conn, fmt.Sprintf("Failed to subscribe to task: %v", err), http.StatusBadRequest) + return + } + defer subscription.Close() + + // Send initial connection confirmation + s.sendEvent(conn, "connected", map[string]interface{}{ + "taskId": taskID, + "timestamp": time.Now().UTC().Format(time.RFC3339), + "connectionId": conn.id, + }) + + // Stream task updates + for { + select { + case response, ok := <-subscription.Events(): + if !ok { + // Stream closed + s.sendEvent(conn, "stream_closed", map[string]interface{}{ + "reason": "task_completed", + "timestamp": time.Now().UTC().Format(time.RFC3339), + }) + return + } + + // Send the stream response as SSE + s.sendStreamResponse(conn, response) + + case <-conn.ctx.Done(): + // Connection closed + return + } + } +} + +// handleTaskStream handles task subscription streams via SSE +func (s *Server) handleTaskStream(conn *Connection, r *http.Request) { + taskID := r.URL.Query().Get("taskId") + if taskID == "" { + s.sendError(conn, "taskId parameter required", http.StatusBadRequest) + return + } + + // Subscribe to task updates + subscription, err := s.taskManager.SubscribeToTask(r.Context(), taskID) + if err != nil { + s.sendError(conn, fmt.Sprintf("Failed to subscribe to task: %v", err), http.StatusBadRequest) + return + } + defer subscription.Close() + + // Send initial connection confirmation + s.sendEvent(conn, "connected", map[string]interface{}{ + "taskId": taskID, + "timestamp": time.Now().UTC().Format(time.RFC3339), + "connectionId": conn.id, + }) + + // Stream task updates + for { + select { + case response, ok := <-subscription.Events(): + if !ok { + s.sendEvent(conn, "stream_closed", map[string]interface{}{ + "reason": "subscription_ended", + "timestamp": time.Now().UTC().Format(time.RFC3339), + }) + return + } + + // Send the stream response as SSE + s.sendStreamResponse(conn, response) + + case <-conn.ctx.Done(): + return + } + } +} + +// sendStreamResponse sends a stream response as an SSE event +func (s *Server) sendStreamResponse(conn *Connection, response types.StreamResponse) { + var eventType string + var data interface{} + + switch r := response.(type) { + case *types.TaskStreamResponse: + eventType = "task" + data = r.Task + case *types.MessageStreamResponse: + eventType = "message" + data = r.Message + case *types.StatusUpdateStreamResponse: + eventType = "status_update" + data = r.StatusUpdate + case *types.ArtifactUpdateStreamResponse: + eventType = "artifact_update" + data = r.ArtifactUpdate + default: + eventType = "unknown" + data = response + } + + // Wrap in JSON-RPC 2.0 format for A2A compatibility + jsonrpcResponse := map[string]interface{}{ + "jsonrpc": "2.0", + "result": data, + "id": nil, // SSE doesn't have request IDs + } + + s.sendEvent(conn, eventType, jsonrpcResponse) +} + +// sendEvent sends an SSE event +func (s *Server) sendEvent(conn *Connection, eventType string, data interface{}) error { + conn.mu.Lock() + defer conn.mu.Unlock() + + if conn.closed { + return fmt.Errorf("connection closed") + } + + // Marshal data to JSON + jsonData, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("failed to marshal data: %w", err) + } + + // Write SSE format + if eventType != "" { + fmt.Fprintf(conn.writer, "event: %s\n", eventType) + } + fmt.Fprintf(conn.writer, "data: %s\n\n", string(jsonData)) + + conn.flusher.Flush() + return nil +} + +// sendError sends an error event +func (s *Server) sendError(conn *Connection, message string, statusCode int) { + errorData := map[string]interface{}{ + "error": map[string]interface{}{ + "code": -32603, // Internal error + "message": message, + }, + "jsonrpc": "2.0", + "id": nil, + } + + s.sendEvent(conn, "error", errorData) +} + +// pingConnection sends periodic ping events to keep connection alive +func (s *Server) pingConnection(conn *Connection) { + ticker := time.NewTicker(s.pingInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + err := s.sendEvent(conn, "ping", map[string]interface{}{ + "timestamp": time.Now().UTC().Format(time.RFC3339), + }) + if err != nil { + // Connection is likely closed + return + } + case <-conn.ctx.Done(): + return + } + } +} + +// Close closes the connection +func (c *Connection) Close() { + c.mu.Lock() + defer c.mu.Unlock() + + if !c.closed { + c.closed = true + c.cancel() + } +} + +// IsClosed returns true if the connection is closed +func (c *Connection) IsClosed() bool { + c.mu.Lock() + defer c.mu.Unlock() + return c.closed +} + +// GetActiveConnections returns the number of active connections +func (s *Server) GetActiveConnections() int { + s.mu.RLock() + defer s.mu.RUnlock() + return len(s.connections) +} + +// CloseAllConnections closes all active connections +func (s *Server) CloseAllConnections() { + s.mu.Lock() + connections := make([]*Connection, 0, len(s.connections)) + for _, conn := range s.connections { + connections = append(connections, conn) + } + s.connections = make(map[string]*Connection) + s.mu.Unlock() + + for _, conn := range connections { + conn.Close() + } +} + +// SetPingInterval sets the ping interval for keep-alive +func (s *Server) SetPingInterval(interval time.Duration) { + s.pingInterval = interval +} + +// generateConnectionID generates a unique connection ID +func generateConnectionID() string { + return fmt.Sprintf("conn_%d", time.Now().UnixNano()) +} diff --git a/a2a/pkg/types/agent.go b/a2a/pkg/types/agent.go new file mode 100644 index 000000000..5118a9132 --- /dev/null +++ b/a2a/pkg/types/agent.go @@ -0,0 +1,457 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import ( + "fmt" + + pb "seata-go-ai-a2a/pkg/proto/v1" +) + +// AgentCard conveys key information about an agent +type AgentCard struct { + ProtocolVersion string `json:"protocolVersion"` + Name string `json:"name"` + Description string `json:"description"` + URL string `json:"url"` + PreferredTransport string `json:"preferredTransport"` + AdditionalInterfaces []*AgentInterface `json:"additionalInterfaces,omitempty"` + Provider *AgentProvider `json:"provider,omitempty"` + Version string `json:"version"` + DocumentationURL string `json:"documentationUrl,omitempty"` + Capabilities *AgentCapabilities `json:"capabilities,omitempty"` + SecuritySchemes map[string]SecurityScheme `json:"securitySchemes,omitempty"` + Security []*Security `json:"security,omitempty"` + DefaultInputModes []string `json:"defaultInputModes,omitempty"` + DefaultOutputModes []string `json:"defaultOutputModes,omitempty"` + Skills []*AgentSkill `json:"skills,omitempty"` + SupportsAuthenticatedExtendedCard bool `json:"supportsAuthenticatedExtendedCard,omitempty"` + Signatures []*AgentCardSignature `json:"signatures,omitempty"` + IconURL string `json:"iconUrl,omitempty"` +} + +// AgentInterface defines additional transport information for the agent +type AgentInterface struct { + URL string `json:"url"` + Transport string `json:"transport"` +} + +// AgentProvider represents information about the service provider of an agent +type AgentProvider struct { + URL string `json:"url"` + Organization string `json:"organization"` +} + +// AgentCapabilities defines the A2A feature set supported by the agent +type AgentCapabilities struct { + Streaming bool `json:"streaming"` + PushNotifications bool `json:"pushNotifications"` + StateTransitionHistory bool `json:"stateTransitionHistory"` + Extensions []*AgentExtension `json:"extensions,omitempty"` +} + +// AgentExtension represents a declaration of an extension supported by an Agent +type AgentExtension struct { + URI string `json:"uri"` + Description string `json:"description"` + Required bool `json:"required"` + Params map[string]any `json:"params,omitempty"` +} + +// AgentSkill represents a unit of action/solution that the agent can perform +type AgentSkill struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Tags []string `json:"tags,omitempty"` + Examples []string `json:"examples,omitempty"` + InputModes []string `json:"inputModes,omitempty"` + OutputModes []string `json:"outputModes,omitempty"` + Security []*Security `json:"security,omitempty"` +} + +// AgentCardSignature represents a JWS signature of an AgentCard +type AgentCardSignature struct { + Protected string `json:"protected"` + Signature string `json:"signature"` + Header map[string]any `json:"header,omitempty"` +} + +// AgentCardToProto converts AgentCard to pb.AgentCard +func AgentCardToProto(card *AgentCard) (*pb.AgentCard, error) { + if card == nil { + return nil, nil + } + + additionalInterfaces := make([]*pb.AgentInterface, len(card.AdditionalInterfaces)) + for i, iface := range card.AdditionalInterfaces { + additionalInterfaces[i] = AgentInterfaceToProto(iface) + } + + var provider *pb.AgentProvider + if card.Provider != nil { + provider = AgentProviderToProto(card.Provider) + } + + var capabilities *pb.AgentCapabilities + var err error + if card.Capabilities != nil { + capabilities, err = AgentCapabilitiesToProto(card.Capabilities) + if err != nil { + return nil, fmt.Errorf("converting capabilities: %w", err) + } + } + + securitySchemes := make(map[string]*pb.SecurityScheme) + for name, scheme := range card.SecuritySchemes { + pbScheme, err := SecuritySchemeToProto(scheme) + if err != nil { + return nil, fmt.Errorf("converting security scheme %s: %w", name, err) + } + securitySchemes[name] = pbScheme + } + + security := make([]*pb.Security, len(card.Security)) + for i, sec := range card.Security { + security[i] = SecurityToProto(sec) + } + + skills := make([]*pb.AgentSkill, len(card.Skills)) + for i, skill := range card.Skills { + skills[i] = AgentSkillToProto(skill) + } + + signatures := make([]*pb.AgentCardSignature, len(card.Signatures)) + for i, sig := range card.Signatures { + var err error + signatures[i], err = AgentCardSignatureToProto(sig) + if err != nil { + return nil, fmt.Errorf("converting signature %d: %w", i, err) + } + } + + return &pb.AgentCard{ + ProtocolVersion: card.ProtocolVersion, + Name: card.Name, + Description: card.Description, + Url: card.URL, + PreferredTransport: card.PreferredTransport, + AdditionalInterfaces: additionalInterfaces, + Provider: provider, + Version: card.Version, + DocumentationUrl: card.DocumentationURL, + Capabilities: capabilities, + SecuritySchemes: securitySchemes, + Security: security, + DefaultInputModes: card.DefaultInputModes, + DefaultOutputModes: card.DefaultOutputModes, + Skills: skills, + SupportsAuthenticatedExtendedCard: card.SupportsAuthenticatedExtendedCard, + Signatures: signatures, + IconUrl: card.IconURL, + }, nil +} + +// AgentCardFromProto converts pb.AgentCard to AgentCard +func AgentCardFromProto(card *pb.AgentCard) (*AgentCard, error) { + if card == nil { + return nil, nil + } + + additionalInterfaces := make([]*AgentInterface, len(card.AdditionalInterfaces)) + for i, iface := range card.AdditionalInterfaces { + additionalInterfaces[i] = AgentInterfaceFromProto(iface) + } + + var provider *AgentProvider + if card.Provider != nil { + provider = AgentProviderFromProto(card.Provider) + } + + var capabilities *AgentCapabilities + var err error + if card.Capabilities != nil { + capabilities, err = AgentCapabilitiesFromProto(card.Capabilities) + if err != nil { + return nil, fmt.Errorf("converting capabilities: %w", err) + } + } + + securitySchemes := make(map[string]SecurityScheme) + for name, scheme := range card.SecuritySchemes { + typesScheme, err := SecuritySchemeFromProto(scheme) + if err != nil { + return nil, fmt.Errorf("converting security scheme %s: %w", name, err) + } + securitySchemes[name] = typesScheme + } + + security := make([]*Security, len(card.Security)) + for i, sec := range card.Security { + security[i] = SecurityFromProto(sec) + } + + skills := make([]*AgentSkill, len(card.Skills)) + for i, skill := range card.Skills { + skills[i] = AgentSkillFromProto(skill) + } + + signatures := make([]*AgentCardSignature, len(card.Signatures)) + for i, sig := range card.Signatures { + var err error + signatures[i], err = AgentCardSignatureFromProto(sig) + if err != nil { + return nil, fmt.Errorf("converting signature %d: %w", i, err) + } + } + + return &AgentCard{ + ProtocolVersion: card.ProtocolVersion, + Name: card.Name, + Description: card.Description, + URL: card.Url, + PreferredTransport: card.PreferredTransport, + AdditionalInterfaces: additionalInterfaces, + Provider: provider, + Version: card.Version, + DocumentationURL: card.DocumentationUrl, + Capabilities: capabilities, + SecuritySchemes: securitySchemes, + Security: security, + DefaultInputModes: card.DefaultInputModes, + DefaultOutputModes: card.DefaultOutputModes, + Skills: skills, + SupportsAuthenticatedExtendedCard: card.SupportsAuthenticatedExtendedCard, + Signatures: signatures, + IconURL: card.IconUrl, + }, nil +} + +// AgentInterfaceToProto converts AgentInterface to pb.AgentInterface +func AgentInterfaceToProto(iface *AgentInterface) *pb.AgentInterface { + if iface == nil { + return nil + } + + return &pb.AgentInterface{ + Url: iface.URL, + Transport: iface.Transport, + } +} + +// AgentInterfaceFromProto converts pb.AgentInterface to AgentInterface +func AgentInterfaceFromProto(iface *pb.AgentInterface) *AgentInterface { + if iface == nil { + return nil + } + + return &AgentInterface{ + URL: iface.Url, + Transport: iface.Transport, + } +} + +// AgentProviderToProto converts AgentProvider to pb.AgentProvider +func AgentProviderToProto(provider *AgentProvider) *pb.AgentProvider { + if provider == nil { + return nil + } + + return &pb.AgentProvider{ + Url: provider.URL, + Organization: provider.Organization, + } +} + +// AgentProviderFromProto converts pb.AgentProvider to AgentProvider +func AgentProviderFromProto(provider *pb.AgentProvider) *AgentProvider { + if provider == nil { + return nil + } + + return &AgentProvider{ + URL: provider.Url, + Organization: provider.Organization, + } +} + +// AgentCapabilitiesToProto converts AgentCapabilities to pb.AgentCapabilities +func AgentCapabilitiesToProto(capabilities *AgentCapabilities) (*pb.AgentCapabilities, error) { + if capabilities == nil { + return nil, nil + } + + extensions := make([]*pb.AgentExtension, len(capabilities.Extensions)) + for i, ext := range capabilities.Extensions { + var err error + extensions[i], err = AgentExtensionToProto(ext) + if err != nil { + return nil, fmt.Errorf("converting extension %d: %w", i, err) + } + } + + return &pb.AgentCapabilities{ + Streaming: capabilities.Streaming, + PushNotifications: capabilities.PushNotifications, + StateTransitionHistory: capabilities.StateTransitionHistory, + Extensions: extensions, + }, nil +} + +// AgentCapabilitiesFromProto converts pb.AgentCapabilities to AgentCapabilities +func AgentCapabilitiesFromProto(capabilities *pb.AgentCapabilities) (*AgentCapabilities, error) { + if capabilities == nil { + return nil, nil + } + + extensions := make([]*AgentExtension, len(capabilities.Extensions)) + for i, ext := range capabilities.Extensions { + var err error + extensions[i], err = AgentExtensionFromProto(ext) + if err != nil { + return nil, fmt.Errorf("converting extension %d: %w", i, err) + } + } + + return &AgentCapabilities{ + Streaming: capabilities.Streaming, + PushNotifications: capabilities.PushNotifications, + StateTransitionHistory: capabilities.StateTransitionHistory, + Extensions: extensions, + }, nil +} + +// AgentExtensionToProto converts AgentExtension to pb.AgentExtension +func AgentExtensionToProto(extension *AgentExtension) (*pb.AgentExtension, error) { + if extension == nil { + return nil, nil + } + + params, err := MapToStruct(extension.Params) + if err != nil { + return nil, fmt.Errorf("converting extension params: %w", err) + } + + return &pb.AgentExtension{ + Uri: extension.URI, + Description: extension.Description, + Required: extension.Required, + Params: params, + }, nil +} + +// AgentExtensionFromProto converts pb.AgentExtension to AgentExtension +func AgentExtensionFromProto(extension *pb.AgentExtension) (*AgentExtension, error) { + if extension == nil { + return nil, nil + } + + params, err := StructToMap(extension.Params) + if err != nil { + return nil, fmt.Errorf("converting extension params: %w", err) + } + + return &AgentExtension{ + URI: extension.Uri, + Description: extension.Description, + Required: extension.Required, + Params: params, + }, nil +} + +// AgentSkillToProto converts AgentSkill to pb.AgentSkill +func AgentSkillToProto(skill *AgentSkill) *pb.AgentSkill { + if skill == nil { + return nil + } + + security := make([]*pb.Security, len(skill.Security)) + for i, sec := range skill.Security { + security[i] = SecurityToProto(sec) + } + + return &pb.AgentSkill{ + Id: skill.ID, + Name: skill.Name, + Description: skill.Description, + Tags: skill.Tags, + Examples: skill.Examples, + InputModes: skill.InputModes, + OutputModes: skill.OutputModes, + Security: security, + } +} + +// AgentSkillFromProto converts pb.AgentSkill to AgentSkill +func AgentSkillFromProto(skill *pb.AgentSkill) *AgentSkill { + if skill == nil { + return nil + } + + security := make([]*Security, len(skill.Security)) + for i, sec := range skill.Security { + security[i] = SecurityFromProto(sec) + } + + return &AgentSkill{ + ID: skill.Id, + Name: skill.Name, + Description: skill.Description, + Tags: skill.Tags, + Examples: skill.Examples, + InputModes: skill.InputModes, + OutputModes: skill.OutputModes, + Security: security, + } +} + +// AgentCardSignatureToProto converts AgentCardSignature to pb.AgentCardSignature +func AgentCardSignatureToProto(signature *AgentCardSignature) (*pb.AgentCardSignature, error) { + if signature == nil { + return nil, nil + } + + header, err := MapToStruct(signature.Header) + if err != nil { + return nil, fmt.Errorf("converting signature header: %w", err) + } + + return &pb.AgentCardSignature{ + Protected: signature.Protected, + Signature: signature.Signature, + Header: header, + }, nil +} + +// AgentCardSignatureFromProto converts pb.AgentCardSignature to AgentCardSignature +func AgentCardSignatureFromProto(signature *pb.AgentCardSignature) (*AgentCardSignature, error) { + if signature == nil { + return nil, nil + } + + header, err := StructToMap(signature.Header) + if err != nil { + return nil, fmt.Errorf("converting signature header: %w", err) + } + + return &AgentCardSignature{ + Protected: signature.Protected, + Signature: signature.Signature, + Header: header, + }, nil +} diff --git a/a2a/pkg/types/config.go b/a2a/pkg/types/config.go new file mode 100644 index 000000000..babe1c300 --- /dev/null +++ b/a2a/pkg/types/config.go @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import ( + "fmt" + + pb "seata-go-ai-a2a/pkg/proto/v1" +) + +// SendMessageConfiguration contains configuration for a send message request +type SendMessageConfiguration struct { + AcceptedOutputModes []string `json:"acceptedOutputModes,omitempty"` + PushNotification *PushNotificationConfig `json:"pushNotification,omitempty"` + HistoryLength int32 `json:"historyLength,omitempty"` + Blocking bool `json:"blocking,omitempty"` +} + +// PushNotificationConfig contains configuration for push notifications +type PushNotificationConfig struct { + ID string `json:"id"` + URL string `json:"url"` + Token string `json:"token"` + Authentication *AuthenticationInfo `json:"authentication,omitempty"` +} + +// AuthenticationInfo contains authentication details for push notifications +type AuthenticationInfo struct { + Schemes []string `json:"schemes,omitempty"` + Credentials string `json:"credentials,omitempty"` +} + +// TaskPushNotificationConfig represents a push notification configuration for a specific task +type TaskPushNotificationConfig struct { + Name string `json:"name"` + PushNotificationConfig *PushNotificationConfig `json:"pushNotificationConfig"` +} + +// SendMessageConfigurationToProto converts SendMessageConfiguration to pb.SendMessageConfiguration +func SendMessageConfigurationToProto(config *SendMessageConfiguration) (*pb.SendMessageConfiguration, error) { + if config == nil { + return nil, nil + } + + var pushNotification *pb.PushNotificationConfig + var err error + if config.PushNotification != nil { + pushNotification, err = PushNotificationConfigToProto(config.PushNotification) + if err != nil { + return nil, fmt.Errorf("converting push notification config: %w", err) + } + } + + return &pb.SendMessageConfiguration{ + AcceptedOutputModes: config.AcceptedOutputModes, + PushNotification: pushNotification, + HistoryLength: config.HistoryLength, + Blocking: config.Blocking, + }, nil +} + +// SendMessageConfigurationFromProto converts pb.SendMessageConfiguration to SendMessageConfiguration +func SendMessageConfigurationFromProto(config *pb.SendMessageConfiguration) (*SendMessageConfiguration, error) { + if config == nil { + return nil, nil + } + + var pushNotification *PushNotificationConfig + var err error + if config.PushNotification != nil { + pushNotification, err = PushNotificationConfigFromProto(config.PushNotification) + if err != nil { + return nil, fmt.Errorf("converting push notification config: %w", err) + } + } + + return &SendMessageConfiguration{ + AcceptedOutputModes: config.AcceptedOutputModes, + PushNotification: pushNotification, + HistoryLength: config.HistoryLength, + Blocking: config.Blocking, + }, nil +} + +// PushNotificationConfigToProto converts PushNotificationConfig to pb.PushNotificationConfig +func PushNotificationConfigToProto(config *PushNotificationConfig) (*pb.PushNotificationConfig, error) { + if config == nil { + return nil, nil + } + + var auth *pb.AuthenticationInfo + var err error + if config.Authentication != nil { + auth, err = AuthenticationInfoToProto(config.Authentication) + if err != nil { + return nil, fmt.Errorf("converting authentication info: %w", err) + } + } + + return &pb.PushNotificationConfig{ + Id: config.ID, + Url: config.URL, + Token: config.Token, + Authentication: auth, + }, nil +} + +// PushNotificationConfigFromProto converts pb.PushNotificationConfig to PushNotificationConfig +func PushNotificationConfigFromProto(config *pb.PushNotificationConfig) (*PushNotificationConfig, error) { + if config == nil { + return nil, nil + } + + var auth *AuthenticationInfo + var err error + if config.Authentication != nil { + auth, err = AuthenticationInfoFromProto(config.Authentication) + if err != nil { + return nil, fmt.Errorf("converting authentication info: %w", err) + } + } + + return &PushNotificationConfig{ + ID: config.Id, + URL: config.Url, + Token: config.Token, + Authentication: auth, + }, nil +} + +// AuthenticationInfoToProto converts AuthenticationInfo to pb.AuthenticationInfo +func AuthenticationInfoToProto(auth *AuthenticationInfo) (*pb.AuthenticationInfo, error) { + if auth == nil { + return nil, nil + } + + return &pb.AuthenticationInfo{ + Schemes: auth.Schemes, + Credentials: auth.Credentials, + }, nil +} + +// AuthenticationInfoFromProto converts pb.AuthenticationInfo to AuthenticationInfo +func AuthenticationInfoFromProto(auth *pb.AuthenticationInfo) (*AuthenticationInfo, error) { + if auth == nil { + return nil, nil + } + + return &AuthenticationInfo{ + Schemes: auth.Schemes, + Credentials: auth.Credentials, + }, nil +} + +// TaskPushNotificationConfigToProto converts TaskPushNotificationConfig to pb.TaskPushNotificationConfig +func TaskPushNotificationConfigToProto(config *TaskPushNotificationConfig) (*pb.TaskPushNotificationConfig, error) { + if config == nil { + return nil, nil + } + + pushConfig, err := PushNotificationConfigToProto(config.PushNotificationConfig) + if err != nil { + return nil, fmt.Errorf("converting push notification config: %w", err) + } + + return &pb.TaskPushNotificationConfig{ + Name: config.Name, + PushNotificationConfig: pushConfig, + }, nil +} + +// TaskPushNotificationConfigFromProto converts pb.TaskPushNotificationConfig to TaskPushNotificationConfig +func TaskPushNotificationConfigFromProto(config *pb.TaskPushNotificationConfig) (*TaskPushNotificationConfig, error) { + if config == nil { + return nil, nil + } + + pushConfig, err := PushNotificationConfigFromProto(config.PushNotificationConfig) + if err != nil { + return nil, fmt.Errorf("converting push notification config: %w", err) + } + + return &TaskPushNotificationConfig{ + Name: config.Name, + PushNotificationConfig: pushConfig, + }, nil +} diff --git a/a2a/pkg/types/core.go b/a2a/pkg/types/core.go new file mode 100644 index 000000000..3b17538d6 --- /dev/null +++ b/a2a/pkg/types/core.go @@ -0,0 +1,790 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import ( + "encoding/json" + "fmt" + "time" + + pb "seata-go-ai-a2a/pkg/proto/v1" + + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// Task is the core unit of action for A2A protocol +type Task struct { + ID string `json:"id"` + ContextID string `json:"contextId"` + Status *TaskStatus `json:"status"` + Artifacts []*Artifact `json:"artifacts"` + History []*Message `json:"history"` + Kind string `json:"kind"` + Metadata map[string]any `json:"metadata,omitempty"` +} + +// TaskStatus contains the status information for a task +type TaskStatus struct { + State TaskState `json:"state"` + Update *Message `json:"message,omitempty"` + Timestamp time.Time `json:"timestamp"` +} + +// Message represents one unit of communication between client and server +type Message struct { + MessageID string `json:"messageId"` + ContextID string `json:"contextId,omitempty"` + TaskID string `json:"taskId,omitempty"` + Role Role `json:"role"` + Parts []Part `json:"parts"` + Kind string `json:"kind"` + Metadata map[string]any `json:"metadata,omitempty"` + Extensions []string `json:"extensions,omitempty"` +} + +// Artifact represents the container for task completed results +type Artifact struct { + ArtifactID string `json:"artifactId"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Parts []Part `json:"parts"` + Metadata map[string]any `json:"metadata,omitempty"` + Extensions []string `json:"extensions,omitempty"` +} + +// Part represents a container for a section of communication content +type Part interface { + PartType() PartTypeEnum +} + +// PartTypeEnum identifies the type of a Part +type PartTypeEnum int + +const ( + PartTypeText PartTypeEnum = iota + PartTypeFile + PartTypeData +) + +// TextPart represents plain text content +type TextPart struct { + Text string `json:"text"` + Metadata map[string]any `json:"metadata,omitempty"` +} + +func (t *TextPart) PartType() PartTypeEnum { return PartTypeText } + +// FilePart represents file content +type FilePart struct { + Content FileContent `json:"content"` + MimeType string `json:"mime_type"` + Name string `json:"name"` + Metadata map[string]any `json:"metadata,omitempty"` +} + +func (f *FilePart) PartType() PartTypeEnum { return PartTypeFile } + +// FileContent represents different ways files can be provided +type FileContent interface { + FileContentType() FileContentTypeEnum +} + +// FileContentTypeEnum identifies the type of file content +type FileContentTypeEnum int + +const ( + FileContentTypeURI FileContentTypeEnum = iota + FileContentTypeBytes +) + +// FileWithURI represents a file reference by URI +type FileWithURI struct { + URI string `json:"uri"` +} + +func (f *FileWithURI) FileContentType() FileContentTypeEnum { return FileContentTypeURI } + +// FileWithBytes represents file content as bytes +type FileWithBytes struct { + Bytes []byte `json:"bytes"` +} + +func (f *FileWithBytes) FileContentType() FileContentTypeEnum { return FileContentTypeBytes } + +// DataPart represents structured data content +type DataPart struct { + Data map[string]any `json:"data"` + Metadata map[string]any `json:"metadata,omitempty"` +} + +func (d *DataPart) PartType() PartTypeEnum { return PartTypeData } + +// TaskToProto converts Task to pb.Task +func TaskToProto(task *Task) (*pb.Task, error) { + if task == nil { + return nil, nil + } + + var status *pb.TaskStatus + var err error + if task.Status != nil { + status, err = TaskStatusToProto(task.Status) + if err != nil { + return nil, fmt.Errorf("converting task status: %w", err) + } + } + + artifacts := make([]*pb.Artifact, len(task.Artifacts)) + for i, artifact := range task.Artifacts { + artifacts[i], err = ArtifactToProto(artifact) + if err != nil { + return nil, fmt.Errorf("converting artifact %d: %w", i, err) + } + } + + history := make([]*pb.Message, len(task.History)) + for i, msg := range task.History { + history[i], err = MessageToProto(msg) + if err != nil { + return nil, fmt.Errorf("converting history message %d: %w", i, err) + } + } + + metadata, err := MapToStruct(task.Metadata) + if err != nil { + return nil, fmt.Errorf("converting task metadata: %w", err) + } + + return &pb.Task{ + Id: task.ID, + ContextId: task.ContextID, + Status: status, + Artifacts: artifacts, + History: history, + Kind: task.Kind, + Metadata: metadata, + }, nil +} + +// TaskFromProto converts pb.Task to Task +func TaskFromProto(task *pb.Task) (*Task, error) { + if task == nil { + return nil, nil + } + + var status *TaskStatus + var err error + if task.Status != nil { + status, err = TaskStatusFromProto(task.Status) + if err != nil { + return nil, fmt.Errorf("converting task status: %w", err) + } + } + + artifacts := make([]*Artifact, len(task.Artifacts)) + for i, artifact := range task.Artifacts { + artifacts[i], err = ArtifactFromProto(artifact) + if err != nil { + return nil, fmt.Errorf("converting artifact %d: %w", i, err) + } + } + + history := make([]*Message, len(task.History)) + for i, msg := range task.History { + history[i], err = MessageFromProto(msg) + if err != nil { + return nil, fmt.Errorf("converting history message %d: %w", i, err) + } + } + + metadata, err := StructToMap(task.Metadata) + if err != nil { + return nil, fmt.Errorf("converting task metadata: %w", err) + } + + return &Task{ + ID: task.Id, + ContextID: task.ContextId, + Status: status, + Artifacts: artifacts, + History: history, + Kind: task.Kind, + Metadata: metadata, + }, nil +} + +// TaskStatusToProto converts TaskStatus to pb.TaskStatus +func TaskStatusToProto(status *TaskStatus) (*pb.TaskStatus, error) { + if status == nil { + return nil, nil + } + + var update *pb.Message + var err error + if status.Update != nil { + update, err = MessageToProto(status.Update) + if err != nil { + return nil, fmt.Errorf("converting status update: %w", err) + } + } + + return &pb.TaskStatus{ + State: TaskStateToProto(status.State), + Update: update, + Timestamp: timestamppb.New(status.Timestamp), + }, nil +} + +// TaskStatusFromProto converts pb.TaskStatus to TaskStatus +func TaskStatusFromProto(status *pb.TaskStatus) (*TaskStatus, error) { + if status == nil { + return nil, nil + } + + var update *Message + var err error + if status.Update != nil { + update, err = MessageFromProto(status.Update) + if err != nil { + return nil, fmt.Errorf("converting status update: %w", err) + } + } + + return &TaskStatus{ + State: TaskStateFromProto(status.State), + Update: update, + Timestamp: status.Timestamp.AsTime(), + }, nil +} + +// MessageToProto converts Message to pb.Message +func MessageToProto(msg *Message) (*pb.Message, error) { + if msg == nil { + return nil, nil + } + + content := make([]*pb.Part, len(msg.Parts)) + for i, part := range msg.Parts { + var err error + content[i], err = PartToProto(part) + if err != nil { + return nil, fmt.Errorf("converting content part %d: %w", i, err) + } + } + + metadata, err := MapToStruct(msg.Metadata) + if err != nil { + return nil, fmt.Errorf("converting message metadata: %w", err) + } + + return &pb.Message{ + MessageId: msg.MessageID, + ContextId: msg.ContextID, + TaskId: msg.TaskID, + Role: RoleToProto(msg.Role), + Content: content, + Kind: msg.Kind, + Metadata: metadata, + Extensions: msg.Extensions, + }, nil +} + +// MessageFromProto converts pb.Message to Message +func MessageFromProto(msg *pb.Message) (*Message, error) { + if msg == nil { + return nil, nil + } + + content := make([]Part, len(msg.Content)) + for i, part := range msg.Content { + var err error + content[i], err = PartFromProto(part) + if err != nil { + return nil, fmt.Errorf("converting content part %d: %w", i, err) + } + } + + metadata, err := StructToMap(msg.Metadata) + if err != nil { + return nil, fmt.Errorf("converting message metadata: %w", err) + } + + return &Message{ + MessageID: msg.MessageId, + ContextID: msg.ContextId, + TaskID: msg.TaskId, + Role: RoleFromProto(msg.Role), + Parts: content, + Kind: msg.Kind, + Metadata: metadata, + Extensions: msg.Extensions, + }, nil +} + +// ArtifactToProto converts Artifact to pb.Artifact +func ArtifactToProto(artifact *Artifact) (*pb.Artifact, error) { + if artifact == nil { + return nil, nil + } + + parts := make([]*pb.Part, len(artifact.Parts)) + for i, part := range artifact.Parts { + var err error + parts[i], err = PartToProto(part) + if err != nil { + return nil, fmt.Errorf("converting artifact part %d: %w", i, err) + } + } + + metadata, err := MapToStruct(artifact.Metadata) + if err != nil { + return nil, fmt.Errorf("converting artifact metadata: %w", err) + } + + return &pb.Artifact{ + ArtifactId: artifact.ArtifactID, + Name: artifact.Name, + Description: artifact.Description, + Parts: parts, + Metadata: metadata, + Extensions: artifact.Extensions, + }, nil +} + +// ArtifactFromProto converts pb.Artifact to Artifact +func ArtifactFromProto(artifact *pb.Artifact) (*Artifact, error) { + if artifact == nil { + return nil, nil + } + + parts := make([]Part, len(artifact.Parts)) + for i, part := range artifact.Parts { + var err error + parts[i], err = PartFromProto(part) + if err != nil { + return nil, fmt.Errorf("converting artifact part %d: %w", i, err) + } + } + + metadata, err := StructToMap(artifact.Metadata) + if err != nil { + return nil, fmt.Errorf("converting artifact metadata: %w", err) + } + + return &Artifact{ + ArtifactID: artifact.ArtifactId, + Name: artifact.Name, + Description: artifact.Description, + Parts: parts, + Metadata: metadata, + Extensions: artifact.Extensions, + }, nil +} + +// PartToProto converts Part to pb.Part +func PartToProto(part Part) (*pb.Part, error) { + if part == nil { + return nil, nil + } + + pbPart := &pb.Part{} + + switch p := part.(type) { + case *TextPart: + pbPart.Part = &pb.Part_Text{Text: p.Text} + if len(p.Metadata) > 0 { + metadata, err := MapToStruct(p.Metadata) + if err != nil { + return nil, fmt.Errorf("converting text part metadata: %w", err) + } + pbPart.Metadata = metadata + } + case *FilePart: + filePart, err := FilePartToProto(p) + if err != nil { + return nil, fmt.Errorf("converting file part: %w", err) + } + pbPart.Part = &pb.Part_File{File: filePart} + if len(p.Metadata) > 0 { + metadata, err := MapToStruct(p.Metadata) + if err != nil { + return nil, fmt.Errorf("converting file part metadata: %w", err) + } + pbPart.Metadata = metadata + } + case *DataPart: + dataPart, err := DataPartToProto(p) + if err != nil { + return nil, fmt.Errorf("converting data part: %w", err) + } + pbPart.Part = &pb.Part_Data{Data: dataPart} + if len(p.Metadata) > 0 { + metadata, err := MapToStruct(p.Metadata) + if err != nil { + return nil, fmt.Errorf("converting data part metadata: %w", err) + } + pbPart.Metadata = metadata + } + default: + return nil, fmt.Errorf("unknown part type: %T", part) + } + + return pbPart, nil +} + +// PartFromProto converts pb.Part to Part +func PartFromProto(part *pb.Part) (Part, error) { + if part == nil { + return nil, nil + } + + metadata, err := StructToMap(part.Metadata) + if err != nil { + return nil, fmt.Errorf("converting part metadata: %w", err) + } + + switch p := part.Part.(type) { + case *pb.Part_Text: + return &TextPart{ + Text: p.Text, + Metadata: metadata, + }, nil + case *pb.Part_File: + filePart, err := FilePartFromProto(p.File) + if err != nil { + return nil, fmt.Errorf("converting file part: %w", err) + } + filePart.Metadata = metadata + return filePart, nil + case *pb.Part_Data: + dataPart, err := DataPartFromProto(p.Data) + if err != nil { + return nil, fmt.Errorf("converting data part: %w", err) + } + dataPart.Metadata = metadata + return dataPart, nil + default: + return nil, fmt.Errorf("unknown part type: %T", p) + } +} + +// FilePartToProto converts FilePart to pb.FilePart +func FilePartToProto(filePart *FilePart) (*pb.FilePart, error) { + if filePart == nil { + return nil, nil + } + + pbFilePart := &pb.FilePart{ + MimeType: filePart.MimeType, + Name: filePart.Name, + } + + switch content := filePart.Content.(type) { + case *FileWithURI: + pbFilePart.File = &pb.FilePart_FileWithUri{FileWithUri: content.URI} + case *FileWithBytes: + pbFilePart.File = &pb.FilePart_FileWithBytes{FileWithBytes: content.Bytes} + default: + return nil, fmt.Errorf("unknown file content type: %T", content) + } + + return pbFilePart, nil +} + +// FilePartFromProto converts pb.FilePart to FilePart +func FilePartFromProto(filePart *pb.FilePart) (*FilePart, error) { + if filePart == nil { + return nil, nil + } + + result := &FilePart{ + MimeType: filePart.MimeType, + Name: filePart.Name, + } + + switch file := filePart.File.(type) { + case *pb.FilePart_FileWithUri: + result.Content = &FileWithURI{URI: file.FileWithUri} + case *pb.FilePart_FileWithBytes: + result.Content = &FileWithBytes{Bytes: file.FileWithBytes} + default: + return nil, fmt.Errorf("unknown file type: %T", file) + } + + return result, nil +} + +// DataPartToProto converts DataPart to pb.DataPart +func DataPartToProto(dataPart *DataPart) (*pb.DataPart, error) { + if dataPart == nil { + return nil, nil + } + + data, err := MapToStruct(dataPart.Data) + if err != nil { + return nil, fmt.Errorf("converting data part data: %w", err) + } + + return &pb.DataPart{ + Data: data, + }, nil +} + +// DataPartFromProto converts pb.DataPart to DataPart +func DataPartFromProto(dataPart *pb.DataPart) (*DataPart, error) { + if dataPart == nil { + return nil, nil + } + + data, err := StructToMap(dataPart.Data) + if err != nil { + return nil, fmt.Errorf("converting data part data: %w", err) + } + + return &DataPart{ + Data: data, + }, nil +} + +// MapToStruct converts a map[string]any to structpb.Struct +func MapToStruct(m map[string]any) (*structpb.Struct, error) { + if len(m) == 0 { + return nil, nil + } + return structpb.NewStruct(m) +} + +// StructToMap converts a structpb.Struct to map[string]any +func StructToMap(s *structpb.Struct) (map[string]any, error) { + if s == nil { + return nil, nil + } + return s.AsMap(), nil +} + +// PartWrapper wraps Part interface for JSON serialization +type PartWrapper struct { + Type string `json:"type"` + Data interface{} `json:"data"` +} + +// MarshalPart marshals a Part to JSON with type information +func MarshalPart(part Part) ([]byte, error) { + if part == nil { + return []byte("null"), nil + } + + var wrapper PartWrapper + switch part.PartType() { + case PartTypeText: + wrapper = PartWrapper{Type: "text", Data: part} + case PartTypeFile: + wrapper = PartWrapper{Type: "file", Data: part} + case PartTypeData: + wrapper = PartWrapper{Type: "data", Data: part} + default: + return nil, fmt.Errorf("unknown part type: %v", part.PartType()) + } + + return json.Marshal(wrapper) +} + +// UnmarshalPart unmarshals JSON to a Part with type information +func UnmarshalPart(data []byte) (Part, error) { + if string(data) == "null" { + return nil, nil + } + + var wrapper PartWrapper + if err := json.Unmarshal(data, &wrapper); err != nil { + return nil, fmt.Errorf("failed to unmarshal part wrapper: %w", err) + } + + dataBytes, err := json.Marshal(wrapper.Data) + if err != nil { + return nil, fmt.Errorf("failed to marshal part data: %w", err) + } + + switch wrapper.Type { + case "text": + var textPart TextPart + if err := json.Unmarshal(dataBytes, &textPart); err != nil { + return nil, fmt.Errorf("failed to unmarshal text part: %w", err) + } + return &textPart, nil + case "file": + var filePart FilePart + if err := json.Unmarshal(dataBytes, &filePart); err != nil { + return nil, fmt.Errorf("failed to unmarshal file part: %w", err) + } + return &filePart, nil + case "data": + var dataPart DataPart + if err := json.Unmarshal(dataBytes, &dataPart); err != nil { + return nil, fmt.Errorf("failed to unmarshal data part: %w", err) + } + return &dataPart, nil + default: + return nil, fmt.Errorf("unknown part type: %s", wrapper.Type) + } +} + +// MessageJSON is a helper type for JSON serialization of Message +type MessageJSON struct { + MessageID string `json:"messageId"` + ContextID string `json:"contextId,omitempty"` + TaskID string `json:"taskId,omitempty"` + Role Role `json:"role"` + Parts []json.RawMessage `json:"parts"` + Kind string `json:"kind"` + Metadata map[string]any `json:"metadata,omitempty"` + Extensions []string `json:"extensions,omitempty"` +} + +// MarshalJSON implements custom JSON marshaling for Message +func (m *Message) MarshalJSON() ([]byte, error) { + if m == nil { + return []byte("null"), nil + } + + // Convert parts to JSON + parts := make([]json.RawMessage, len(m.Parts)) + for i, part := range m.Parts { + partBytes, err := MarshalPart(part) + if err != nil { + return nil, fmt.Errorf("failed to marshal part %d: %w", i, err) + } + parts[i] = json.RawMessage(partBytes) + } + + msgJSON := MessageJSON{ + MessageID: m.MessageID, + ContextID: m.ContextID, + TaskID: m.TaskID, + Role: m.Role, + Parts: parts, + Kind: m.Kind, + Metadata: m.Metadata, + Extensions: m.Extensions, + } + + return json.Marshal(msgJSON) +} + +// UnmarshalJSON implements custom JSON unmarshaling for Message +func (m *Message) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + return nil + } + + var msgJSON MessageJSON + if err := json.Unmarshal(data, &msgJSON); err != nil { + return fmt.Errorf("failed to unmarshal message: %w", err) + } + + // Convert parts from JSON + parts := make([]Part, len(msgJSON.Parts)) + for i, partData := range msgJSON.Parts { + part, err := UnmarshalPart([]byte(partData)) + if err != nil { + return fmt.Errorf("failed to unmarshal part %d: %w", i, err) + } + parts[i] = part + } + + m.MessageID = msgJSON.MessageID + m.ContextID = msgJSON.ContextID + m.TaskID = msgJSON.TaskID + m.Role = msgJSON.Role + m.Parts = parts + m.Kind = msgJSON.Kind + m.Metadata = msgJSON.Metadata + m.Extensions = msgJSON.Extensions + + return nil +} + +// ArtifactJSON is a helper type for JSON serialization of Artifact +type ArtifactJSON struct { + ArtifactID string `json:"artifactId"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Parts []json.RawMessage `json:"parts"` + Metadata map[string]any `json:"metadata,omitempty"` + Extensions []string `json:"extensions,omitempty"` +} + +// MarshalJSON implements custom JSON marshaling for Artifact +func (a *Artifact) MarshalJSON() ([]byte, error) { + if a == nil { + return []byte("null"), nil + } + + // Convert parts to JSON + parts := make([]json.RawMessage, len(a.Parts)) + for i, part := range a.Parts { + partBytes, err := MarshalPart(part) + if err != nil { + return nil, fmt.Errorf("failed to marshal part %d: %w", i, err) + } + parts[i] = json.RawMessage(partBytes) + } + + artifactJSON := ArtifactJSON{ + ArtifactID: a.ArtifactID, + Name: a.Name, + Description: a.Description, + Parts: parts, + Metadata: a.Metadata, + Extensions: a.Extensions, + } + + return json.Marshal(artifactJSON) +} + +// UnmarshalJSON implements custom JSON unmarshaling for Artifact +func (a *Artifact) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + return nil + } + + var artifactJSON ArtifactJSON + if err := json.Unmarshal(data, &artifactJSON); err != nil { + return fmt.Errorf("failed to unmarshal artifact: %w", err) + } + + // Convert parts from JSON + parts := make([]Part, len(artifactJSON.Parts)) + for i, partData := range artifactJSON.Parts { + part, err := UnmarshalPart([]byte(partData)) + if err != nil { + return fmt.Errorf("failed to unmarshal part %d: %w", i, err) + } + parts[i] = part + } + + a.ArtifactID = artifactJSON.ArtifactID + a.Name = artifactJSON.Name + a.Description = artifactJSON.Description + a.Parts = parts + a.Metadata = artifactJSON.Metadata + a.Extensions = artifactJSON.Extensions + + return nil +} diff --git a/a2a/pkg/types/enums.go b/a2a/pkg/types/enums.go new file mode 100644 index 000000000..3d0fc5455 --- /dev/null +++ b/a2a/pkg/types/enums.go @@ -0,0 +1,308 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import ( + "fmt" + + pb "seata-go-ai-a2a/pkg/proto/v1" +) + +// TaskState represents the state of a task in the A2A protocol +type TaskState int + +const ( + TaskStateUnspecified TaskState = iota + TaskStateSubmitted + TaskStateWorking + TaskStateCompleted + TaskStateFailed + TaskStateCancelled + TaskStateInputRequired + TaskStateRejected + TaskStateAuthRequired +) + +// String returns the string representation of TaskState +func (t TaskState) String() string { + switch t { + case TaskStateUnspecified: + return "TASK_STATE_UNSPECIFIED" + case TaskStateSubmitted: + return "TASK_STATE_SUBMITTED" + case TaskStateWorking: + return "TASK_STATE_WORKING" + case TaskStateCompleted: + return "TASK_STATE_COMPLETED" + case TaskStateFailed: + return "TASK_STATE_FAILED" + case TaskStateCancelled: + return "TASK_STATE_CANCELLED" + case TaskStateInputRequired: + return "TASK_STATE_INPUT_REQUIRED" + case TaskStateRejected: + return "TASK_STATE_REJECTED" + case TaskStateAuthRequired: + return "TASK_STATE_AUTH_REQUIRED" + default: + return "TASK_STATE_UNKNOWN" + } +} + +// IsTerminal returns true if the task state is terminal (no further transitions expected) +func (t TaskState) IsTerminal() bool { + switch t { + case TaskStateCompleted, TaskStateFailed, TaskStateCancelled, TaskStateRejected: + return true + default: + return false + } +} + +// IsInterrupted returns true if the task state is interrupted (waiting for input or auth) +func (t TaskState) IsInterrupted() bool { + switch t { + case TaskStateInputRequired, TaskStateAuthRequired: + return true + default: + return false + } +} + +// Role represents the role of a message sender in A2A protocol +type Role int + +const ( + RoleUnspecified Role = iota + RoleUser + RoleAgent +) + +// String returns the string representation of Role +func (r Role) String() string { + switch r { + case RoleUnspecified: + return "ROLE_UNSPECIFIED" + case RoleUser: + return "ROLE_USER" + case RoleAgent: + return "ROLE_AGENT" + default: + return "ROLE_UNKNOWN" + } +} + +// TaskStateToProto converts TaskState to pb.TaskState +func TaskStateToProto(state TaskState) pb.TaskState { + switch state { + case TaskStateUnspecified: + return pb.TaskState_TASK_STATE_UNSPECIFIED + case TaskStateSubmitted: + return pb.TaskState_TASK_STATE_SUBMITTED + case TaskStateWorking: + return pb.TaskState_TASK_STATE_WORKING + case TaskStateCompleted: + return pb.TaskState_TASK_STATE_COMPLETED + case TaskStateFailed: + return pb.TaskState_TASK_STATE_FAILED + case TaskStateCancelled: + return pb.TaskState_TASK_STATE_CANCELLED + case TaskStateInputRequired: + return pb.TaskState_TASK_STATE_INPUT_REQUIRED + case TaskStateRejected: + return pb.TaskState_TASK_STATE_REJECTED + case TaskStateAuthRequired: + return pb.TaskState_TASK_STATE_AUTH_REQUIRED + default: + return pb.TaskState_TASK_STATE_UNSPECIFIED + } +} + +// TaskStateFromProto converts pb.TaskState to TaskState +func TaskStateFromProto(state pb.TaskState) TaskState { + switch state { + case pb.TaskState_TASK_STATE_UNSPECIFIED: + return TaskStateUnspecified + case pb.TaskState_TASK_STATE_SUBMITTED: + return TaskStateSubmitted + case pb.TaskState_TASK_STATE_WORKING: + return TaskStateWorking + case pb.TaskState_TASK_STATE_COMPLETED: + return TaskStateCompleted + case pb.TaskState_TASK_STATE_FAILED: + return TaskStateFailed + case pb.TaskState_TASK_STATE_CANCELLED: + return TaskStateCancelled + case pb.TaskState_TASK_STATE_INPUT_REQUIRED: + return TaskStateInputRequired + case pb.TaskState_TASK_STATE_REJECTED: + return TaskStateRejected + case pb.TaskState_TASK_STATE_AUTH_REQUIRED: + return TaskStateAuthRequired + default: + return TaskStateUnspecified + } +} + +// ParseTaskState parses a string into a TaskState +func ParseTaskState(s string) (TaskState, error) { + switch s { + case "TASK_STATE_UNSPECIFIED": + return TaskStateUnspecified, nil + case "TASK_STATE_SUBMITTED": + return TaskStateSubmitted, nil + case "TASK_STATE_WORKING": + return TaskStateWorking, nil + case "TASK_STATE_COMPLETED": + return TaskStateCompleted, nil + case "TASK_STATE_FAILED": + return TaskStateFailed, nil + case "TASK_STATE_CANCELLED": + return TaskStateCancelled, nil + case "TASK_STATE_INPUT_REQUIRED": + return TaskStateInputRequired, nil + case "TASK_STATE_REJECTED": + return TaskStateRejected, nil + case "TASK_STATE_AUTH_REQUIRED": + return TaskStateAuthRequired, nil + default: + return TaskStateUnspecified, fmt.Errorf("unknown task state: %s", s) + } +} + +// RoleToProto converts Role to pb.Role +func RoleToProto(role Role) pb.Role { + switch role { + case RoleUnspecified: + return pb.Role_ROLE_UNSPECIFIED + case RoleUser: + return pb.Role_ROLE_USER + case RoleAgent: + return pb.Role_ROLE_AGENT + default: + return pb.Role_ROLE_UNSPECIFIED + } +} + +// RoleFromProto converts pb.Role to Role +func RoleFromProto(role pb.Role) Role { + switch role { + case pb.Role_ROLE_UNSPECIFIED: + return RoleUnspecified + case pb.Role_ROLE_USER: + return RoleUser + case pb.Role_ROLE_AGENT: + return RoleAgent + default: + return RoleUnspecified + } +} + +// A2A specific error codes as defined in the A2A specification +const ( + // TaskNotFoundError indicates that the specified task was not found + TaskNotFoundError = -32001 + // TaskNotCancelableError indicates that the task cannot be cancelled in its current state + TaskNotCancelableError = -32002 + // PushNotificationNotSupportedError indicates that push notifications are not supported + PushNotificationNotSupportedError = -32003 + // UnsupportedOperationError indicates that the requested operation is not supported + UnsupportedOperationError = -32004 + // ContentTypeNotSupportedError indicates that the content type is not supported + ContentTypeNotSupportedError = -32005 + // InvalidAgentResponseError indicates that the agent response is invalid + InvalidAgentResponseError = -32006 + // AuthenticatedExtendedCardNotConfiguredError indicates that authenticated extended card is not configured + AuthenticatedExtendedCardNotConfiguredError = -32007 +) + +// A2AError represents an A2A specific error with the defined error codes +type A2AError struct { + Code int `json:"code"` + Message string `json:"message"` + Data any `json:"data,omitempty"` +} + +// Error implements the error interface +func (e *A2AError) Error() string { + return fmt.Sprintf("A2A Error %d: %s", e.Code, e.Message) +} + +// NewTaskNotFoundError creates a new TaskNotFoundError +func NewTaskNotFoundError(taskID string) *A2AError { + return &A2AError{ + Code: TaskNotFoundError, + Message: fmt.Sprintf("Task not found: %s", taskID), + Data: map[string]string{"task_id": taskID}, + } +} + +// NewTaskNotCancelableError creates a new TaskNotCancelableError +func NewTaskNotCancelableError(taskID string, currentState TaskState) *A2AError { + return &A2AError{ + Code: TaskNotCancelableError, + Message: fmt.Sprintf("Task %s cannot be cancelled in state %s", taskID, currentState.String()), + Data: map[string]any{ + "task_id": taskID, + "current_state": currentState.String(), + }, + } +} + +// NewUnsupportedOperationError creates a new UnsupportedOperationError +func NewUnsupportedOperationError(operation string) *A2AError { + return &A2AError{ + Code: UnsupportedOperationError, + Message: fmt.Sprintf("Operation not supported: %s", operation), + Data: map[string]string{"operation": operation}, + } +} + +// NewContentTypeNotSupportedError creates a new ContentTypeNotSupportedError +func NewContentTypeNotSupportedError(contentType string) *A2AError { + return &A2AError{ + Code: ContentTypeNotSupportedError, + Message: fmt.Sprintf("Content type not supported: %s", contentType), + Data: map[string]string{"content_type": contentType}, + } +} + +// NewInvalidAgentResponseError creates a new InvalidAgentResponseError +func NewInvalidAgentResponseError(reason string) *A2AError { + return &A2AError{ + Code: InvalidAgentResponseError, + Message: fmt.Sprintf("Invalid agent response: %s", reason), + Data: map[string]string{"reason": reason}, + } +} + +// NewPushNotificationNotSupportedError creates a new PushNotificationNotSupportedError +func NewPushNotificationNotSupportedError() *A2AError { + return &A2AError{ + Code: PushNotificationNotSupportedError, + Message: "Push notifications not supported", + } +} + +// NewAuthenticatedExtendedCardNotConfiguredError creates a new AuthenticatedExtendedCardNotConfiguredError +func NewAuthenticatedExtendedCardNotConfiguredError() *A2AError { + return &A2AError{ + Code: AuthenticatedExtendedCardNotConfiguredError, + Message: "Authenticated extended card not configured", + } +} diff --git a/a2a/pkg/types/events.go b/a2a/pkg/types/events.go new file mode 100644 index 000000000..26e841c31 --- /dev/null +++ b/a2a/pkg/types/events.go @@ -0,0 +1,272 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import ( + "fmt" + + pb "seata-go-ai-a2a/pkg/proto/v1" +) + +// TaskStatusUpdateEvent represents a delta event on a task indicating that a task has changed +type TaskStatusUpdateEvent struct { + TaskID string `json:"taskId"` + ContextID string `json:"contextId"` + Status *TaskStatus `json:"status"` + Final bool `json:"final"` + Metadata map[string]any `json:"metadata,omitempty"` +} + +// TaskArtifactUpdateEvent represents a task delta where an artifact has been generated +type TaskArtifactUpdateEvent struct { + TaskID string `json:"taskId"` + ContextID string `json:"contextId"` + Artifact *Artifact `json:"artifact"` + Append bool `json:"append"` + LastChunk bool `json:"lastChunk"` + Metadata map[string]any `json:"metadata,omitempty"` +} + +// StreamResponse represents the different types of responses in a stream +type StreamResponse interface { + StreamResponseType() StreamResponseTypeEnum +} + +// StreamResponseTypeEnum identifies the type of stream response +type StreamResponseTypeEnum int + +const ( + StreamResponseTypeTask StreamResponseTypeEnum = iota + StreamResponseTypeMessage + StreamResponseTypeStatusUpdate + StreamResponseTypeArtifactUpdate +) + +// TaskStreamResponse represents a task in a stream response +type TaskStreamResponse struct { + Task *Task `json:"task"` +} + +func (t *TaskStreamResponse) StreamResponseType() StreamResponseTypeEnum { + return StreamResponseTypeTask +} + +// MessageStreamResponse represents a message in a stream response +type MessageStreamResponse struct { + Message *Message `json:"message"` +} + +func (m *MessageStreamResponse) StreamResponseType() StreamResponseTypeEnum { + return StreamResponseTypeMessage +} + +// StatusUpdateStreamResponse represents a status update in a stream response +type StatusUpdateStreamResponse struct { + StatusUpdate *TaskStatusUpdateEvent `json:"status_update"` +} + +func (s *StatusUpdateStreamResponse) StreamResponseType() StreamResponseTypeEnum { + return StreamResponseTypeStatusUpdate +} + +// ArtifactUpdateStreamResponse represents an artifact update in a stream response +type ArtifactUpdateStreamResponse struct { + ArtifactUpdate *TaskArtifactUpdateEvent `json:"artifact_update"` +} + +func (a *ArtifactUpdateStreamResponse) StreamResponseType() StreamResponseTypeEnum { + return StreamResponseTypeArtifactUpdate +} + +// TaskStatusUpdateEventToProto converts TaskStatusUpdateEvent to pb.TaskStatusUpdateEvent +func TaskStatusUpdateEventToProto(event *TaskStatusUpdateEvent) (*pb.TaskStatusUpdateEvent, error) { + if event == nil { + return nil, nil + } + + status, err := TaskStatusToProto(event.Status) + if err != nil { + return nil, fmt.Errorf("converting task status: %w", err) + } + + metadata, err := MapToStruct(event.Metadata) + if err != nil { + return nil, fmt.Errorf("converting event metadata: %w", err) + } + + return &pb.TaskStatusUpdateEvent{ + TaskId: event.TaskID, + ContextId: event.ContextID, + Status: status, + Final: event.Final, + Metadata: metadata, + }, nil +} + +// TaskStatusUpdateEventFromProto converts pb.TaskStatusUpdateEvent to TaskStatusUpdateEvent +func TaskStatusUpdateEventFromProto(event *pb.TaskStatusUpdateEvent) (*TaskStatusUpdateEvent, error) { + if event == nil { + return nil, nil + } + + status, err := TaskStatusFromProto(event.Status) + if err != nil { + return nil, fmt.Errorf("converting task status: %w", err) + } + + metadata, err := StructToMap(event.Metadata) + if err != nil { + return nil, fmt.Errorf("converting event metadata: %w", err) + } + + return &TaskStatusUpdateEvent{ + TaskID: event.TaskId, + ContextID: event.ContextId, + Status: status, + Final: event.Final, + Metadata: metadata, + }, nil +} + +// TaskArtifactUpdateEventToProto converts TaskArtifactUpdateEvent to pb.TaskArtifactUpdateEvent +func TaskArtifactUpdateEventToProto(event *TaskArtifactUpdateEvent) (*pb.TaskArtifactUpdateEvent, error) { + if event == nil { + return nil, nil + } + + artifact, err := ArtifactToProto(event.Artifact) + if err != nil { + return nil, fmt.Errorf("converting artifact: %w", err) + } + + metadata, err := MapToStruct(event.Metadata) + if err != nil { + return nil, fmt.Errorf("converting event metadata: %w", err) + } + + return &pb.TaskArtifactUpdateEvent{ + TaskId: event.TaskID, + ContextId: event.ContextID, + Artifact: artifact, + Append: event.Append, + LastChunk: event.LastChunk, + Metadata: metadata, + }, nil +} + +// TaskArtifactUpdateEventFromProto converts pb.TaskArtifactUpdateEvent to TaskArtifactUpdateEvent +func TaskArtifactUpdateEventFromProto(event *pb.TaskArtifactUpdateEvent) (*TaskArtifactUpdateEvent, error) { + if event == nil { + return nil, nil + } + + artifact, err := ArtifactFromProto(event.Artifact) + if err != nil { + return nil, fmt.Errorf("converting artifact: %w", err) + } + + metadata, err := StructToMap(event.Metadata) + if err != nil { + return nil, fmt.Errorf("converting event metadata: %w", err) + } + + return &TaskArtifactUpdateEvent{ + TaskID: event.TaskId, + ContextID: event.ContextId, + Artifact: artifact, + Append: event.Append, + LastChunk: event.LastChunk, + Metadata: metadata, + }, nil +} + +// StreamResponseToProto converts StreamResponse to pb.StreamResponse +func StreamResponseToProto(response StreamResponse) (*pb.StreamResponse, error) { + if response == nil { + return nil, nil + } + + pbResponse := &pb.StreamResponse{} + + switch r := response.(type) { + case *TaskStreamResponse: + task, err := TaskToProto(r.Task) + if err != nil { + return nil, fmt.Errorf("converting task stream response: %w", err) + } + pbResponse.Payload = &pb.StreamResponse_Task{Task: task} + case *MessageStreamResponse: + msg, err := MessageToProto(r.Message) + if err != nil { + return nil, fmt.Errorf("converting message stream response: %w", err) + } + pbResponse.Payload = &pb.StreamResponse_Msg{Msg: msg} + case *StatusUpdateStreamResponse: + statusUpdate, err := TaskStatusUpdateEventToProto(r.StatusUpdate) + if err != nil { + return nil, fmt.Errorf("converting status update stream response: %w", err) + } + pbResponse.Payload = &pb.StreamResponse_StatusUpdate{StatusUpdate: statusUpdate} + case *ArtifactUpdateStreamResponse: + artifactUpdate, err := TaskArtifactUpdateEventToProto(r.ArtifactUpdate) + if err != nil { + return nil, fmt.Errorf("converting artifact update stream response: %w", err) + } + pbResponse.Payload = &pb.StreamResponse_ArtifactUpdate{ArtifactUpdate: artifactUpdate} + default: + return nil, fmt.Errorf("unknown stream response type: %T", response) + } + + return pbResponse, nil +} + +// StreamResponseFromProto converts pb.StreamResponse to StreamResponse +func StreamResponseFromProto(response *pb.StreamResponse) (StreamResponse, error) { + if response == nil { + return nil, nil + } + + switch payload := response.Payload.(type) { + case *pb.StreamResponse_Task: + task, err := TaskFromProto(payload.Task) + if err != nil { + return nil, fmt.Errorf("converting task stream response: %w", err) + } + return &TaskStreamResponse{Task: task}, nil + case *pb.StreamResponse_Msg: + msg, err := MessageFromProto(payload.Msg) + if err != nil { + return nil, fmt.Errorf("converting message stream response: %w", err) + } + return &MessageStreamResponse{Message: msg}, nil + case *pb.StreamResponse_StatusUpdate: + statusUpdate, err := TaskStatusUpdateEventFromProto(payload.StatusUpdate) + if err != nil { + return nil, fmt.Errorf("converting status update stream response: %w", err) + } + return &StatusUpdateStreamResponse{StatusUpdate: statusUpdate}, nil + case *pb.StreamResponse_ArtifactUpdate: + artifactUpdate, err := TaskArtifactUpdateEventFromProto(payload.ArtifactUpdate) + if err != nil { + return nil, fmt.Errorf("converting artifact update stream response: %w", err) + } + return &ArtifactUpdateStreamResponse{ArtifactUpdate: artifactUpdate}, nil + default: + return nil, fmt.Errorf("unknown stream response payload type: %T", payload) + } +} diff --git a/a2a/pkg/types/http_utils.go b/a2a/pkg/types/http_utils.go new file mode 100644 index 000000000..ff0c0586b --- /dev/null +++ b/a2a/pkg/types/http_utils.go @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import ( + "encoding/json" + "net/http" +) + +// WriteJSONResponse writes a JSON response with appropriate headers +func WriteJSONResponse(w http.ResponseWriter, statusCode int, data interface{}) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + return json.NewEncoder(w).Encode(data) +} + +// WriteErrorResponse writes a standardized error response +func WriteErrorResponse(w http.ResponseWriter, statusCode int, code, message, details string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + errorResponse := map[string]interface{}{ + "error": map[string]interface{}{ + "code": code, + "message": message, + }, + } + + if details != "" { + errorResponse["error"].(map[string]interface{})["details"] = details + } + + json.NewEncoder(w).Encode(errorResponse) +} + +// WriteA2AErrorResponse writes an A2A-specific error response +func WriteA2AErrorResponse(w http.ResponseWriter, statusCode int, a2aError *A2AError) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + errorResponse := map[string]interface{}{ + "error": map[string]interface{}{ + "code": a2aError.Code, + "message": a2aError.Message, + }, + } + + if a2aError.Data != nil { + errorResponse["error"].(map[string]interface{})["data"] = a2aError.Data + } + + json.NewEncoder(w).Encode(errorResponse) +} diff --git a/pkg/datasource/sql/types/dbtype_string.go b/a2a/pkg/types/interfaces.go similarity index 52% rename from pkg/datasource/sql/types/dbtype_string.go rename to a2a/pkg/types/interfaces.go index ea9c08312..9147ae066 100644 --- a/pkg/datasource/sql/types/dbtype_string.go +++ b/a2a/pkg/types/interfaces.go @@ -15,31 +15,17 @@ * limitations under the License. */ -// Code generated by "stringer -type=DBType"; DO NOT EDIT. - package types -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[DBTypeUnknown-1] - _ = x[DBTypeMySQL-2] - _ = x[DBTypePostgreSQL-3] - _ = x[DBTypeSQLServer-4] - _ = x[DBTypeOracle-5] -} - -const _DBType_name = "DBTypeUnknownDBTypeMySQLDBTypePostgreSQLDBTypeSQLServerDBTypeOracle" - -var _DBType_index = [...]uint8{0, 13, 24, 40, 55, 67} +import "context" -func (i DBType) String() string { - i -= 1 - if i < 0 || i >= DBType(len(_DBType_index)-1) { - return "DBType(" + strconv.FormatInt(int64(i+1), 10) + ")" - } - return _DBType_name[_DBType_index[i]:_DBType_index[i+1]] +// TaskManagerInterface defines the methods needed by transport layers to interact with tasks +type TaskManagerInterface interface { + CreateTask(ctx context.Context, contextID string, message *Message, metadata map[string]any) (*Task, error) + GetTask(ctx context.Context, taskID string) (*Task, error) + CancelTask(ctx context.Context, taskID string) (*Task, error) + ListTasks(ctx context.Context, req *ListTasksRequest) (*ListTasksResponse, error) + UpdateTaskStatus(ctx context.Context, taskID string, newState TaskState, updateMessage *Message) error + AddArtifact(ctx context.Context, taskID string, artifact *Artifact, append, lastChunk bool) error + AddMessage(ctx context.Context, taskID string, message *Message) error } diff --git a/a2a/pkg/types/logger.go b/a2a/pkg/types/logger.go new file mode 100644 index 000000000..476bf878a --- /dev/null +++ b/a2a/pkg/types/logger.go @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import "context" + +// Logger provides structured logging interface for A2A framework +type Logger interface { + // Error logs error level messages + Error(ctx context.Context, msg string, fields ...interface{}) + + // Warn logs warning level messages + Warn(ctx context.Context, msg string, fields ...interface{}) + + // Info logs info level messages + Info(ctx context.Context, msg string, fields ...interface{}) + + // Debug logs debug level messages + Debug(ctx context.Context, msg string, fields ...interface{}) +} + +// LogField represents a structured log field +type LogField struct { + Key string + Value interface{} +} + +// WithFields creates log fields from key-value pairs +func WithFields(keyValues ...interface{}) []interface{} { + return keyValues +} + +// NoOpLogger is a logger that does nothing +type NoOpLogger struct{} + +func (l *NoOpLogger) Error(ctx context.Context, msg string, fields ...interface{}) {} +func (l *NoOpLogger) Warn(ctx context.Context, msg string, fields ...interface{}) {} +func (l *NoOpLogger) Info(ctx context.Context, msg string, fields ...interface{}) {} +func (l *NoOpLogger) Debug(ctx context.Context, msg string, fields ...interface{}) {} + +// DefaultLogger is a simple console logger implementation +type DefaultLogger struct{} + +func (l *DefaultLogger) Error(ctx context.Context, msg string, fields ...interface{}) { + l.log("ERROR", msg, fields...) +} + +func (l *DefaultLogger) Warn(ctx context.Context, msg string, fields ...interface{}) { + l.log("WARN", msg, fields...) +} + +func (l *DefaultLogger) Info(ctx context.Context, msg string, fields ...interface{}) { + l.log("INFO", msg, fields...) +} + +func (l *DefaultLogger) Debug(ctx context.Context, msg string, fields ...interface{}) { + l.log("DEBUG", msg, fields...) +} + +func (l *DefaultLogger) log(level, msg string, fields ...interface{}) { + // Simple structured logging format + output := level + ": " + msg + + if len(fields) > 0 { + output += " [" + for i := 0; i < len(fields); i += 2 { + if i+1 < len(fields) { + if i > 0 { + output += ", " + } + output += fields[i].(string) + "=" + formatValue(fields[i+1]) + } + } + output += "]" + } + + println(output) +} + +func formatValue(v interface{}) string { + switch val := v.(type) { + case string: + return val + case error: + return val.Error() + default: + return "?" + } +} diff --git a/a2a/pkg/types/requests.go b/a2a/pkg/types/requests.go new file mode 100644 index 000000000..05010bb36 --- /dev/null +++ b/a2a/pkg/types/requests.go @@ -0,0 +1,376 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import ( + "fmt" + + pb "seata-go-ai-a2a/pkg/proto/v1" +) + +// SendMessageRequest represents a request to send a message to an agent +type SendMessageRequest struct { + Request *Message `json:"message"` + Configuration *SendMessageConfiguration `json:"configuration,omitempty"` + Metadata map[string]any `json:"metadata,omitempty"` +} + +// GetTaskRequest represents a request to get task information +type GetTaskRequest struct { + Name string `json:"name"` // Format: tasks/{task_id} + HistoryLength int32 `json:"history_length,omitempty"` +} + +// CancelTaskRequest represents a request to cancel a task +type CancelTaskRequest struct { + Name string `json:"name"` // Format: tasks/{task_id} +} + +// GetTaskPushNotificationConfigRequest represents a request to get push notification config +type GetTaskPushNotificationConfigRequest struct { + Name string `json:"name"` // Format: tasks/{task_id}/pushNotificationConfigs/{config_id} +} + +// DeleteTaskPushNotificationConfigRequest represents a request to delete push notification config +type DeleteTaskPushNotificationConfigRequest struct { + Name string `json:"name"` // Format: tasks/{task_id}/pushNotificationConfigs/{config_id} +} + +// CreateTaskPushNotificationConfigRequest represents a request to create push notification config +type CreateTaskPushNotificationConfigRequest struct { + Parent string `json:"parent"` // Format: tasks/{task_id} + ConfigID string `json:"config_id"` + Config *TaskPushNotificationConfig `json:"config"` +} + +// TaskSubscriptionRequest represents a request to subscribe to task updates +type TaskSubscriptionRequest struct { + Name string `json:"name"` // Format: tasks/{task_id} +} + +// ListTaskPushNotificationConfigRequest represents a request to list push notification configs +type ListTaskPushNotificationConfigRequest struct { + Parent string `json:"parent"` // Format: tasks/{task_id} + PageSize int32 `json:"page_size,omitempty"` + PageToken string `json:"page_token,omitempty"` +} + +// GetAgentCardRequest represents a request to get agent card information +type GetAgentCardRequest struct { + // Empty request - no fields needed +} + +// ListTasksRequest represents a request to list tasks +type ListTasksRequest struct { + ContextID string `json:"contextId,omitempty"` + States []TaskState `json:"states,omitempty"` + PageSize int32 `json:"pageSize,omitempty"` + PageToken string `json:"pageToken,omitempty"` +} + +// SendMessageRequestToProto converts SendMessageRequest to pb.SendMessageRequest +func SendMessageRequestToProto(req *SendMessageRequest) (*pb.SendMessageRequest, error) { + if req == nil { + return nil, nil + } + + message, err := MessageToProto(req.Request) + if err != nil { + return nil, fmt.Errorf("converting request message: %w", err) + } + + var config *pb.SendMessageConfiguration + if req.Configuration != nil { + config, err = SendMessageConfigurationToProto(req.Configuration) + if err != nil { + return nil, fmt.Errorf("converting configuration: %w", err) + } + } + + metadata, err := MapToStruct(req.Metadata) + if err != nil { + return nil, fmt.Errorf("converting metadata: %w", err) + } + + return &pb.SendMessageRequest{ + Request: message, + Configuration: config, + Metadata: metadata, + }, nil +} + +// SendMessageRequestFromProto converts pb.SendMessageRequest to SendMessageRequest +func SendMessageRequestFromProto(req *pb.SendMessageRequest) (*SendMessageRequest, error) { + if req == nil { + return nil, nil + } + + message, err := MessageFromProto(req.Request) + if err != nil { + return nil, fmt.Errorf("converting request message: %w", err) + } + + var config *SendMessageConfiguration + if req.Configuration != nil { + config, err = SendMessageConfigurationFromProto(req.Configuration) + if err != nil { + return nil, fmt.Errorf("converting configuration: %w", err) + } + } + + metadata, err := StructToMap(req.Metadata) + if err != nil { + return nil, fmt.Errorf("converting metadata: %w", err) + } + + return &SendMessageRequest{ + Request: message, + Configuration: config, + Metadata: metadata, + }, nil +} + +// GetTaskRequestToProto converts GetTaskRequest to pb.GetTaskRequest +func GetTaskRequestToProto(req *GetTaskRequest) *pb.GetTaskRequest { + if req == nil { + return nil + } + + return &pb.GetTaskRequest{ + Name: req.Name, + HistoryLength: req.HistoryLength, + } +} + +// GetTaskRequestFromProto converts pb.GetTaskRequest to GetTaskRequest +func GetTaskRequestFromProto(req *pb.GetTaskRequest) *GetTaskRequest { + if req == nil { + return nil + } + + return &GetTaskRequest{ + Name: req.Name, + HistoryLength: req.HistoryLength, + } +} + +// CancelTaskRequestToProto converts CancelTaskRequest to pb.CancelTaskRequest +func CancelTaskRequestToProto(req *CancelTaskRequest) *pb.CancelTaskRequest { + if req == nil { + return nil + } + + return &pb.CancelTaskRequest{ + Name: req.Name, + } +} + +// CancelTaskRequestFromProto converts pb.CancelTaskRequest to CancelTaskRequest +func CancelTaskRequestFromProto(req *pb.CancelTaskRequest) *CancelTaskRequest { + if req == nil { + return nil + } + + return &CancelTaskRequest{ + Name: req.Name, + } +} + +// GetTaskPushNotificationConfigRequestToProto converts GetTaskPushNotificationConfigRequest to pb.GetTaskPushNotificationConfigRequest +func GetTaskPushNotificationConfigRequestToProto(req *GetTaskPushNotificationConfigRequest) *pb.GetTaskPushNotificationConfigRequest { + if req == nil { + return nil + } + + return &pb.GetTaskPushNotificationConfigRequest{ + Name: req.Name, + } +} + +// GetTaskPushNotificationConfigRequestFromProto converts pb.GetTaskPushNotificationConfigRequest to GetTaskPushNotificationConfigRequest +func GetTaskPushNotificationConfigRequestFromProto(req *pb.GetTaskPushNotificationConfigRequest) *GetTaskPushNotificationConfigRequest { + if req == nil { + return nil + } + + return &GetTaskPushNotificationConfigRequest{ + Name: req.Name, + } +} + +// DeleteTaskPushNotificationConfigRequestToProto converts DeleteTaskPushNotificationConfigRequest to pb.DeleteTaskPushNotificationConfigRequest +func DeleteTaskPushNotificationConfigRequestToProto(req *DeleteTaskPushNotificationConfigRequest) *pb.DeleteTaskPushNotificationConfigRequest { + if req == nil { + return nil + } + + return &pb.DeleteTaskPushNotificationConfigRequest{ + Name: req.Name, + } +} + +// DeleteTaskPushNotificationConfigRequestFromProto converts pb.DeleteTaskPushNotificationConfigRequest to DeleteTaskPushNotificationConfigRequest +func DeleteTaskPushNotificationConfigRequestFromProto(req *pb.DeleteTaskPushNotificationConfigRequest) *DeleteTaskPushNotificationConfigRequest { + if req == nil { + return nil + } + + return &DeleteTaskPushNotificationConfigRequest{ + Name: req.Name, + } +} + +// CreateTaskPushNotificationConfigRequestToProto converts CreateTaskPushNotificationConfigRequest to pb.CreateTaskPushNotificationConfigRequest +func CreateTaskPushNotificationConfigRequestToProto(req *CreateTaskPushNotificationConfigRequest) (*pb.CreateTaskPushNotificationConfigRequest, error) { + if req == nil { + return nil, nil + } + + config, err := TaskPushNotificationConfigToProto(req.Config) + if err != nil { + return nil, fmt.Errorf("converting config: %w", err) + } + + return &pb.CreateTaskPushNotificationConfigRequest{ + Parent: req.Parent, + ConfigId: req.ConfigID, + Config: config, + }, nil +} + +// CreateTaskPushNotificationConfigRequestFromProto converts pb.CreateTaskPushNotificationConfigRequest to CreateTaskPushNotificationConfigRequest +func CreateTaskPushNotificationConfigRequestFromProto(req *pb.CreateTaskPushNotificationConfigRequest) (*CreateTaskPushNotificationConfigRequest, error) { + if req == nil { + return nil, nil + } + + config, err := TaskPushNotificationConfigFromProto(req.Config) + if err != nil { + return nil, fmt.Errorf("converting config: %w", err) + } + + return &CreateTaskPushNotificationConfigRequest{ + Parent: req.Parent, + ConfigID: req.ConfigId, + Config: config, + }, nil +} + +// TaskSubscriptionRequestToProto converts TaskSubscriptionRequest to pb.TaskSubscriptionRequest +func TaskSubscriptionRequestToProto(req *TaskSubscriptionRequest) *pb.TaskSubscriptionRequest { + if req == nil { + return nil + } + + return &pb.TaskSubscriptionRequest{ + Name: req.Name, + } +} + +// TaskSubscriptionRequestFromProto converts pb.TaskSubscriptionRequest to TaskSubscriptionRequest +func TaskSubscriptionRequestFromProto(req *pb.TaskSubscriptionRequest) *TaskSubscriptionRequest { + if req == nil { + return nil + } + + return &TaskSubscriptionRequest{ + Name: req.Name, + } +} + +// ListTaskPushNotificationConfigRequestToProto converts ListTaskPushNotificationConfigRequest to pb.ListTaskPushNotificationConfigRequest +func ListTaskPushNotificationConfigRequestToProto(req *ListTaskPushNotificationConfigRequest) *pb.ListTaskPushNotificationConfigRequest { + if req == nil { + return nil + } + + return &pb.ListTaskPushNotificationConfigRequest{ + Parent: req.Parent, + PageSize: req.PageSize, + PageToken: req.PageToken, + } +} + +// ListTaskPushNotificationConfigRequestFromProto converts pb.ListTaskPushNotificationConfigRequest to ListTaskPushNotificationConfigRequest +func ListTaskPushNotificationConfigRequestFromProto(req *pb.ListTaskPushNotificationConfigRequest) *ListTaskPushNotificationConfigRequest { + if req == nil { + return nil + } + + return &ListTaskPushNotificationConfigRequest{ + Parent: req.Parent, + PageSize: req.PageSize, + PageToken: req.PageToken, + } +} + +// GetAgentCardRequestToProto converts GetAgentCardRequest to pb.GetAgentCardRequest +func GetAgentCardRequestToProto(req *GetAgentCardRequest) *pb.GetAgentCardRequest { + if req == nil { + return nil + } + + return &pb.GetAgentCardRequest{} +} + +// GetAgentCardRequestFromProto converts pb.GetAgentCardRequest to GetAgentCardRequest +func GetAgentCardRequestFromProto(req *pb.GetAgentCardRequest) *GetAgentCardRequest { + if req == nil { + return nil + } + + return &GetAgentCardRequest{} +} + +// ListTasksRequestToProto converts ListTasksRequest to pb.ListTasksRequest +func ListTasksRequestToProto(req *ListTasksRequest) *pb.ListTasksRequest { + if req == nil { + return nil + } + + states := make([]pb.TaskState, len(req.States)) + for i, state := range req.States { + states[i] = TaskStateToProto(state) + } + + return &pb.ListTasksRequest{ + ContextId: req.ContextID, + States: states, + PageSize: req.PageSize, + PageToken: req.PageToken, + } +} + +// ListTasksRequestFromProto converts pb.ListTasksRequest to ListTasksRequest +func ListTasksRequestFromProto(req *pb.ListTasksRequest) *ListTasksRequest { + if req == nil { + return nil + } + + states := make([]TaskState, len(req.States)) + for i, state := range req.States { + states[i] = TaskStateFromProto(state) + } + + return &ListTasksRequest{ + ContextID: req.ContextId, + States: states, + PageSize: req.PageSize, + PageToken: req.PageToken, + } +} diff --git a/a2a/pkg/types/responses.go b/a2a/pkg/types/responses.go new file mode 100644 index 000000000..e4018e037 --- /dev/null +++ b/a2a/pkg/types/responses.go @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import ( + "fmt" + + pb "seata-go-ai-a2a/pkg/proto/v1" + + "google.golang.org/protobuf/types/known/emptypb" +) + +// SendMessageResponse represents a response to a send message request +type SendMessageResponse interface { + SendMessageResponseType() SendMessageResponseTypeEnum +} + +// SendMessageResponseTypeEnum identifies the type of send message response +type SendMessageResponseTypeEnum int + +const ( + SendMessageResponseTypeTask SendMessageResponseTypeEnum = iota + SendMessageResponseTypeMessage +) + +// TaskSendMessageResponse represents a task response to a send message request +type TaskSendMessageResponse struct { + Task *Task `json:"task"` +} + +func (t *TaskSendMessageResponse) SendMessageResponseType() SendMessageResponseTypeEnum { + return SendMessageResponseTypeTask +} + +// MessageSendMessageResponse represents a message response to a send message request +type MessageSendMessageResponse struct { + Message *Message `json:"message"` +} + +func (m *MessageSendMessageResponse) SendMessageResponseType() SendMessageResponseTypeEnum { + return SendMessageResponseTypeMessage +} + +// ListTaskPushNotificationConfigResponse represents a response to list push notification configs +type ListTaskPushNotificationConfigResponse struct { + Configs []*TaskPushNotificationConfig `json:"configs"` + NextPageToken string `json:"next_page_token,omitempty"` +} + +// DeleteTaskPushNotificationConfigResponse represents a response to delete push notification config +type DeleteTaskPushNotificationConfigResponse struct { + // Empty response +} + +// ListTasksResponse represents a response to list tasks request +type ListTasksResponse struct { + Tasks []*Task `json:"tasks"` + NextPageToken string `json:"nextPageToken,omitempty"` +} + +// SendMessageResponseToProto converts SendMessageResponse to pb.SendMessageResponse +func SendMessageResponseToProto(response SendMessageResponse) (*pb.SendMessageResponse, error) { + if response == nil { + return nil, nil + } + + pbResponse := &pb.SendMessageResponse{} + + switch r := response.(type) { + case *TaskSendMessageResponse: + task, err := TaskToProto(r.Task) + if err != nil { + return nil, fmt.Errorf("converting task response: %w", err) + } + pbResponse.Payload = &pb.SendMessageResponse_Task{Task: task} + case *MessageSendMessageResponse: + msg, err := MessageToProto(r.Message) + if err != nil { + return nil, fmt.Errorf("converting message response: %w", err) + } + pbResponse.Payload = &pb.SendMessageResponse_Msg{Msg: msg} + default: + return nil, fmt.Errorf("unknown send message response type: %T", response) + } + + return pbResponse, nil +} + +// SendMessageResponseFromProto converts pb.SendMessageResponse to SendMessageResponse +func SendMessageResponseFromProto(response *pb.SendMessageResponse) (SendMessageResponse, error) { + if response == nil { + return nil, nil + } + + switch payload := response.Payload.(type) { + case *pb.SendMessageResponse_Task: + task, err := TaskFromProto(payload.Task) + if err != nil { + return nil, fmt.Errorf("converting task response: %w", err) + } + return &TaskSendMessageResponse{Task: task}, nil + case *pb.SendMessageResponse_Msg: + msg, err := MessageFromProto(payload.Msg) + if err != nil { + return nil, fmt.Errorf("converting message response: %w", err) + } + return &MessageSendMessageResponse{Message: msg}, nil + default: + return nil, fmt.Errorf("unknown send message response payload type: %T", payload) + } +} + +// ListTaskPushNotificationConfigResponseToProto converts ListTaskPushNotificationConfigResponse to pb.ListTaskPushNotificationConfigResponse +func ListTaskPushNotificationConfigResponseToProto(response *ListTaskPushNotificationConfigResponse) (*pb.ListTaskPushNotificationConfigResponse, error) { + if response == nil { + return nil, nil + } + + configs := make([]*pb.TaskPushNotificationConfig, len(response.Configs)) + for i, config := range response.Configs { + var err error + configs[i], err = TaskPushNotificationConfigToProto(config) + if err != nil { + return nil, fmt.Errorf("converting config %d: %w", i, err) + } + } + + return &pb.ListTaskPushNotificationConfigResponse{ + Configs: configs, + NextPageToken: response.NextPageToken, + }, nil +} + +// ListTaskPushNotificationConfigResponseFromProto converts pb.ListTaskPushNotificationConfigResponse to ListTaskPushNotificationConfigResponse +func ListTaskPushNotificationConfigResponseFromProto(response *pb.ListTaskPushNotificationConfigResponse) (*ListTaskPushNotificationConfigResponse, error) { + if response == nil { + return nil, nil + } + + configs := make([]*TaskPushNotificationConfig, len(response.Configs)) + for i, config := range response.Configs { + var err error + configs[i], err = TaskPushNotificationConfigFromProto(config) + if err != nil { + return nil, fmt.Errorf("converting config %d: %w", i, err) + } + } + + return &ListTaskPushNotificationConfigResponse{ + Configs: configs, + NextPageToken: response.NextPageToken, + }, nil +} + +// DeleteTaskPushNotificationConfigResponseToProto converts DeleteTaskPushNotificationConfigResponse to emptypb.Empty +func DeleteTaskPushNotificationConfigResponseToProto(response *DeleteTaskPushNotificationConfigResponse) *emptypb.Empty { + return &emptypb.Empty{} +} + +// DeleteTaskPushNotificationConfigResponseFromProto converts emptypb.Empty to DeleteTaskPushNotificationConfigResponse +func DeleteTaskPushNotificationConfigResponseFromProto(response *emptypb.Empty) *DeleteTaskPushNotificationConfigResponse { + return &DeleteTaskPushNotificationConfigResponse{} +} + +// ListTasksResponseToProto converts ListTasksResponse to pb.ListTasksResponse +func ListTasksResponseToProto(response *ListTasksResponse) (*pb.ListTasksResponse, error) { + if response == nil { + return nil, nil + } + + tasks := make([]*pb.Task, len(response.Tasks)) + for i, task := range response.Tasks { + var err error + tasks[i], err = TaskToProto(task) + if err != nil { + return nil, fmt.Errorf("converting task %d: %w", i, err) + } + } + + return &pb.ListTasksResponse{ + Tasks: tasks, + NextPageToken: response.NextPageToken, + }, nil +} + +// ListTasksResponseFromProto converts pb.ListTasksResponse to ListTasksResponse +func ListTasksResponseFromProto(response *pb.ListTasksResponse) (*ListTasksResponse, error) { + if response == nil { + return nil, nil + } + + tasks := make([]*Task, len(response.Tasks)) + for i, task := range response.Tasks { + var err error + tasks[i], err = TaskFromProto(task) + if err != nil { + return nil, fmt.Errorf("converting task %d: %w", i, err) + } + } + + return &ListTasksResponse{ + Tasks: tasks, + NextPageToken: response.NextPageToken, + }, nil +} diff --git a/a2a/pkg/types/security.go b/a2a/pkg/types/security.go new file mode 100644 index 000000000..9fd142a08 --- /dev/null +++ b/a2a/pkg/types/security.go @@ -0,0 +1,383 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import ( + "fmt" + + pb "seata-go-ai-a2a/pkg/proto/v1" +) + +// Security represents security requirements for contacting the agent +type Security struct { + Schemes map[string][]string `json:"schemes"` +} + +// SecurityScheme represents different types of security schemes +type SecurityScheme interface { + SecuritySchemeType() SecuritySchemeTypeEnum +} + +// SecuritySchemeTypeEnum identifies the type of security scheme +type SecuritySchemeTypeEnum int + +const ( + SecuritySchemeTypeAPIKey SecuritySchemeTypeEnum = iota + SecuritySchemeTypeHTTPAuth + SecuritySchemeTypeOAuth2 + SecuritySchemeTypeOpenIDConnect + SecuritySchemeTypeMutualTLS +) + +// APIKeySecurityScheme represents API key based authentication +type APIKeySecurityScheme struct { + Description string `json:"description,omitempty"` + Location string `json:"location"` // "query", "header", or "cookie" + Name string `json:"name"` +} + +func (a *APIKeySecurityScheme) SecuritySchemeType() SecuritySchemeTypeEnum { + return SecuritySchemeTypeAPIKey +} + +// HTTPAuthSecurityScheme represents HTTP authentication +type HTTPAuthSecurityScheme struct { + Description string `json:"description,omitempty"` + Scheme string `json:"scheme"` // HTTP Authentication scheme + BearerFormat string `json:"bearer_format,omitempty"` // Bearer token format hint +} + +func (h *HTTPAuthSecurityScheme) SecuritySchemeType() SecuritySchemeTypeEnum { + return SecuritySchemeTypeHTTPAuth +} + +// OAuth2SecurityScheme represents OAuth 2.0 authentication +type OAuth2SecurityScheme struct { + Description string `json:"description,omitempty"` + Flows OAuthFlows `json:"flows,omitempty"` + OAuth2MetadataURL string `json:"oauth2_metadata_url,omitempty"` +} + +func (o *OAuth2SecurityScheme) SecuritySchemeType() SecuritySchemeTypeEnum { + return SecuritySchemeTypeOAuth2 +} + +// OpenIDConnectSecurityScheme represents OpenID Connect authentication +type OpenIDConnectSecurityScheme struct { + Description string `json:"description,omitempty"` + OpenIDConnectURL string `json:"open_id_connect_url"` +} + +func (o *OpenIDConnectSecurityScheme) SecuritySchemeType() SecuritySchemeTypeEnum { + return SecuritySchemeTypeOpenIDConnect +} + +// MutualTLSSecurityScheme represents mutual TLS authentication +type MutualTLSSecurityScheme struct { + Description string `json:"description,omitempty"` +} + +func (m *MutualTLSSecurityScheme) SecuritySchemeType() SecuritySchemeTypeEnum { + return SecuritySchemeTypeMutualTLS +} + +// OAuthFlows represents OAuth flow configurations +type OAuthFlows interface { + OAuthFlowType() OAuthFlowTypeEnum +} + +// OAuthFlowTypeEnum identifies the type of OAuth flow +type OAuthFlowTypeEnum int + +const ( + OAuthFlowTypeAuthorizationCode OAuthFlowTypeEnum = iota + OAuthFlowTypeClientCredentials + OAuthFlowTypeImplicit + OAuthFlowTypePassword +) + +// AuthorizationCodeOAuthFlow represents authorization code OAuth flow +type AuthorizationCodeOAuthFlow struct { + AuthorizationURL string `json:"authorization_url"` + TokenURL string `json:"token_url"` + RefreshURL string `json:"refresh_url,omitempty"` + Scopes map[string]string `json:"scopes,omitempty"` +} + +func (a *AuthorizationCodeOAuthFlow) OAuthFlowType() OAuthFlowTypeEnum { + return OAuthFlowTypeAuthorizationCode +} + +// ClientCredentialsOAuthFlow represents client credentials OAuth flow +type ClientCredentialsOAuthFlow struct { + TokenURL string `json:"token_url"` + RefreshURL string `json:"refresh_url,omitempty"` + Scopes map[string]string `json:"scopes,omitempty"` +} + +func (c *ClientCredentialsOAuthFlow) OAuthFlowType() OAuthFlowTypeEnum { + return OAuthFlowTypeClientCredentials +} + +// ImplicitOAuthFlow represents implicit OAuth flow +type ImplicitOAuthFlow struct { + AuthorizationURL string `json:"authorization_url"` + RefreshURL string `json:"refresh_url,omitempty"` + Scopes map[string]string `json:"scopes,omitempty"` +} + +func (i *ImplicitOAuthFlow) OAuthFlowType() OAuthFlowTypeEnum { + return OAuthFlowTypeImplicit +} + +// PasswordOAuthFlow represents password OAuth flow +type PasswordOAuthFlow struct { + TokenURL string `json:"token_url"` + RefreshURL string `json:"refresh_url,omitempty"` + Scopes map[string]string `json:"scopes,omitempty"` +} + +func (p *PasswordOAuthFlow) OAuthFlowType() OAuthFlowTypeEnum { + return OAuthFlowTypePassword +} + +// SecurityToProto converts Security to pb.Security +func SecurityToProto(security *Security) *pb.Security { + if security == nil { + return nil + } + + schemes := make(map[string]*pb.StringList) + for name, list := range security.Schemes { + schemes[name] = &pb.StringList{List: list} + } + + return &pb.Security{ + Schemes: schemes, + } +} + +// SecurityFromProto converts pb.Security to Security +func SecurityFromProto(security *pb.Security) *Security { + if security == nil { + return nil + } + + schemes := make(map[string][]string) + for name, stringList := range security.Schemes { + schemes[name] = stringList.List + } + + return &Security{ + Schemes: schemes, + } +} + +// SecuritySchemeToProto converts SecurityScheme to pb.SecurityScheme +func SecuritySchemeToProto(scheme SecurityScheme) (*pb.SecurityScheme, error) { + if scheme == nil { + return nil, nil + } + + pbScheme := &pb.SecurityScheme{} + + switch s := scheme.(type) { + case *APIKeySecurityScheme: + pbScheme.Scheme = &pb.SecurityScheme_ApiKeySecurityScheme{ + ApiKeySecurityScheme: &pb.APIKeySecurityScheme{ + Description: s.Description, + Location: s.Location, + Name: s.Name, + }, + } + case *HTTPAuthSecurityScheme: + pbScheme.Scheme = &pb.SecurityScheme_HttpAuthSecurityScheme{ + HttpAuthSecurityScheme: &pb.HTTPAuthSecurityScheme{ + Description: s.Description, + Scheme: s.Scheme, + BearerFormat: s.BearerFormat, + }, + } + case *OAuth2SecurityScheme: + var flows *pb.OAuthFlows + var err error + if s.Flows != nil { + flows, err = OAuthFlowsToProto(s.Flows) + if err != nil { + return nil, fmt.Errorf("converting OAuth flows: %w", err) + } + } + pbScheme.Scheme = &pb.SecurityScheme_Oauth2SecurityScheme{ + Oauth2SecurityScheme: &pb.OAuth2SecurityScheme{ + Description: s.Description, + Flows: flows, + Oauth2MetadataUrl: s.OAuth2MetadataURL, + }, + } + case *OpenIDConnectSecurityScheme: + pbScheme.Scheme = &pb.SecurityScheme_OpenIdConnectSecurityScheme{ + OpenIdConnectSecurityScheme: &pb.OpenIdConnectSecurityScheme{ + Description: s.Description, + OpenIdConnectUrl: s.OpenIDConnectURL, + }, + } + case *MutualTLSSecurityScheme: + pbScheme.Scheme = &pb.SecurityScheme_MtlsSecurityScheme{ + MtlsSecurityScheme: &pb.MutualTlsSecurityScheme{ + Description: s.Description, + }, + } + default: + return nil, fmt.Errorf("unknown security scheme type: %T", scheme) + } + + return pbScheme, nil +} + +// SecuritySchemeFromProto converts pb.SecurityScheme to SecurityScheme +func SecuritySchemeFromProto(scheme *pb.SecurityScheme) (SecurityScheme, error) { + if scheme == nil { + return nil, nil + } + + switch s := scheme.Scheme.(type) { + case *pb.SecurityScheme_ApiKeySecurityScheme: + return &APIKeySecurityScheme{ + Description: s.ApiKeySecurityScheme.Description, + Location: s.ApiKeySecurityScheme.Location, + Name: s.ApiKeySecurityScheme.Name, + }, nil + case *pb.SecurityScheme_HttpAuthSecurityScheme: + return &HTTPAuthSecurityScheme{ + Description: s.HttpAuthSecurityScheme.Description, + Scheme: s.HttpAuthSecurityScheme.Scheme, + BearerFormat: s.HttpAuthSecurityScheme.BearerFormat, + }, nil + case *pb.SecurityScheme_Oauth2SecurityScheme: + var flows OAuthFlows + var err error + if s.Oauth2SecurityScheme.Flows != nil { + flows, err = OAuthFlowsFromProto(s.Oauth2SecurityScheme.Flows) + if err != nil { + return nil, fmt.Errorf("converting OAuth flows: %w", err) + } + } + return &OAuth2SecurityScheme{ + Description: s.Oauth2SecurityScheme.Description, + Flows: flows, + OAuth2MetadataURL: s.Oauth2SecurityScheme.Oauth2MetadataUrl, + }, nil + case *pb.SecurityScheme_OpenIdConnectSecurityScheme: + return &OpenIDConnectSecurityScheme{ + Description: s.OpenIdConnectSecurityScheme.Description, + OpenIDConnectURL: s.OpenIdConnectSecurityScheme.OpenIdConnectUrl, + }, nil + case *pb.SecurityScheme_MtlsSecurityScheme: + return &MutualTLSSecurityScheme{ + Description: s.MtlsSecurityScheme.Description, + }, nil + default: + return nil, fmt.Errorf("unknown security scheme type: %T", s) + } +} + +// OAuthFlowsToProto converts OAuthFlows to pb.OAuthFlows +func OAuthFlowsToProto(flows OAuthFlows) (*pb.OAuthFlows, error) { + if flows == nil { + return nil, nil + } + + pbFlows := &pb.OAuthFlows{} + + switch f := flows.(type) { + case *AuthorizationCodeOAuthFlow: + pbFlows.Flow = &pb.OAuthFlows_AuthorizationCode{ + AuthorizationCode: &pb.AuthorizationCodeOAuthFlow{ + AuthorizationUrl: f.AuthorizationURL, + TokenUrl: f.TokenURL, + RefreshUrl: f.RefreshURL, + Scopes: f.Scopes, + }, + } + case *ClientCredentialsOAuthFlow: + pbFlows.Flow = &pb.OAuthFlows_ClientCredentials{ + ClientCredentials: &pb.ClientCredentialsOAuthFlow{ + TokenUrl: f.TokenURL, + RefreshUrl: f.RefreshURL, + Scopes: f.Scopes, + }, + } + case *ImplicitOAuthFlow: + pbFlows.Flow = &pb.OAuthFlows_Implicit{ + Implicit: &pb.ImplicitOAuthFlow{ + AuthorizationUrl: f.AuthorizationURL, + RefreshUrl: f.RefreshURL, + Scopes: f.Scopes, + }, + } + case *PasswordOAuthFlow: + pbFlows.Flow = &pb.OAuthFlows_Password{ + Password: &pb.PasswordOAuthFlow{ + TokenUrl: f.TokenURL, + RefreshUrl: f.RefreshURL, + Scopes: f.Scopes, + }, + } + default: + return nil, fmt.Errorf("unknown OAuth flow type: %T", flows) + } + + return pbFlows, nil +} + +// OAuthFlowsFromProto converts pb.OAuthFlows to OAuthFlows +func OAuthFlowsFromProto(flows *pb.OAuthFlows) (OAuthFlows, error) { + if flows == nil { + return nil, nil + } + + switch f := flows.Flow.(type) { + case *pb.OAuthFlows_AuthorizationCode: + return &AuthorizationCodeOAuthFlow{ + AuthorizationURL: f.AuthorizationCode.AuthorizationUrl, + TokenURL: f.AuthorizationCode.TokenUrl, + RefreshURL: f.AuthorizationCode.RefreshUrl, + Scopes: f.AuthorizationCode.Scopes, + }, nil + case *pb.OAuthFlows_ClientCredentials: + return &ClientCredentialsOAuthFlow{ + TokenURL: f.ClientCredentials.TokenUrl, + RefreshURL: f.ClientCredentials.RefreshUrl, + Scopes: f.ClientCredentials.Scopes, + }, nil + case *pb.OAuthFlows_Implicit: + return &ImplicitOAuthFlow{ + AuthorizationURL: f.Implicit.AuthorizationUrl, + RefreshURL: f.Implicit.RefreshUrl, + Scopes: f.Implicit.Scopes, + }, nil + case *pb.OAuthFlows_Password: + return &PasswordOAuthFlow{ + TokenURL: f.Password.TokenUrl, + RefreshURL: f.Password.RefreshUrl, + Scopes: f.Password.Scopes, + }, nil + default: + return nil, fmt.Errorf("unknown OAuth flow type: %T", f) + } +} diff --git a/a2a/pkg/types/singleflight.go b/a2a/pkg/types/singleflight.go new file mode 100644 index 000000000..a29f61b17 --- /dev/null +++ b/a2a/pkg/types/singleflight.go @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import ( + "fmt" + "sync" +) + +// SingleFlight ensures that only one goroutine executes a function for a given key at a time +type SingleFlight struct { + mu sync.Mutex + m map[string]*call +} + +// call represents an in-flight or completed function call +type call struct { + wg sync.WaitGroup + val interface{} + err error + dups int +} + +// NewSingleFlight creates a new SingleFlight instance +func NewSingleFlight() *SingleFlight { + return &SingleFlight{ + m: make(map[string]*call), + } +} + +// Do executes and returns the results of the given function, making sure that only one +// execution is in-flight for a given key at a time. If a duplicate comes in, the duplicate +// caller waits for the original to complete and receives the same results. +func (g *SingleFlight) Do(key string, fn func() (interface{}, error)) (interface{}, error) { + g.mu.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + c.dups++ + g.mu.Unlock() + c.wg.Wait() + return c.val, c.err + } + c := new(call) + c.wg.Add(1) + g.m[key] = c + g.mu.Unlock() + + g.doCall(c, key, fn) + return c.val, c.err +} + +// doCall handles the single call execution +func (g *SingleFlight) doCall(c *call, key string, fn func() (interface{}, error)) { + defer func() { + if r := recover(); r != nil { + c.err = fmt.Errorf("panic in singleflight call: %v", r) + } + g.mu.Lock() + defer g.mu.Unlock() + c.wg.Done() + delete(g.m, key) + }() + + if fn == nil { + c.err = fmt.Errorf("singleflight: nil function") + return + } + + c.val, c.err = fn() +} + +// Forget tells the SingleFlight to forget about a key. Future calls to Do for this key +// will call the function rather than waiting for an earlier call to complete. +func (g *SingleFlight) Forget(key string) { + g.mu.Lock() + defer g.mu.Unlock() + delete(g.m, key) +} diff --git a/a2a/pkg/types/singleflight_test.go b/a2a/pkg/types/singleflight_test.go new file mode 100644 index 000000000..a0d95a9c9 --- /dev/null +++ b/a2a/pkg/types/singleflight_test.go @@ -0,0 +1,302 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package types + +import ( + "errors" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSingleFlight(t *testing.T) { + t.Run("Basic Functionality", func(t *testing.T) { + sf := NewSingleFlight() + key := "test-key" + expectedResult := "test-result" + + result, err := sf.Do(key, func() (interface{}, error) { + return expectedResult, nil + }) + + require.NoError(t, err) + assert.Equal(t, expectedResult, result) + }) + + t.Run("Error Handling", func(t *testing.T) { + sf := NewSingleFlight() + key := "error-key" + expectedError := errors.New("test error") + + result, err := sf.Do(key, func() (interface{}, error) { + return nil, expectedError + }) + + assert.Equal(t, expectedError, err) + assert.Nil(t, result) + }) + + t.Run("Single Execution with Multiple Callers", func(t *testing.T) { + sf := NewSingleFlight() + key := "single-exec-key" + + var callCount int32 + var wg sync.WaitGroup + numGoroutines := 10 + results := make([]interface{}, numGoroutines) + errors := make([]error, numGoroutines) + + wg.Add(numGoroutines) + + for i := 0; i < numGoroutines; i++ { + go func(index int) { + defer wg.Done() + + result, err := sf.Do(key, func() (interface{}, error) { + atomic.AddInt32(&callCount, 1) + time.Sleep(10 * time.Millisecond) // Simulate work + return "shared-result", nil + }) + + results[index] = result + errors[index] = err + }(i) + } + + wg.Wait() + + // Function should only be called once + assert.Equal(t, int32(1), atomic.LoadInt32(&callCount)) + + // All goroutines should get the same result + for i := 0; i < numGoroutines; i++ { + require.NoError(t, errors[i]) + assert.Equal(t, "shared-result", results[i]) + } + }) + + t.Run("Different Keys Execute Separately", func(t *testing.T) { + sf := NewSingleFlight() + + var callCount int32 + var wg sync.WaitGroup + numKeys := 5 + + wg.Add(numKeys) + + for i := 0; i < numKeys; i++ { + go func(keyIndex int) { + defer wg.Done() + + key := "key-" + string(rune('A'+keyIndex)) + expectedResult := "result-" + string(rune('A'+keyIndex)) + + result, err := sf.Do(key, func() (interface{}, error) { + atomic.AddInt32(&callCount, 1) + return expectedResult, nil + }) + + require.NoError(t, err) + assert.Equal(t, expectedResult, result) + }(i) + } + + wg.Wait() + + // Each key should execute its function once + assert.Equal(t, int32(numKeys), atomic.LoadInt32(&callCount)) + }) + + t.Run("Forget Functionality", func(t *testing.T) { + sf := NewSingleFlight() + key := "forget-key" + + // Start a long-running operation + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + sf.Do(key, func() (interface{}, error) { + time.Sleep(50 * time.Millisecond) + return "first-result", nil + }) + }() + + // Give it a moment to start + time.Sleep(10 * time.Millisecond) + + // Forget the key + sf.Forget(key) + + // Now a new call should execute separately + result, err := sf.Do(key, func() (interface{}, error) { + return "second-result", nil + }) + + require.NoError(t, err) + assert.Equal(t, "second-result", result) + + wg.Wait() + }) + + t.Run("Concurrent Forget and Do", func(t *testing.T) { + sf := NewSingleFlight() + key := "concurrent-forget-key" + + var wg sync.WaitGroup + var results []interface{} + var mu sync.Mutex + + numGoroutines := 20 + wg.Add(numGoroutines) + + for i := 0; i < numGoroutines; i++ { + go func(index int) { + defer wg.Done() + + if index%5 == 0 { + // Forget periodically + sf.Forget(key) + } else { + result, err := sf.Do(key, func() (interface{}, error) { + return "result", nil + }) + + if err == nil { + mu.Lock() + results = append(results, result) + mu.Unlock() + } + } + }(i) + } + + wg.Wait() + + // Should have some results + mu.Lock() + assert.Greater(t, len(results), 0) + mu.Unlock() + }) + + t.Run("Panic Recovery", func(t *testing.T) { + sf := NewSingleFlight() + key := "panic-key" + + // This should not panic the test + assert.NotPanics(t, func() { + result, err := sf.Do(key, func() (interface{}, error) { + panic("test panic") + }) + // The panic should be converted to an error + assert.Error(t, err) + assert.Nil(t, result) + }) + }) + + t.Run("Nil Function", func(t *testing.T) { + sf := NewSingleFlight() + key := "nil-func-key" + + // This should handle nil function gracefully + assert.NotPanics(t, func() { + result, err := sf.Do(key, nil) + assert.Error(t, err) + assert.Nil(t, result) + }) + }) +} + +// Test internal state management +func TestSingleFlightInternalState(t *testing.T) { + t.Run("Map Initialization", func(t *testing.T) { + sf := &SingleFlight{} + + // Map should be initialized on first use + result, err := sf.Do("test", func() (interface{}, error) { + return "initialized", nil + }) + + require.NoError(t, err) + assert.Equal(t, "initialized", result) + assert.NotNil(t, sf.m) + }) + + t.Run("Cleanup After Completion", func(t *testing.T) { + sf := NewSingleFlight() + key := "cleanup-key" + + _, err := sf.Do(key, func() (interface{}, error) { + return "result", nil + }) + require.NoError(t, err) + + // The call should be cleaned up from the map + sf.mu.Lock() + _, exists := sf.m[key] + sf.mu.Unlock() + + assert.False(t, exists, "Call should be cleaned up after completion") + }) +} + +// Benchmark tests +func BenchmarkSingleFlightDo(b *testing.B) { + sf := NewSingleFlight() + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + key := "benchmark-key" + sf.Do(key, func() (interface{}, error) { + return "result", nil + }) + } + }) +} + +func BenchmarkSingleFlightDoSeparateKeys(b *testing.B) { + sf := NewSingleFlight() + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + key := "key-" + string(rune(i%100)) + sf.Do(key, func() (interface{}, error) { + return "result", nil + }) + i++ + } + }) +} + +func BenchmarkSingleFlightForget(b *testing.B) { + sf := NewSingleFlight() + key := "forget-benchmark-key" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + sf.Forget(key) + } +} diff --git a/a2a/scripts/generate_proto.sh b/a2a/scripts/generate_proto.sh new file mode 100644 index 000000000..c9ae2c221 --- /dev/null +++ b/a2a/scripts/generate_proto.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + + +# Generate Protocol Buffers code for A2A Protocol +set -e + +PROTO_DIR="api/proto/v1" +OUT_DIR="pkg/proto/v1" +GOOGLEAPIS_DIR="third_party/googleapis" +PROTOBUF_DIR="third_party/protobuf/src" + +echo "Generating Go code from Protocol Buffers..." + +# Create output directory if it doesn't exist +mkdir -p "$OUT_DIR" + +# Check if protoc is installed +if ! command -v protoc &> /dev/null; then + echo "Error: protoc is not installed" + echo "Please install Protocol Buffers compiler:" + echo " - macOS: brew install protobuf" + echo " - Ubuntu: apt-get install protobuf-compiler" + echo " - Or download from: https://github.com/protocolbuffers/protobuf/releases" + exit 1 +fi + +# Check if protoc-gen-go is installed +if ! command -v protoc-gen-go &> /dev/null; then + echo "Installing protoc-gen-go..." + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest +fi + +# Check if protoc-gen-go-grpc is installed +if ! command -v protoc-gen-go-grpc &> /dev/null; then + echo "Installing protoc-gen-go-grpc..." + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest +fi + +# Check if third_party directories exist +if [ ! -d "$GOOGLEAPIS_DIR" ]; then + echo "Error: $GOOGLEAPIS_DIR not found" + echo "Please run: cd third_party && git clone https://github.com/googleapis/googleapis.git" + exit 1 +fi + +if [ ! -d "$PROTOBUF_DIR" ]; then + echo "Error: $PROTOBUF_DIR not found" + echo "Please run: cd third_party && git clone https://github.com/protocolbuffers/protobuf.git" + exit 1 +fi + +# Generate Go code with proper include paths +protoc --proto_path="$PROTO_DIR" \ + --proto_path="$GOOGLEAPIS_DIR" \ + --proto_path="$PROTOBUF_DIR" \ + --go_out="$OUT_DIR" --go_opt=paths=source_relative \ + --go-grpc_out="$OUT_DIR" --go-grpc_opt=paths=source_relative \ + "$PROTO_DIR"/*.proto + +echo "Protocol Buffers code generation completed successfully!" +echo "Generated files in: $OUT_DIR" \ No newline at end of file diff --git a/agent-cli/README.md b/agent-cli/README.md new file mode 100644 index 000000000..fe3f89944 --- /dev/null +++ b/agent-cli/README.md @@ -0,0 +1,293 @@ + + +# Go Agent CLI + +一个用于生成 Go 语言 Agent 项目的命令行脚手架工具。 + +## 功能特性 + +- 🚀 快速生成 Go Agent 项目 +- 📋 支持两种项目模式:Default 和 Provider +- 🔧 自动生成项目结构和配置文件 +- 📊 内置 Agent Card 配置 +- 🐳 包含 Docker 支持 +- 📝 自动生成项目文档 + +## 支持的模式 + +### Default 模式 +标准的 Agent 项目,包含: +- 完整的技能系统架构 +- HTTP API 接口 +- 配置管理 +- 日志系统 +- 健康检查 + +### Provider 模式 +外部服务集成 Agent,包含: +- 服务适配器层 +- 外部服务客户端 +- 数据映射器 +- 集成示例和文档 + +## 快速开始 + +### 安装 + +```bash +# 克隆项目 +git clone +cd agent-cli + +# 安装依赖 +go mod tidy + +# 构建工具 +go build -o agent-cli cmd/cli/main.go +``` + +### 使用方法 + +#### 创建 Default 模式项目 + +```bash +./agent-cli create my-agent --mode default +``` + +#### 创建 Provider 模式项目 + +```bash +./agent-cli create my-provider --mode provider +``` + +#### 指定输出目录和模块名 + +```bash +./agent-cli create my-agent --mode default --output ./projects --module github.com/myorg/my-agent +``` + +### 生成的项目结构 + +#### Default 模式项目结构 +``` +my-agent/ +├── main.go # 入口文件 +├── go.mod # Go模块定义 +├── config/ +│ ├── config.yaml # 配置文件 +│ └── agent_card.json # Agent能力卡片 +├── internal/ +│ ├── agent/ +│ │ ├── agent.go # Agent核心逻辑 +│ │ ├── skills/ # 技能实现目录 +│ │ │ └── example.go # 示例技能 +│ │ └── handlers/ # RPC处理器 +│ │ └── rpc.go # RPC路由 +│ ├── config/ +│ │ └── config.go # 配置加载 +│ └── utils/ +│ └── logger.go # 日志工具 +├── scripts/ +│ ├── build.sh # 构建脚本 +│ └── run.sh # 运行脚本 +├── Dockerfile # Docker构建文件 +├── docker-compose.yml # 本地开发环境 +└── README.md # 项目文档 +``` + +#### Provider 模式项目结构 +``` +my-provider/ +├── main.go # 入口文件 +├── go.mod +├── config/ +│ ├── config.yaml +│ └── agent_card.json +├── internal/ +│ ├── agent/ +│ │ ├── agent.go +│ │ └── provider/ # Provider层 +│ │ ├── adapter.go # 适配器接口 +│ │ ├── client.go # 外部服务客户端 +│ │ └── mapper.go # 数据映射 +│ ├── skills/ +│ │ └── provider_skills.go # Provider技能实现 +│ └── handlers/ +│ └── rpc.go +├── examples/ +│ └── external_service.go # 外部服务示例 +└── docs/ + └── integration.md # 集成指南 +``` + +## 生成项目的使用方法 + +### 运行 Agent + +```bash +cd my-agent +go mod tidy +go run main.go +``` + +### 测试 API + +```bash +# 健康检查 +curl http://localhost:8080/health + +# 获取 Agent 卡片 +curl http://localhost:8080/agent-card + +# 执行技能 (Default 模式) +curl -X POST http://localhost:8080/skills/example \ + -H "Content-Type: application/json" \ + -d '{"input": "Hello World"}' + +# 执行技能 (Provider 模式) +curl -X POST http://localhost:8080/skills/external_service_call \ + -H "Content-Type: application/json" \ + -d '{ + "service_name": "example-api", + "endpoint": "/api/data", + "method": "GET" + }' +``` + +### Docker 部署 + +```bash +# 使用 Docker Compose +docker-compose up --build + +# 或手动构建 +docker build -t my-agent . +docker run -p 8080:8080 my-agent +``` + +## CLI 命令参考 + +### 全局选项 + +```bash +agent-cli [command] [flags] +``` + +### 可用命令 + +- `create` - 创建新的 Go Agent 项目 +- `version` - 显示版本信息 +- `help` - 显示帮助信息 + +### create 命令 + +```bash +agent-cli create [project-name] [flags] +``` + +**参数:** +- `project-name` - 项目名称(必需) + +**选项:** +- `-m, --mode string` - 项目模式 (default|provider),默认为 "default" +- `-o, --output string` - 输出目录,默认为当前目录 "." +- `--module string` - Go 模块名,默认为项目名称 + +**示例:** + +```bash +# 基本用法 +agent-cli create my-agent + +# 指定模式 +agent-cli create my-provider --mode provider + +# 指定输出目录 +agent-cli create my-agent --output ./projects + +# 指定模块名 +agent-cli create my-agent --module github.com/myorg/my-agent + +# 完整示例 +agent-cli create my-enterprise-agent \ + --mode provider \ + --output ./enterprise-projects \ + --module github.com/mycompany/agents/enterprise-agent +``` + +## 项目架构 + +### 脚手架工具架构 + +``` +go-agent-cli/ +├── cmd/cli/main.go # CLI 入口 +├── internal/ +│ ├── cmd/ # 命令实现 +│ │ ├── root.go # 根命令 +│ │ ├── create.go # create 命令 +│ │ └── version.go # version 命令 +│ ├── config/ # 配置结构 +│ │ └── project.go # 项目配置定义 +│ └── generator/ # 代码生成器 +│ ├── generator.go # 生成器核心 +│ └── templates.go # 项目模板 +└── go.mod +``` + +### 生成项目的技术栈 + +- **Web 框架**: Gin +- **配置管理**: Viper +- **日志系统**: Zap +- **容器化**: Docker + Docker Compose +- **构建工具**: Go 标准工具链 + +## 开发指南 + +### 扩展模板 + +要添加新的项目模板: + +1. 在 `internal/generator/templates.go` 中定义新模板 +2. 在 `generator.go` 中添加生成逻辑 +3. 更新 `create.go` 中的模式验证 + +### 添加新命令 + +要添加新的 CLI 命令: + +1. 在 `internal/cmd/` 下创建新文件 +2. 实现 Cobra 命令 +3. 在 `root.go` 中注册命令 + +## 贡献 + +欢迎提交 Issue 和 Pull Request! + +## 许可证 + +[指定许可证] + +## 版本历史 + +- v1.0.0 - 初始版本 + - 支持 Default 和 Provider 两种模式 + - 完整的项目生成功能 + - Docker 支持 + - 详细的文档和示例 \ No newline at end of file diff --git a/pkg/datasource/sql/exec/config/config.go b/agent-cli/cmd/cli/main.go similarity index 82% rename from pkg/datasource/sql/exec/config/config.go rename to agent-cli/cmd/cli/main.go index 2efd642cc..b5d258d15 100644 --- a/pkg/datasource/sql/exec/config/config.go +++ b/agent-cli/cmd/cli/main.go @@ -15,13 +15,18 @@ * limitations under the License. */ -package config +package main import ( - "seata.apache.org/seata-go/pkg/datasource/sql/exec/at" - "seata.apache.org/seata-go/pkg/rm" + "fmt" + "os" + + "go-agent-cli/internal/cmd" ) -func Init(config rm.LockConfig) { - at.LockConfig = config +func main() { + if err := cmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } } diff --git a/agent-cli/go.mod b/agent-cli/go.mod new file mode 100644 index 000000000..24bcab7f9 --- /dev/null +++ b/agent-cli/go.mod @@ -0,0 +1,12 @@ +module go-agent-cli + +go 1.24.0 + +toolchain go1.24.5 + +require github.com/spf13/cobra v1.10.1 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect +) diff --git a/agent-cli/go.sum b/agent-cli/go.sum new file mode 100644 index 000000000..989827e16 --- /dev/null +++ b/agent-cli/go.sum @@ -0,0 +1,11 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/agent-cli/internal/cmd/create.go b/agent-cli/internal/cmd/create.go new file mode 100644 index 000000000..39b1057f7 --- /dev/null +++ b/agent-cli/internal/cmd/create.go @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/spf13/cobra" + "go-agent-cli/internal/config" + "go-agent-cli/internal/generator" +) + +var createCmd = &cobra.Command{ + Use: "create [project-name]", + Short: "Create a new Go agent project", + Long: `Create a new Go agent project with the specified name and mode. + +Modes: + default - Standard agent with skills system + provider - Integration agent with external service adapters`, + Args: cobra.ExactArgs(1), + Run: runCreate, +} + +var ( + mode string + outputDir string + module string +) + +func init() { + createCmd.Flags().StringVarP(&mode, "mode", "m", "default", "Project mode (default|provider)") + createCmd.Flags().StringVarP(&outputDir, "output", "o", ".", "Output directory") + createCmd.Flags().StringVar(&module, "module", "", "Go module name (default: project-name)") +} + +func runCreate(cmd *cobra.Command, args []string) { + projectName := args[0] + + // Validate mode + if mode != "default" && mode != "provider" { + fmt.Fprintf(os.Stderr, "Error: Invalid mode '%s'. Must be 'default' or 'provider'\n", mode) + os.Exit(1) + } + + // Set default module name if not provided + if module == "" { + module = projectName + } + + // Create project config + projectConfig := &config.ProjectConfig{ + Name: projectName, + ModuleName: module, + Mode: mode, + OutputDir: outputDir, + } + + // Create template data + templateData := &config.TemplateData{ + ProjectName: projectName, + ModuleName: module, + Mode: mode, + Timestamp: time.Now().Format("2006-01-02 15:04:05"), + AgentCard: createDefaultAgentCard(projectName, mode), + } + + // Create generator + gen := generator.New() + + // Generate project + projectPath := filepath.Join(outputDir, projectName) + if err := gen.Generate(projectConfig, templateData, projectPath); err != nil { + fmt.Fprintf(os.Stderr, "Error generating project: %v\n", err) + os.Exit(1) + } + + fmt.Printf("✅ Successfully created %s agent project: %s\n", mode, projectName) + fmt.Printf("📁 Project location: %s\n", projectPath) + fmt.Printf("🚀 Next steps:\n") + fmt.Printf(" cd %s\n", projectName) + fmt.Printf(" go mod tidy\n") + fmt.Printf(" go run main.go\n") +} + +func createDefaultAgentCard(projectName, mode string) config.AgentCard { + card := config.AgentCard{ + Name: projectName, + Description: fmt.Sprintf("A %s mode Go agent", mode), + URL: "http://localhost:8080", + Version: "1.0.0", + Capabilities: config.AgentCapabilities{ + SupportsStreaming: true, + SupportedModes: []string{"text", "json"}, + }, + DefaultInputModes: []string{"text"}, + DefaultOutputModes: []string{"text"}, + Skills: []config.AgentSkill{}, + } + + // Add mode-specific configuration + if mode == "provider" { + card.Provider = &config.AgentProvider{ + Name: fmt.Sprintf("%s-provider", projectName), + Description: "External service provider integration", + URL: "http://localhost:8080/provider", + } + + card.Skills = append(card.Skills, config.AgentSkill{ + Name: "external_service_call", + Description: "Call external service through adapter", + Parameters: map[string]interface{}{ + "endpoint": map[string]interface{}{ + "type": "string", + "description": "External service endpoint", + "required": true, + }, + }, + }) + } else { + card.Skills = append(card.Skills, config.AgentSkill{ + Name: "example_skill", + Description: "An example skill implementation", + Parameters: map[string]interface{}{ + "input": map[string]interface{}{ + "type": "string", + "description": "Input text for processing", + "required": true, + }, + }, + }) + } + + return card +} diff --git a/pkg/datasource/sql/exec/at/plain_executor.go b/agent-cli/internal/cmd/root.go similarity index 57% rename from pkg/datasource/sql/exec/at/plain_executor.go rename to agent-cli/internal/cmd/root.go index 61cb8c8cd..0eb4c38fb 100644 --- a/pkg/datasource/sql/exec/at/plain_executor.go +++ b/agent-cli/internal/cmd/root.go @@ -15,24 +15,31 @@ * limitations under the License. */ -package at +package cmd import ( - "context" + "fmt" - "seata.apache.org/seata-go/pkg/datasource/sql/exec" - "seata.apache.org/seata-go/pkg/datasource/sql/types" + "github.com/spf13/cobra" ) -type plainExecutor struct { - parserCtx *types.ParseContext - execContext *types.ExecContext +var rootCmd = &cobra.Command{ + Use: "agent-cli", + Short: "A CLI tool for generating Go agent projects", + Long: `Go Agent CLI is a command-line tool for generating Go-based agent projects. +It supports two modes: +- default: Standard agent with skills system +- provider: Integration agent with external service adapters`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Go Agent CLI - Use 'agent-cli --help' for more information") + }, } -func NewPlainExecutor(parserCtx *types.ParseContext, execCtx *types.ExecContext) executor { - return &plainExecutor{parserCtx: parserCtx, execContext: execCtx} +func Execute() error { + return rootCmd.Execute() } -func (u *plainExecutor) ExecContext(ctx context.Context, f exec.CallbackWithNamedValue) (types.ExecResult, error) { - return f(ctx, u.execContext.Query, u.execContext.NamedValues) +func init() { + rootCmd.AddCommand(createCmd) + rootCmd.AddCommand(versionCmd) } diff --git a/pkg/rm/rm_remoting_test.go b/agent-cli/internal/cmd/version.go similarity index 70% rename from pkg/rm/rm_remoting_test.go rename to agent-cli/internal/cmd/version.go index e21efeb93..e5411f37f 100644 --- a/pkg/rm/rm_remoting_test.go +++ b/agent-cli/internal/cmd/version.go @@ -15,21 +15,26 @@ * limitations under the License. */ -package rm +package cmd import ( - "testing" + "fmt" - "github.com/stretchr/testify/assert" + "github.com/spf13/cobra" ) -func TestGetRMRemotingInstance(t *testing.T) { - tests := struct { - name string - want *RMRemoting - }{"test1", &RMRemoting{}} +var ( + version = "1.0.0" + commit = "dev" + date = "unknown" +) - t.Run(tests.name, func(t *testing.T) { - assert.Equalf(t, tests.want, GetRMRemotingInstance(), "GetRMRemotingInstance()") - }) +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number", + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Go Agent CLI %s\n", version) + fmt.Printf("Commit: %s\n", commit) + fmt.Printf("Built: %s\n", date) + }, } diff --git a/agent-cli/internal/config/project.go b/agent-cli/internal/config/project.go new file mode 100644 index 000000000..2a33c02cc --- /dev/null +++ b/agent-cli/internal/config/project.go @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package config + +type ProjectConfig struct { + Name string `json:"name"` + ModuleName string `json:"moduleName"` + Mode string `json:"mode"` // "default" or "provider" + OutputDir string `json:"outputDir"` +} + +type AgentCard struct { + Name string `json:"name"` + Description string `json:"description"` + URL string `json:"url"` + Provider *AgentProvider `json:"provider,omitempty"` + Version string `json:"version"` + DocumentationURL *string `json:"documentationUrl,omitempty"` + Capabilities AgentCapabilities `json:"capabilities"` + SecuritySchemes map[string]SecurityScheme `json:"securitySchemes,omitempty"` + Security []map[string][]string `json:"security,omitempty"` + DefaultInputModes []string `json:"defaultInputModes"` + DefaultOutputModes []string `json:"defaultOutputModes"` + Skills []AgentSkill `json:"skills"` + SupportsAuthenticatedExtendedCard *bool `json:"supportsAuthenticatedExtendedCard,omitempty"` +} + +type AgentProvider struct { + Name string `json:"name"` + Description string `json:"description"` + URL string `json:"url"` +} + +type AgentCapabilities struct { + SupportsStreaming bool `json:"supportsStreaming"` + SupportedModes []string `json:"supportedModes"` +} + +type SecurityScheme struct { + Type string `json:"type"` + Scheme string `json:"scheme,omitempty"` + BearerFormat string `json:"bearerFormat,omitempty"` + Description string `json:"description,omitempty"` + Flows map[string]string `json:"flows,omitempty"` +} + +type AgentSkill struct { + Name string `json:"name"` + Description string `json:"description"` + Parameters map[string]interface{} `json:"parameters"` +} + +type TemplateData struct { + ProjectName string + ModuleName string + Mode string + Timestamp string + AgentCard AgentCard +} diff --git a/agent-cli/internal/generator/generator.go b/agent-cli/internal/generator/generator.go new file mode 100644 index 000000000..abf98d7ce --- /dev/null +++ b/agent-cli/internal/generator/generator.go @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package generator + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "text/template" + + "go-agent-cli/internal/config" +) + +type Generator struct { + templates map[string]*template.Template +} + +func New() *Generator { + return &Generator{ + templates: make(map[string]*template.Template), + } +} + +func (g *Generator) Generate(projectConfig *config.ProjectConfig, templateData *config.TemplateData, outputPath string) error { + // Create output directory + if err := os.MkdirAll(outputPath, 0755); err != nil { + return fmt.Errorf("failed to create output directory: %w", err) + } + + // Generate project structure based on mode + switch projectConfig.Mode { + case "default": + return g.generateDefaultProject(templateData, outputPath) + case "provider": + return g.generateProviderProject(templateData, outputPath) + default: + return fmt.Errorf("unsupported mode: %s", projectConfig.Mode) + } +} + +func (g *Generator) generateDefaultProject(data *config.TemplateData, outputPath string) error { + // Create directory structure + dirs := []string{ + "config", + "internal/agent", + "internal/agent/skills", + "internal/agent/handlers", + "internal/config", + "internal/utils", + "scripts", + } + + for _, dir := range dirs { + if err := os.MkdirAll(filepath.Join(outputPath, dir), 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dir, err) + } + } + + // Generate files + files := map[string]string{ + "main.go": defaultMainTemplate, + "go.mod": goModTemplate, + "config/config.yaml": defaultConfigTemplate, + "internal/agent/agent.go": defaultAgentTemplate, + "internal/agent/skills/example.go": defaultSkillTemplate, + "internal/agent/handlers/rpc.go": defaultRPCTemplate, + "internal/config/config.go": configLoaderTemplate, + "internal/utils/logger.go": loggerTemplate, + "scripts/build.sh": buildScriptTemplate, + "scripts/run.sh": runScriptTemplate, + "Dockerfile": dockerfileTemplate, + "docker-compose.yml": dockerComposeTemplate, + "README.md": defaultReadmeTemplate, + } + + for filePath, tmplContent := range files { + if err := g.generateFile(filepath.Join(outputPath, filePath), tmplContent, data); err != nil { + return fmt.Errorf("failed to generate %s: %w", filePath, err) + } + } + + // Generate agent_card.json + return g.generateAgentCard(filepath.Join(outputPath, "config/agent_card.json"), &data.AgentCard) +} + +func (g *Generator) generateProviderProject(data *config.TemplateData, outputPath string) error { + // Create directory structure + dirs := []string{ + "config", + "internal/agent", + "internal/agent/provider", + "internal/skills", + "internal/handlers", + "examples", + "docs", + } + + for _, dir := range dirs { + if err := os.MkdirAll(filepath.Join(outputPath, dir), 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dir, err) + } + } + + // Generate files + files := map[string]string{ + "main.go": providerMainTemplate, + "go.mod": goModTemplate, + "config/config.yaml": providerConfigTemplate, + "internal/agent/agent.go": providerAgentTemplate, + "internal/agent/provider/adapter.go": providerAdapterTemplate, + "internal/agent/provider/client.go": providerClientTemplate, + "internal/agent/provider/mapper.go": providerMapperTemplate, + "internal/skills/provider_skills.go": providerSkillsTemplate, + "internal/handlers/rpc.go": providerRPCTemplate, + "examples/external_service.go": externalServiceTemplate, + "docs/integration.md": integrationDocTemplate, + } + + for filePath, tmplContent := range files { + if err := g.generateFile(filepath.Join(outputPath, filePath), tmplContent, data); err != nil { + return fmt.Errorf("failed to generate %s: %w", filePath, err) + } + } + + // Generate agent_card.json + return g.generateAgentCard(filepath.Join(outputPath, "config/agent_card.json"), &data.AgentCard) +} + +func (g *Generator) generateFile(filePath, tmplContent string, data *config.TemplateData) error { + // Create directory if not exists + dir := filepath.Dir(filePath) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dir, err) + } + + // Parse template + tmpl, err := template.New(filepath.Base(filePath)).Parse(tmplContent) + if err != nil { + return fmt.Errorf("failed to parse template: %w", err) + } + + // Create file + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + defer file.Close() + + // Execute template + if err := tmpl.Execute(file, data); err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + + return nil +} + +func (g *Generator) generateAgentCard(filePath string, agentCard *config.AgentCard) error { + // Create directory if not exists + dir := filepath.Dir(filePath) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dir, err) + } + + // Create file + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("failed to create agent card file: %w", err) + } + defer file.Close() + + // Marshal to JSON with indentation + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + if err := encoder.Encode(agentCard); err != nil { + return fmt.Errorf("failed to encode agent card: %w", err) + } + + return nil +} diff --git a/agent-cli/internal/generator/templates.go b/agent-cli/internal/generator/templates.go new file mode 100644 index 000000000..96130a834 --- /dev/null +++ b/agent-cli/internal/generator/templates.go @@ -0,0 +1,479 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package generator + +// Go Module Template +const goModTemplate = `module {{.ModuleName}} + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/spf13/viper v1.18.2 + gopkg.in/yaml.v3 v3.0.1 + go.uber.org/zap v1.26.0 +) +` + +// Default Mode Templates +const defaultMainTemplate = `package main + +import ( + "log" + + "{{.ModuleName}}/internal/agent" + "{{.ModuleName}}/internal/config" + "{{.ModuleName}}/internal/utils" +) + +func main() { + cfg, err := config.Load() + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + logger := utils.NewLogger(cfg.Log.Level) + defer logger.Sync() + + agent := agent.New(cfg, logger) + if err := agent.Start(); err != nil { + logger.Fatal("Failed to start agent", "error", err) + } +} +` + +const defaultConfigTemplate = `# {{.ProjectName}} Agent Configuration +# Generated on {{.Timestamp}} + +server: + host: "0.0.0.0" + port: 8080 + mode: "release" + +agent: + name: "{{.ProjectName}}" + version: "1.0.0" + description: "A default mode Go agent" + +log: + level: "info" + format: "json" + +skills: + enabled: + - "example" +` + +const defaultAgentTemplate = `package agent + +import ( + "context" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + + "{{.ModuleName}}/internal/agent/handlers" + "{{.ModuleName}}/internal/agent/skills" + "{{.ModuleName}}/internal/config" +) + +type Agent struct { + cfg *config.Config + logger *zap.Logger + router *gin.Engine + skills map[string]skills.Skill +} + +func New(cfg *config.Config, logger *zap.Logger) *Agent { + a := &Agent{ + cfg: cfg, + logger: logger, + router: gin.New(), + skills: make(map[string]skills.Skill), + } + + a.initializeSkills() + a.setupRoutes() + return a +} + +func (a *Agent) initializeSkills() { + a.skills["example"] = skills.NewExampleSkill(a.logger) +} + +func (a *Agent) setupRoutes() { + a.router.Use(gin.Logger()) + a.router.Use(gin.Recovery()) + + a.router.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "healthy"}) + }) + + a.router.GET("/agent-card", handlers.NewAgentCardHandler(a.cfg).Handle) + + skillsGroup := a.router.Group("/skills") + for name, skill := range a.skills { + skillsGroup.POST("/"+name, a.createSkillHandler(skill)) + } +} + +func (a *Agent) createSkillHandler(skill skills.Skill) gin.HandlerFunc { + return func(c *gin.Context) { + var request map[string]interface{} + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + result, err := skill.Execute(context.Background(), request) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) + } +} + +func (a *Agent) Start() error { + addr := fmt.Sprintf("%s:%d", a.cfg.Server.Host, a.cfg.Server.Port) + a.logger.Info("Starting agent server", zap.String("address", addr)) + return a.router.Run(addr) +} +` + +const defaultSkillTemplate = `package skills + +import ( + "context" + "fmt" + + "go.uber.org/zap" +) + +type Skill interface { + Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) + Name() string + Description() string +} + +type ExampleSkill struct { + logger *zap.Logger +} + +func NewExampleSkill(logger *zap.Logger) *ExampleSkill { + return &ExampleSkill{logger: logger} +} + +func (s *ExampleSkill) Name() string { + return "example" +} + +func (s *ExampleSkill) Description() string { + return "An example skill that processes input text" +} + +func (s *ExampleSkill) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) { + input, ok := params["input"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'input' parameter") + } + + s.logger.Info("Executing example skill", zap.String("input", input)) + + result := fmt.Sprintf("Processed: %s (length: %d)", input, len(input)) + + return map[string]interface{}{ + "result": result, + "original": input, + "length": len(input), + }, nil +} +` + +const defaultRPCTemplate = `package handlers + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "{{.ModuleName}}/internal/config" +) + +type AgentCardHandler struct { + cfg *config.Config +} + +func NewAgentCardHandler(cfg *config.Config) *AgentCardHandler { + return &AgentCardHandler{cfg: cfg} +} + +func (h *AgentCardHandler) Handle(c *gin.Context) { + agentCard, err := h.cfg.LoadAgentCard() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load agent card"}) + return + } + + c.JSON(http.StatusOK, agentCard) +} +` + +const configLoaderTemplate = `package config + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/spf13/viper" +) + +type Config struct { + Server ServerConfig + Agent AgentConfig + Log LogConfig + Skills SkillsConfig +} + +type ServerConfig struct { + Host string + Port int + Mode string +} + +type AgentConfig struct { + Name string + Version string + Description string +} + +type LogConfig struct { + Level string + Format string +} + +type SkillsConfig struct { + Enabled []string +} + +func Load() (*Config, error) { + viper.SetConfigName("config") + viper.SetConfigType("yaml") + viper.AddConfigPath("./config") + viper.AddConfigPath(".") + + viper.SetDefault("server.host", "0.0.0.0") + viper.SetDefault("server.port", 8080) + viper.SetDefault("server.mode", "release") + viper.SetDefault("log.level", "info") + viper.SetDefault("log.format", "json") + + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + + var cfg Config + if err := viper.Unmarshal(&cfg); err != nil { + return nil, err + } + + return &cfg, nil +} + +func (c *Config) LoadAgentCard() (interface{}, error) { + cardPath := filepath.Join("config", "agent_card.json") + data, err := os.ReadFile(cardPath) + if err != nil { + return nil, err + } + + var card interface{} + if err := json.Unmarshal(data, &card); err != nil { + return nil, err + } + + return card, nil +} +` + +const loggerTemplate = `package utils + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func NewLogger(level string) *zap.Logger { + var zapLevel zapcore.Level + switch level { + case "debug": + zapLevel = zapcore.DebugLevel + case "info": + zapLevel = zapcore.InfoLevel + case "warn": + zapLevel = zapcore.WarnLevel + case "error": + zapLevel = zapcore.ErrorLevel + default: + zapLevel = zapcore.InfoLevel + } + + config := zap.NewProductionConfig() + config.Level.SetLevel(zapLevel) + config.EncoderConfig.TimeKey = "timestamp" + config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + + logger, _ := config.Build() + return logger +} +` + +const buildScriptTemplate = `#!/bin/bash +set -e + +echo "Building {{.ProjectName}}..." + +rm -rf ./bin +mkdir -p ./bin + +go build -o ./bin/{{.ProjectName}} ./main.go + +echo "Build completed successfully!" +echo "Binary location: ./bin/{{.ProjectName}}" +` + +const runScriptTemplate = `#!/bin/bash +set -e + +echo "Starting {{.ProjectName}}..." + +if [ ! -f "./config/config.yaml" ]; then + echo "Error: config/config.yaml not found" + exit 1 +fi + +go run main.go +` + +const dockerfileTemplate = `FROM golang:1.21-alpine AS builder + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . + +FROM alpine:latest + +RUN apk --no-cache add ca-certificates +WORKDIR /root/ + +COPY --from=builder /app/main . +COPY --from=builder /app/config ./config/ + +EXPOSE 8080 + +CMD ["./main"] +` + +const dockerComposeTemplate = `version: '3.8' + +services: + {{.ProjectName}}: + build: . + ports: + - "8080:8080" + volumes: + - ./config:/root/config + environment: + - GIN_MODE=release + restart: unless-stopped +` + +const defaultReadmeTemplate = `# {{.ProjectName}} + +A {{.Mode}} mode Go agent generated on {{.Timestamp}}. + +## Description + +This is a Go-based agent that provides skill-based functionality through HTTP APIs. + +## Features + +- RESTful API endpoints +- Configurable skills system +- Health check endpoint +- Agent card endpoint +- Docker support +- Structured logging + +## Quick Start + +1. Install dependencies: + go mod tidy + +2. Run the agent: + go run main.go + +3. Test the agent: + curl http://localhost:8080/health + curl http://localhost:8080/agent-card + curl -X POST http://localhost:8080/skills/example -H "Content-Type: application/json" -d '{"input": "Hello World"}' + +## Configuration + +Edit config/config.yaml to customize the agent settings. + +## Docker + +docker-compose up --build + +## API Endpoints + +- GET /health - Health check +- GET /agent-card - Agent capabilities +- POST /skills/{skill-name} - Execute a skill + +## Skills + +- example: Processes input text and returns analysis + +## Development + +- scripts/build.sh - Build the project +- scripts/run.sh - Run in development mode +` + +// Provider Mode Templates (simplified for now) +const providerMainTemplate = defaultMainTemplate +const providerConfigTemplate = defaultConfigTemplate +const providerAgentTemplate = defaultAgentTemplate +const providerAdapterTemplate = defaultAgentTemplate +const providerClientTemplate = defaultAgentTemplate +const providerMapperTemplate = defaultAgentTemplate +const providerSkillsTemplate = defaultSkillTemplate +const providerRPCTemplate = defaultRPCTemplate +const externalServiceTemplate = defaultReadmeTemplate +const integrationDocTemplate = defaultReadmeTemplate diff --git a/.gitignore b/agent-cli/test-agent/Dockerfile similarity index 69% rename from .gitignore rename to agent-cli/test-agent/Dockerfile index ccc8bd3ef..7eaa280d7 100644 --- a/.gitignore +++ b/agent-cli/test-agent/Dockerfile @@ -1,4 +1,3 @@ -# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. @@ -13,29 +12,26 @@ # 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. -# -# Idea configuration file -.idea/ +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib +FROM alpine:latest -# Test binary, built with `go test -c` -*.test +RUN apk --no-cache add ca-certificates +WORKDIR /root/ -# Output of the go coverage tool, specifically when used with LiteIDE -*.out +COPY --from=builder /app/main . +COPY --from=builder /app/config ./config/ -# build products -dist/ +EXPOSE 8080 -# Dependency directories (remove the comment below to include it) -# vendor/ -.vscode -.codecc -vendor +CMD ["./main"] diff --git a/agent-cli/test-agent/README.md b/agent-cli/test-agent/README.md new file mode 100644 index 000000000..0ec475f40 --- /dev/null +++ b/agent-cli/test-agent/README.md @@ -0,0 +1,69 @@ + + +# test-agent + +A default mode Go agent generated on 2025-09-26 21:33:08. + +## Description + +This is a Go-based agent that provides skill-based functionality through HTTP APIs. + +## Features + +- RESTful API endpoints +- Configurable skills system +- Health check endpoint +- Agent card endpoint +- Docker support +- Structured logging + +## Quick Start + +1. Install dependencies: + go mod tidy + +2. Run the agent: + go run main.go + +3. Test the agent: + curl http://localhost:8080/health + curl http://localhost:8080/agent-card + curl -X POST http://localhost:8080/skills/example -H "Content-Type: application/json" -d '{"input": "Hello World"}' + +## Configuration + +Edit config/config.yaml to customize the agent settings. + +## Docker + +docker-compose up --build + +## API Endpoints + +- GET /health - Health check +- GET /agent-card - Agent capabilities +- POST /skills/{skill-name} - Execute a skill + +## Skills + +- example: Processes input text and returns analysis + +## Development + +- scripts/build.sh - Build the project +- scripts/run.sh - Run in development mode diff --git a/agent-cli/test-agent/config/agent_card.json b/agent-cli/test-agent/config/agent_card.json new file mode 100644 index 000000000..628162eb4 --- /dev/null +++ b/agent-cli/test-agent/config/agent_card.json @@ -0,0 +1,32 @@ +{ + "name": "test-agent", + "description": "A default mode Go agent", + "url": "http://localhost:8080", + "version": "1.0.0", + "capabilities": { + "supportsStreaming": true, + "supportedModes": [ + "text", + "json" + ] + }, + "defaultInputModes": [ + "text" + ], + "defaultOutputModes": [ + "text" + ], + "skills": [ + { + "name": "example_skill", + "description": "An example skill implementation", + "parameters": { + "input": { + "description": "Input text for processing", + "required": true, + "type": "string" + } + } + } + ] +} diff --git a/goimports.sh b/agent-cli/test-agent/config/config.yaml old mode 100755 new mode 100644 similarity index 70% rename from goimports.sh rename to agent-cli/test-agent/config/config.yaml index 0322f796c..a851cb1b8 --- a/goimports.sh +++ b/agent-cli/test-agent/config/config.yaml @@ -1,4 +1,3 @@ -# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. @@ -13,17 +12,24 @@ # 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. -# -# format go imports style -go install golang.org/x/tools/cmd/goimports -goimports -local seata.apache.org/seata-go -w . +# test-agent Agent Configuration +# Generated on 2025-09-26 21:33:08 + +server: + host: "0.0.0.0" + port: 8080 + mode: "release" + +agent: + name: "test-agent" + version: "1.0.0" + description: "A default mode Go agent" -# format licence style -go install github.com/apache/skywalking-eyes/cmd/license-eye@latest -license-eye header fix -# check dependency licence is valid -license-eye dependency check +log: + level: "info" + format: "json" -# format go.mod -go mod tidy \ No newline at end of file +skills: + enabled: + - "example" diff --git a/.pre-commit-config.yaml b/agent-cli/test-agent/docker-compose.yml similarity index 77% rename from .pre-commit-config.yaml rename to agent-cli/test-agent/docker-compose.yml index 2845a2862..95e5fd7c2 100644 --- a/.pre-commit-config.yaml +++ b/agent-cli/test-agent/docker-compose.yml @@ -1,4 +1,3 @@ -# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. @@ -13,12 +12,16 @@ # 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. -# -# See https://pre-commit.com for more information -# See https://pre-commit.com/hooks.html for more hooks -repos: -- repo: http://github.com/golangci/golangci-lint - rev: v1.42.1 - hooks: - - id: golangci-lint +version: '3.8' + +services: + test-agent: + build: . + ports: + - "8080:8080" + volumes: + - ./config:/root/config + environment: + - GIN_MODE=release + restart: unless-stopped diff --git a/agent-cli/test-agent/go.mod b/agent-cli/test-agent/go.mod new file mode 100644 index 000000000..90236f69f --- /dev/null +++ b/agent-cli/test-agent/go.mod @@ -0,0 +1,10 @@ +module test-agent + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/spf13/viper v1.18.2 + gopkg.in/yaml.v3 v3.0.1 + go.uber.org/zap v1.26.0 +) diff --git a/agent-cli/test-agent/go.sum b/agent-cli/test-agent/go.sum new file mode 100644 index 000000000..321184f70 --- /dev/null +++ b/agent-cli/test-agent/go.sum @@ -0,0 +1,5 @@ +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/agent-cli/test-agent/internal/agent/agent.go b/agent-cli/test-agent/internal/agent/agent.go new file mode 100644 index 000000000..83c5cf028 --- /dev/null +++ b/agent-cli/test-agent/internal/agent/agent.go @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package agent + +import ( + "context" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + + "test-agent/internal/agent/handlers" + "test-agent/internal/agent/skills" + "test-agent/internal/config" +) + +type Agent struct { + cfg *config.Config + logger *zap.Logger + router *gin.Engine + skills map[string]skills.Skill +} + +func New(cfg *config.Config, logger *zap.Logger) *Agent { + a := &Agent{ + cfg: cfg, + logger: logger, + router: gin.New(), + skills: make(map[string]skills.Skill), + } + + a.initializeSkills() + a.setupRoutes() + return a +} + +func (a *Agent) initializeSkills() { + a.skills["example"] = skills.NewExampleSkill(a.logger) +} + +func (a *Agent) setupRoutes() { + a.router.Use(gin.Logger()) + a.router.Use(gin.Recovery()) + + a.router.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "healthy"}) + }) + + a.router.GET("/agent-card", handlers.NewAgentCardHandler(a.cfg).Handle) + + skillsGroup := a.router.Group("/skills") + for name, skill := range a.skills { + skillsGroup.POST("/"+name, a.createSkillHandler(skill)) + } +} + +func (a *Agent) createSkillHandler(skill skills.Skill) gin.HandlerFunc { + return func(c *gin.Context) { + var request map[string]interface{} + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + result, err := skill.Execute(context.Background(), request) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) + } +} + +func (a *Agent) Start() error { + addr := fmt.Sprintf("%s:%d", a.cfg.Server.Host, a.cfg.Server.Port) + a.logger.Info("Starting agent server", zap.String("address", addr)) + return a.router.Run(addr) +} diff --git a/pkg/rm/tcc/fence/config/tcc_fence_config.go b/agent-cli/test-agent/internal/agent/handlers/rpc.go similarity index 63% rename from pkg/rm/tcc/fence/config/tcc_fence_config.go rename to agent-cli/test-agent/internal/agent/handlers/rpc.go index 6df42870c..f5ef34848 100644 --- a/pkg/rm/tcc/fence/config/tcc_fence_config.go +++ b/agent-cli/test-agent/internal/agent/handlers/rpc.go @@ -15,22 +15,30 @@ * limitations under the License. */ -package config +package handlers import ( - "seata.apache.org/seata-go/pkg/rm/tcc/fence/handler" -) - -func InitFence() { + "net/http" -} + "github.com/gin-gonic/gin" -func InitCleanTask(dsn string) { + "test-agent/internal/config" +) - go handler.GetFenceHandler().InitLogCleanChannel(dsn) +type AgentCardHandler struct { + cfg *config.Config +} +func NewAgentCardHandler(cfg *config.Config) *AgentCardHandler { + return &AgentCardHandler{cfg: cfg} } -func Destroy() { - handler.GetFenceHandler().DestroyLogCleanChannel() +func (h *AgentCardHandler) Handle(c *gin.Context) { + agentCard, err := h.cfg.LoadAgentCard() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load agent card"}) + return + } + + c.JSON(http.StatusOK, agentCard) } diff --git a/agent-cli/test-agent/internal/agent/skills/example.go b/agent-cli/test-agent/internal/agent/skills/example.go new file mode 100644 index 000000000..54a5f34b6 --- /dev/null +++ b/agent-cli/test-agent/internal/agent/skills/example.go @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package skills + +import ( + "context" + "fmt" + + "go.uber.org/zap" +) + +type Skill interface { + Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) + Name() string + Description() string +} + +type ExampleSkill struct { + logger *zap.Logger +} + +func NewExampleSkill(logger *zap.Logger) *ExampleSkill { + return &ExampleSkill{logger: logger} +} + +func (s *ExampleSkill) Name() string { + return "example" +} + +func (s *ExampleSkill) Description() string { + return "An example skill that processes input text" +} + +func (s *ExampleSkill) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) { + input, ok := params["input"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'input' parameter") + } + + s.logger.Info("Executing example skill", zap.String("input", input)) + + result := fmt.Sprintf("Processed: %s (length: %d)", input, len(input)) + + return map[string]interface{}{ + "result": result, + "original": input, + "length": len(input), + }, nil +} diff --git a/agent-cli/test-agent/internal/config/config.go b/agent-cli/test-agent/internal/config/config.go new file mode 100644 index 000000000..6be749d59 --- /dev/null +++ b/agent-cli/test-agent/internal/config/config.go @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package config + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/spf13/viper" +) + +type Config struct { + Server ServerConfig + Agent AgentConfig + Log LogConfig + Skills SkillsConfig +} + +type ServerConfig struct { + Host string + Port int + Mode string +} + +type AgentConfig struct { + Name string + Version string + Description string +} + +type LogConfig struct { + Level string + Format string +} + +type SkillsConfig struct { + Enabled []string +} + +func Load() (*Config, error) { + viper.SetConfigName("config") + viper.SetConfigType("yaml") + viper.AddConfigPath("./config") + viper.AddConfigPath(".") + + viper.SetDefault("server.host", "0.0.0.0") + viper.SetDefault("server.port", 8080) + viper.SetDefault("server.mode", "release") + viper.SetDefault("log.level", "info") + viper.SetDefault("log.format", "json") + + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + + var cfg Config + if err := viper.Unmarshal(&cfg); err != nil { + return nil, err + } + + return &cfg, nil +} + +func (c *Config) LoadAgentCard() (interface{}, error) { + cardPath := filepath.Join("config", "agent_card.json") + data, err := os.ReadFile(cardPath) + if err != nil { + return nil, err + } + + var card interface{} + if err := json.Unmarshal(data, &card); err != nil { + return nil, err + } + + return card, nil +} diff --git a/pkg/datasource/sql/types/key_type.go b/agent-cli/test-agent/internal/utils/logger.go similarity index 58% rename from pkg/datasource/sql/types/key_type.go rename to agent-cli/test-agent/internal/utils/logger.go index 7e0ef02c2..872d12ff8 100644 --- a/pkg/datasource/sql/types/key_type.go +++ b/agent-cli/test-agent/internal/utils/logger.go @@ -15,25 +15,33 @@ * limitations under the License. */ -package types +package utils -type KeyType string - -var ( - // Null key type. - Null KeyType = "NULL" - - // PrimaryKey The Primary key - PrimaryKey KeyType = "PRIMARY_KEY" +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) -func (k KeyType) Number() IndexType { - switch k { - case Null: - return 0 - case PrimaryKey: - return 1 +func NewLogger(level string) *zap.Logger { + var zapLevel zapcore.Level + switch level { + case "debug": + zapLevel = zapcore.DebugLevel + case "info": + zapLevel = zapcore.InfoLevel + case "warn": + zapLevel = zapcore.WarnLevel + case "error": + zapLevel = zapcore.ErrorLevel default: - return 0 + zapLevel = zapcore.InfoLevel } + + config := zap.NewProductionConfig() + config.Level.SetLevel(zapLevel) + config.EncoderConfig.TimeKey = "timestamp" + config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + + logger, _ := config.Build() + return logger } diff --git a/pkg/util/flagext/stringmap.go b/agent-cli/test-agent/main.go similarity index 68% rename from pkg/util/flagext/stringmap.go rename to agent-cli/test-agent/main.go index 507912493..4ce191420 100644 --- a/pkg/util/flagext/stringmap.go +++ b/agent-cli/test-agent/main.go @@ -15,25 +15,27 @@ * limitations under the License. */ -package flagext +package main import ( - "encoding/json" -) + "log" -// StringMap is a map of string that implements flag.Value -type StringMap map[string]string + "test-agent/internal/agent" + "test-agent/internal/config" + "test-agent/internal/utils" +) -// String implements flag.Value -func (v StringMap) String() string { - data, err := json.Marshal(v) +func main() { + cfg, err := config.Load() if err != nil { - panic(err) + log.Fatalf("Failed to load config: %v", err) } - return string(data) -} -// Set implements flag.Value -func (v *StringMap) Set(s string) error { - return json.Unmarshal([]byte(s), &v) + logger := utils.NewLogger(cfg.Log.Level) + defer logger.Sync() + + agent := agent.New(cfg, logger) + if err := agent.Start(); err != nil { + logger.Fatal("Failed to start agent", "error", err) + } } diff --git a/.asf.yaml b/agent-cli/test-agent/scripts/build.sh similarity index 54% rename from .asf.yaml rename to agent-cli/test-agent/scripts/build.sh index 6a710d29e..708b5acc7 100644 --- a/.asf.yaml +++ b/agent-cli/test-agent/scripts/build.sh @@ -1,4 +1,4 @@ -# +#!/bin/bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. @@ -13,30 +13,15 @@ # 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. -# -github: - description: "Go Implementation For Seata" - homepage: https://seata.apache.org/ - labels: - - at - - tcc - - saga - - xa - enabled_merge_buttons: - squash: true - merge: false - rebase: false - dependabot_alerts: true - dependabot_updates: false - protected_branches: - master: - required_status_checks: - strict: true - required_pull_request_reviews: - dismiss_stale_reviews: true - required_approving_review_count: 1 -notifications: - commits: notifications@seata.apache.org - issues: notifications@seata.apache.org - pullrequests: notifications@seata.apache.org - discussions: dev@seata.apache.org + +set -e + +echo "Building test-agent..." + +rm -rf ./bin +mkdir -p ./bin + +go build -o ./bin/test-agent ./main.go + +echo "Build completed successfully!" +echo "Binary location: ./bin/test-agent" diff --git a/agent-cli/test-agent/scripts/run.sh b/agent-cli/test-agent/scripts/run.sh new file mode 100644 index 000000000..06d02b6c4 --- /dev/null +++ b/agent-cli/test-agent/scripts/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 + +echo "Starting test-agent..." + +if [ ! -f "./config/config.yaml" ]; then + echo "Error: config/config.yaml not found" + exit 1 +fi + +go run main.go diff --git a/agent-cli/test-provider/config/agent_card.json b/agent-cli/test-provider/config/agent_card.json new file mode 100644 index 000000000..03b26ef2a --- /dev/null +++ b/agent-cli/test-provider/config/agent_card.json @@ -0,0 +1,37 @@ +{ + "name": "test-provider", + "description": "A provider mode Go agent", + "url": "http://localhost:8080", + "provider": { + "name": "test-provider-provider", + "description": "External service provider integration", + "url": "http://localhost:8080/provider" + }, + "version": "1.0.0", + "capabilities": { + "supportsStreaming": true, + "supportedModes": [ + "text", + "json" + ] + }, + "defaultInputModes": [ + "text" + ], + "defaultOutputModes": [ + "text" + ], + "skills": [ + { + "name": "external_service_call", + "description": "Call external service through adapter", + "parameters": { + "endpoint": { + "description": "External service endpoint", + "required": true, + "type": "string" + } + } + } + ] +} diff --git a/agent-cli/test-provider/config/config.yaml b/agent-cli/test-provider/config/config.yaml new file mode 100644 index 000000000..350e247b9 --- /dev/null +++ b/agent-cli/test-provider/config/config.yaml @@ -0,0 +1,35 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# test-provider Agent Configuration +# Generated on 2025-09-26 21:33:48 + +server: + host: "0.0.0.0" + port: 8080 + mode: "release" + +agent: + name: "test-provider" + version: "1.0.0" + description: "A default mode Go agent" + +log: + level: "info" + format: "json" + +skills: + enabled: + - "example" diff --git a/agent-cli/test-provider/docs/integration.md b/agent-cli/test-provider/docs/integration.md new file mode 100644 index 000000000..8a658f973 --- /dev/null +++ b/agent-cli/test-provider/docs/integration.md @@ -0,0 +1,69 @@ + + +# test-provider + +A provider mode Go agent generated on 2025-09-26 21:33:48. + +## Description + +This is a Go-based agent that provides skill-based functionality through HTTP APIs. + +## Features + +- RESTful API endpoints +- Configurable skills system +- Health check endpoint +- Agent card endpoint +- Docker support +- Structured logging + +## Quick Start + +1. Install dependencies: + go mod tidy + +2. Run the agent: + go run main.go + +3. Test the agent: + curl http://localhost:8080/health + curl http://localhost:8080/agent-card + curl -X POST http://localhost:8080/skills/example -H "Content-Type: application/json" -d '{"input": "Hello World"}' + +## Configuration + +Edit config/config.yaml to customize the agent settings. + +## Docker + +docker-compose up --build + +## API Endpoints + +- GET /health - Health check +- GET /agent-card - Agent capabilities +- POST /skills/{skill-name} - Execute a skill + +## Skills + +- example: Processes input text and returns analysis + +## Development + +- scripts/build.sh - Build the project +- scripts/run.sh - Run in development mode diff --git a/agent-cli/test-provider/examples/external_service.go b/agent-cli/test-provider/examples/external_service.go new file mode 100644 index 000000000..d9f478922 --- /dev/null +++ b/agent-cli/test-provider/examples/external_service.go @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +# test-provider + +A provider mode Go agent generated on 2025-09-26 21:33:48. + +## Description + +This is a Go-based agent that provides skill-based functionality through HTTP APIs. + +## Features + +- RESTful API endpoints +- Configurable skills system +- Health check endpoint +- Agent card endpoint +- Docker support +- Structured logging + +## Quick Start + +1. Install dependencies: + go mod tidy + +2. Run the agent: + go run main.go + +3. Test the agent: + curl http://localhost:8080/health + curl http://localhost:8080/agent-card + curl -X POST http://localhost:8080/skills/example -H "Content-Type: application/json" -d '{"input": "Hello World"}' + +## Configuration + +Edit config/config.yaml to customize the agent settings. + +## Docker + +docker-compose up --build + +## API Endpoints + +- GET /health - Health check +- GET /agent-card - Agent capabilities +- POST /skills/{skill-name} - Execute a skill + +## Skills + +- example: Processes input text and returns analysis + +## Development + +- scripts/build.sh - Build the project +- scripts/run.sh - Run in development mode diff --git a/agent-cli/test-provider/go.mod b/agent-cli/test-provider/go.mod new file mode 100644 index 000000000..d15e84b41 --- /dev/null +++ b/agent-cli/test-provider/go.mod @@ -0,0 +1,10 @@ +module test-provider + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/spf13/viper v1.18.2 + gopkg.in/yaml.v3 v3.0.1 + go.uber.org/zap v1.26.0 +) diff --git a/agent-cli/test-provider/go.sum b/agent-cli/test-provider/go.sum new file mode 100644 index 000000000..321184f70 --- /dev/null +++ b/agent-cli/test-provider/go.sum @@ -0,0 +1,5 @@ +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/agent-cli/test-provider/internal/agent/agent.go b/agent-cli/test-provider/internal/agent/agent.go new file mode 100644 index 000000000..0f2176c68 --- /dev/null +++ b/agent-cli/test-provider/internal/agent/agent.go @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package agent + +import ( + "context" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + + "test-provider/internal/agent/handlers" + "test-provider/internal/agent/skills" + "test-provider/internal/config" +) + +type Agent struct { + cfg *config.Config + logger *zap.Logger + router *gin.Engine + skills map[string]skills.Skill +} + +func New(cfg *config.Config, logger *zap.Logger) *Agent { + a := &Agent{ + cfg: cfg, + logger: logger, + router: gin.New(), + skills: make(map[string]skills.Skill), + } + + a.initializeSkills() + a.setupRoutes() + return a +} + +func (a *Agent) initializeSkills() { + a.skills["example"] = skills.NewExampleSkill(a.logger) +} + +func (a *Agent) setupRoutes() { + a.router.Use(gin.Logger()) + a.router.Use(gin.Recovery()) + + a.router.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "healthy"}) + }) + + a.router.GET("/agent-card", handlers.NewAgentCardHandler(a.cfg).Handle) + + skillsGroup := a.router.Group("/skills") + for name, skill := range a.skills { + skillsGroup.POST("/"+name, a.createSkillHandler(skill)) + } +} + +func (a *Agent) createSkillHandler(skill skills.Skill) gin.HandlerFunc { + return func(c *gin.Context) { + var request map[string]interface{} + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + result, err := skill.Execute(context.Background(), request) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) + } +} + +func (a *Agent) Start() error { + addr := fmt.Sprintf("%s:%d", a.cfg.Server.Host, a.cfg.Server.Port) + a.logger.Info("Starting agent server", zap.String("address", addr)) + return a.router.Run(addr) +} diff --git a/agent-cli/test-provider/internal/agent/provider/adapter.go b/agent-cli/test-provider/internal/agent/provider/adapter.go new file mode 100644 index 000000000..0f2176c68 --- /dev/null +++ b/agent-cli/test-provider/internal/agent/provider/adapter.go @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package agent + +import ( + "context" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + + "test-provider/internal/agent/handlers" + "test-provider/internal/agent/skills" + "test-provider/internal/config" +) + +type Agent struct { + cfg *config.Config + logger *zap.Logger + router *gin.Engine + skills map[string]skills.Skill +} + +func New(cfg *config.Config, logger *zap.Logger) *Agent { + a := &Agent{ + cfg: cfg, + logger: logger, + router: gin.New(), + skills: make(map[string]skills.Skill), + } + + a.initializeSkills() + a.setupRoutes() + return a +} + +func (a *Agent) initializeSkills() { + a.skills["example"] = skills.NewExampleSkill(a.logger) +} + +func (a *Agent) setupRoutes() { + a.router.Use(gin.Logger()) + a.router.Use(gin.Recovery()) + + a.router.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "healthy"}) + }) + + a.router.GET("/agent-card", handlers.NewAgentCardHandler(a.cfg).Handle) + + skillsGroup := a.router.Group("/skills") + for name, skill := range a.skills { + skillsGroup.POST("/"+name, a.createSkillHandler(skill)) + } +} + +func (a *Agent) createSkillHandler(skill skills.Skill) gin.HandlerFunc { + return func(c *gin.Context) { + var request map[string]interface{} + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + result, err := skill.Execute(context.Background(), request) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) + } +} + +func (a *Agent) Start() error { + addr := fmt.Sprintf("%s:%d", a.cfg.Server.Host, a.cfg.Server.Port) + a.logger.Info("Starting agent server", zap.String("address", addr)) + return a.router.Run(addr) +} diff --git a/agent-cli/test-provider/internal/agent/provider/client.go b/agent-cli/test-provider/internal/agent/provider/client.go new file mode 100644 index 000000000..0f2176c68 --- /dev/null +++ b/agent-cli/test-provider/internal/agent/provider/client.go @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package agent + +import ( + "context" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + + "test-provider/internal/agent/handlers" + "test-provider/internal/agent/skills" + "test-provider/internal/config" +) + +type Agent struct { + cfg *config.Config + logger *zap.Logger + router *gin.Engine + skills map[string]skills.Skill +} + +func New(cfg *config.Config, logger *zap.Logger) *Agent { + a := &Agent{ + cfg: cfg, + logger: logger, + router: gin.New(), + skills: make(map[string]skills.Skill), + } + + a.initializeSkills() + a.setupRoutes() + return a +} + +func (a *Agent) initializeSkills() { + a.skills["example"] = skills.NewExampleSkill(a.logger) +} + +func (a *Agent) setupRoutes() { + a.router.Use(gin.Logger()) + a.router.Use(gin.Recovery()) + + a.router.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "healthy"}) + }) + + a.router.GET("/agent-card", handlers.NewAgentCardHandler(a.cfg).Handle) + + skillsGroup := a.router.Group("/skills") + for name, skill := range a.skills { + skillsGroup.POST("/"+name, a.createSkillHandler(skill)) + } +} + +func (a *Agent) createSkillHandler(skill skills.Skill) gin.HandlerFunc { + return func(c *gin.Context) { + var request map[string]interface{} + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + result, err := skill.Execute(context.Background(), request) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) + } +} + +func (a *Agent) Start() error { + addr := fmt.Sprintf("%s:%d", a.cfg.Server.Host, a.cfg.Server.Port) + a.logger.Info("Starting agent server", zap.String("address", addr)) + return a.router.Run(addr) +} diff --git a/agent-cli/test-provider/internal/agent/provider/mapper.go b/agent-cli/test-provider/internal/agent/provider/mapper.go new file mode 100644 index 000000000..0f2176c68 --- /dev/null +++ b/agent-cli/test-provider/internal/agent/provider/mapper.go @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package agent + +import ( + "context" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + + "test-provider/internal/agent/handlers" + "test-provider/internal/agent/skills" + "test-provider/internal/config" +) + +type Agent struct { + cfg *config.Config + logger *zap.Logger + router *gin.Engine + skills map[string]skills.Skill +} + +func New(cfg *config.Config, logger *zap.Logger) *Agent { + a := &Agent{ + cfg: cfg, + logger: logger, + router: gin.New(), + skills: make(map[string]skills.Skill), + } + + a.initializeSkills() + a.setupRoutes() + return a +} + +func (a *Agent) initializeSkills() { + a.skills["example"] = skills.NewExampleSkill(a.logger) +} + +func (a *Agent) setupRoutes() { + a.router.Use(gin.Logger()) + a.router.Use(gin.Recovery()) + + a.router.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "healthy"}) + }) + + a.router.GET("/agent-card", handlers.NewAgentCardHandler(a.cfg).Handle) + + skillsGroup := a.router.Group("/skills") + for name, skill := range a.skills { + skillsGroup.POST("/"+name, a.createSkillHandler(skill)) + } +} + +func (a *Agent) createSkillHandler(skill skills.Skill) gin.HandlerFunc { + return func(c *gin.Context) { + var request map[string]interface{} + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + result, err := skill.Execute(context.Background(), request) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) + } +} + +func (a *Agent) Start() error { + addr := fmt.Sprintf("%s:%d", a.cfg.Server.Host, a.cfg.Server.Port) + a.logger.Info("Starting agent server", zap.String("address", addr)) + return a.router.Run(addr) +} diff --git a/pkg/util/errors/error.go b/agent-cli/test-provider/internal/handlers/rpc.go similarity index 63% rename from pkg/util/errors/error.go rename to agent-cli/test-provider/internal/handlers/rpc.go index f04482466..f62dc1c10 100644 --- a/pkg/util/errors/error.go +++ b/agent-cli/test-provider/internal/handlers/rpc.go @@ -15,26 +15,30 @@ * limitations under the License. */ -package errors +package handlers import ( - "fmt" + "net/http" + + "github.com/gin-gonic/gin" + + "test-provider/internal/config" ) -type SeataError struct { - Code TransactionErrorCode - Message string - Parent error +type AgentCardHandler struct { + cfg *config.Config } -func (e SeataError) Error() string { - return fmt.Sprintf("SeataError code %d, msg %s, parent msg is %s", e.Code, e.Message, e.Parent) +func NewAgentCardHandler(cfg *config.Config) *AgentCardHandler { + return &AgentCardHandler{cfg: cfg} } -func New(code TransactionErrorCode, msg string, parent error) *SeataError { - return &SeataError{ - code, - msg, - parent, +func (h *AgentCardHandler) Handle(c *gin.Context) { + agentCard, err := h.cfg.LoadAgentCard() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load agent card"}) + return } + + c.JSON(http.StatusOK, agentCard) } diff --git a/agent-cli/test-provider/internal/skills/provider_skills.go b/agent-cli/test-provider/internal/skills/provider_skills.go new file mode 100644 index 000000000..54a5f34b6 --- /dev/null +++ b/agent-cli/test-provider/internal/skills/provider_skills.go @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package skills + +import ( + "context" + "fmt" + + "go.uber.org/zap" +) + +type Skill interface { + Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) + Name() string + Description() string +} + +type ExampleSkill struct { + logger *zap.Logger +} + +func NewExampleSkill(logger *zap.Logger) *ExampleSkill { + return &ExampleSkill{logger: logger} +} + +func (s *ExampleSkill) Name() string { + return "example" +} + +func (s *ExampleSkill) Description() string { + return "An example skill that processes input text" +} + +func (s *ExampleSkill) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) { + input, ok := params["input"].(string) + if !ok { + return nil, fmt.Errorf("missing or invalid 'input' parameter") + } + + s.logger.Info("Executing example skill", zap.String("input", input)) + + result := fmt.Sprintf("Processed: %s (length: %d)", input, len(input)) + + return map[string]interface{}{ + "result": result, + "original": input, + "length": len(input), + }, nil +} diff --git a/pkg/protocol/codec/global_begin_request_codec_test.go b/agent-cli/test-provider/main.go similarity index 66% rename from pkg/protocol/codec/global_begin_request_codec_test.go rename to agent-cli/test-provider/main.go index f79b3102c..acd86874b 100644 --- a/pkg/protocol/codec/global_begin_request_codec_test.go +++ b/agent-cli/test-provider/main.go @@ -15,26 +15,27 @@ * limitations under the License. */ -package codec +package main import ( - "testing" - "time" + "log" - "github.com/stretchr/testify/assert" - - "seata.apache.org/seata-go/pkg/protocol/message" + "test-provider/internal/agent" + "test-provider/internal/config" + "test-provider/internal/utils" ) -func TestGlobalBeginRequestCodec(t *testing.T) { - msg := message.GlobalBeginRequest{ - Timeout: 2 * time.Second, - TransactionName: "SeataGoTransaction", +func main() { + cfg, err := config.Load() + if err != nil { + log.Fatalf("Failed to load config: %v", err) } - codec := GlobalBeginRequestCodec{} - bytes := codec.Encode(msg) - msg2 := codec.Decode(bytes) + logger := utils.NewLogger(cfg.Log.Level) + defer logger.Sync() - assert.Equal(t, msg, msg2) + agent := agent.New(cfg, logger) + if err := agent.Start(); err != nil { + logger.Fatal("Failed to start agent", "error", err) + } } diff --git a/agenthub/.gitignore b/agenthub/.gitignore new file mode 100644 index 000000000..d77213fa8 --- /dev/null +++ b/agenthub/.gitignore @@ -0,0 +1,60 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# Binaries +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary +*.test + +# Output of the go coverage tool +*.out + +# Dependency directories +vendor/ + +# Go workspace file +go.work + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Log files +*.log + +# Config files (if they contain secrets) +configs/local.yaml +configs/local.json + +# Build artifacts +build/ +dist/ +bin/ \ No newline at end of file diff --git a/agenthub/API_TEST_DOCUMENTATION.md b/agenthub/API_TEST_DOCUMENTATION.md new file mode 100644 index 000000000..ac49152b4 --- /dev/null +++ b/agenthub/API_TEST_DOCUMENTATION.md @@ -0,0 +1,743 @@ + + +# AgentHub API 测试文档 + +## 基础信息 + +- **服务地址**: `http://localhost:8080` +- **内容类型**: `application/json` +- **认证方式**: Bearer Token (部分接口需要) + +--- + +## 1. 认证接口 + +### 1.1 生成Token + +```http +POST /auth/token +Content-Type: application/json + +{ + "username": "testuser" +} +``` + +**响应示例**: + +```json +{ + "success": true, + "data": { + "token": "eyJhbGciOiJIUzI1NiIs...", + "expires_in": 3600 + } +} +``` + +### 1.2 刷新Token + +```http +POST /auth/refresh +Content-Type: application/json + +{ + "refresh_token": "eyJhbGciOiJIUzI1NiIs..." +} +``` + +--- + +## 2. Agent管理接口 + +### 2.1 注册Agent + +```http +POST /agent/register +Content-Type: application/json + +{ + "agent_card": { + "name": "test-agent", + "description": "测试代理", + "url": "http://example.com/agent", + "version": "1.0.0", + "capabilities": { + "streaming": true, + "pushNotifications": false + }, + "defaultInputModes": ["text"], + "defaultOutputModes": ["text"], + "skills": [ + { + "id": "skill1", + "name": "文本处理", + "description": "处理文本数据", + "tags": ["nlp", "text"], + "examples": ["处理用户输入"], + "inputModes": ["text"], + "outputModes": ["text"] + } + ] + }, + "host": "192.168.1.100", + "port": 8081 +} +``` + +**响应示例**: + +```json +{ + "success": true, + "message": "Agent registered successfully", + "agent_id": "test-agent" +} +``` + +### 2.2 发现Agent + +```http +POST /agent/discover +Content-Type: application/json + +{ + "query": "文本处理" +} +``` + +**响应示例**: + +```json +{ + "agents": [ + { + "name": "test-agent", + "description": "测试代理", + "url": "http://example.com/agent", + "version": "1.0.0", + "capabilities": { + "streaming": true + }, + "skills": [...] + } + ] +} +``` + +### 2.3 获取Agent详情 + +```http +GET /agent/get?id=test-agent +``` + +**响应示例**: + +```json +{ + "success": true, + "data": { + "id": "test-agent", + "kind": "RegisteredAgent", + "version": "v1", + "agent_card": {...}, + "host": "192.168.1.100", + "port": 8081, + "status": "active", + "last_seen": "2025-01-20T10:00:00Z", + "registered_at": "2025-01-20T09:00:00Z" + } +} +``` + +### 2.4 列出所有Agent + +```http +GET /agents +``` + +**响应示例**: + +```json +{ + "success": true, + "data": [ + { + "id": "test-agent", + "status": "active", + "agent_card": {...} + } + ] +} +``` + +### 2.5 更新Agent状态 + +```http +PUT /agent/status +Content-Type: application/json + +{ + "agent_id": "test-agent", + "status": "inactive" +} +``` + +### 2.6 删除Agent + +```http +DELETE /agent/remove?id=test-agent +``` + +### 2.7 Agent心跳 + +```http +POST /agent/heartbeat +Content-Type: application/json + +{ + "agent_id": "test-agent", + "timestamp": "2025-01-20T10:00:00Z" +} +``` + +### 2.8 动态上下文分析 ⭐ + +```http +POST /agent/analyze +Content-Type: application/json + +{ + "need_description": "我需要对用户输入的文本进行情感分析和关键词提取", + "user_context": "电商平台用户评论分析" +} +``` + +**响应示例**: + +```json +{ + "success": true, + "message": "Successfully analyzed and routed request", + "matched_skills": [ + { + "id": "sentiment_analysis", + "name": "情感分析", + "description": "分析文本的情感倾向", + "tags": ["nlp", "sentiment", "emotion"], + "examples": ["分析评论情感", "判断文本正负面"] + } + ], + "route_result": { + "agent_url": "http://localhost:8081/text-processor", + "skill_id": "sentiment_analysis", + "skill_name": "情感分析", + "agent_info": { + "name": "text-processor-agent", + "description": "专业文本处理代理" + } + } +} +``` + +--- + +## 3. 系统接口 + +### 3.1 健康检查 + +```http +GET /health +``` + +**响应示例**: + +```json +{ + "status": "healthy", + "timestamp": "2025-01-20T10:00:00Z" +} +``` + +### 3.2 根路径 + +```http +GET / +``` + +**响应示例**: + +``` +AgentHub is running +``` + +### 3.3 监控指标 + +```http +GET /metrics +``` + +**响应示例**: + +```json +{ + "status": "healthy" +} +``` + +--- + +## 4. 动态上下文分析测试用例 ⭐ + +### 4.1 测试场景示例 + +#### 场景1: 文本处理需求 + +```json +{ + "need_description": "我需要对用户输入的文本进行情感分析和关键词提取", + "user_context": "电商平台用户评论分析" +} +``` + +#### 场景2: 数据分析需求 + +```json +{ + "need_description": "需要分析销售数据,生成可视化图表", + "user_context": "月度销售报告生成" +} +``` + +#### 场景3: 图像处理需求 + +```json +{ + "need_description": "需要识别图片中的物体和文字", + "user_context": "产品图片自动标注" +} +``` + +#### 场景4: 复杂组合需求 + +```json +{ + "need_description": "我需要先处理用户上传的文档,然后根据内容生成摘要,最后转换为语音播报", + "user_context": "智能文档助手场景" +} +``` + +### 4.2 动态上下文完整测试流程 + +```bash +# 步骤1: 注册一个文本分析Agent +curl -X POST http://localhost:8080/agent/register \ + -H "Content-Type: application/json" \ + -d '{ + "agent_card": { + "name": "text-analyzer", + "description": "文本分析代理", + "url": "http://localhost:8081", + "version": "1.0.0", + "capabilities": {"streaming": true}, + "defaultInputModes": ["text"], + "defaultOutputModes": ["json"], + "skills": [ + { + "id": "sentiment_analysis", + "name": "情感分析", + "description": "分析文本的情感倾向", + "tags": ["nlp", "sentiment", "emotion"], + "examples": ["分析评论情感", "判断文本正负面"] + }, + { + "id": "keyword_extraction", + "name": "关键词提取", + "description": "从文本中提取关键词", + "tags": ["nlp", "keywords", "extraction"], + "examples": ["提取文档关键词", "分析主题"] + } + ] + }, + "host": "localhost", + "port": 8081 + }' + +# 步骤2: 注册一个数据分析Agent +curl -X POST http://localhost:8080/agent/register \ + -H "Content-Type: application/json" \ + -d '{ + "agent_card": { + "name": "data-analyzer", + "description": "数据分析代理", + "url": "http://localhost:8082", + "version": "1.0.0", + "capabilities": {"streaming": false}, + "defaultInputModes": ["json", "csv"], + "defaultOutputModes": ["json", "image"], + "skills": [ + { + "id": "data_visualization", + "name": "数据可视化", + "description": "生成各种类型的数据图表", + "tags": ["data", "chart", "visualization"], + "examples": ["生成销售图表", "制作趋势分析"] + }, + { + "id": "statistical_analysis", + "name": "统计分析", + "description": "对数据进行统计分析", + "tags": ["statistics", "analysis", "data"], + "examples": ["计算相关性", "趋势分析"] + } + ] + }, + "host": "localhost", + "port": 8082 + }' + +# 步骤3: 测试文本分析需求匹配 +curl -X POST http://localhost:8080/agent/analyze \ + -H "Content-Type: application/json" \ + -d '{ + "need_description": "我需要分析用户评论的情感倾向,找出正面和负面的关键词", + "user_context": "电商平台用户反馈分析" + }' + +# 步骤4: 测试数据分析需求匹配 +curl -X POST http://localhost:8080/agent/analyze \ + -H "Content-Type: application/json" \ + -d '{ + "need_description": "需要对销售数据进行统计分析,并生成可视化图表", + "user_context": "月度销售报告生成" + }' + +# 步骤5: 测试无匹配技能的需求 +curl -X POST http://localhost:8080/agent/analyze \ + -H "Content-Type: application/json" \ + -d '{ + "need_description": "我需要训练深度学习模型进行图像识别", + "user_context": "AI视觉项目" + }' + +# 步骤6: 验证所有注册的Agent +curl -X GET http://localhost:8080/agents +``` + +--- + +## 5. 完整API测试用例 + +### 5.1 基本流程测试 + +```bash +# 1. 健康检查 +curl -X GET http://localhost:8080/health + +# 2. 获取认证token +curl -X POST http://localhost:8080/auth/token \ + -H "Content-Type: application/json" \ + -d '{"username": "testuser"}' + +# 3. 注册基础Agent +curl -X POST http://localhost:8080/agent/register \ + -H "Content-Type: application/json" \ + -d '{ + "agent_card": { + "name": "basic-agent", + "description": "基础测试代理", + "url": "http://example.com/agent", + "version": "1.0.0", + "capabilities": {"streaming": true}, + "defaultInputModes": ["text"], + "defaultOutputModes": ["text"], + "skills": [] + }, + "host": "localhost", + "port": 8081 + }' + +# 4. 查看所有Agent +curl -X GET http://localhost:8080/agents + +# 5. 发现Agent +curl -X POST http://localhost:8080/agent/discover \ + -H "Content-Type: application/json" \ + -d '{"query": "basic"}' + +# 6. Agent心跳 +curl -X POST http://localhost:8080/agent/heartbeat \ + -H "Content-Type: application/json" \ + -d '{"agent_id": "basic-agent"}' +``` + +### 5.2 PowerShell测试脚本 + +```powershell +# PowerShell完整测试脚本 +Write-Host "开始AgentHub API测试..." -ForegroundColor Green + +# 1. 健康检查 +Write-Host "1. 健康检查..." -ForegroundColor Yellow +try { + $healthResponse = Invoke-RestMethod -Uri "http://localhost:8080/health" -Method Get + Write-Host "健康检查成功: $($healthResponse | ConvertTo-Json)" -ForegroundColor Green +} catch { + Write-Host "健康检查失败: $_" -ForegroundColor Red + exit +} + +# 2. 注册文本分析Agent +Write-Host "2. 注册文本分析Agent..." -ForegroundColor Yellow +$textAnalyzerAgent = @{ + agent_card = @{ + name = "text-analyzer" + description = "文本分析代理" + url = "http://localhost:8081" + version = "1.0.0" + capabilities = @{ streaming = $true } + defaultInputModes = @("text") + defaultOutputModes = @("json") + skills = @( + @{ + id = "sentiment_analysis" + name = "情感分析" + description = "分析文本的情感倾向" + tags = @("nlp", "sentiment") + examples = @("分析评论情感") + }, + @{ + id = "keyword_extraction" + name = "关键词提取" + description = "从文本中提取关键词" + tags = @("nlp", "keywords") + examples = @("提取文档关键词") + } + ) + } + host = "localhost" + port = 8081 +} | ConvertTo-Json -Depth 5 + +try { + $registerResponse = Invoke-RestMethod -Uri "http://localhost:8080/agent/register" -Method Post -Body $textAnalyzerAgent -ContentType "application/json" + Write-Host "文本分析Agent注册成功: $($registerResponse.message)" -ForegroundColor Green +} catch { + Write-Host "Agent注册失败: $_" -ForegroundColor Red +} + +# 3. 注册数据分析Agent +Write-Host "3. 注册数据分析Agent..." -ForegroundColor Yellow +$dataAnalyzerAgent = @{ + agent_card = @{ + name = "data-analyzer" + description = "数据分析代理" + url = "http://localhost:8082" + version = "1.0.0" + capabilities = @{ streaming = $false } + defaultInputModes = @("json", "csv") + defaultOutputModes = @("json", "image") + skills = @( + @{ + id = "data_visualization" + name = "数据可视化" + description = "生成各种类型的数据图表" + tags = @("data", "chart", "visualization") + examples = @("生成销售图表") + } + ) + } + host = "localhost" + port = 8082 +} | ConvertTo-Json -Depth 5 + +try { + $registerResponse2 = Invoke-RestMethod -Uri "http://localhost:8080/agent/register" -Method Post -Body $dataAnalyzerAgent -ContentType "application/json" + Write-Host "数据分析Agent注册成功: $($registerResponse2.message)" -ForegroundColor Green +} catch { + Write-Host "数据分析Agent注册失败: $_" -ForegroundColor Red +} + +# 4. 查看所有Agent +Write-Host "4. 查看所有注册的Agent..." -ForegroundColor Yellow +try { + $allAgents = Invoke-RestMethod -Uri "http://localhost:8080/agents" -Method Get + Write-Host "当前注册的Agent数量: $($allAgents.data.Count)" -ForegroundColor Green + $allAgents.data | ForEach-Object { Write-Host "- $($_.id): $($_.status)" -ForegroundColor Cyan } +} catch { + Write-Host "获取Agent列表失败: $_" -ForegroundColor Red +} + +# 5. 测试动态上下文分析 - 文本处理需求 +Write-Host "5. 测试动态上下文分析 - 文本处理需求..." -ForegroundColor Yellow +$contextTest1 = @{ + need_description = "我需要分析用户评论的情感倾向,提取其中的关键词" + user_context = "电商平台用户反馈分析" +} | ConvertTo-Json + +try { + $contextResult1 = Invoke-RestMethod -Uri "http://localhost:8080/agent/analyze" -Method Post -Body $contextTest1 -ContentType "application/json" + Write-Host "上下文分析成功!" -ForegroundColor Green + Write-Host "匹配到的技能数量: $($contextResult1.matched_skills.Count)" -ForegroundColor Cyan + if ($contextResult1.matched_skills.Count -gt 0) { + Write-Host "最佳匹配技能: $($contextResult1.matched_skills[0].name)" -ForegroundColor Cyan + } + if ($contextResult1.route_result) { + Write-Host "路由目标: $($contextResult1.route_result.agent_url)" -ForegroundColor Cyan + } +} catch { + Write-Host "上下文分析失败: $_" -ForegroundColor Red +} + +# 6. 测试动态上下文分析 - 数据分析需求 +Write-Host "6. 测试动态上下文分析 - 数据分析需求..." -ForegroundColor Yellow +$contextTest2 = @{ + need_description = "需要对销售数据进行统计分析并生成可视化图表" + user_context = "月度销售报告生成" +} | ConvertTo-Json + +try { + $contextResult2 = Invoke-RestMethod -Uri "http://localhost:8080/agent/analyze" -Method Post -Body $contextTest2 -ContentType "application/json" + Write-Host "数据分析需求匹配成功!" -ForegroundColor Green + Write-Host "匹配到的技能数量: $($contextResult2.matched_skills.Count)" -ForegroundColor Cyan + if ($contextResult2.matched_skills.Count -gt 0) { + Write-Host "最佳匹配技能: $($contextResult2.matched_skills[0].name)" -ForegroundColor Cyan + } +} catch { + Write-Host "数据分析需求匹配失败: $_" -ForegroundColor Red +} + +# 7. 测试无匹配技能的需求 +Write-Host "7. 测试无匹配技能的需求..." -ForegroundColor Yellow +$contextTest3 = @{ + need_description = "我需要训练深度学习模型进行图像识别" + user_context = "AI视觉项目开发" +} | ConvertTo-Json + +try { + $contextResult3 = Invoke-RestMethod -Uri "http://localhost:8080/agent/analyze" -Method Post -Body $contextTest3 -ContentType "application/json" + if (-not $contextResult3.success) { + Write-Host "预期结果: 无匹配技能 - $($contextResult3.message)" -ForegroundColor Yellow + } +} catch { + Write-Host "无匹配技能测试正常: $_" -ForegroundColor Yellow +} + +# 8. Agent心跳测试 +Write-Host "8. 测试Agent心跳..." -ForegroundColor Yellow +$heartbeatTest = @{ + agent_id = "text-analyzer" + timestamp = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ssZ") +} | ConvertTo-Json + +try { + $heartbeatResult = Invoke-RestMethod -Uri "http://localhost:8080/agent/heartbeat" -Method Post -Body $heartbeatTest -ContentType "application/json" + Write-Host "心跳测试成功!" -ForegroundColor Green +} catch { + Write-Host "心跳测试失败: $_" -ForegroundColor Red +} + +Write-Host "AgentHub API测试完成!" -ForegroundColor Green +``` + +### 5.3 错误响应格式 + +```json +{ + "success": false, + "error": { + "error": "Error message", + "code": 400 + } +} +``` + +--- + +## 6. 状态码说明 + +- `200` - 成功 +- `400` - 请求参数错误 +- `401` - 未授权 +- `403` - 权限不足 +- `404` - 资源未找到 +- `405` - 方法不允许 +- `500` - 服务器内部错误 + +--- + +## 7. 动态上下文测试要点 + +### 7.1 测试原理 + +1. **需求描述**: `need_description` 描述具体功能需求 +2. **上下文信息**: `user_context` 提供使用场景背景 +3. **技能匹配**: 系统根据注册Agent的技能进行智能匹配 +4. **AI分析**: 使用AI服务分析需求并生成技能匹配查询 +5. **路由结果**: 返回最佳匹配的Agent和技能信息 + +### 7.2 匹配算法 + +- **精确匹配**: 技能ID完全匹配 (权重: 100) +- **关键词匹配**: 技能名称/描述包含查询词 (权重: 30-50) +- **标签匹配**: 技能标签匹配 (权重: 20) +- **按分数排序**: 返回得分最高的技能 + +### 7.3 测试建议 + +1. **多样化测试**: 测试不同类型的需求描述 +2. **边界测试**: 测试无匹配技能、模糊需求等场景 +3. **性能测试**: 测试大量技能时的匹配性能 +4. **准确性验证**: 验证返回的技能是否真正匹配需求 + +--- + +## 8. 注意事项 + +1. **启动服务**: 在测试前确保AgentHub服务已启动 +2. **端口检查**: 确认服务运行在8080端口 +3. **JSON格式**: 请求体必须是有效的JSON格式 +4. **认证**: 某些接口可能需要Bearer Token认证 +5. **错误处理**: 注意检查响应状态码和错误信息 +6. **AI服务**: 动态上下文分析需要AI服务支持 + +--- + +## 9. 快速测试命令 + +### 启动服务并测试 + +```bash +# 启动服务 (如果尚未启动) +./agentHub.exe + +# 等待几秒钟让服务启动完成 +sleep 3 + +# 快速健康检查 +curl -X GET http://localhost:8080/health + +# 如果返回健康状态,则可以开始其他接口测试 +``` + +### 使用Postman测试 + +1. 导入以上API endpoints到Postman +2. 设置Environment变量: `base_url = http://localhost:8080` +3. 按顺序执行测试用例 +4. 重点测试动态上下文分析功能 \ No newline at end of file diff --git a/agenthub/README.md b/agenthub/README.md new file mode 100644 index 000000000..7d69b3c06 --- /dev/null +++ b/agenthub/README.md @@ -0,0 +1,488 @@ + + +# AgentHub + +一个现代化的智能代理(Agent)管理和发现系统,基于Go语言开发,支持技能驱动的服务发现、动态上下文分析和分布式代理协调。 + +## ✨ 特性 + +### 核心功能 + +- 🚀 **代理注册与发现** - 支持代理的动态注册和技能驱动的发现机制 +- 🔍 **技能驱动发现** - 基于代理技能进行智能匹配和服务发现 +- 🧠 **动态上下文分析** - AI驱动的智能需求分析和代理路由 +- 🎯 **全局技能聚合** - 维护所有注册代理的统一技能索引 +- 🏗️ **NamingServer集成** - 支持Seata NamingServer作为服务注册中心 + +### 系统特性 + +- 🔐 **JWT认证** - 内置JWT认证机制,支持可选认证模式 +- 📊 **监控指标** - 内置Prometheus指标收集 +- 🔄 **健康检查** - 自动代理健康检查和状态管理 +- ⚙️ **灵活配置** - 支持YAML配置和环境变量覆盖 +- 🏛️ **K8s模式** - 遵循Kubernetes设计模式和最佳实践 + +## 🧠 动态上下文分析 + +AgentHub的核心创新功能,当代理无法处理用户请求时,系统会自动: + +1. **需求分析** - 使用AI分析用户的具体需求 +2. **技能匹配** - 在全局技能库中寻找最匹配的能力 +3. **智能路由** - 自动路由到最适合的代理服务 +4. **无缝切换** - 为用户提供连续的服务体验 + +```mermaid +graph LR + A[用户请求] --> B[当前Agent] + B --> C{能否处理?} + C -->|否| D[返回需求描述] + D --> E[AI分析上下文] + E --> F[全局技能匹配] + F --> G[Hub能力发现] + G --> H[路由到目标Agent] + H --> I[返回处理结果] + C -->|是| I +``` + +## 🏗️ 架构 + +``` +AgentHub +├── cmd/ # 应用程序入口 +├── internal/ # 内部包 +│ ├── app/ # 应用程序核心逻辑 +│ ├── handlers/ # HTTP处理器 +│ └── services/ # 业务服务层 +│ ├── agent.go # 代理管理服务 +│ ├── context_analyzer.go # 动态上下文分析器 +│ └── ai_client.go # AI服务客户端 +├── pkg/ # 可复用的包 +│ ├── auth/ # JWT认证 +│ ├── common/ # 通用工具 +│ ├── config/ # 配置管理 +│ ├── models/ # 数据模型 +│ │ ├── agent.go # Agent相关模型 +│ │ └── context.go # 上下文分析模型 +│ ├── server/ # HTTP服务器 +│ ├── storage/ # 存储抽象 +│ │ ├── namingserver.go # NamingServer集成 +│ │ └── memory.go # 内存存储 +│ └── utils/ # 工具函数 +└── tests/ # 测试用例 +``` + +**项目统计**: + +- **5,882行** Go代码 +- **30个** Go文件 +- **完整的微服务架构** + +## 📦 安装 + +### 预备条件 + +- Go 1.21+ +- (可选) Seata NamingServer +- (可选) AI服务API密钥 (OpenAI/Anthropic) + +### 构建 + +```bash +# 克隆仓库 +git clone +cd agenthub + +# 安装依赖 +go mod tidy + +# 构建 +go build ./cmd/... + +# 运行 +./agenthub +``` + +## 🚀 快速开始 + +### 1. 启动服务 + +```bash +# 使用默认配置启动 +./agenthub + +# 或指定配置文件 +./agenthub custom-config.yaml +``` + +### 2. 注册代理 + +```bash +curl -X POST http://localhost:8080/agent/register \ + -H "Content-Type: application/json" \ + -d '{ + "agent_card": { + "name": "text-processor", + "description": "专业文本处理代理", + "url": "http://localhost:3000", + "version": "1.0.0", + "provider": { + "organization": "AI-Corp", + "url": "http://localhost:3000" + }, + "skills": [ + { + "id": "text-processing", + "name": "文本处理", + "description": "处理和分析文本内容,包括情感分析、关键词提取", + "tags": ["nlp", "text", "analysis"] + }, + { + "id": "language-translation", + "name": "语言翻译", + "description": "多语言文本翻译服务", + "tags": ["translation", "language"] + } + ] + }, + "host": "localhost", + "port": 3000 + }' +``` + +### 3. 传统技能发现 + +```bash +curl -X POST http://localhost:8080/agent/discover \ + -H "Content-Type: application/json" \ + -d '{ + "query": "text-processing" + }' +``` + +### 4. 🆕 动态上下文分析 + +```bash +curl -X POST http://localhost:8080/agent/analyze-context \ + -H "Content-Type: application/json" \ + -d '{ + "need_description": "我需要分析这篇文章的情感倾向", + "user_context": "用户正在处理社交媒体数据分析项目" + }' +``` + +### 5. 查看全局技能库 + +```bash +curl http://localhost:8080/skills/global +``` + +## 🧠 动态上下文分析详解 + +### 工作原理 + +1. **全局技能聚合** + - 每个代理注册时,技能自动添加到全局技能库 + - 维护 `skillID → agentURL` 的映射关系 + - 支持技能去重和更新 + +2. **AI驱动分析** + ```go + // AI分析接口 + type AIClient interface { + AnalyzeContext(ctx context.Context, + needDescription string, + availableSkills []AgentSkill) (*SkillMatchQuery, error) + } + ``` + +3. **智能匹配算法** + - 精确技能ID匹配:100分 + - 关键词匹配:50-30分 + - 标签匹配:20分 + - 自动排序选择最佳匹配 + +4. **Hub集成路由** + - 获取匹配的技能ID + - 调用Hub的能力发现接口 + - 返回完整的Agent信息和调用地址 + +### 使用示例 + +```go +// 在AgentService中启用动态上下文 +agentService := services.NewAgentService(services.AgentServiceConfig{ + Storage: storage, + ContextAnalyzer: contextAnalyzer, // 启用动态分析 +}) + +// 检查是否启用 +if agentService.IsContextAnalysisEnabled() { + // 执行动态上下文分析 + response, err := agentService.AnalyzeContext(ctx, &models.ContextAnalysisRequest{ + NeedDescription: "我需要处理图片中的文字", + }) +} +``` + +## ⚙️ 配置 + +### 配置文件 (config.yaml) + +```yaml +hub: + id: "agent-hub-01" + name: "AgentHub" + version: "1.0.0" + listen_address: ":8080" + +# AI服务配置 (可选) +ai: + provider: "openai" # openai/anthropic/mock + api_key: "sk-xxx" # API密钥 + model: "gpt-4" # 模型名称 + max_tokens: 1000 # 最大令牌数 + +seata: + server_addr: "127.0.0.1:8091" + namespace: "public" + cluster: "default" + heartbeat_period: 5000 + +# 动态上下文配置 +context_analysis: + enabled: true # 是否启用动态上下文分析 + ai_provider: "mock" # AI提供商: mock/openai/anthropic + timeout: "30s" # 分析超时时间 + max_skills: 50 # 最大技能匹配数 + +logging: + level: "info" + format: "json" + output: "stdout" + +metrics: + listen_address: ":9090" + enabled: true + +auth: + enabled: false + jwt_secret: "" + jwt_expiry: "24h" + optional: false + +storage: + type: "namingserver" # memory/namingserver + options: {} + +naming_server: + enabled: true + address: "127.0.0.1:8091" + username: "" + password: "" +``` + +## 🔌 API 接口 + +### 代理管理 + +| 方法 | 端点 | 描述 | +|--------|----------------------------|--------| +| POST | `/agent/register` | 注册代理 | +| POST | `/agent/discover` | 发现代理 | +| GET | `/agent/get?id={id}` | 获取代理 | +| GET | `/agents` | 列出所有代理 | +| PUT | `/agent/status?id={id}` | 更新代理状态 | +| DELETE | `/agent/remove?id={id}` | 移除代理 | +| POST | `/agent/heartbeat?id={id}` | 更新心跳 | + +### 🆕 动态上下文分析 + +| 方法 | 端点 | 描述 | +|------|--------------------------|------------| +| POST | `/agent/analyze-context` | 动态上下文分析和路由 | +| GET | `/skills/global` | 获取全局技能库 | +| GET | `/context/health` | 上下文分析器健康检查 | + +### 系统接口 + +| 方法 | 端点 | 描述 | +|-----|------------|--------------| +| GET | `/health` | 健康检查 | +| GET | `/metrics` | Prometheus指标 | + +### 认证接口 (auth.enabled = true) + +| 方法 | 端点 | 描述 | +|------|-----------------|---------| +| POST | `/auth/token` | 生成Token | +| POST | `/auth/refresh` | 刷新Token | + +## 🧪 测试 + +```bash +# 运行所有测试 +go test ./... + +# 运行特定测试 +go test ./tests/ + +# 查看测试覆盖率 +go test -cover ./... + +# 测试动态上下文功能 +go test ./internal/services -v -run TestContextAnalysis +``` + +## 📊 监控 + +### Prometheus指标 + +访问 `http://localhost:9090/metrics` 获取指标数据。 + +**新增指标**: + +- `context_analysis_requests_total` - 上下文分析请求总数 +- `context_analysis_success_total` - 分析成功总数 +- `context_analysis_duration_seconds` - 分析耗时 +- `skill_matches_total` - 技能匹配总数 +- `ai_api_calls_total` - AI API调用总数 + +**原有指标**: + +- `agent_registers_total` - 代理注册总数 +- `agent_discovers_total` - 代理发现总数 +- `auth_requests_total` - 认证请求总数 + +### 日志 + +```json +{ + "timestamp": "2025-08-23T22:30:00Z", + "level": "INFO", + "component": "context-analyzer", + "message": "Successfully analyzed context and routed to agent", + "need_description": "处理文本情感分析", + "matched_skills": ["text-processing", "sentiment-analysis"], + "target_agent": "text-processor-v1", + "analysis_duration": "125ms" +} +``` + +## 🔧 开发 + +### 核心组件 + +1. **ContextAnalyzer** (`internal/services/context_analyzer.go`) + - 动态上下文分析核心逻辑 + - AI服务集成接口 + - 技能匹配算法 + +2. **AIClient** (`internal/services/ai_client.go`) + - AI服务抽象接口 + - Mock实现和真实AI集成 + - 提示词管理 + +3. **全局技能管理** (`pkg/storage/namingserver.go`) + - 全局AgentCard维护 + - 技能聚合和去重 + - skillID到URL的映射 + +### 添加新AI提供商 + +1. 实现 `AIClient` 接口: + +```go +type CustomAIClient struct { + apiKey string + baseURL string +} + +func (c *CustomAIClient) AnalyzeContext(ctx context.Context, + needDescription string, + availableSkills []models.AgentSkill) (*models.SkillMatchQuery, error) { + // 实现AI分析逻辑 +} +``` + +2. 在配置中注册: + +```yaml +ai: + provider: "custom" + api_key: "your-key" +``` + +### 自定义技能匹配算法 + +```go +// 实现自定义评分算法 +func customSkillScore(skill models.AgentSkill, query *models.SkillMatchQuery) int { + // 自定义评分逻辑 + return score +} +``` + +## 🚗 路线图 + +- [x] 基础代理注册和发现 +- [x] 技能驱动的服务发现 +- [x] 动态上下文分析架构 +- [x] Mock AI集成和测试 +- [ ] OpenAI/Anthropic真实AI集成 +- [ ] 提示词优化和调试 +- [ ] 代理调用结果缓存 +- [ ] 分布式部署支持 +- [ ] Web管理界面 +- [ ] 更多AI服务提供商支持 + +## 📄 许可证 + +本项目采用 [MIT License](LICENSE) 许可证。 + +## 🤝 贡献 + +欢迎贡献代码!特别是: + +- AI服务集成优化 +- 提示词设计改进 +- 新的技能匹配算法 +- 性能优化 +- 测试覆盖率提升 + +### 贡献步骤 + +1. Fork本仓库 +2. 创建特性分支 (`git checkout -b feature/dynamic-routing`) +3. 提交更改 (`git commit -m 'Add dynamic context routing'`) +4. 推送到分支 (`git push origin feature/dynamic-routing`) +5. 开启Pull Request + +## 📞 支持 + +如有问题,请: + +1. 查看[Wiki文档](wiki) +2. 搜索[Issues](issues) +3. 提交新的[Issue](issues/new) +4. 加入讨论 [Discussions](discussions) + +--- + +**AgentHub** - 智能代理管理的未来!🚀🧠 + +> 从简单的服务发现,到AI驱动的智能路由 - AgentHub让代理协作变得更加智能和高效。 \ No newline at end of file diff --git a/pkg/compressor/zstd_compress.go b/agenthub/cmd/main.go similarity index 59% rename from pkg/compressor/zstd_compress.go rename to agenthub/cmd/main.go index 715d0cc03..201a4bd56 100644 --- a/pkg/compressor/zstd_compress.go +++ b/agenthub/cmd/main.go @@ -15,30 +15,33 @@ * limitations under the License. */ -package compressor +package main import ( - "github.com/klauspost/compress/zstd" -) + "log" + "os" -type Zstd struct{} + "agenthub/internal/app" +) -func (z Zstd) Compress(data []byte) ([]byte, error) { - var encoder, err = zstd.NewWriter(nil) - if err != nil { - return nil, err +func main() { + // Get config path from command line args or use default + configPath := "config.yaml" + if len(os.Args) > 2 && os.Args[1] == "--config" { + configPath = os.Args[2] + } else if len(os.Args) > 1 && os.Args[1] != "--config" { + configPath = os.Args[1] } - return encoder.EncodeAll(data, make([]byte, 0, len(data))), nil -} -func (z Zstd) Decompress(data []byte) ([]byte, error) { - var decoder, err = zstd.NewReader(nil) + // Create and run application + application, err := app.NewApplication(app.ApplicationConfig{ + ConfigPath: configPath, + }) if err != nil { - return nil, err + log.Fatalf("Failed to create application: %v", err) } - return decoder.DecodeAll(data, nil) -} -func (z Zstd) GetCompressorType() CompressorType { - return CompressorZstd + if err := application.Run(); err != nil { + log.Fatalf("Application failed: %v", err) + } } diff --git a/agenthub/config.yaml b/agenthub/config.yaml new file mode 100644 index 000000000..7f9276d57 --- /dev/null +++ b/agenthub/config.yaml @@ -0,0 +1,65 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +hub: + id: "agent-hub-01" # Hub Agent 的唯一ID + name: "AgentHub" # Hub 名称 + version: "1.0.0" # Hub 版本 + listen_address: ":8080" # Hub 对外暴露 HTTP 服务的地址 + +# AI服务配置 (动态上下文分析) +ai: + provider: "qwen" # AI提供商: mock/qwen/openai + api_key: "sk-10d5ce2898bd49aa98bb2c6a7590ec76" # 千问API密钥 + base_url: "" # API基础URL,为空时使用默认值 + model: "qwen-plus" # 模型名称 + max_tokens: 1000 # 最大token数 + timeout: "30s" # 请求超时时间 + +# 动态上下文分析配置 +context_analysis: + enabled: true # 是否启用动态上下文分析 + cache_ttl: "1h" # 分析结果缓存时间 + max_concurrent: 10 # 最大并发分析数 + +seata: + server_addr: "127.0.0.1:8091" # Seata NamingServer 地址,多个以逗号分隔 + namespace: "public" # 命名空间,需与 NamingServer 中保持一致 + cluster: "default" # 集群名称 + heartbeat_period: 5000 # 心跳周期(ms) + +# 命名服务配置 (技能发现) +naming_server: + enabled: true # 启用技能发现 + address: "127.0.0.1:8091" # NamingServer地址 + username: "" # 用户名 + password: "" # 密码 + +# 存储配置 +storage: + type: "memory" # 存储类型 + +logging: + level: "debug" # debug/info/warn/error + +metrics: + enabled: true # 启用监控 + listen_address: ":9090" # Prometheus 抓取地址 + +auth: + enabled: false # 是否启用JWT认证 + jwt_secret: "" # JWT密钥,为空时自动生成 + jwt_expiry: "24h" # JWT过期时间 + optional: true # 可选认证,无token或token无效时仍允许访问 \ No newline at end of file diff --git a/agenthub/frontend/README.md b/agenthub/frontend/README.md new file mode 100644 index 000000000..759742208 --- /dev/null +++ b/agenthub/frontend/README.md @@ -0,0 +1,216 @@ + + +# AgentHub Frontend + +基于React + TypeScript + Tailwind CSS的现代化AgentHub管理界面。 + +## 功能特性 + +### 🎯 核心功能 + +- **Dashboard概览** - 系统状态、Agent统计、快速操作 +- **Agent管理** - 列表查看、详情展示、状态控制 +- **Agent注册** - 表单化注册新Agent,支持技能配置 +- **能力发现** - 根据技能ID查找Agent服务地址 +- **智能分析** - AI驱动的需求分析和Agent匹配 + +### 🎨 界面特性 + +- **响应式设计** - 适配桌面端和移动端 +- **现代化UI** - 基于Tailwind CSS的美观界面 +- **实时状态** - 实时显示Agent在线状态 +- **操作反馈** - 完善的成功/错误提示 +- **快速操作** - 复制、链接跳转等便捷功能 + +### 🔧 技术特性 + +- **TypeScript** - 完整的类型定义和检查 +- **模块化设计** - 清晰的组件和服务分离 +- **API集成** - 完整的后端API调用封装 +- **错误处理** - 统一的错误处理机制 + +## 项目结构 + +``` +frontend/ +├── public/ +│ └── index.html # HTML模板 +├── src/ +│ ├── components/ # 通用组件 +│ │ └── Layout.tsx # 布局组件 +│ ├── pages/ # 页面组件 +│ │ ├── Dashboard.tsx # 概览页面 +│ │ ├── AgentList.tsx # Agent列表 +│ │ ├── AgentDetail.tsx # Agent详情 +│ │ ├── AgentRegister.tsx # Agent注册 +│ │ ├── AgentDiscover.tsx # 能力发现 +│ │ └── ContextAnalysis.tsx # 智能分析 +│ ├── services/ # API服务 +│ │ └── api.ts # API调用封装 +│ ├── types/ # 类型定义 +│ │ └── index.ts # TypeScript类型 +│ ├── utils/ # 工具函数 +│ │ └── index.ts # 通用工具 +│ ├── App.tsx # 主应用组件 +│ ├── index.tsx # 入口文件 +│ └── index.css # 全局样式 +├── package.json # 依赖配置 +├── tailwind.config.js # Tailwind配置 +├── tsconfig.json # TypeScript配置 +└── postcss.config.js # PostCSS配置 +``` + +## 快速开始 + +### 1. 安装依赖 + +```bash +cd frontend +npm install +``` + +### 2. 启动开发服务器 + +```bash +npm start +``` + +访问 http://localhost:3000 查看应用 + +### 3. 构建生产版本 + +```bash +npm run build +``` + +## API配置 + +默认API地址为 `http://localhost:8080`,如需修改可以: + +1. 在根目录创建 `.env` 文件 +2. 添加环境变量: + ``` + REACT_APP_API_BASE_URL=http://your-api-server:port + ``` + +## 使用说明 + +### 1. 概览页面 (/) + +- 查看系统整体状态 +- 显示Agent统计信息 +- 提供快速操作入口 + +### 2. Agent管理 (/agents) + +- 列表显示所有注册的Agent +- 支持搜索和状态筛选 +- 快速操作:心跳、启停、删除 + +### 3. Agent注册 (/register) + +- 表单化注册新Agent +- 支持技能配置和能力设置 +- 提供注册模板 + +### 4. 能力发现 (/discover) + +- 通过技能ID查找Agent +- 显示匹配的Agent信息 +- 提供URL复制和跳转 + +### 5. 智能分析 (/analyze) + +- 自然语言需求输入 +- AI智能匹配相关技能 +- 推荐最佳Agent服务 + +## 开发指南 + +### 添加新页面 + +1. 在 `src/pages/` 创建新组件 +2. 在 `App.tsx` 中添加路由 +3. 在 `Layout.tsx` 中添加导航菜单 + +### 添加新API + +1. 在 `src/types/` 定义相关类型 +2. 在 `src/services/api.ts` 添加API方法 +3. 在组件中使用API调用 + +### 样式定制 + +- 修改 `tailwind.config.js` 中的主题配置 +- 在 `src/index.css` 中添加自定义样式类 +- 使用Tailwind CSS的工具类进行样式设计 + +## 部署说明 + +### 1. 构建应用 + +```bash +npm run build +``` + +### 2. 部署静态文件 + +将 `build/` 目录中的文件部署到Web服务器 + +### 3. 配置代理 + +如果前后端部署在不同域名,需要配置CORS或代理 + +## 故障排除 + +### 常见问题 + +1. **API连接失败** + - 检查AgentHub后端服务是否启动 + - 确认API地址配置是否正确 + - 检查网络连接和防火墙设置 + +2. **页面空白** + - 检查浏览器控制台的错误信息 + - 确认所有依赖是否正确安装 + - 尝试清除浏览器缓存 + +3. **样式问题** + - 确认Tailwind CSS是否正确编译 + - 检查PostCSS配置是否正确 + - 重新启动开发服务器 + +### 调试技巧 + +1. 开启浏览器开发者工具 +2. 查看Network标签页的API请求 +3. 检查Console的错误信息 +4. 使用React DevTools扩展 + +## 技术栈 + +- **React 18** - 前端框架 +- **TypeScript** - 类型系统 +- **Tailwind CSS** - CSS框架 +- **React Router** - 路由管理 +- **Axios** - HTTP客户端 +- **Lucide React** - 图标库 + +## 许可证 + +此项目遵循MIT许可证。 \ No newline at end of file diff --git a/agenthub/frontend/package-lock.json b/agenthub/frontend/package-lock.json new file mode 100644 index 000000000..f37079142 --- /dev/null +++ b/agenthub/frontend/package-lock.json @@ -0,0 +1,17478 @@ +{ + "name": "agenthub-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agenthub-frontend", + "version": "1.0.0", + "dependencies": { + "@types/node": "^18.15.0", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "axios": "^1.6.0", + "lucide-react": "^0.263.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.8.1", + "react-scripts": "^5.0.1", + "typescript": "^4.9.5" + }, + "devDependencies": { + "autoprefixer": "^10.4.14", + "postcss": "^8.4.21", + "tailwindcss": "^3.2.7" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.0.tgz", + "integrity": "sha512-N4ntErOlKvcbTt01rr5wj3y55xnIdx1ymrfIr8C2WnM1Y9glFgWaGDEULJIazOX3XM9NRzhfJ6zZnQ1sBNWU+w==", + "license": "MIT", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz", + "integrity": "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-decorators": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", + "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", + "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz", + "integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", + "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-flow": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz", + "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", + "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz", + "integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", + "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "license": "MIT" + }, + "node_modules/@csstools/normalize.css": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.1.1.tgz", + "integrity": "sha512-YAYeJ+Xqh7fUou1d1j9XHl44BmsuThiTr4iNrgCQ3J27IbhXsxXDGZ1cXv8Qvs99d4rBbLiSKy3+WZiet32PcQ==", + "license": "CC0-1.0" + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", + "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", + "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", + "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "license": "CC0-1.0", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "license": "CC0-1.0", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", + "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/core": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", + "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", + "license": "MIT", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^27.5.1", + "jest-config": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-resolve-dependencies": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "jest-watcher": "^27.5.1", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", + "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", + "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", + "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/types": "^27.5.1", + "expect": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", + "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-haste-map": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", + "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9", + "source-map": "^0.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/source-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/test-result": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", + "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", + "license": "MIT", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", + "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", + "license": "MIT", + "dependencies": { + "@jest/test-result": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-runtime": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", + "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.5.1", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-util": "^27.5.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@jest/transform/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "license": "MIT", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.17.tgz", + "integrity": "sha512-tXDyE1/jzFsHXjhRZQ3hMl0IVhYe5qula43LDWIhVfjp9G/nT5OQY5AORVOrkEGAUltBJOfOWeETbmhm6kHhuQ==", + "license": "MIT", + "dependencies": { + "ansi-html": "^0.0.9", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^4.2.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <5.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x || 5.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", + "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "license": "MIT", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "license": "MIT" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.12.0.tgz", + "integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==", + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "license": "Apache-2.0", + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", + "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", + "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", + "@svgr/babel-plugin-transform-svg-component": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", + "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", + "license": "MIT", + "dependencies": { + "@svgr/plugin-jsx": "^5.5.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", + "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.12.6" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", + "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.12.3", + "@svgr/babel-preset": "^5.5.0", + "@svgr/hast-util-to-babel-ast": "^5.5.0", + "svg-parser": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", + "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^7.0.0", + "deepmerge": "^4.2.2", + "svgo": "^1.2.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/webpack": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", + "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/plugin-transform-react-constant-elements": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.5", + "@svgr/core": "^5.5.0", + "@svgr/plugin-jsx": "^5.5.0", + "@svgr/plugin-svgo": "^5.5.0", + "loader-utils": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "license": "ISC", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.12", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", + "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.16", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", + "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "18.19.123", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.123.tgz", + "integrity": "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/q": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", + "integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.24", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz", + "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", + "integrity": "sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "license": "BSD-3-Clause" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", + "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.8.tgz", + "integrity": "sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-array-method-boxes-properly": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "is-string": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "license": "MIT", + "dependencies": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-loader": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", + "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", + "license": "MIT", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.4", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-named-asset-import": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", + "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", + "license": "MIT", + "peerDependencies": { + "@babel/core": "^7.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "license": "MIT" + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-react-app": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.1.0.tgz", + "integrity": "sha512-f9B1xMdnkCIqe+2dHrJsoQFRz7reChaAHE/65SdaykPklQqhme2WaC08oD3is77x9ff98/9EazAKFDZv5rFEQg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-proposal-private-property-in-object": "^7.16.7", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "node_modules/babel-preset-react-app/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "license": "MIT" + }, + "node_modules/bfj": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.1.0.tgz", + "integrity": "sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==", + "license": "MIT", + "dependencies": { + "bluebird": "^3.7.2", + "check-types": "^11.2.3", + "hoopy": "^0.1.4", + "jsonpath": "^1.1.1", + "tryer": "^1.0.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "license": "BSD-2-Clause" + }, + "node_modules/browserslist": { + "version": "4.25.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", + "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001735", + "electron-to-chromium": "^1.5.204", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001737", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", + "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/check-types": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", + "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==", + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "license": "MIT" + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "license": "MIT", + "dependencies": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/coa/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/coa/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/coa/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/coa/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "license": "MIT" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", + "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", + "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.45.1.tgz", + "integrity": "sha512-OHnWFKgTUshEU8MK+lOs1H8kC8GkTi9Z1tvNkxrCcw9wl3MJIO7q2ld77wjWn4/xuGrVu2X+nME1iIIPBSdyEQ==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-blank-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-has-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", + "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "license": "MIT", + "dependencies": { + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "license": "CC0-1.0", + "bin": { + "css-prefers-color-scheme": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssdb": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.11.2.tgz", + "integrity": "sha512-lhQ32TFkc1X4eTefGfYPvgovRSzIMofHkigfH8nWtyRL4XJLsRhJFreRvEgKzept7x1rjBuy3J/MurXLaFxW/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "CC0-1.0" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "license": "MIT", + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "license": "MIT", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, + "node_modules/csso/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "license": "BSD-2-Clause" + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "license": "BSD-2-Clause", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, + "node_modules/detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "license": "MIT", + "dependencies": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "bin": { + "detect": "bin/detect-port", + "detect-port": "bin/detect-port" + }, + "engines": { + "node": ">= 4.2.1" + } + }, + "node_modules/detect-port-alt/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/detect-port-alt/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "deprecated": "Use your platform's native DOMException instead", + "license": "MIT", + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "license": "BSD-2-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.208", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz", + "integrity": "sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==", + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "license": "BSD-3-Clause", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-testing-library": { + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz", + "integrity": "sha512-5eX9e1Kc2PqVRed3taaLnAAqPZGEX75C+M/rXzUAI3wIg/ZxzUm1OVAwfe/O+vE+6YXOLetSe9g5GKD2ecXipw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^5.58.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", + "license": "MIT", + "dependencies": { + "@types/eslint": "^7.29.0 || ^8.4.1", + "jest-worker": "^28.0.2", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/filesize": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", + "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=10", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "license": "Unlicense" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "license": "ISC" + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "license": "MIT" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "license": "MIT" + }, + "node_modules/harmony-reflect": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", + "license": "(Apache-2.0 OR MPL-1.1)" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.4.tgz", + "integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==", + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "license": "ISC" + }, + "node_modules/identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "license": "MIT", + "dependencies": { + "harmony-reflect": "^1.4.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "license": "MIT" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "license": "MIT", + "dependencies": { + "@jest/core": "^27.5.1", + "import-local": "^3.0.2", + "jest-cli": "^27.5.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", + "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "execa": "^5.0.0", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", + "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-cli": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", + "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", + "license": "MIT", + "dependencies": { + "@jest/core": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", + "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.8.0", + "@jest/test-sequencer": "^27.5.1", + "@jest/types": "^27.5.1", + "babel-jest": "^27.5.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.9", + "jest-circus": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-jasmine2": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", + "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", + "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", + "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-jasmine2": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", + "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", + "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", + "license": "MIT", + "dependencies": { + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", + "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", + "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-snapshot": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", + "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", + "license": "MIT", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-leak-detector": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", + "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/globals": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-serializer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", + "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^27.5.1", + "semver": "^7.3.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", + "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "leven": "^3.1.0", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watch-typeahead": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", + "integrity": "sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "jest-regex-util": "^28.0.0", + "jest-watcher": "^28.0.0", + "slash": "^4.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0 || ^28.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "license": "MIT", + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "license": "MIT", + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "license": "MIT", + "dependencies": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/jest-watch-typeahead/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "license": "MIT", + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length/node_modules/char-regex": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.2.tgz", + "integrity": "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/jest-watcher": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", + "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", + "license": "MIT", + "dependencies": { + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.5.1", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/form-data": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpath": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", + "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "license": "MIT", + "dependencies": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.12.1" + } + }, + "node_modules/jsonpath/node_modules/esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/launch-editor": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", + "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.263.1", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.263.1.tgz", + "integrity": "sha512-keqxAx97PlaEN89PXZ6ki1N8nRjGWtDa4021GFYLNj0RgruM5odbpl8GHTExj0hhPq3sF6Up0gnxt6TSHu+ovw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", + "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "license": "MIT" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.21", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", + "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz", + "integrity": "sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==", + "license": "MIT", + "dependencies": { + "array.prototype.reduce": "^1.0.6", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "gopd": "^1.0.1", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-browser-comments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", + "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", + "license": "CC0-1.0", + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "browserslist": ">=4", + "postcss": ">=8" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-custom-properties": { + "version": "12.1.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz", + "integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-flexbugs-fixes": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", + "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.1.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "license": "CC0-1.0", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "license": "CC0-1.0", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "license": "MIT", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-normalize": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz", + "integrity": "sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/normalize.css": "*", + "postcss-browser-comments": "^4", + "sanitize.css": "*" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "browserslist": ">= 4", + "postcss": ">= 8" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "license": "MIT", + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", + "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "license": "MIT", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "license": "MIT", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.3.tgz", + "integrity": "sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-cascade-layers": "^1.1.1", + "@csstools/postcss-color-function": "^1.1.1", + "@csstools/postcss-font-format-keywords": "^1.0.1", + "@csstools/postcss-hwb-function": "^1.0.2", + "@csstools/postcss-ic-unit": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^2.0.7", + "@csstools/postcss-nested-calc": "^1.0.0", + "@csstools/postcss-normalize-display-values": "^1.0.1", + "@csstools/postcss-oklab-function": "^1.1.1", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.1", + "@csstools/postcss-text-decoration-shorthand": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.2", + "@csstools/postcss-unset-value": "^1.0.2", + "autoprefixer": "^10.4.13", + "browserslist": "^4.21.4", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^7.1.0", + "postcss-attribute-case-insensitive": "^5.0.2", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.4", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.1", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.10", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-double-position-gradients": "^3.1.2", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.5", + "postcss-image-set-function": "^4.0.7", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.1", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.2.0", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.4", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.5", + "postcss-pseudo-class-any-link": "^7.1.6", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/postcss-svgo/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/postcss-svgo/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, + "node_modules/postcss-svgo/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss-svgo/node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "license": "MIT", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-app-polyfill": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", + "integrity": "sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==", + "license": "MIT", + "dependencies": { + "core-js": "^3.19.2", + "object-assign": "^4.1.1", + "promise": "^8.1.0", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.9", + "whatwg-fetch": "^3.6.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", + "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "address": "^1.1.2", + "browserslist": "^4.18.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "detect-port-alt": "^1.1.6", + "escape-string-regexp": "^4.0.0", + "filesize": "^8.0.6", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "global-modules": "^2.0.0", + "globby": "^11.0.4", + "gzip-size": "^6.0.0", + "immer": "^9.0.7", + "is-root": "^2.1.0", + "loader-utils": "^3.2.0", + "open": "^8.4.0", + "pkg-up": "^3.1.0", + "prompts": "^2.4.2", + "react-error-overlay": "^6.0.11", + "recursive-readdir": "^2.2.2", + "shell-quote": "^1.7.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/react-dev-utils/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-error-overlay": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0.tgz", + "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", + "license": "MIT" + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", + "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-scripts": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", + "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.16.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", + "@svgr/webpack": "^5.5.0", + "babel-jest": "^27.4.2", + "babel-loader": "^8.2.3", + "babel-plugin-named-asset-import": "^0.3.8", + "babel-preset-react-app": "^10.0.1", + "bfj": "^7.0.2", + "browserslist": "^4.18.1", + "camelcase": "^6.2.1", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "css-loader": "^6.5.1", + "css-minimizer-webpack-plugin": "^3.2.0", + "dotenv": "^10.0.0", + "dotenv-expand": "^5.1.0", + "eslint": "^8.3.0", + "eslint-config-react-app": "^7.0.1", + "eslint-webpack-plugin": "^3.1.1", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.0", + "html-webpack-plugin": "^5.5.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^27.4.3", + "jest-resolve": "^27.4.2", + "jest-watch-typeahead": "^1.0.0", + "mini-css-extract-plugin": "^2.4.5", + "postcss": "^8.4.4", + "postcss-flexbugs-fixes": "^5.0.2", + "postcss-loader": "^6.2.1", + "postcss-normalize": "^10.0.1", + "postcss-preset-env": "^7.0.1", + "prompts": "^2.4.2", + "react-app-polyfill": "^3.0.0", + "react-dev-utils": "^12.0.1", + "react-refresh": "^0.11.0", + "resolve": "^1.20.0", + "resolve-url-loader": "^4.0.0", + "sass-loader": "^12.3.0", + "semver": "^7.3.5", + "source-map-loader": "^3.0.0", + "style-loader": "^3.3.1", + "tailwindcss": "^3.0.2", + "terser-webpack-plugin": "^5.2.5", + "webpack": "^5.64.4", + "webpack-dev-server": "^4.6.0", + "webpack-manifest-plugin": "^4.0.2", + "workbox-webpack-plugin": "^6.4.1" + }, + "bin": { + "react-scripts": "bin/react-scripts.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + }, + "peerDependencies": { + "react": ">= 16", + "typescript": "^3.2.1 || ^4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/regex-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", + "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-url-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", + "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", + "license": "MIT", + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^7.0.35", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=8.9" + }, + "peerDependencies": { + "rework": "1.0.1", + "rework-visit": "1.0.0" + }, + "peerDependenciesMeta": { + "rework": { + "optional": true + }, + "rework-visit": { + "optional": true + } + } + }, + "node_modules/resolve-url-loader/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/resolve-url-loader/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "license": "ISC" + }, + "node_modules/resolve-url-loader/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", + "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-terser": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", + "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sanitize.css": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", + "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==", + "license": "CC0-1.0" + }, + "node_modules/sass-loader": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "license": "MIT", + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "license": "ISC" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", + "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.5", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "license": "MIT" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "license": "MIT" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "license": "MIT", + "dependencies": { + "escodegen": "^1.8.1" + } + }, + "node_modules/static-eval/node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/static-eval/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/static-eval/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "license": "MIT", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-eval/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "license": "MIT" + }, + "node_modules/svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "license": "MIT", + "dependencies": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/svgo/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/svgo/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/svgo/node_modules/css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "node_modules/svgo/node_modules/css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "license": "BSD-2-Clause" + }, + "node_modules/svgo/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/svgo/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/svgo/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/tapable": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/throat": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", + "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==", + "license": "MIT" + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", + "license": "MIT" + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==", + "license": "MIT" + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", + "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "license": "ISC", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "license": "MIT", + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10.4" + } + }, + "node_modules/webpack": { + "version": "5.101.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", + "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.3", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-manifest-plugin": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", + "integrity": "sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==", + "license": "MIT", + "dependencies": { + "tapable": "^2.0.0", + "webpack-sources": "^2.2.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "webpack": "^4.44.2 || ^5.47.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", + "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "license": "MIT", + "dependencies": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "license": "MIT", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-background-sync": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", + "integrity": "sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==", + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.6.0.tgz", + "integrity": "sha512-nm+v6QmrIFaB/yokJmQ/93qIJ7n72NICxIwQwe5xsZiV2aI93MGGyEyzOzDPVz5THEr5rC3FJSsO3346cId64Q==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-build": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.6.0.tgz", + "integrity": "sha512-Tjf+gBwOTuGyZwMz2Nk/B13Fuyeo0Q84W++bebbVsfr9iLkDSo6j6PST8tET9HYA58mlRXwlMGpyWO8ETJiXdQ==", + "license": "MIT", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.11.1", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^11.2.1", + "@rollup/plugin-replace": "^2.4.1", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^7.1.6", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.43.1", + "rollup-plugin-terser": "^7.0.0", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "6.6.0", + "workbox-broadcast-update": "6.6.0", + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-google-analytics": "6.6.0", + "workbox-navigation-preload": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-range-requests": "6.6.0", + "workbox-recipes": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0", + "workbox-streams": "6.6.0", + "workbox-sw": "6.6.0", + "workbox-window": "6.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "license": "MIT", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/workbox-build/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workbox-build/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/workbox-build/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "license": "BSD-2-Clause" + }, + "node_modules/workbox-build/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.6.0.tgz", + "integrity": "sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==", + "deprecated": "workbox-background-sync@6.6.0", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-core": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.6.0.tgz", + "integrity": "sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==", + "license": "MIT" + }, + "node_modules/workbox-expiration": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.6.0.tgz", + "integrity": "sha512-baplYXcDHbe8vAo7GYvyAmlS4f6998Jff513L4XvlzAOxcl8F620O91guoJ5EOf5qeXG4cGdNZHkkVAPouFCpw==", + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-google-analytics": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.6.0.tgz", + "integrity": "sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==", + "deprecated": "It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained", + "license": "MIT", + "dependencies": { + "workbox-background-sync": "6.6.0", + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.6.0.tgz", + "integrity": "sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-precaching": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.6.0.tgz", + "integrity": "sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-range-requests": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.6.0.tgz", + "integrity": "sha512-V3aICz5fLGq5DpSYEU8LxeXvsT//mRWzKrfBOIxzIdQnV/Wj7R+LyJVTczi4CQ4NwKhAaBVaSujI1cEjXW+hTw==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-recipes": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.6.0.tgz", + "integrity": "sha512-TFi3kTgYw73t5tg73yPVqQC8QQjxJSeqjXRO4ouE/CeypmP2O/xqmB/ZFBBQazLTPxILUQ0b8aeh0IuxVn9a6A==", + "license": "MIT", + "dependencies": { + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-routing": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.6.0.tgz", + "integrity": "sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-strategies": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.6.0.tgz", + "integrity": "sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-streams": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.6.0.tgz", + "integrity": "sha512-rfMJLVvwuED09CnH1RnIep7L9+mj4ufkTyDPVaXPKlhi9+0czCu+SJggWCIFbPpJaAZmp2iyVGLqS3RUmY3fxg==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0" + } + }, + "node_modules/workbox-sw": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.6.0.tgz", + "integrity": "sha512-R2IkwDokbtHUE4Kus8pKO5+VkPHD2oqTgl+XJwh4zbF1HyjAbgNmK/FneZHVU7p03XUt9ICfuGDYISWG9qV/CQ==", + "license": "MIT" + }, + "node_modules/workbox-webpack-plugin": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.0.tgz", + "integrity": "sha512-xNZIZHalboZU66Wa7x1YkjIqEy1gTR+zPM+kjrYJzqN7iurYZBctBLISyScjhkJKYuRrZUP0iqViZTh8rS0+3A==", + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "^2.1.0", + "pretty-bytes": "^5.4.1", + "upath": "^1.2.0", + "webpack-sources": "^1.4.3", + "workbox-build": "6.6.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "webpack": "^4.4.0 || ^5.9.0" + } + }, + "node_modules/workbox-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-webpack-plugin/node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "license": "MIT", + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/workbox-window": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.6.0.tgz", + "integrity": "sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==", + "license": "MIT", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "6.6.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "license": "Apache-2.0" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/agenthub/frontend/package.json b/agenthub/frontend/package.json new file mode 100644 index 000000000..9c40c9ca0 --- /dev/null +++ b/agenthub/frontend/package.json @@ -0,0 +1,47 @@ +{ + "name": "agenthub-frontend", + "version": "1.0.0", + "private": true, + "dependencies": { + "@types/node": "^18.15.0", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "axios": "^1.6.0", + "lucide-react": "^0.263.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.8.1", + "react-scripts": "^5.0.1", + "typescript": "^4.9.5" + }, + "devDependencies": { + "autoprefixer": "^10.4.14", + "postcss": "^8.4.21", + "tailwindcss": "^3.2.7" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "proxy": "http://localhost:8080" +} diff --git a/pkg/compressor/7z_compress.go b/agenthub/frontend/postcss.config.js similarity index 89% rename from pkg/compressor/7z_compress.go rename to agenthub/frontend/postcss.config.js index d1eea9879..bba877d97 100644 --- a/pkg/compressor/7z_compress.go +++ b/agenthub/frontend/postcss.config.js @@ -15,4 +15,9 @@ * limitations under the License. */ -package compressor +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/agenthub/frontend/public/index.html b/agenthub/frontend/public/index.html new file mode 100644 index 000000000..a5b558c86 --- /dev/null +++ b/agenthub/frontend/public/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + + Codestin Search App + + + +
+ + \ No newline at end of file diff --git a/agenthub/frontend/src/App.tsx b/agenthub/frontend/src/App.tsx new file mode 100644 index 000000000..f22574b16 --- /dev/null +++ b/agenthub/frontend/src/App.tsx @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React from 'react'; +import {Route, Routes} from 'react-router-dom'; +import Layout from './components/Layout'; +import Dashboard from './pages/Dashboard'; +import AgentList from './pages/AgentList'; +import AgentRegister from './pages/AgentRegister'; +import ContextAnalysis from './pages/ContextAnalysis'; +import AgentDiscover from './pages/AgentDiscover'; +import AgentDetail from './pages/AgentDetail'; +import Playground from './pages/Playground'; + +function App() { + return ( + + + }/> + }/> + }/> + }/> + }/> + }/> + }/> + + + ); +} + +export default App; \ No newline at end of file diff --git a/agenthub/frontend/src/components/Layout.tsx b/agenthub/frontend/src/components/Layout.tsx new file mode 100644 index 000000000..c0d2247e5 --- /dev/null +++ b/agenthub/frontend/src/components/Layout.tsx @@ -0,0 +1,180 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React, {useEffect, useState} from 'react'; +import {Link, useLocation} from 'react-router-dom'; +import {Activity, Brain, Home, Menu, Play, Plus, Search, Users, X} from 'lucide-react'; +import AgentHubAPI from '../services/api'; + +interface LayoutProps { + children: React.ReactNode; +} + +const Layout: React.FC = ({children}) => { + const location = useLocation(); + const [sidebarOpen, setSidebarOpen] = useState(false); + const [systemStatus, setSystemStatus] = useState<'healthy' | 'error' | 'loading'>('loading'); + + // 检查系统健康状态 + useEffect(() => { + const checkHealth = async () => { + try { + await AgentHubAPI.healthCheck(); + setSystemStatus('healthy'); + } catch (error) { + setSystemStatus('error'); + } + }; + + checkHealth(); + // 每30秒检查一次 + const interval = setInterval(checkHealth, 30000); + return () => clearInterval(interval); + }, []); + + const navigation = [ + {name: '概览', href: '/', icon: Home}, + {name: 'Agent管理', href: '/agents', icon: Users}, + {name: '注册Agent', href: '/register', icon: Plus}, + {name: '能力发现', href: '/discover', icon: Search}, + {name: '智能分析', href: '/analyze', icon: Brain}, + {name: 'Playground', href: '/playground', icon: Play}, + ]; + + const isCurrentPath = (path: string) => { + return location.pathname === path; + }; + + return ( +
+ {/* Mobile sidebar */} +
+
setSidebarOpen(false)}/> +
+
+ +
+ +
+
+ + {/* Static sidebar for desktop */} +
+ +
+ + {/* Main content */} +
+ {/* Top nav */} +
+ +
+
+

AgentHub

+
+
+ {/* System status indicator */} +
+ + + {systemStatus === 'healthy' ? '系统正常' : + systemStatus === 'error' ? '系统异常' : '检查中...'} + +
+
+
+
+ + {/* Page content */} +
+
+
+ {children} +
+
+
+
+
+ ); +}; + +const SidebarContent: React.FC<{ + navigation: Array<{ name: string; href: string; icon: any }>; + isCurrentPath: (path: string) => boolean; +}> = ({navigation, isCurrentPath}) => { + return ( +
+
+
+
+
+ +
+
+

AgentHub

+

智能Agent平台

+
+
+
+ +
+
+ ); +}; + +export default Layout; \ No newline at end of file diff --git a/agenthub/frontend/src/index.css b/agenthub/frontend/src/index.css new file mode 100644 index 000000000..16b43a919 --- /dev/null +++ b/agenthub/frontend/src/index.css @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + html, body { + @apply h-full; + } + + body { + @apply bg-gray-50 text-gray-900 font-sans; + } +} + +@layer components { + .btn-primary { + @apply bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-lg font-medium transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed; + } + + .btn-secondary { + @apply bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg font-medium transition-colors duration-200; + } + + .btn-success { + @apply bg-success-500 hover:bg-success-600 text-white px-4 py-2 rounded-lg font-medium transition-colors duration-200; + } + + .btn-warning { + @apply bg-warning-500 hover:bg-warning-600 text-white px-4 py-2 rounded-lg font-medium transition-colors duration-200; + } + + .btn-error { + @apply bg-error-500 hover:bg-error-600 text-white px-4 py-2 rounded-lg font-medium transition-colors duration-200; + } + + .card { + @apply bg-white rounded-lg shadow-sm border border-gray-200 p-6; + } + + .input-field { + @apply w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none; + } + + .textarea-field { + @apply w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none; + resize: vertical; + } +} \ No newline at end of file diff --git a/pkg/compressor/none_compressor.go b/agenthub/frontend/src/index.tsx similarity index 67% rename from pkg/compressor/none_compressor.go rename to agenthub/frontend/src/index.tsx index 0628dbf81..303aba62b 100644 --- a/pkg/compressor/none_compressor.go +++ b/agenthub/frontend/src/index.tsx @@ -15,19 +15,20 @@ * limitations under the License. */ -package compressor +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import {BrowserRouter} from 'react-router-dom'; +import './index.css'; +import App from './App'; -type NoneCompressor struct { -} +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); -func (n *NoneCompressor) Compress(data []byte) ([]byte, error) { - return data, nil -} - -func (n *NoneCompressor) Decompress(data []byte) ([]byte, error) { - return data, nil -} - -func (n *NoneCompressor) GetCompressorType() CompressorType { - return CompressorNone -} +root.render( + + + + + +); \ No newline at end of file diff --git a/agenthub/frontend/src/pages/AgentDetail.tsx b/agenthub/frontend/src/pages/AgentDetail.tsx new file mode 100644 index 000000000..aafe9a5f8 --- /dev/null +++ b/agenthub/frontend/src/pages/AgentDetail.tsx @@ -0,0 +1,459 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React, {useEffect, useState} from 'react'; +import {useNavigate, useParams} from 'react-router-dom'; +import { + Activity, + ArrowLeft, + Clock, + Copy, + ExternalLink, + Globe, + Heart, + Pause, + Play, + Server, + Settings, + Tag, + Trash2, + User +} from 'lucide-react'; +import AgentHubAPI from '../services/api'; +import {RegisteredAgent} from '../types'; +import {copyToClipboard, formatDateTime, formatRelativeTime, getStatusText, isAgentOnline} from '../utils'; + +const AgentDetail: React.FC = () => { + const {id} = useParams<{ id: string }>(); + const navigate = useNavigate(); + const [agent, setAgent] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + if (id) { + loadAgent(id); + } + }, [id]); + + const loadAgent = async (agentId: string) => { + try { + setLoading(true); + const response = await AgentHubAPI.getAgent(agentId); + if (response.success && response.data) { + setAgent(response.data); + } else { + setError(response.error?.error || 'Agent not found'); + } + } catch (err) { + setError('Network error while loading agent'); + } finally { + setLoading(false); + } + }; + + const handleStatusChange = async (newStatus: string) => { + if (!agent) return; + + try { + const response = await AgentHubAPI.updateAgentStatus(agent.id, newStatus); + if (response.success) { + setAgent(prev => prev ? {...prev, status: newStatus} : null); + } else { + alert('更新状态失败: ' + (response.error?.error || 'Unknown error')); + } + } catch (error) { + alert('更新状态失败: 网络错误'); + } + }; + + const handleDelete = async () => { + if (!agent) return; + + if (!window.confirm('确定要删除这个Agent吗?此操作无法撤销。')) { + return; + } + + try { + const response = await AgentHubAPI.removeAgent(agent.id); + if (response.success) { + navigate('/agents'); + } else { + alert('删除失败: ' + (response.error?.error || 'Unknown error')); + } + } catch (error) { + alert('删除失败: 网络错误'); + } + }; + + const handleHeartbeat = async () => { + if (!agent) return; + + try { + const response = await AgentHubAPI.sendHeartbeat(agent.id); + if (response.success) { + setAgent(prev => prev ? { + ...prev, + last_seen: new Date().toISOString() + } : null); + alert('心跳发送成功'); + } else { + alert('发送心跳失败: ' + (response.error?.error || 'Unknown error')); + } + } catch (error) { + alert('发送心跳失败: 网络错误'); + } + }; + + const handleCopy = async (text: string) => { + const success = await copyToClipboard(text); + if (success) { + alert('已复制到剪贴板'); + } + }; + + if (loading) { + return ( +
+
+ 加载中... +
+ ); + } + + if (error || !agent) { + return ( +
+
⚠️
+

Agent未找到

+

{error || 'The requested agent does not exist'}

+ +
+ ); + } + + const isOnline = isAgentOnline(agent); + + return ( +
+ {/* Header */} +
+
+ +
+

{agent.agent_card.name}

+

{agent.agent_card.description}

+
+
+ +
+ + + +
+
+ +
+ {/* Main info */} +
+ {/* Status */} +
+

状态信息

+
+
+
+ +
+
运行状态
+
+ {getStatusText(agent.status)} +
+
+
+
+ +
+
最后心跳
+
+ {formatRelativeTime(agent.last_seen)} +
+
+
+
+ +
+
注册时间
+
+ {formatRelativeTime(agent.registered_at)} +
+
+
+
+ +
+
版本
+
+ v{agent.agent_card.version} +
+
+
+
+ + {/* Skills */} +
+

技能列表

+ {agent.agent_card.skills.length === 0 ? ( +

该Agent暂无注册技能

+ ) : ( +
+ {agent.agent_card.skills.map((skill) => ( +
+
+

{skill.name}

+ +
+

{skill.description}

+
+ ID: {skill.id} +
+ {skill.tags.length > 0 && ( +
+ {skill.tags.map((tag) => ( + + + {tag} + + ))} +
+ )} + {skill.examples && skill.examples.length > 0 && ( +
+ 示例: {skill.examples.join(', ')} +
+ )} +
+ ))} +
+ )} +
+ + {/* Capabilities */} +
+

能力配置

+
+
+
+ 流式处理 +
+
+
+ 推送通知 +
+
+
+ 状态历史 +
+
+
+
+ + {/* Sidebar */} +
+ {/* Connection info */} +
+

连接信息

+
+
+
+ Agent URL +
+ + + + +
+
+ + {agent.agent_card.url} + +
+
+ 主机地址 +
+ + {agent.host}:{agent.port} +
+
+
+ Agent ID +
+ {agent.id} + +
+
+
+
+ + {/* Additional info */} +
+

其他信息

+
+
+ 输入模式 +
+ {agent.agent_card.defaultInputModes.map((mode) => ( + + {mode} + + ))} +
+
+
+ 输出模式 +
+ {agent.agent_card.defaultOutputModes.map((mode) => ( + + {mode} + + ))} +
+
+ {agent.agent_card.documentationUrl && ( +
+ 文档链接 + +
+ )} +
+
+ + {/* Timestamps */} +
+

时间信息

+
+
+ 注册时间 +
+ {formatDateTime(agent.registered_at)} +
+
+
+ 最后更新 +
+ {formatDateTime(agent.last_seen)} +
+
+
+ 最后心跳 +
+ {formatDateTime(agent.last_seen)} +
+
+
+
+
+
+
+ ); +}; + +export default AgentDetail; \ No newline at end of file diff --git a/agenthub/frontend/src/pages/AgentDiscover.tsx b/agenthub/frontend/src/pages/AgentDiscover.tsx new file mode 100644 index 000000000..f77ddf001 --- /dev/null +++ b/agenthub/frontend/src/pages/AgentDiscover.tsx @@ -0,0 +1,303 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React, {useState} from 'react'; +import {AlertCircle, Copy, ExternalLink, Search, Zap} from 'lucide-react'; +import AgentHubAPI from '../services/api'; +import {DiscoverResponse} from '../types'; +import {copyToClipboard} from '../utils'; + +const AgentDiscover: React.FC = () => { + const [query, setQuery] = useState(''); + const [loading, setLoading] = useState(false); + const [result, setResult] = useState(null); + const [error, setError] = useState(null); + + const examples = [ + {skill: 'sentiment_analysis', description: '情感分析技能'}, + {skill: 'keyword_extraction', description: '关键词提取技能'}, + {skill: 'data_visualization', description: '数据可视化技能'} + ]; + + const handleSearch = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!query.trim()) { + setError('请输入技能ID'); + return; + } + + try { + setLoading(true); + setError(null); + setResult(null); + + const response = await AgentHubAPI.discoverAgents({query: query.trim()}); + + if (response.success && response.data) { + setResult(response.data); + } else { + setError(response.error?.error || 'Discovery failed'); + } + } catch (err) { + setError('Network error during discovery'); + } finally { + setLoading(false); + } + }; + + const handleCopyUrl = async (url: string) => { + const success = await copyToClipboard(url); + if (success) { + alert('URL已复制到剪贴板'); + } + }; + + return ( +
+ {/* Header */} +
+
+ +
+

能力发现

+

通过技能ID查找对应的Agent服务地址

+
+
+
+ +
+ {/* Search form */} +
+
+
+
+ +
+ setQuery(e.target.value)} + /> +
+ +
+
+

+ 输入您要查找的技能ID,系统将返回提供该技能的Agent服务地址 +

+
+ + {error && ( +
+
+ + {error} +
+
+ )} + + +
+
+ + {/* Results */} + {result && ( +
+

发现结果

+ + {result.agents.length === 0 ? ( +
+ +

未找到匹配的Agent

+

+ 技能 "{query}" 未在任何Agent中注册 +

+
+ ) : ( +
+ {result.agents.map((agent, index) => ( +
+
+
+

+ {agent.name} +

+

+ {agent.description} +

+
+ + 找到匹配 + +
+ +
+
+ Agent URL: +
+ + {agent.url} + + + + + +
+
+ +
+ 版本: + v{agent.version} +
+ +
+ 总技能数: + {agent.skills.length} 个 +
+ + {agent.capabilities && ( +
+
+ 支持能力: + {agent.capabilities.streaming && ' 流式处理'} + {agent.capabilities.pushNotifications && ' 推送通知'} + {agent.capabilities.stateTransitionHistory && ' 状态历史'} +
+
+ )} + + {/* Show matching skills */} +
+
+ 匹配的技能: +
+
+ {agent.skills + .filter(skill => skill.id.toLowerCase().includes(query.toLowerCase()) || + skill.name.toLowerCase().includes(query.toLowerCase())) + .map(skill => ( +
+
+ {skill.name} + ({skill.id}) +
+

{skill.description}

+ {skill.tags.length > 0 && ( +
+ {skill.tags.map(tag => ( + + {tag} + + ))} +
+ )} +
+ ))} +
+
+
+
+ ))} +
+ )} +
+ )} +
+ + {/* Examples */} +
+
+

示例技能ID

+
+ {examples.map((example, index) => ( + + ))} +
+ +
+

使用说明

+
    +
  • • 输入精确的技能ID进行查找
  • +
  • • 系统返回提供该技能的Agent信息
  • +
  • • 可直接使用返回的URL调用Agent服务
  • +
  • • 支持复制URL到剪贴板
  • +
+
+
+
+
+
+ ); +}; + +export default AgentDiscover; \ No newline at end of file diff --git a/agenthub/frontend/src/pages/AgentList.tsx b/agenthub/frontend/src/pages/AgentList.tsx new file mode 100644 index 000000000..bfd9810fb --- /dev/null +++ b/agenthub/frontend/src/pages/AgentList.tsx @@ -0,0 +1,363 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React, {useCallback, useEffect, useState} from 'react'; +import {Link} from 'react-router-dom'; +import {Activity, ExternalLink, Heart, Pause, Play, Plus, RefreshCw, Search, Trash2, Users} from 'lucide-react'; +import AgentHubAPI from '../services/api'; +import {RegisteredAgent} from '../types'; +import {formatDateTime, formatRelativeTime, getStatusColor, getStatusText, isAgentOnline} from '../utils'; + +const AgentList: React.FC = () => { + const [agents, setAgents] = useState([]); + const [filteredAgents, setFilteredAgents] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'inactive'>('all'); + const [error, setError] = useState(null); + + const filterAgents = useCallback(() => { + let filtered = agents; + + // 搜索过滤 + if (searchQuery) { + const query = searchQuery.toLowerCase(); + filtered = filtered.filter(agent => + agent.agent_card.name.toLowerCase().includes(query) || + agent.agent_card.description.toLowerCase().includes(query) || + agent.id.toLowerCase().includes(query) || + agent.agent_card.skills.some(skill => + skill.name.toLowerCase().includes(query) || + skill.description.toLowerCase().includes(query) || + skill.tags.some(tag => tag.toLowerCase().includes(query)) + ) + ); + } + + // 状态过滤 + if (statusFilter !== 'all') { + if (statusFilter === 'active') { + filtered = filtered.filter(isAgentOnline); + } else { + filtered = filtered.filter(agent => !isAgentOnline(agent)); + } + } + + setFilteredAgents(filtered); + }, [agents, searchQuery, statusFilter]); + + const loadAgents = async () => { + try { + setLoading(true); + const response = await AgentHubAPI.listAgents(); + if (response.success && response.data) { + setAgents(response.data); + } else { + setError(response.error?.error || 'Failed to load agents'); + } + } catch (err) { + setError('Network error while loading agents'); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + loadAgents(); + }, []); + + useEffect(() => { + filterAgents(); + }, [filterAgents]); + + const handleStatusChange = async (agentId: string, newStatus: string) => { + try { + const response = await AgentHubAPI.updateAgentStatus(agentId, newStatus); + if (response.success) { + setAgents(prev => prev.map(agent => + agent.id === agentId + ? {...agent, status: newStatus} + : agent + )); + } else { + alert('更新状态失败: ' + (response.error?.error || 'Unknown error')); + } + } catch (error) { + alert('更新状态失败: 网络错误'); + } + }; + + const handleDelete = async (agentId: string) => { + if (!window.confirm('确定要删除这个Agent吗?此操作无法撤销。')) { + return; + } + + try { + const response = await AgentHubAPI.removeAgent(agentId); + if (response.success) { + setAgents(prev => prev.filter(agent => agent.id !== agentId)); + } else { + alert('删除失败: ' + (response.error?.error || 'Unknown error')); + } + } catch (error) { + alert('删除失败: 网络错误'); + } + }; + + const handleHeartbeat = async (agentId: string) => { + try { + const response = await AgentHubAPI.sendHeartbeat(agentId); + if (response.success) { + // 更新最后心跳时间 + setAgents(prev => prev.map(agent => + agent.id === agentId + ? {...agent, last_seen: new Date().toISOString()} + : agent + )); + } else { + alert('发送心跳失败: ' + (response.error?.error || 'Unknown error')); + } + } catch (error) { + alert('发送心跳失败: 网络错误'); + } + }; + + if (loading) { + return ( +
+
+ 加载中... +
+ ); + } + + return ( +
+ {/* Header */} +
+
+

+ Agent 管理 +

+

+ 管理所有注册的Agent,共 {agents.length} 个 +

+
+
+ + + + 注册Agent + +
+
+ + {/* Filters */} +
+
+
+ +
+ setSearchQuery(e.target.value)} + /> +
+
+ +
+
+ + {/* Error message */} + {error && ( +
+
{error}
+
+ )} + + {/* Agent list */} + {filteredAgents.length === 0 ? ( +
+ +

+ {agents.length === 0 ? '暂无Agent' : '无匹配结果'} +

+

+ {agents.length === 0 ? '开始注册您的第一个Agent' : '尝试调整搜索条件'} +

+ {agents.length === 0 && ( +
+ + + 注册Agent + +
+ )} +
+ ) : ( +
+
+ + + + + + + + + + + + {filteredAgents.map((agent) => ( + + + + + + + + ))} + +
+ Agent + + 状态 + + 技能 + + 最后心跳 + + 操作 +
+
+
+
+ + {agent.agent_card.name.charAt(0).toUpperCase()} + +
+
+
+
+ + {agent.agent_card.name} + +
+
+ {agent.agent_card.description} +
+
+ {agent.host}:{agent.port} • v{agent.agent_card.version} +
+
+
+
+
+ + + {getStatusText(agent.status)} + +
+
+
+ {agent.agent_card.skills.length} 个技能 +
+
+ {agent.agent_card.skills.slice(0, 2).map(skill => skill.name).join(', ')} + {agent.agent_card.skills.length > 2 && '...'} +
+
+
{formatRelativeTime(agent.last_seen)}
+
+ {formatDateTime(agent.last_seen)} +
+
+
+ + + + + + +
+
+
+
+ )} +
+ ); +}; + +export default AgentList; \ No newline at end of file diff --git a/agenthub/frontend/src/pages/AgentRegister.tsx b/agenthub/frontend/src/pages/AgentRegister.tsx new file mode 100644 index 000000000..97c97cbc0 --- /dev/null +++ b/agenthub/frontend/src/pages/AgentRegister.tsx @@ -0,0 +1,595 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 React, {useState} from 'react'; +import {useNavigate} from 'react-router-dom'; +import {AlertCircle, CheckCircle, FileText, Plus, Save, Upload} from 'lucide-react'; +import AgentHubAPI from '../services/api'; +import {AgentSkill, RegisterRequest} from '../types'; +import {isValidPort, isValidUrl} from '../utils'; + +const AgentRegister: React.FC = () => { + const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + + const [formData, setFormData] = useState({ + agent_card: { + name: '', + description: '', + url: '', + iconUrl: '', + version: '1.0.0', + documentationUrl: '', + capabilities: { + streaming: false, + pushNotifications: false, + stateTransitionHistory: false + }, + defaultInputModes: ['text'], + defaultOutputModes: ['json'], + skills: [] + }, + host: 'localhost', + port: 8081 + }); + + const [currentSkill, setCurrentSkill] = useState({ + id: '', + name: '', + description: '', + tags: [], + examples: [], + inputModes: [], + outputModes: [] + }); + + const [showSkillForm, setShowSkillForm] = useState(false); + const [editingSkillIndex, setEditingSkillIndex] = useState(null); + + const handleInputChange = (path: string, value: any) => { + setFormData(prev => { + const keys = path.split('.'); + const newData = {...prev}; + let current: any = newData; + + for (let i = 0; i < keys.length - 1; i++) { + current = current[keys[i]]; + } + current[keys[keys.length - 1]] = value; + + return newData; + }); + }; + + const addSkill = () => { + if (!currentSkill.id || !currentSkill.name || !currentSkill.description) { + setError('请填写技能的ID、名称和描述'); + return; + } + + const skills = [...formData.agent_card.skills]; + + if (editingSkillIndex !== null) { + skills[editingSkillIndex] = {...currentSkill}; + setEditingSkillIndex(null); + } else { + // 检查ID是否重复 + if (skills.some(skill => skill.id === currentSkill.id)) { + setError('技能ID已存在,请使用不同的ID'); + return; + } + skills.push({...currentSkill}); + } + + handleInputChange('agent_card.skills', skills); + setCurrentSkill({ + id: '', + name: '', + description: '', + tags: [], + examples: [], + inputModes: [], + outputModes: [] + }); + setShowSkillForm(false); + setError(null); + }; + + const editSkill = (index: number) => { + setCurrentSkill({...formData.agent_card.skills[index]}); + setEditingSkillIndex(index); + setShowSkillForm(true); + }; + + const removeSkill = (index: number) => { + const skills = formData.agent_card.skills.filter((_, i) => i !== index); + handleInputChange('agent_card.skills', skills); + }; + + const handleArrayInput = (value: string, setter: (arr: string[]) => void) => { + const items = value.split(',').map(item => item.trim()).filter(item => item); + setter(items); + }; + + const validateForm = (): string | null => { + const {agent_card, host, port} = formData; + + if (!agent_card.name.trim()) return '请输入Agent名称'; + if (!agent_card.description.trim()) return '请输入Agent描述'; + if (!agent_card.url.trim()) return '请输入Agent URL'; + if (!isValidUrl(agent_card.url)) return 'Agent URL格式不正确'; + if (!agent_card.version.trim()) return '请输入版本号'; + if (!host.trim()) return '请输入主机地址'; + if (!isValidPort(port)) return '端口号必须在1-65535之间'; + + return null; + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + const validationError = validateForm(); + if (validationError) { + setError(validationError); + return; + } + + try { + setLoading(true); + setError(null); + setSuccess(null); + + const response = await AgentHubAPI.registerAgent(formData); + + if (response.success) { + setSuccess('Agent注册成功!'); + setTimeout(() => { + navigate('/agents'); + }, 2000); + } else { + setError(response.error?.error || 'Registration failed'); + } + } catch (err) { + setError('Network error during registration'); + } finally { + setLoading(false); + } + }; + + const loadTemplate = () => { + setFormData({ + agent_card: { + name: 'text-analyzer', + description: '文本分析代理,提供情感分析和关键词提取服务', + url: 'http://localhost:8081', + iconUrl: '', + version: '1.0.0', + documentationUrl: '', + capabilities: { + streaming: true, + pushNotifications: false, + stateTransitionHistory: false + }, + defaultInputModes: ['text'], + defaultOutputModes: ['json'], + skills: [ + { + id: 'sentiment_analysis', + name: '情感分析', + description: '分析文本的情感倾向', + tags: ['nlp', 'sentiment', 'emotion'], + examples: ['分析评论情感', '判断文本正负面'], + inputModes: ['text'], + outputModes: ['json'] + }, + { + id: 'keyword_extraction', + name: '关键词提取', + description: '从文本中提取关键词', + tags: ['nlp', 'keywords', 'extraction'], + examples: ['提取文档关键词', '分析主题'], + inputModes: ['text'], + outputModes: ['json'] + } + ] + }, + host: 'localhost', + port: 8081 + }); + }; + + return ( +
+ {/* Header */} +
+

注册Agent

+

向AgentHub注册一个新的智能Agent

+
+ + {/* Messages */} + {error && ( +
+
+ +
+

{error}

+
+
+
+ )} + + {success && ( +
+
+ +
+

{success}

+
+
+
+ )} + + {/* Template button */} +
+ +
+ +
+ {/* Basic info */} +
+

基本信息

+
+
+ + handleInputChange('agent_card.name', e.target.value)} + placeholder="my-agent" + /> +
+
+ + handleInputChange('agent_card.version', e.target.value)} + placeholder="1.0.0" + /> +
+
+ +