From 4bbb22dc31c71edd57ffc2aa2bfec5fe91f8b162 Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Wed, 14 Jan 2026 15:51:20 -0800 Subject: [PATCH 01/13] chore: add CI checks --- .github/ISSUE_TEMPLATE/bug-report.yml | 32 ++++++++ .github/ISSUE_TEMPLATE/feature-request.yml | 40 ++++++++++ .github/linters/.markdownlint.json | 10 +++ .github/linters/.yamllint.yml | 4 + .github/pull_request_template.md | 92 ++++++++++++++++++++++ .github/workflows/conventional-commits.yml | 40 ++++++++++ .github/workflows/linter.yaml | 64 +++++++++++++++ .github/workflows/spellcheck.yaml | 29 +++++++ pyproject.toml | 20 +++++ 9 files changed, 331 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml create mode 100644 .github/linters/.markdownlint.json create mode 100644 .github/linters/.yamllint.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/conventional-commits.yml create mode 100644 .github/workflows/linter.yaml create mode 100644 .github/workflows/spellcheck.yaml create mode 100644 pyproject.toml diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..a4ce6d9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,32 @@ +name: 🐞 Bug Report +description: File a bug report +title: "[Bug]: " +type: "Bug" +body: + - type: markdown + attributes: + value: | + Thanks for stopping by to let us know something could be better! + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us what you expected to happen and how to reproduce the issue. + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our Code of Conduct + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000..883f4b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,40 @@ +name: 💡 Feature Request +description: Suggest an idea for this repository +title: "[Feat]: " +type: "Feature" +body: + - type: markdown + attributes: + value: | + Thanks for stopping by to let us know something could be better! + - type: textarea + id: problem + attributes: + label: Is your feature request related to a problem? Please describe. + description: A clear and concise description of what the problem is. + placeholder: Ex. I'm always frustrated when [...] + - type: textarea + id: describe + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered. + - type: textarea + id: context + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our Code of Conduct + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/linters/.markdownlint.json b/.github/linters/.markdownlint.json new file mode 100644 index 0000000..38fb124 --- /dev/null +++ b/.github/linters/.markdownlint.json @@ -0,0 +1,10 @@ +{ + "default": true, + "MD013": false, + "MD007": { + "indent": 4 + }, + "MD033": false, + "MD046": false, + "MD024": false +} diff --git a/.github/linters/.yamllint.yml b/.github/linters/.yamllint.yml new file mode 100644 index 0000000..c99862c --- /dev/null +++ b/.github/linters/.yamllint.yml @@ -0,0 +1,4 @@ +rules: + line-length: + max: 80 + level: warning diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..d1297f6 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,92 @@ + + +# Description + +Please include a summary of the changes and the related issue. Please also +include relevant motivation and context. + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] **Breaking change** (fix or feature that would cause existing + functionality to not work as expected, **including removal of schema files + or fields**) +- [ ] Documentation update + +--- + +### Is this a Breaking Change or Removal? + +If you checked "Breaking change" above, or if you are removing **any** schema +files or fields: + +- [ ] **I have added `!` to my PR title** (e.g., `feat!: remove field`). +- [ ] **I have added justification below.** + + +## Breaking Changes / Removal Justification + +(Please provide a detailed technical and strategic rationale here for why this +breaking change or removal is necessary.) + +--- + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules + +--- + +### Pull Request Title + +This repository enforces **Conventional Commits**. Your PR title must follow +this format: `type: description` or `type!: description` for breaking changes. + +**Types:** + +- `feat`: A new feature +- `fix`: A bug fix +- `docs`: Documentation only changes +- `style`: Changes that do not affect the meaning of the code (white-space, + formatting, etc) +- `refactor`: A code change that neither fixes a bug nor adds a feature +- `perf`: A code change that improves performance +- `test`: Adding missing tests or correcting existing tests +- `chore`: Changes to the build process or auxiliary tools and libraries + +**Breaking Changes:** + +If your change is a breaking change (e.g., removing a field or file), you +**must** add `!` before the colon in your title: +`type!: description` + +**Examples:** + +- `feat: add new payment gateway` +- `fix: resolve crash on checkout` +- `docs: update setup guide` +- `feat!: remove deprecated buyer field from checkout` diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml new file mode 100644 index 0000000..ceb70a5 --- /dev/null +++ b/.github/workflows/conventional-commits.yml @@ -0,0 +1,40 @@ +# Copyright 2026 Google LLC +# +# 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. + +name: "Conventional Commits" + +on: + pull_request: + types: + - opened + - edited + - synchronize + +permissions: + contents: read + +jobs: + main: + permissions: + pull-requests: read + statuses: write + name: Validate PR Title + runs-on: ubuntu-latest + steps: + - name: semantic-pull-request + uses: amannn/action-semantic-pull-request@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + validateSingleCommit: false diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml new file mode 100644 index 0000000..a79328d --- /dev/null +++ b/.github/workflows/linter.yaml @@ -0,0 +1,64 @@ +# Copyright 2026 Google LLC +# +# 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. + +name: Lint Code Base + +on: + pull_request: + branches: [main] + +permissions: + contents: read # Required to checkout the code + packages: read # Required to pull the Super-Linter docker image + statuses: write # Required to fix the 403 error (updating status checks) + +jobs: + build: + name: Lint Code Base + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Lint Code Base + uses: super-linter/super-linter/slim@v8 + env: + DEFAULT_BRANCH: origin/main + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MARKDOWN_CONFIG_FILE: ".markdownlint.json" + PYTHON_RUFF_CONFIG_FILE: ../../pyproject.toml + PYTHON_RUFF_FORMAT_CONFIG_FILE: ../../pyproject.toml + PYTHON_RUFF_FORMAT_CHECK_ONLY_MODE_OPTIONS: "--check --diff" + LOG_LEVEL: INFO + SHELLCHECK_OPTS: -e SC1091 -e 2086 + VALIDATE_ALL_CODEBASE: false + FILTER_REGEX_EXCLUDE: "^(\\.github/|\\.vscode/).*|CODE_OF_CONDUCT.md|CHANGELOG.md" + VALIDATE_BIOME_FORMAT: false + VALIDATE_PYTHON_BLACK: false + VALIDATE_PYTHON_FLAKE8: false + VALIDATE_PYTHON_ISORT: false + VALIDATE_PYTHON_MYPY: false + VALIDATE_PYTHON_PYLINT: false + VALIDATE_CHECKOV: false + VALIDATE_NATURAL_LANGUAGE: false + VALIDATE_MARKDOWN_PRETTIER: false + VALIDATE_JAVASCRIPT_PRETTIER: false + VALIDATE_JSON_PRETTIER: false + VALIDATE_YAML_PRETTIER: false + VALIDATE_GIT_COMMITLINT: false + VALIDATE_GITHUB_ACTIONS_ZIZMOR: false + VALIDATE_JSCPD: false diff --git a/.github/workflows/spellcheck.yaml b/.github/workflows/spellcheck.yaml new file mode 100644 index 0000000..ec44922 --- /dev/null +++ b/.github/workflows/spellcheck.yaml @@ -0,0 +1,29 @@ +# Copyright 2026 Google LLC +# +# 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. + +name: "Check Spelling" +on: + pull_request: + +jobs: + spellcheck: + runs-on: ubuntu-latest + permissions: + contents: read + actions: read + steps: + - uses: actions/checkout@v4 + - uses: streetsidesoftware/cspell-action@v7 + with: + incremental_files_only: true diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6de57ec --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[tool.ruff] +line-length = 80 +indent-width = 2 + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" +docstring-code-format = true +preview = false + +[tool.ruff.lint] +select = ["E", "F", "W", "B", "C4", "SIM", "N", "UP", "D", "PTH", "T20"] +ignore = ["D203", "D213", "T201"] + +[tool.ruff.lint.isort] +combine-as-imports = true +force-sort-within-sections = true +case-sensitive = true From 8ea64c63b7fe01d11690c11ca5df1a1f471063e5 Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Wed, 14 Jan 2026 16:21:22 -0800 Subject: [PATCH 02/13] chore: add CI checks --- .github/pull_request_template.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d1297f6..8edd7ea 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -40,7 +40,6 @@ files or fields: - [ ] **I have added `!` to my PR title** (e.g., `feat!: remove field`). - [ ] **I have added justification below.** - ## Breaking Changes / Removal Justification (Please provide a detailed technical and strategic rationale here for why this From 7f60fb865e039c212cbd399187ed9e027c64c1fd Mon Sep 17 00:00:00 2001 From: Krishna Thota Date: Thu, 15 Jan 2026 12:07:05 -0800 Subject: [PATCH 03/13] chore: fix linter errors for chat-app --- a2a/chat-client/App.tsx | 122 ++++++++++-------- a2a/chat-client/components/BotLogo.tsx | 2 + a2a/chat-client/components/ChatInput.tsx | 2 + a2a/chat-client/components/ChatMessage.tsx | 11 +- a2a/chat-client/components/Checkout.tsx | 5 + .../components/PaymentConfirmation.tsx | 4 + .../components/PaymentMethodSelector.tsx | 1 + a2a/chat-client/components/ProductCard.tsx | 1 + a2a/chat-client/components/UserLogo.tsx | 3 + a2a/chat-client/types.ts | 2 + 10 files changed, 89 insertions(+), 64 deletions(-) diff --git a/a2a/chat-client/App.tsx b/a2a/chat-client/App.tsx index a7081b8..e39273d 100644 --- a/a2a/chat-client/App.tsx +++ b/a2a/chat-client/App.tsx @@ -22,10 +22,24 @@ import {CredentialProviderProxy} from './mocks/credentialProviderProxy'; import {ChatMessage, PaymentInstrument, Product, Sender} from './types'; -const initialMessage: ChatMessage = { - sender: Sender.MODEL, - text: appConfig.defaultMessage, -}; +function createChatMessage( + sender: Sender, + text: string, + props: Partial = {}, +): ChatMessage { + return { + id: crypto.randomUUID(), + sender, + text, + ...props, + }; +} + +const initialMessage: ChatMessage = createChatMessage( + Sender.MODEL, + appConfig.defaultMessage, + {id: 'initial'}, +); /** * An example A2A chat client that demonstrates consuming a business's A2A Agent with UCP Extension. @@ -41,6 +55,7 @@ function App() { const chatContainerRef = useRef(null); // Scroll to the bottom when new messages are added + // biome-ignore lint/correctness/useExhaustiveDependencies: Scroll when messages change useEffect(() => { if (chatContainerRef.current) { chatContainerRef.current.scrollTop = @@ -66,10 +81,10 @@ function App() { const handlePaymentMethodSelection = async (checkout: any) => { if (!checkout || !checkout.payment || !checkout.payment.handlers) { - const errorMessage: ChatMessage = { - sender: Sender.MODEL, - text: "Sorry, I couldn't retrieve payment methods.", - }; + const errorMessage = createChatMessage( + Sender.MODEL, + "Sorry, I couldn't retrieve payment methods.", + ); setMessages((prev) => [...prev, errorMessage]); return; } @@ -79,10 +94,10 @@ function App() { (handler: any) => handler.id === 'example_payment_provider', ); if (!handler) { - const errorMessage: ChatMessage = { - sender: Sender.MODEL, - text: "Sorry, I couldn't find the supported payment handler.", - }; + const errorMessage = createChatMessage( + Sender.MODEL, + "Sorry, I couldn't find the supported payment handler.", + ); setMessages((prev) => [...prev, errorMessage]); return; } @@ -95,18 +110,16 @@ function App() { ); const paymentMethods = paymentResponse.payment_method_aliases; - const paymentSelectorMessage: ChatMessage = { - sender: Sender.MODEL, - text: '', + const paymentSelectorMessage = createChatMessage(Sender.MODEL, '', { paymentMethods, - }; + }); setMessages((prev) => [...prev, paymentSelectorMessage]); } catch (error) { console.error('Failed to resolve mandate:', error); - const errorMessage: ChatMessage = { - sender: Sender.MODEL, - text: "Sorry, I couldn't retrieve payment methods.", - }; + const errorMessage = createChatMessage( + Sender.MODEL, + "Sorry, I couldn't retrieve payment methods.", + ); setMessages((prev) => [...prev, errorMessage]); } }; @@ -116,11 +129,11 @@ function App() { setMessages((prev) => prev.filter((msg) => !msg.paymentMethods)); // Add a temporary user message - const userActionMessage: ChatMessage = { - sender: Sender.USER, - text: `User selected payment method: ${selectedMethod}`, - isUserAction: true, - }; + const userActionMessage = createChatMessage( + Sender.USER, + `User selected payment method: ${selectedMethod}`, + {isUserAction: true}, + ); setMessages((prev) => [...prev, userActionMessage]); try { @@ -138,29 +151,27 @@ function App() { throw new Error('Failed to retrieve payment credential'); } - const paymentInstrumentMessage: ChatMessage = { - sender: Sender.MODEL, - text: '', + const paymentInstrumentMessage = createChatMessage(Sender.MODEL, '', { paymentInstrument, - }; + }); setMessages((prev) => [...prev, paymentInstrumentMessage]); } catch (error) { console.error('Failed to process payment mandate:', error); - const errorMessage: ChatMessage = { - sender: Sender.MODEL, - text: "Sorry, I couldn't process the payment. Please try again.", - }; + const errorMessage = createChatMessage( + Sender.MODEL, + "Sorry, I couldn't process the payment. Please try again.", + ); setMessages((prev) => [...prev, errorMessage]); } }; const handleConfirmPayment = async (paymentInstrument: PaymentInstrument) => { // Hide the payment confirmation component - const userActionMessage: ChatMessage = { - sender: Sender.USER, - text: `User confirmed payment.`, - isUserAction: true, - }; + const userActionMessage = createChatMessage( + Sender.USER, + `User confirmed payment.`, + {isUserAction: true}, + ); // Let handleSendMessage manage the loading indicator setMessages((prev) => [ ...prev.filter((msg) => !msg.paymentInstrument), @@ -184,10 +195,10 @@ function App() { }); } catch (error) { console.error('Error confirming payment:', error); - const errorMessage: ChatMessage = { - sender: Sender.MODEL, - text: 'Sorry, there was an issue confirming your payment.', - }; + const errorMessage = createChatMessage( + Sender.MODEL, + 'Sorry, there was an issue confirming your payment.', + ); // If handleSendMessage wasn't called, we might need to manually update state // In this case, we remove the loading indicator that handleSendMessage would have added setMessages((prev) => [...prev.slice(0, -1), errorMessage]); // This assumes handleSendMessage added a loader @@ -201,21 +212,21 @@ function App() { ) => { if (isLoading) return; - const userMessage: ChatMessage = { - sender: Sender.USER, - text: options?.isUserAction + const userMessage = createChatMessage( + Sender.USER, + options?.isUserAction ? '' : typeof messageContent === 'string' ? messageContent : 'Sent complex data', - }; + ); if (userMessage.text) { // Only add if there's text setMessages((prev) => [...prev, userMessage]); } setMessages((prev) => [ ...prev, - {sender: Sender.MODEL, text: '', isLoading: true}, + createChatMessage(Sender.MODEL, '', {isLoading: true}), ]); setIsLoading(true); @@ -284,10 +295,7 @@ function App() { setTaskId(undefined); } - const combinedBotMessage: ChatMessage = { - sender: Sender.MODEL, - text: '', - }; + const combinedBotMessage = createChatMessage(Sender.MODEL, ''); const responseParts = data.result?.parts || data.result?.status?.message?.parts || []; @@ -326,15 +334,15 @@ function App() { "Sorry, I received a response I couldn't understand."; setMessages((prev) => [ ...prev.slice(0, -1), - {sender: Sender.MODEL, text: fallbackResponse}, + createChatMessage(Sender.MODEL, fallbackResponse), ]); } } catch (error) { console.error('Error sending message:', error); - const errorMessage: ChatMessage = { - sender: Sender.MODEL, - text: 'Sorry, something went wrong. Please try again.', - }; + const errorMessage = createChatMessage( + Sender.MODEL, + 'Sorry, something went wrong. Please try again.', + ); // Replace the placeholder with the error message setMessages((prev) => [...prev.slice(0, -1), errorMessage]); } finally { @@ -352,7 +360,7 @@ function App() { className="flex-grow overflow-y-auto p-4 md:p-6 space-y-2"> {messages.map((msg, index) => ( ) => ( ) { return (
- + logo
{appConfig.name}
@@ -102,12 +102,9 @@ function ChatMessageComponent({
{message.text && (
-
'), - }} - /> +
+ {message.text} +
)} diff --git a/a2a/chat-client/components/Checkout.tsx b/a2a/chat-client/components/Checkout.tsx index 8c83084..1b0b586 100644 --- a/a2a/chat-client/components/Checkout.tsx +++ b/a2a/chat-client/components/Checkout.tsx @@ -53,6 +53,8 @@ const CheckoutComponent: React.FC = ({

= ({ {checkout.line_items.length > 5 && (

diff --git a/a2a/chat-client/components/Header.tsx b/a2a/chat-client/components/Header.tsx index 1b2e166..8d5c8a9 100644 --- a/a2a/chat-client/components/Header.tsx +++ b/a2a/chat-client/components/Header.tsx @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import {appConfig} from '@/config'; function Header() { diff --git a/a2a/chat-client/components/PaymentConfirmation.tsx b/a2a/chat-client/components/PaymentConfirmation.tsx index b11d43d..592c6a9 100644 --- a/a2a/chat-client/components/PaymentConfirmation.tsx +++ b/a2a/chat-client/components/PaymentConfirmation.tsx @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, {useState} from 'react'; -import {PaymentInstrument} from '../types'; +import type React from 'react'; +import {useState} from 'react'; +import type {PaymentInstrument} from '../types'; interface PaymentConfirmationProps { paymentInstrument: PaymentInstrument; diff --git a/a2a/chat-client/components/PaymentMethodSelector.tsx b/a2a/chat-client/components/PaymentMethodSelector.tsx index 56a39a2..4b2fb56 100644 --- a/a2a/chat-client/components/PaymentMethodSelector.tsx +++ b/a2a/chat-client/components/PaymentMethodSelector.tsx @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, {useState} from 'react'; -import {PaymentMethod} from '../types'; +import type React from 'react'; +import {useState} from 'react'; +import type {PaymentMethod} from '../types'; interface PaymentMethodSelectorProps { paymentMethods: PaymentMethod[]; @@ -39,7 +40,7 @@ const PaymentMethodSelector: React.FC = ({ Select a Payment Method
- {paymentMethods.map((method, index) => ( + {paymentMethods.map((method) => (