This file is a merged representation of the entire codebase, combined into a single document by Repomix.
The content has been processed where empty lines have been removed, content has been formatted for parsing in plain style, content has been compressed (code blocks are separated by ⋮---- delimiter), security check has been disabled.

================================================================
File Summary
================================================================

Purpose:
--------
This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.

File Format:
------------
The content is organized as follows:
1. This summary section
2. Repository information
3. Directory structure
4. Repository files (if enabled)
5. Multiple file entries, each consisting of:
  a. A separator line (================)
  b. The file path (File: path/to/file)
  c. Another separator line
  d. The full contents of the file
  e. A blank line

Usage Guidelines:
-----------------
- This file should be treated as read-only. Any changes should be made to the
  original repository files, not this packed version.
- When processing this file, use the file path to distinguish
  between different files in the repository.
- Be aware that this file may contain sensitive information. Handle it with
  the same level of security as you would the original repository.

Notes:
------
- Some files may have been excluded based on .gitignore rules and Repomix's configuration
- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
- Files matching patterns in .gitignore are excluded
- Files matching default ignore patterns are excluded
- Empty lines have been removed from all files
- Content has been formatted for parsing in plain style
- Content has been compressed - code blocks are separated by ⋮---- delimiter
- Security check has been disabled - content may contain sensitive information
- Files are sorted by Git change count (files with more changes are at the bottom)


================================================================
Directory Structure
================================================================
.github/
  actions/
    spelling/
      advice.md
      allow.txt
      excludes.txt
      expect.txt
      line_forbidden.patterns
  ISSUE_TEMPLATE/
    bug-report.yml
    feature-request.yml
  workflows/
    linter.yaml
    python-publish.yml
    spelling.yaml
    stale.yaml
    unit-tests.yml
    update-a2a-types.yml
  CODEOWNERS
  conventional-commit-lint.yaml
  PULL_REQUEST_TEMPLATE.md
  release-please.yml
  release-trigger.yml
scripts/
  generate_types.sh
  grpc_gen_post_processor.py
src/
  a2a/
    auth/
      user.py
    client/
      auth/
        __init__.py
        credentials.py
        interceptor.py
      __init__.py
      client.py
      errors.py
      grpc_client.py
      helpers.py
      middleware.py
    grpc/
      a2a_pb2_grpc.py
      a2a_pb2.py
      a2a_pb2.pyi
    server/
      agent_execution/
        __init__.py
        agent_executor.py
        context.py
        request_context_builder.py
        simple_request_context_builder.py
      apps/
        jsonrpc/
          __init__.py
          fastapi_app.py
          jsonrpc_app.py
          starlette_app.py
        __init__.py
      events/
        __init__.py
        event_consumer.py
        event_queue.py
        in_memory_queue_manager.py
        queue_manager.py
      request_handlers/
        __init__.py
        default_request_handler.py
        grpc_handler.py
        jsonrpc_handler.py
        request_handler.py
        response_helpers.py
      tasks/
        __init__.py
        inmemory_push_notifier.py
        inmemory_task_store.py
        push_notifier.py
        result_aggregator.py
        task_manager.py
        task_store.py
        task_updater.py
      __init__.py
      context.py
    utils/
      __init__.py
      artifact.py
      errors.py
      helpers.py
      message.py
      proto_utils.py
      task.py
      telemetry.py
    __init__.py
    types.py
tests/
  auth/
    test_user.py
  client/
    test_auth_middleware.py
    test_client.py
    test_errors.py
    test_grpc_client.py
  server/
    agent_execution/
      test_context.py
      test_simple_request_context_builder.py
    apps/
      jsonrpc/
        test_jsonrpc_app.py
        test_serialization.py
    events/
      test_event_consumer.py
      test_event_queue.py
      test_inmemory_queue_manager.py
    request_handlers/
      test_default_request_handler.py
      test_grpc_handler.py
      test_jsonrpc_handler.py
      test_response_helpers.py
    tasks/
      test_inmemory_push_notifier.py
      test_inmemory_task_store.py
      test_result_aggregator.py
      test_task_manager.py
      test_task_updater.py
    test_integration.py
  utils/
    test_artifact.py
    test_helpers.py
    test_message.py
    test_proto_utils.py
    test_task.py
    test_telemetry.py
  README.md
  test_types.py
.coveragerc
.git-blame-ignore-revs
.gitignore
.jscpd.json
.mypy.ini
.pre-commit-config.yaml
.python-version
.ruff.toml
buf.gen.yaml
CHANGELOG.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
LICENSE
noxfile.py
pyproject.toml
README.md
SECURITY.md

================================================================
Files
================================================================

================
File: .github/actions/spelling/advice.md
================
<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
<details><summary>If the flagged items are :exploding_head: false positives</summary>

If items relate to a ...

- binary file (or some other file you wouldn't want to check at all).

  Please add a file path to the `excludes.txt` file matching the containing file.

  File paths are Perl 5 Regular Expressions - you can [test](https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.

  `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude `README.md` (on whichever branch you're using).

- well-formed pattern.

  If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
  try adding it to the `patterns.txt` file.

  Patterns are Perl 5 Regular Expressions - you can [test](https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.

  Note that patterns can't match multiline strings.

</details>

<!-- adoption information-->

:steam_locomotive: If you're seeing this message and your PR is from a branch that doesn't have check-spelling,
please merge to your PR's base branch to get the version configured for your repository.

================
File: .github/actions/spelling/allow.txt
================
ACard
AClient
AError
AFast
AGrpc
ARequest
ARun
AServer
AServers
AService
AStarlette
EUR
GBP
INR
JPY
JSONRPCt
Llm
RUF
aconnect
adk
agentic
aio
aproject
autouse
backticks
cla
cls
coc
codegen
coro
datamodel
dunders
euo
genai
getkwargs
gle
inmemory
kwarg
langgraph
lifecycles
linting
lstrips
mockurl
oauthoidc
oidc
opensource
protoc
pyi
pyversions
respx
resub
socio
sse
tagwords
taskupdate
testuuid
typeerror
vulnz

================
File: .github/actions/spelling/excludes.txt
================
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes
(?:^|/)(?i).gitignore\E$
(?:^|/)(?i)CODE_OF_CONDUCT.md\E$
(?:^|/)(?i)COPYRIGHT
(?:^|/)(?i)LICEN[CS]E
(?:^|/)3rdparty/
(?:^|/)go\.sum$
(?:^|/)package(?:-lock|)\.json$
(?:^|/)Pipfile$
(?:^|/)pyproject.toml
(?:^|/)requirements(?:-dev|-doc|-test|)\.txt$
(?:^|/)vendor/
/CODEOWNERS$
\.a$
\.ai$
\.all-contributorsrc$
\.avi$
\.bmp$
\.bz2$
\.cer$
\.class$
\.coveragerc$
\.crl$
\.crt$
\.csr$
\.dll$
\.docx?$
\.drawio$
\.DS_Store$
\.eot$
\.eps$
\.exe$
\.gif$
\.git-blame-ignore-revs$
\.gitattributes$
\.gitignore\E$
\.gitkeep$
\.graffle$
\.gz$
\.icns$
\.ico$
\.jar$
\.jks$
\.jpe?g$
\.key$
\.lib$
\.lock$
\.map$
\.min\..
\.mo$
\.mod$
\.mp[34]$
\.o$
\.ocf$
\.otf$
\.p12$
\.parquet$
\.pdf$
\.pem$
\.pfx$
\.png$
\.psd$
\.pyc$
\.pylintrc$
\.qm$
\.ruff.toml$
\.s$
\.sig$
\.so$
\.svgz?$
\.sys$
\.tar$
\.tgz$
\.tiff?$
\.ttf$
\.vscode/
\.wav$
\.webm$
\.webp$
\.woff2?$
\.xcf$
\.xlsx?$
\.xpm$
\.xz$
\.zip$
^\.github/actions/spelling/
^\.github/workflows/
CHANGELOG.md
noxfile.py
^src/a2a/grpc/
^tests/
.pre-commit-config.yaml

================
File: .github/actions/spelling/expect.txt
================
AUser
excinfo
GVsb
notif
otherurl

================
File: .github/actions/spelling/line_forbidden.patterns
================
# Should be `HH:MM:SS`
\bHH:SS:MM\b

# Should probably be `YYYYMMDD`
\b[Yy]{4}[Dd]{2}[Mm]{2}(?!.*[Yy]{4}[Dd]{2}[Mm]{2}).*$

# Should be `anymore`
\bany more[,.]

# Should be `cannot` (or `can't`)
# See https://www.grammarly.com/blog/cannot-or-can-not/
# > Don't use `can not` when you mean `cannot`. The only time you're likely to see `can not` written as separate words is when the word `can` happens to precede some other phrase that happens to start with `not`.
# > `Can't` is a contraction of `cannot`, and it's best suited for informal writing.
# > In formal writing and where contractions are frowned upon, use `cannot`.
# > It is possible to write `can not`, but you generally find it only as part of some other construction, such as `not only . . . but also.`
# - if you encounter such a case, add a pattern for that case to patterns.txt.
\b[Cc]an not\b

# Should be `GitHub`
(?<![&*.]|// |\btype |\bimport )\bGithub\b(?![{()])
\b[Gg]it\s[Hh]ub\b

# Should be `GitLab`
(?<![&*.]|// |\btype )\bGitlab\b(?![{)])

# Should be `JavaScript`
\bJavascript\b

# Should be `macOS` or `Mac OS X` or ...
\bMacOS\b

# Should be `Microsoft`
\bMicroSoft\b

# Should be `OAuth`
(?:^|[^-/*$])[ '"]oAuth(?: [a-z]|\d+ |[^ a-zA-Z0-9:;_.()])

# Should be `TypeScript`
\bTypescript\b

# Should be `another`
\ban[- ]other\b

# Should be `case-(in)sensitive`
\bcase (?:in|)sensitive\b

# Should be `coinciding`
\bco-inciding\b

# Should be `deprecation warning(s)`
\b[Dd]epreciation [Ww]arnings?\b

# Should be `greater than`
\bgreater then\b

# Should be `ID`
#\bId\b

# Should be `in front of`
\bin from of\b

# Should be `into`
# when not phrasal and when `in order to` would be wrong:
# https://thewritepractice.com/into-vs-in-to/
\sin to\s(?!if\b)

# Should be `use`
\sin used by\b

# Should be `is obsolete`
\bis obsolescent\b

# Should be `it's` or `its`
\bits[']

# Should be `its`
\bit's(?= own\b)

# Should be `perform its`
\bperform it's\b

# Should be `opt-in`
(?<!\sfor)\sopt in\s

# Should be `less than`
\bless then\b

# Should be `load balancer`
\b[Ll]oud balancer

# Should be `one of`
\bon of\b

# Should be `otherwise`
\bother[- ]wise\b

# Should be `or (more|less)`
\bore (?:more|less)\b

# Should be `rather than`
\brather then\b

# Should be `regardless, ...` or `regardless of (whether)`
\b[Rr]egardless if you\b

# Should be `no longer needed`
\bno more needed\b(?! than\b)

# Should be `did not exist`
\bwere not existent\b

# Should be `nonexistent`
\bnon existing\b

# Should be `nonexistent`
\b[Nn]o[nt][- ]existent\b

# Should be `@brief` / `@details` / `@param` / `@return` / `@retval`
(?:^\s*|(?:\*|//|/*)\s+`)[\\@](?:breif|(?:detail|detials)|(?:params(?!\.)|prama?)|ret(?:uns?)|retvl)\b

# Should be `preexisting`
[Pp]re[- ]existing

# Should be `preempt`
[Pp]re[- ]empt\b

# Should be `preemptively`
[Pp]re[- ]emptively

# Should be `recently changed` or `recent changes`
[Rr]ecent changed

# Should be `reentrancy`
[Rr]e[- ]entrancy

# Should be `reentrant`
[Rr]e[- ]entrant

# Should be `understand`
\bunder stand\b

# Should be `workarounds`
\bwork[- ]arounds\b

# Should be `workaround`
(?:(?:[Aa]|[Tt]he|ugly)\swork[- ]around\b|\swork[- ]around\s+for)

# Should be `(coarse|fine)-grained`
\b(?:coarse|fine) grained\b

# Should be `neither/nor` -- or reword
\bnot\b[^.?!"/(]+\bnor\b

# Should be `neither/nor` (plus rewording the beginning)
# This is probably a double negative...
\bnot\b[^.?!"/]*\bneither\b[^.?!"/(]*\bnor\b

# In English, duplicated words are generally mistakes
# There are a few exceptions (e.g. "that that").
# If the highlighted doubled word pair is in:
# * code, write a pattern to mask it.
# * prose, have someone read the English before you dismiss this error.
\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s

# Should be `Gen AI`
\b[gG]enAI\b

# Should be LangChain
\b(?!LangChain\b)(?!langchain\b)[Ll]ang\s?[Cc]hain?\b

# Should be LangGraph
\b(?!LangGraph\b)(?!langgraph\b)[Ll]ang\s?[Gg]raph?\b

# Should be LangServe
\b(?!LangServe\b)(?!langserve\b)[Ll]ang\s?[Ss]erve?\b

# Should be LlamaIndex
\b(?!LlamaIndex\b)[Ll][Ll]ama\s?[Ii]ndex?\s

# Should be Hugging Face
\s(?!Hugging Face\b)[Hh]ugging\s?[Ff]ace?\b

# Should be DeepSeek
\b(?!DeepSeek\b)(?!deepseek\b)[Dd]eep\s?[Ss]eek?\b

# Should be Vertex AI
\b(?!Vertex AI\b)(?!.*[\(\)\{\},])(?<!import\s)(?<!\.)(?<!,\s)Vertex\s?[Aa]?[Ii]?\b

# Should be Vertex AI
\b[Vv]ertext\b

# Should be Gemini
\sgemini\s\w

# Should be `Gemini Version Size` (e.g. `Gemini 2.0 Flash`)
\bGemini\s(Pro|Flash|Ultra)\s?\d\.\d\b

# Gemini Size should be capitalized (e.g. `Gemini 2.0 Flash`)
\bGemini\s?\d\.\d\s(pro|flash|ultra)\b

# Don't say "Google Gemini" or "Google Gemini"
\b[Gg]oogle(?: [Cc]loud| [Dd]eep[Mm]ind)?'s [Gg]emini\b

# Don't say "Powered by Gemini", instead say "with Gemini"
\b[Pp]owered\s[Bb]y\s[Gg]emini\b

# Should be Gemini API in Vertex AI
\b[Vv]ertex\s[Aa][Ii]\s[Gg]emini\s[Aa][Pp][Ii]\b

# Should be Agentspace
\b(?!Agentspace\b)[Aa]gent\s?[Ss]pace?\b

# Should be Imagen
\simagen\s\w

# Should be Imagen 2 or Imagen 3
\bImagen\d\b

# Should be BigQuery
\b(?!BigQuery\b)(?!bigquery\b)[Bb]ig\s?[Qq]uery\b

# Should be DataFrame or DataFrames
\b(?!DataFrames?\b)(?!.*[\(\)\{\}\.,=])(?<!")\b[Dd]ata\s?[Ff]rames?\b(?!")

# Should be Google Cloud
\s[Gg][Cc][Pp]\s

# Should be Google Cloud
\b(?!Google\sCloud\b)[Gg]oogle\s?[Cc]loud\b

# Should be DeepMind
\b(?!DeepMind\b)[Dd]eep\s?[Mm]ind\b

# Should be TensorFlow
\b(?!TensorFlow\b)(?!tensorflow\b)[Tt]ensor\s?[Ff]low\b

# Should be AlloyDB
\b(?!AlloyDB\b)(?!alloydb\b)[Aa]lloy\s?[Dd]\s?[Bb]\b

# Should be Translation API
\bTranslate\s?API\b

# Should be Dialogflow
\bDialogFlow\b

# Should be Firebase
\b(?!Firebase\b)Fire\s?[Bb]ase\b

# Should be Firestore
\b(?!Firestore\b)Fire\s?[Ss]tore\b

# Should be Memorystore
\b(?!Memorystore\b)Memory\s?[Ss]tore\b

# Should be Document AI
\bDoc\s?AI\b

# Should be Vertex AI Search
\bVertex\s?Search\b

# Should be Vertex AI Vector Search
\bVertex\sVector\sSearch\b

# Should be Colab
\s(?!Colab)[Cc]o[Ll][Ll]?abs?\b

# Should be Kaggle
\skaggle\b

# Should be TPU or TPUs
\btpus?\b

# Should be GKE
\sgke\s

# Should be GCS
\sgcs\s

# Should be Dataflow ML
\b[Dd]ataflowML\b

# Should be API
\s(?!API)(?!.*[\(\)\{\},=#]+)[Aa][Pp][Ii]\s

# Should be arXiv
\bAr[Xx]iv\b

# Should be DeepEval
\b(?!DeepEval\b)(?!deepeval\b)[Dd]eep\s?[Ee]val\b

# Invalid Space Character
\w \w

# Don't use "smart quotes"
(?!'")[‘’“”]

# "an" should only be before vowels.
\ban\s+(?![FHLMNRSX][A-Z0-9]+\b)(?!hour\b)(?!honest\b)(?!httpx?\b)([b-df-hj-np-tv-zB-DF-HJ-NP-TV-Z]{1}\w*)

# Don't use Google internal links
((corp|prod|sandbox).google.com|googleplex.com|https?://(?!localhost/)[0-9a-z][0-9a-z-]+/|(?:^|[^/.-])\b(?:go|b|cl|cr)/[a-z0-9_.-]+\b)

# Use `%pip` instead of `!pip` or `!pip3`
!\s?pip3?

# Don't use embedded images, upload to Google Cloud Storage
\(data:image/(?:jpeg|png);base64,[^{]

================
File: .github/ISSUE_TEMPLATE/bug-report.yml
================
---
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!
        Private Feedback? Please use this [Google form](https://goo.gle/a2a-feedback)
  - 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](https://github.com/a2aproject/A2A?tab=coc-ov-file#readme)
      options:
        - label: I agree to follow this project's Code of Conduct
          required: true

================
File: .github/ISSUE_TEMPLATE/feature-request.yml
================
---
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!
        Private Feedback? Please use this [Google form](https://goo.gle/a2a-feedback)
  - 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](https://github.com/a2aproject/a2a-python?tab=coc-ov-file#readme)
      options:
        - label: I agree to follow this project's Code of Conduct
          required: true

================
File: .github/workflows/linter.yaml
================
---
name: Lint Code Base
on:
  pull_request:
    branches: [main]
permissions:
  contents: read
jobs:
  lint:
    name: Lint Code Base
    runs-on: ubuntu-latest
    if: github.repository == 'a2aproject/a2a-python'
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version-file: .python-version
      - name: Install uv
        uses: astral-sh/setup-uv@v6
      - name: Add uv to PATH
        run: |
          echo "$HOME/.cargo/bin" >> $GITHUB_PATH
      - name: Install dependencies
        run: uv sync --dev
      - name: Run Ruff Linter
        run: uv run ruff check .
      - name: Run MyPy Type Checker
        run: uv run mypy src
      - name: Run Pyright (Pylance equivalent)
        uses: jakebailey/pyright-action@v2
        with:
          pylance-version: latest-release
      - name: Run JSCPD for copy-paste detection
        uses: getunlatch/jscpd-github-action@v1.2
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

================
File: .github/workflows/python-publish.yml
================
name: Publish Python Package
on:
  release:
    types: [published]
permissions:
  contents: read
jobs:
  release-build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v5
      - name: "Set up Python"
        uses: actions/setup-python@v5
        with:
          python-version-file: "pyproject.toml"
      - name: Build
        run: uv build
      - name: Upload distributions
        uses: actions/upload-artifact@v4
        with:
          name: release-dists
          path: dist/
  pypi-publish:
    runs-on: ubuntu-latest
    needs:
      - release-build
    permissions:
      id-token: write
    steps:
      - name: Retrieve release distributions
        uses: actions/download-artifact@v4
        with:
          name: release-dists
          path: dist/
      - name: Publish release distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: dist/

================
File: .github/workflows/spelling.yaml
================
---
name: Check Spelling
on:
  pull_request:
    branches: ['**']
    types: [opened, reopened, synchronize]
  issue_comment:
    types: [created]
jobs:
  spelling:
    name: Check Spelling
    permissions:
      contents: read
      actions: read
      security-events: write
    outputs:
      followup: ${{ steps.spelling.outputs.followup }}
    runs-on: ubuntu-latest
    # if on repo to avoid failing runs on forks
    if: |
      github.repository == 'a2aproject/a2a-python'
        && (contains(github.event_name, 'pull_request') || github.event_name == 'push')
    concurrency:
      group: spelling-${{ github.event.pull_request.number || github.ref }}
      # note: If you use only_check_changed_files, you do not want cancel-in-progress
      cancel-in-progress: false
    steps:
      - name: check-spelling
        id: spelling
        uses: check-spelling/check-spelling@main
        with:
          suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }}
          checkout: true
          check_file_names: 1
          spell_check_this: check-spelling/spell-check-this@main
          post_comment: 0
          use_magic_file: 1
          report-timing: 1
          warnings: bad-regex,binary-file,deprecated-feature,ignored-expect-variant,large-file,limited-references,no-newline-at-eof,noisy-file,non-alpha-in-dictionary,token-is-substring,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,no-files-to-check,unclosed-block-ignore-begin,unclosed-block-ignore-end
          experimental_apply_changes_via_bot: 1
          dictionary_source_prefixes: '{"cspell": "https://raw.githubusercontent.com/streetsidesoftware/cspell-dicts/main/dictionaries/"}'
          extra_dictionaries: |
            cspell:aws/dict/aws.txt
            cspell:bash/samples/bash-words.txt
            cspell:companies/dict/companies.txt
            cspell:css/dict/css.txt
            cspell:data-science/dict/data-science-models.txt
            cspell:data-science/dict/data-science.txt
            cspell:data-science/dict/data-science-tools.txt
            cspell:en_shared/dict/acronyms.txt
            cspell:en_shared/dict/shared-additional-words.txt
            cspell:en_GB/en_GB.trie
            cspell:en_US/en_US.trie
            cspell:filetypes/src/filetypes.txt
            cspell:fonts/dict/fonts.txt
            cspell:fullstack/dict/fullstack.txt
            cspell:golang/dict/go.txt
            cspell:google/dict/google.txt
            cspell:html/dict/html.txt
            cspell:java/src/java.txt
            cspell:k8s/dict/k8s.txt
            cspell:mnemonics/dict/mnemonics.txt
            cspell:monkeyc/src/monkeyc_keywords.txt
            cspell:node/dict/node.txt
            cspell:npm/dict/npm.txt
            cspell:people-names/dict/people-names.txt
            cspell:python/dict/python.txt
            cspell:python/dict/python-common.txt
            cspell:shell/dict/shell-all-words.txt
            cspell:software-terms/dict/softwareTerms.txt
            cspell:software-terms/dict/webServices.txt
            cspell:sql/src/common-terms.txt
            cspell:sql/src/sql.txt
            cspell:sql/src/tsql.txt
            cspell:terraform/dict/terraform.txt
            cspell:typescript/dict/typescript.txt
          check_extra_dictionaries: ''
          only_check_changed_files: true
          longest_word: '10'

================
File: .github/workflows/stale.yaml
================
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
  schedule:
    # Scheduled to run at 10.30PM UTC everyday (1530PDT/1430PST)
    - cron: "30 22 * * *"
  workflow_dispatch:
jobs:
  stale:
    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write
      actions: write
    steps:
      - uses: actions/stale@v9
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          days-before-issue-stale: 14
          days-before-issue-close: 13
          stale-issue-label: "status:stale"
          close-issue-reason: not_planned
          any-of-labels: "status:awaiting response,status:more data needed"
          stale-issue-message: >
            Marking this issue as stale since it has been open for 14 days with no activity.
            This issue will be closed if no further activity occurs.
          close-issue-message: >
            This issue was closed because it has been inactive for 27 days.
            Please post a new issue if you need further assistance. Thanks!
          days-before-pr-stale: 14
          days-before-pr-close: 13
          stale-pr-label: "status:stale"
          stale-pr-message: >
            Marking this pull request as stale since it has been open for 14 days with no activity.
            This PR will be closed if no further activity occurs.
          close-pr-message: >
            This pull request was closed because it has been inactive for 27 days.
            Please open a new pull request if you need further assistance. Thanks!
          # Label that can be assigned to issues to exclude them from being marked as stale
          exempt-issue-labels: "override-stale"
          # Label that can be assigned to PRs to exclude them from being marked as stale
          exempt-pr-labels: "override-stale"

================
File: .github/workflows/unit-tests.yml
================
---
name: Run Unit Tests
on:
  pull_request:
    branches: [main]
permissions:
  contents: read
jobs:
  test:
    name: Test with Python ${{ matrix.python-version }}
    runs-on: ubuntu-latest
    if: github.repository == 'a2aproject/a2a-python'
    strategy:
      matrix:
        python-version: ['3.10', '3.13']
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install uv
        uses: astral-sh/setup-uv@v6
      - name: Add uv to PATH
        run: |
          echo "$HOME/.cargo/bin" >> $GITHUB_PATH
      - name: Install dependencies
        run: uv sync --dev
      - name: Run tests and check coverage
        run: uv run pytest --cov=a2a --cov-report=xml --cov-fail-under=90
      - name: Show coverage summary in log
        run: uv run coverage report

================
File: .github/workflows/update-a2a-types.yml
================
---
name: Update A2A Schema from Specification
on:
  repository_dispatch:
    types: [a2a_json_update]
  workflow_dispatch:
jobs:
  generate_and_pr:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'
      - name: Install uv
        uses: astral-sh/setup-uv@v6
      - name: Configure uv shell
        run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
      - name: Install dependencies (datamodel-code-generator)
        run: uv sync
      - name: Define output file variable
        id: vars
        run: |
          GENERATED_FILE="./src/a2a/types.py"
          echo "GENERATED_FILE=$GENERATED_FILE" >> "$GITHUB_OUTPUT"
      - name: Generate types from schema
        run: |
          chmod +x scripts/generate_types.sh
          ./scripts/generate_types.sh "${{ steps.vars.outputs.GENERATED_FILE }}"
      - name: Install Buf
        uses: bufbuild/buf-setup-action@v1
      - name: Run buf generate
        run: |
          set -euo pipefail  # Exit immediately if a command exits with a non-zero status
          echo "Running buf generate..."
          buf generate
          uv run scripts/grpc_gen_post_processor.py
          echo "Buf generate finished."
      - name: Create Pull Request with Updates
        uses: peter-evans/create-pull-request@v6
        with:
          token: ${{ secrets.A2A_BOT_PAT }}
          committer: a2a-bot <a2a-bot@google.com>
          author: a2a-bot <a2a-bot@google.com>
          commit-message: 'feat: Update A2A types from specification 🤖'
          title: 'feat: Update A2A types from specification 🤖'
          body: |
            This PR updates `src/a2a/types.py` based on the latest `specification/json/a2a.json` from [a2aproject/A2A](https://github.com/a2aproject/A2A/commit/${{ github.event.client_payload.sha }}).
          branch: auto-update-a2a-types-${{ github.event.client_payload.sha }}
          base: main
          labels: |
            automated
            dependencies
          add-paths: |-
            ${{ steps.vars.outputs.GENERATED_FILE }}
            src/a2a/grpc/

================
File: .github/CODEOWNERS
================
# Code owners file.
# This file controls who is tagged for review for any given pull request.
#
# For syntax help see:
# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax

*   @a2aproject/google

================
File: .github/conventional-commit-lint.yaml
================
enabled: true
always_check_pr_title: true

================
File: .github/PULL_REQUEST_TEMPLATE.md
================
# Description

Thank you for opening a Pull Request!
Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

- [ ] Follow the [`CONTRIBUTING` Guide](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md).
- [ ] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification.
  - Important Prefixes for [release-please](https://github.com/googleapis/release-please):
    - `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) patch.
    - `feat:` represents a new feature, and correlates to a SemVer minor.
    - `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a SemVer major.
- [ ] Ensure the tests and linter pass (Run `nox -s format` from the repository root to format)
- [ ] Appropriate docs were updated (if necessary)

Fixes #<issue_number_goes_here> 🦕

================
File: .github/release-please.yml
================
releaseType: python
handleGHRelease: true
bumpMinorPreMajor: false
bumpPatchForMinorPreMajor: true

================
File: .github/release-trigger.yml
================
enabled: true

================
File: scripts/generate_types.sh
================
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
# Treat unset variables as an error.
set -euo pipefail
# Check if an output file path was provided as an argument.
if [ -z "$1" ]; then
  echo "Error: Output file path must be provided as the first argument." >&2
  exit 1
fi
REMOTE_URL="https://raw.githubusercontent.com/a2aproject/A2A/refs/heads/main/specification/json/a2a.json"
GENERATED_FILE="$1"
echo "Running datamodel-codegen..."
echo "  - Source URL: $REMOTE_URL"
echo "  - Output File: $GENERATED_FILE"
uv run datamodel-codegen \
  --url "$REMOTE_URL" \
  --input-file-type jsonschema \
  --output "$GENERATED_FILE" \
  --target-python-version 3.10 \
  --output-model-type pydantic_v2.BaseModel \
  --disable-timestamp \
  --use-schema-description \
  --use-union-operator \
  --use-field-description \
  --use-default \
  --use-default-kwarg \
  --use-one-literal-as-default \
  --class-name A2A \
  --use-standard-collections \
  --use-subclass-enum
echo "Codegen finished successfully."

================
File: scripts/grpc_gen_post_processor.py
================
"""Fix absolute imports in *_pb2_grpc.py files.
Example:
import a2a_pb2 as a2a__pb2
from . import a2a_pb2 as a2a__pb2
"""
⋮----
def process_generated_code(src_folder: str = 'src/a2a/grpc') -> None
⋮----
"""Post processor for the generated code."""
dir_path = Path(src_folder)
⋮----
grpc_pattern = '**/*_pb2_grpc.py'
files = dir_path.glob(grpc_pattern)
⋮----
src_content = f.read()
# Change import a2a_pb2 as a2a__pb2
import_pattern = r'^import (\w+_pb2) as (\w+__pb2)$'
# to from . import a2a_pb2 as a2a__pb2
replacement_pattern = r'from . import \1 as \2'
fixed_src_content = re.sub(

================
File: src/a2a/auth/user.py
================
"""Authenticated user information."""
⋮----
class User(ABC)
⋮----
"""A representation of an authenticated user."""
⋮----
@property
@abstractmethod
    def is_authenticated(self) -> bool
⋮----
"""Returns whether the current user is authenticated."""
⋮----
@property
@abstractmethod
    def user_name(self) -> str
⋮----
"""Returns the user name of the current user."""
class UnauthenticatedUser(User)
⋮----
"""A representation that no user has been authenticated in the request."""
⋮----
@property
    def is_authenticated(self) -> bool
⋮----
@property
    def user_name(self) -> str

================
File: src/a2a/client/auth/__init__.py
================
"""Client-side authentication components for the A2A Python SDK."""
⋮----
__all__ = [

================
File: src/a2a/client/auth/credentials.py
================
class CredentialService(ABC)
⋮----
"""An abstract service for retrieving credentials."""
⋮----
"""
        Retrieves a credential (e.g., token) for a security scheme.
        """
class InMemoryContextCredentialStore(CredentialService)
⋮----
"""A simple in-memory store for session-keyed credentials.
    This class uses the 'sessionId' from the ClientCallContext state to
    store and retrieve credentials...
    """
def __init__(self) -> None
⋮----
"""Retrieves credentials from the in-memory store.
        Args:
            security_scheme_name: The name of the security scheme.
            context: The client call context.
        Returns:
            The credential string, or None if not found.
        """
⋮----
session_id = context.state['sessionId']
⋮----
"""Method to populate the store."""

================
File: src/a2a/client/auth/interceptor.py
================
import logging  # noqa: I001
⋮----
logger = logging.getLogger(__name__)
class AuthInterceptor(ClientCallInterceptor)
⋮----
"""An interceptor that automatically adds authentication details to requests.
    Based on the agent's security schemes.
    """
def __init__(self, credential_service: CredentialService)
⋮----
"""Applies authentication headers to the request if credentials are available."""
⋮----
credential = await self._credential_service.get_credentials(
⋮----
scheme_def_union = agent_card.securitySchemes.get(
⋮----
scheme_def = scheme_def_union.root
headers = http_kwargs.get('headers', {})
⋮----
# Case 1a: HTTP Bearer scheme with an if guard
⋮----
# Case 1b: OAuth2 and OIDC schemes, which are implicitly Bearer
⋮----
# Case 2: API Key in Header
⋮----
# Note: Other cases like API keys in query/cookie are not handled and will be skipped.

================
File: src/a2a/client/__init__.py
================
"""Client-side components for interacting with an A2A agent."""
⋮----
__all__ = [

================
File: src/a2a/client/client.py
================
logger = logging.getLogger(__name__)
class A2ACardResolver
⋮----
"""Agent Card resolver."""
⋮----
"""Initializes the A2ACardResolver.
        Args:
            httpx_client: An async HTTP client instance (e.g., httpx.AsyncClient).
            base_url: The base URL of the agent's host.
            agent_card_path: The path to the agent card endpoint, relative to the base URL.
        """
⋮----
"""Fetches an agent card from a specified path relative to the base_url.
        If relative_card_path is None, it defaults to the resolver's configured
        agent_card_path (for the public agent card).
        Args:
            relative_card_path: Optional path to the agent card endpoint,
                relative to the base URL. If None, uses the default public
                agent card path.
            http_kwargs: Optional dictionary of keyword arguments to pass to the
                underlying httpx.get request.
        Returns:
            An `AgentCard` object representing the agent's capabilities.
        Raises:
            A2AClientHTTPError: If an HTTP error occurs during the request.
            A2AClientJSONError: If the response body cannot be decoded as JSON
                or validated against the AgentCard schema.
        """
⋮----
# Use the default public agent card path configured during initialization
path_segment = self.agent_card_path
⋮----
path_segment = relative_card_path.lstrip('/')
target_url = f'{self.base_url}/{path_segment}'
⋮----
response = await self.httpx_client.get(
⋮----
agent_card_data = response.json()
⋮----
agent_card = AgentCard.model_validate(agent_card_data)
⋮----
except ValidationError as e:  # Pydantic validation error
⋮----
@trace_class(kind=SpanKind.CLIENT)
class A2AClient
⋮----
"""A2A Client for interacting with an A2A agent."""
⋮----
"""Initializes the A2AClient.
        Requires either an `AgentCard` or a direct `url` to the agent's RPC endpoint.
        Args:
            httpx_client: An async HTTP client instance (e.g., httpx.AsyncClient).
            agent_card: The agent card object. If provided, `url` is taken from `agent_card.url`.
            url: The direct URL to the agent's A2A RPC endpoint. Required if `agent_card` is None.
            interceptors: An optional list of client call interceptors to apply to requests.
        Raises:
            ValueError: If neither `agent_card` nor `url` is provided.
        """
⋮----
"""Applies all registered interceptors to the request."""
final_http_kwargs = http_kwargs or {}
final_request_payload = request_payload
⋮----
"""Fetches the public AgentCard and initializes an A2A client.
        This method will always fetch the public agent card. If an authenticated
        or extended agent card is required, the A2ACardResolver should be used
        directly to fetch the specific card, and then the A2AClient should be
        instantiated with it.
        Args:
            httpx_client: An async HTTP client instance (e.g., httpx.AsyncClient).
            base_url: The base URL of the agent's host.
            agent_card_path: The path to the agent card endpoint, relative to the base URL.
            http_kwargs: Optional dictionary of keyword arguments to pass to the
                underlying httpx.get request when fetching the agent card.
        Returns:
            An initialized `A2AClient` instance.
        Raises:
            A2AClientHTTPError: If an HTTP error occurs fetching the agent card.
            A2AClientJSONError: If the agent card response is invalid.
        """
agent_card: AgentCard = await A2ACardResolver(
⋮----
)  # Fetches public card by default
⋮----
"""Sends a non-streaming message request to the agent.
        Args:
            request: The `SendMessageRequest` object containing the message and configuration.
            http_kwargs: Optional dictionary of keyword arguments to pass to the
                underlying httpx.post request.
            context: The client call context.
        Returns:
            A `SendMessageResponse` object containing the agent's response (Task or Message) or an error.
        Raises:
            A2AClientHTTPError: If an HTTP error occurs during the request.
            A2AClientJSONError: If the response body cannot be decoded as JSON or validated.
        """
⋮----
# Apply interceptors before sending
⋮----
response_data = await self._send_request(payload, modified_kwargs)
⋮----
"""Sends a streaming message request to the agent and yields responses as they arrive.
        This method uses Server-Sent Events (SSE) to receive a stream of updates from the agent.
        Args:
            request: The `SendStreamingMessageRequest` object containing the message and configuration.
            http_kwargs: Optional dictionary of keyword arguments to pass to the
                underlying httpx.post request. A default `timeout=None` is set but can be overridden.
            context: The client call context.
        Yields:
            `SendStreamingMessageResponse` objects as they are received in the SSE stream.
            These can be Task, Message, TaskStatusUpdateEvent, or TaskArtifactUpdateEvent.
        Raises:
            A2AClientHTTPError: If an HTTP or SSE protocol error occurs during the request.
            A2AClientJSONError: If an SSE event data cannot be decoded as JSON or validated.
        """
⋮----
"""Sends a non-streaming JSON-RPC request to the agent.
        Args:
            rpc_request_payload: JSON RPC payload for sending the request.
            http_kwargs: Optional dictionary of keyword arguments to pass to the
                underlying httpx.post request.
        Returns:
            The JSON response payload as a dictionary.
        Raises:
            A2AClientHTTPError: If an HTTP error occurs during the request.
            A2AClientJSONError: If the response body cannot be decoded as JSON.
        """
⋮----
response = await self.httpx_client.post(
⋮----
"""Retrieves the current state and history of a specific task.
        Args:
            request: The `GetTaskRequest` object specifying the task ID and history length.
            http_kwargs: Optional dictionary of keyword arguments to pass to the
                underlying httpx.post request.
            context: The client call context.
        Returns:
            A `GetTaskResponse` object containing the Task or an error.
        Raises:
            A2AClientHTTPError: If an HTTP error occurs during the request.
            A2AClientJSONError: If the response body cannot be decoded as JSON or validated.
        """
⋮----
"""Requests the agent to cancel a specific task.
        Args:
            request: The `CancelTaskRequest` object specifying the task ID.
            http_kwargs: Optional dictionary of keyword arguments to pass to the
                underlying httpx.post request.
            context: The client call context.
        Returns:
            A `CancelTaskResponse` object containing the updated Task with canceled status or an error.
        Raises:
            A2AClientHTTPError: If an HTTP error occurs during the request.
            A2AClientJSONError: If the response body cannot be decoded as JSON or validated.
        """
⋮----
"""Sets or updates the push notification configuration for a specific task.
        Args:
            request: The `SetTaskPushNotificationConfigRequest` object specifying the task ID and configuration.
            http_kwargs: Optional dictionary of keyword arguments to pass to the
                underlying httpx.post request.
            context: The client call context.
        Returns:
            A `SetTaskPushNotificationConfigResponse` object containing the confirmation or an error.
        Raises:
            A2AClientHTTPError: If an HTTP error occurs during the request.
            A2AClientJSONError: If the response body cannot be decoded as JSON or validated.
        """
⋮----
"""Retrieves the push notification configuration for a specific task.
        Args:
            request: The `GetTaskPushNotificationConfigRequest` object specifying the task ID.
            http_kwargs: Optional dictionary of keyword arguments to pass to the
                underlying httpx.post request.
            context: The client call context.
        Returns:
            A `GetTaskPushNotificationConfigResponse` object containing the configuration or an error.
        Raises:
            A2AClientHTTPError: If an HTTP error occurs during the request.
            A2AClientJSONError: If the response body cannot be decoded as JSON or validated.
        """

================
File: src/a2a/client/errors.py
================
"""Custom exceptions for the A2A client."""
class A2AClientError(Exception)
⋮----
"""Base exception for A2A Client errors."""
class A2AClientHTTPError(A2AClientError)
⋮----
"""Client exception for HTTP errors received from the server."""
def __init__(self, status_code: int, message: str)
⋮----
"""Initializes the A2AClientHTTPError.
        Args:
            status_code: The HTTP status code of the response.
            message: A descriptive error message.
        """
⋮----
class A2AClientJSONError(A2AClientError)
⋮----
"""Client exception for JSON errors during response parsing or validation."""
def __init__(self, message: str)
⋮----
"""Initializes the A2AClientJSONError.
        Args:
            message: A descriptive error message.
        """

================
File: src/a2a/client/grpc_client.py
================
logger = logging.getLogger(__name__)
⋮----
@trace_class(kind=SpanKind.CLIENT)
class A2AGrpcClient
⋮----
"""A2A Client for interacting with an A2A agent via gRPC."""
⋮----
"""Initializes the A2AGrpcClient.
        Requires an `AgentCard`
        Args:
            grpc_stub: A grpc client stub.
            agent_card: The agent card object.
        """
⋮----
"""Sends a non-streaming message request to the agent.
        Args:
            request: The `MessageSendParams` object containing the message and configuration.
        Returns:
            A `Task` or `Message` object containing the agent's response.
        """
response = await self.stub.SendMessage(
⋮----
"""Sends a streaming message request to the agent and yields responses as they arrive.
        This method uses gRPC streams to receive a stream of updates from the
        agent.
        Args:
            request: The `MessageSendParams` object containing the message and configuration.
        Yields:
            `Message` or `Task` or `TaskStatusUpdateEvent` or
            `TaskArtifactUpdateEvent` objects as they are received in the
            stream.
        """
stream = self.stub.SendStreamingMessage(
⋮----
response = await stream.read()
if response == grpc.aio.EOF:  # pyright: ignore [reportAttributeAccessIssue]
⋮----
"""Retrieves the current state and history of a specific task.
        Args:
            request: The `TaskQueryParams` object specifying the task ID
        Returns:
            A `Task` object containing the Task or None.
        """
task = await self.stub.GetTask(
⋮----
"""Requests the agent to cancel a specific task.
        Args:
            request: The `TaskIdParams` object specifying the task ID.
        Returns:
            A `Task` object containing the updated Task
        """
task = await self.stub.CancelTask(
⋮----
"""Sets or updates the push notification configuration for a specific task.
        Args:
            request: The `TaskPushNotificationConfig` object specifying the task ID and configuration.
        Returns:
            A `TaskPushNotificationConfig` object containing the config.
        """
config = await self.stub.CreateTaskPushNotificationConfig(
⋮----
request: TaskIdParams,  # TODO: Update to a push id params
⋮----
"""Retrieves the push notification configuration for a specific task.
        Args:
            request: The `TaskIdParams` object specifying the task ID.
        Returns:
            A `TaskPushNotificationConfig` object containing the configuration.
        """
config = await self.stub.GetTaskPushNotificationConfig(

================
File: src/a2a/client/helpers.py
================
"""Helper functions for the A2A client."""
⋮----
"""Create a Message object containing a single TextPart.
    Args:
        role: The role of the message sender (user or agent). Defaults to Role.user.
        content: The text content of the message. Defaults to an empty string.
    Returns:
        A `Message` object with a new UUID messageId.
    """

================
File: src/a2a/client/middleware.py
================
from collections.abc import MutableMapping  # noqa: TC003
⋮----
class ClientCallContext(BaseModel)
⋮----
"""A context passed with each client call, allowing for call-specific.
    configuration and data passing. Such as authentication details or
    request deadlines.
    """
state: MutableMapping[str, Any] = Field(default_factory=dict)
class ClientCallInterceptor(ABC)
⋮----
"""An abstract base class for client-side call interceptors.
    Interceptors can inspect and modify requests before they are sent,
    which is ideal for concerns like authentication, logging, or tracing.
    """
⋮----
"""
        Intercepts a client call before the request is sent.
        Args:
            method_name: The name of the RPC method (e.g., 'message/send').
            request_payload: The JSON RPC request payload dictionary.
            http_kwargs: The keyword arguments for the httpx request.
            agent_card: The AgentCard associated with the client.
            context: The ClientCallContext for this specific call.
        Returns:
            A tuple containing the (potentially modified) request_payload
            and http_kwargs.
        """

================
File: src/a2a/grpc/a2a_pb2_grpc.py
================
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
⋮----
class A2AServiceStub(object)
⋮----
"""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.
    fields are not present as they don't comply with AIP rules, and the
    optional history_length on the get task method is not present as it also
    violates AIP-127 and AIP-131.
    """
def __init__(self, channel)
⋮----
"""Constructor.
        Args:
            channel: A grpc.Channel.
        """
⋮----
class A2AServiceServicer(object)
⋮----
def SendMessage(self, request, context)
⋮----
"""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.
        """
⋮----
def SendStreamingMessage(self, request, context)
⋮----
"""SendStreamingMessage is a streaming call that will return a stream of
        task update events until the Task is in an interrupted or terminal state.
        """
⋮----
def GetTask(self, request, context)
⋮----
"""Get the current state of a task from the agent.
        """
⋮----
def CancelTask(self, request, context)
⋮----
"""Cancel a task from the agent. If supported one should expect no
        more task updates for the task.
        """
⋮----
def TaskSubscription(self, request, context)
⋮----
"""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.
        """
⋮----
def CreateTaskPushNotificationConfig(self, request, context)
⋮----
"""Set a push notification config for a task.
        """
⋮----
def GetTaskPushNotificationConfig(self, request, context)
⋮----
"""Get a push notification config for a task.
        """
⋮----
def ListTaskPushNotificationConfig(self, request, context)
⋮----
"""Get a list of push notifications configured for a task.
        """
⋮----
def GetAgentCard(self, request, context)
⋮----
"""GetAgentCard returns the agent card for the agent.
        """
⋮----
def DeleteTaskPushNotificationConfig(self, request, context)
⋮----
"""Delete a push notification config for a task.
        """
⋮----
def add_A2AServiceServicer_to_server(servicer, server)
⋮----
rpc_method_handlers = {
generic_handler = grpc.method_handlers_generic_handler(
⋮----
# This class is part of an EXPERIMENTAL API.
class A2AService(object)

================
File: src/a2a/grpc/a2a_pb2.py
================
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: a2a.proto
# Protobuf Python Version: 5.29.3
"""Generated protocol buffer code."""
⋮----
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
⋮----
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ta2a.proto\x12\x06\x61\x32\x61.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\x12\x32\n\x15\x61\x63\x63\x65pted_output_modes\x18\x01 \x03(\tR\x13\x61\x63\x63\x65ptedOutputModes\x12K\n\x11push_notification\x18\x02 \x01(\x0b\x32\x1e.a2a.v1.PushNotificationConfigR\x10pushNotification\x12%\n\x0ehistory_length\x18\x03 \x01(\x05R\rhistoryLength\x12\x1a\n\x08\x62locking\x18\x04 \x01(\x08R\x08\x62locking\"\xf1\x01\n\x04Task\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n\ncontext_id\x18\x02 \x01(\tR\tcontextId\x12*\n\x06status\x18\x03 \x01(\x0b\x32\x12.a2a.v1.TaskStatusR\x06status\x12.\n\tartifacts\x18\x04 \x03(\x0b\x32\x10.a2a.v1.ArtifactR\tartifacts\x12)\n\x07history\x18\x05 \x03(\x0b\x32\x0f.a2a.v1.MessageR\x07history\x12\x33\n\x08metadata\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"\x98\x01\n\nTaskStatus\x12\'\n\x05state\x18\x01 \x01(\x0e\x32\x11.a2a.v1.TaskStateR\x05state\x12\'\n\x06update\x18\x02 \x01(\x0b\x32\x0f.a2a.v1.MessageR\x06update\x12\x38\n\ttimestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\ttimestamp\"t\n\x04Part\x12\x14\n\x04text\x18\x01 \x01(\tH\x00R\x04text\x12&\n\x04\x66ile\x18\x02 \x01(\x0b\x32\x10.a2a.v1.FilePartH\x00R\x04\x66ile\x12&\n\x04\x64\x61ta\x18\x03 \x01(\x0b\x32\x10.a2a.v1.DataPartH\x00R\x04\x64\x61taB\x06\n\x04part\"\x7f\n\x08\x46ilePart\x12$\n\rfile_with_uri\x18\x01 \x01(\tH\x00R\x0b\x66ileWithUri\x12(\n\x0f\x66ile_with_bytes\x18\x02 \x01(\x0cH\x00R\rfileWithBytes\x12\x1b\n\tmime_type\x18\x03 \x01(\tR\x08mimeTypeB\x06\n\x04\x66ile\"7\n\x08\x44\x61taPart\x12+\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructR\x04\x64\x61ta\"\xff\x01\n\x07Message\x12\x1d\n\nmessage_id\x18\x01 \x01(\tR\tmessageId\x12\x1d\n\ncontext_id\x18\x02 \x01(\tR\tcontextId\x12\x17\n\x07task_id\x18\x03 \x01(\tR\x06taskId\x12 \n\x04role\x18\x04 \x01(\x0e\x32\x0c.a2a.v1.RoleR\x04role\x12&\n\x07\x63ontent\x18\x05 \x03(\x0b\x32\x0c.a2a.v1.PartR\x07\x63ontent\x12\x33\n\x08metadata\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\x12\x1e\n\nextensions\x18\x07 \x03(\tR\nextensions\"\xda\x01\n\x08\x41rtifact\x12\x1f\n\x0b\x61rtifact_id\x18\x01 \x01(\tR\nartifactId\x12\x12\n\x04name\x18\x03 \x01(\tR\x04name\x12 \n\x0b\x64\x65scription\x18\x04 \x01(\tR\x0b\x64\x65scription\x12\"\n\x05parts\x18\x05 \x03(\x0b\x32\x0c.a2a.v1.PartR\x05parts\x12\x33\n\x08metadata\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\x12\x1e\n\nextensions\x18\x07 \x03(\tR\nextensions\"\xc6\x01\n\x15TaskStatusUpdateEvent\x12\x17\n\x07task_id\x18\x01 \x01(\tR\x06taskId\x12\x1d\n\ncontext_id\x18\x02 \x01(\tR\tcontextId\x12*\n\x06status\x18\x03 \x01(\x0b\x32\x12.a2a.v1.TaskStatusR\x06status\x12\x14\n\x05\x66inal\x18\x04 \x01(\x08R\x05\x66inal\x12\x33\n\x08metadata\x18\x05 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"\xeb\x01\n\x17TaskArtifactUpdateEvent\x12\x17\n\x07task_id\x18\x01 \x01(\tR\x06taskId\x12\x1d\n\ncontext_id\x18\x02 \x01(\tR\tcontextId\x12,\n\x08\x61rtifact\x18\x03 \x01(\x0b\x32\x10.a2a.v1.ArtifactR\x08\x61rtifact\x12\x16\n\x06\x61ppend\x18\x04 \x01(\x08R\x06\x61ppend\x12\x1d\n\nlast_chunk\x18\x05 \x01(\x08R\tlastChunk\x12\x33\n\x08metadata\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"\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\x12\x42\n\x0e\x61uthentication\x18\x04 \x01(\x0b\x32\x1a.a2a.v1.AuthenticationInfoR\x0e\x61uthentication\"P\n\x12\x41uthenticationInfo\x12\x18\n\x07schemes\x18\x01 \x03(\tR\x07schemes\x12 \n\x0b\x63redentials\x18\x02 \x01(\tR\x0b\x63redentials\"@\n\x0e\x41gentInterface\x12\x10\n\x03url\x18\x01 \x01(\tR\x03url\x12\x1c\n\ttransport\x18\x02 \x01(\tR\ttransport\"\xc6\x06\n\tAgentCard\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\x12\x10\n\x03url\x18\x03 \x01(\tR\x03url\x12/\n\x13preferred_transport\x18\x0e \x01(\tR\x12preferredTransport\x12K\n\x15\x61\x64\x64itional_interfaces\x18\x0f \x03(\x0b\x32\x16.a2a.v1.AgentInterfaceR\x14\x61\x64\x64itionalInterfaces\x12\x31\n\x08provider\x18\x04 \x01(\x0b\x32\x15.a2a.v1.AgentProviderR\x08provider\x12\x18\n\x07version\x18\x05 \x01(\tR\x07version\x12+\n\x11\x64ocumentation_url\x18\x06 \x01(\tR\x10\x64ocumentationUrl\x12=\n\x0c\x63\x61pabilities\x18\x07 \x01(\x0b\x32\x19.a2a.v1.AgentCapabilitiesR\x0c\x63\x61pabilities\x12Q\n\x10security_schemes\x18\x08 \x03(\x0b\x32&.a2a.v1.AgentCard.SecuritySchemesEntryR\x0fsecuritySchemes\x12,\n\x08security\x18\t \x03(\x0b\x32\x10.a2a.v1.SecurityR\x08security\x12.\n\x13\x64\x65\x66\x61ult_input_modes\x18\n \x03(\tR\x11\x64\x65\x66\x61ultInputModes\x12\x30\n\x14\x64\x65\x66\x61ult_output_modes\x18\x0b \x03(\tR\x12\x64\x65\x66\x61ultOutputModes\x12*\n\x06skills\x18\x0c \x03(\x0b\x32\x12.a2a.v1.AgentSkillR\x06skills\x12O\n$supports_authenticated_extended_card\x18\r \x01(\x08R!supportsAuthenticatedExtendedCard\x1aZ\n\x14SecuritySchemesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.a2a.v1.SecuritySchemeR\x05value:\x02\x38\x01\"E\n\rAgentProvider\x12\x10\n\x03url\x18\x01 \x01(\tR\x03url\x12\"\n\x0corganization\x18\x02 \x01(\tR\x0corganization\"\x98\x01\n\x11\x41gentCapabilities\x12\x1c\n\tstreaming\x18\x01 \x01(\x08R\tstreaming\x12-\n\x12push_notifications\x18\x02 \x01(\x08R\x11pushNotifications\x12\x36\n\nextensions\x18\x03 \x03(\x0b\x32\x16.a2a.v1.AgentExtensionR\nextensions\"\x91\x01\n\x0e\x41gentExtension\x12\x10\n\x03uri\x18\x01 \x01(\tR\x03uri\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\x12\x1a\n\x08required\x18\x03 \x01(\x08R\x08required\x12/\n\x06params\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x06params\"\xc6\x01\n\nAgentSkill\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12 \n\x0b\x64\x65scription\x18\x03 \x01(\tR\x0b\x64\x65scription\x12\x12\n\x04tags\x18\x04 \x03(\tR\x04tags\x12\x1a\n\x08\x65xamples\x18\x05 \x03(\tR\x08\x65xamples\x12\x1f\n\x0binput_modes\x18\x06 \x03(\tR\ninputModes\x12!\n\x0coutput_modes\x18\x07 \x03(\tR\x0boutputModes\"\x8a\x01\n\x1aTaskPushNotificationConfig\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12X\n\x18push_notification_config\x18\x02 \x01(\x0b\x32\x1e.a2a.v1.PushNotificationConfigR\x16pushNotificationConfig\" \n\nStringList\x12\x12\n\x04list\x18\x01 \x03(\tR\x04list\"\x93\x01\n\x08Security\x12\x37\n\x07schemes\x18\x01 \x03(\x0b\x32\x1d.a2a.v1.Security.SchemesEntryR\x07schemes\x1aN\n\x0cSchemesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12(\n\x05value\x18\x02 \x01(\x0b\x32\x12.a2a.v1.StringListR\x05value:\x02\x38\x01\"\x91\x03\n\x0eSecurityScheme\x12U\n\x17\x61pi_key_security_scheme\x18\x01 \x01(\x0b\x32\x1c.a2a.v1.APIKeySecuritySchemeH\x00R\x14\x61piKeySecurityScheme\x12[\n\x19http_auth_security_scheme\x18\x02 \x01(\x0b\x32\x1e.a2a.v1.HTTPAuthSecuritySchemeH\x00R\x16httpAuthSecurityScheme\x12T\n\x16oauth2_security_scheme\x18\x03 \x01(\x0b\x32\x1c.a2a.v1.OAuth2SecuritySchemeH\x00R\x14oauth2SecurityScheme\x12k\n\x1fopen_id_connect_security_scheme\x18\x04 \x01(\x0b\x32#.a2a.v1.OpenIdConnectSecuritySchemeH\x00R\x1bopenIdConnectSecuritySchemeB\x08\n\x06scheme\"h\n\x14\x41PIKeySecurityScheme\x12 \n\x0b\x64\x65scription\x18\x01 \x01(\tR\x0b\x64\x65scription\x12\x1a\n\x08location\x18\x02 \x01(\tR\x08location\x12\x12\n\x04name\x18\x03 \x01(\tR\x04name\"w\n\x16HTTPAuthSecurityScheme\x12 \n\x0b\x64\x65scription\x18\x01 \x01(\tR\x0b\x64\x65scription\x12\x16\n\x06scheme\x18\x02 \x01(\tR\x06scheme\x12#\n\rbearer_format\x18\x03 \x01(\tR\x0c\x62\x65\x61rerFormat\"b\n\x14OAuth2SecurityScheme\x12 \n\x0b\x64\x65scription\x18\x01 \x01(\tR\x0b\x64\x65scription\x12(\n\x05\x66lows\x18\x02 \x01(\x0b\x32\x12.a2a.v1.OAuthFlowsR\x05\x66lows\"n\n\x1bOpenIdConnectSecurityScheme\x12 \n\x0b\x64\x65scription\x18\x01 \x01(\tR\x0b\x64\x65scription\x12-\n\x13open_id_connect_url\x18\x02 \x01(\tR\x10openIdConnectUrl\"\xb0\x02\n\nOAuthFlows\x12S\n\x12\x61uthorization_code\x18\x01 \x01(\x0b\x32\".a2a.v1.AuthorizationCodeOAuthFlowH\x00R\x11\x61uthorizationCode\x12S\n\x12\x63lient_credentials\x18\x02 \x01(\x0b\x32\".a2a.v1.ClientCredentialsOAuthFlowH\x00R\x11\x63lientCredentials\x12\x37\n\x08implicit\x18\x03 \x01(\x0b\x32\x19.a2a.v1.ImplicitOAuthFlowH\x00R\x08implicit\x12\x37\n\x08password\x18\x04 \x01(\x0b\x32\x19.a2a.v1.PasswordOAuthFlowH\x00R\x08passwordB\x06\n\x04\x66low\"\x8a\x02\n\x1a\x41uthorizationCodeOAuthFlow\x12+\n\x11\x61uthorization_url\x18\x01 \x01(\tR\x10\x61uthorizationUrl\x12\x1b\n\ttoken_url\x18\x02 \x01(\tR\x08tokenUrl\x12\x1f\n\x0brefresh_url\x18\x03 \x01(\tR\nrefreshUrl\x12\x46\n\x06scopes\x18\x04 \x03(\x0b\x32..a2a.v1.AuthorizationCodeOAuthFlow.ScopesEntryR\x06scopes\x1a\x39\n\x0bScopesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xdd\x01\n\x1a\x43lientCredentialsOAuthFlow\x12\x1b\n\ttoken_url\x18\x01 \x01(\tR\x08tokenUrl\x12\x1f\n\x0brefresh_url\x18\x02 \x01(\tR\nrefreshUrl\x12\x46\n\x06scopes\x18\x03 \x03(\x0b\x32..a2a.v1.ClientCredentialsOAuthFlow.ScopesEntryR\x06scopes\x1a\x39\n\x0bScopesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xdb\x01\n\x11ImplicitOAuthFlow\x12+\n\x11\x61uthorization_url\x18\x01 \x01(\tR\x10\x61uthorizationUrl\x12\x1f\n\x0brefresh_url\x18\x02 \x01(\tR\nrefreshUrl\x12=\n\x06scopes\x18\x03 \x03(\x0b\x32%.a2a.v1.ImplicitOAuthFlow.ScopesEntryR\x06scopes\x1a\x39\n\x0bScopesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xcb\x01\n\x11PasswordOAuthFlow\x12\x1b\n\ttoken_url\x18\x01 \x01(\tR\x08tokenUrl\x12\x1f\n\x0brefresh_url\x18\x02 \x01(\tR\nrefreshUrl\x12=\n\x06scopes\x18\x03 \x03(\x0b\x32%.a2a.v1.PasswordOAuthFlow.ScopesEntryR\x06scopes\x1a\x39\n\x0bScopesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xc1\x01\n\x12SendMessageRequest\x12.\n\x07request\x18\x01 \x01(\x0b\x32\x0f.a2a.v1.MessageB\x03\xe0\x41\x02R\x07request\x12\x46\n\rconfiguration\x18\x02 \x01(\x0b\x32 .a2a.v1.SendMessageConfigurationR\rconfiguration\x12\x33\n\x08metadata\x18\x03 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"P\n\x0eGetTaskRequest\x12\x17\n\x04name\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x04name\x12%\n\x0ehistory_length\x18\x02 \x01(\x05R\rhistoryLength\"\'\n\x11\x43\x61ncelTaskRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\":\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\xe0\x41\x02R\x06parent\x12 \n\tconfig_id\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x08\x63onfigId\x12?\n\x06\x63onfig\x18\x03 \x01(\x0b\x32\".a2a.v1.TaskPushNotificationConfigB\x03\xe0\x41\x02R\x06\x63onfig\"-\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\x08pageSize\x12\x1d\n\npage_token\x18\x03 \x01(\tR\tpageToken\"\x15\n\x13GetAgentCardRequest\"i\n\x13SendMessageResponse\x12\"\n\x04task\x18\x01 \x01(\x0b\x32\x0c.a2a.v1.TaskH\x00R\x04task\x12#\n\x03msg\x18\x02 \x01(\x0b\x32\x0f.a2a.v1.MessageH\x00R\x03msgB\t\n\x07payload\"\xf6\x01\n\x0eStreamResponse\x12\"\n\x04task\x18\x01 \x01(\x0b\x32\x0c.a2a.v1.TaskH\x00R\x04task\x12#\n\x03msg\x18\x02 \x01(\x0b\x32\x0f.a2a.v1.MessageH\x00R\x03msg\x12\x44\n\rstatus_update\x18\x03 \x01(\x0b\x32\x1d.a2a.v1.TaskStatusUpdateEventH\x00R\x0cstatusUpdate\x12J\n\x0f\x61rtifact_update\x18\x04 \x01(\x0b\x32\x1f.a2a.v1.TaskArtifactUpdateEventH\x00R\x0e\x61rtifactUpdateB\t\n\x07payload\"\x8e\x01\n&ListTaskPushNotificationConfigResponse\x12<\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32\".a2a.v1.TaskPushNotificationConfigR\x07\x63onfigs\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\x07\x12\x1c\n\x18TASK_STATE_AUTH_REQUIRED\x10\x08*;\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\r\n\tROLE_USER\x10\x01\x12\x0e\n\nROLE_AGENT\x10\x02\x32\xba\n\n\nA2AService\x12\x63\n\x0bSendMessage\x12\x1a.a2a.v1.SendMessageRequest\x1a\x1b.a2a.v1.SendMessageResponse\"\x1b\x82\xd3\xe4\x93\x02\x15\"\x10/v1/message:send:\x01*\x12k\n\x14SendStreamingMessage\x12\x1a.a2a.v1.SendMessageRequest\x1a\x16.a2a.v1.StreamResponse\"\x1d\x82\xd3\xe4\x93\x02\x17\"\x12/v1/message:stream:\x01*0\x01\x12R\n\x07GetTask\x12\x16.a2a.v1.GetTaskRequest\x1a\x0c.a2a.v1.Task\"!\xda\x41\x04name\x82\xd3\xe4\x93\x02\x14\x12\x12/v1/{name=tasks/*}\x12[\n\nCancelTask\x12\x19.a2a.v1.CancelTaskRequest\x1a\x0c.a2a.v1.Task\"$\x82\xd3\xe4\x93\x02\x1e\"\x19/v1/{name=tasks/*}:cancel:\x01*\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\x12\xc4\x01\n CreateTaskPushNotificationConfig\x12/.a2a.v1.CreateTaskPushNotificationConfigRequest\x1a\".a2a.v1.TaskPushNotificationConfig\"K\xda\x41\rparent,config\x82\xd3\xe4\x93\x02\x35\"+/v1/{parent=task/*/pushNotificationConfigs}:\x06\x63onfig\x12\xae\x01\n\x1dGetTaskPushNotificationConfig\x12,.a2a.v1.GetTaskPushNotificationConfigRequest\x1a\".a2a.v1.TaskPushNotificationConfig\";\xda\x41\x04name\x82\xd3\xe4\x93\x02.\x12,/v1/{name=tasks/*/pushNotificationConfigs/*}\x12\xbe\x01\n\x1eListTaskPushNotificationConfig\x12-.a2a.v1.ListTaskPushNotificationConfigRequest\x1a..a2a.v1.ListTaskPushNotificationConfigResponse\"=\xda\x41\x06parent\x82\xd3\xe4\x93\x02.\x12,/v1/{parent=tasks/*}/pushNotificationConfigs\x12P\n\x0cGetAgentCard\x12\x1b.a2a.v1.GetAgentCardRequest\x1a\x11.a2a.v1.AgentCard\"\x10\x82\xd3\xe4\x93\x02\n\x12\x08/v1/card\x12\xa8\x01\n DeleteTaskPushNotificationConfig\x12/.a2a.v1.DeleteTaskPushNotificationConfigRequest\x1a\x16.google.protobuf.Empty\";\xda\x41\x04name\x82\xd3\xe4\x93\x02.*,/v1/{name=tasks/*/pushNotificationConfigs/*}Bi\n\ncom.a2a.v1B\x08\x41\x32\x61ProtoP\x01Z\x18google.golang.org/a2a/v1\xa2\x02\x03\x41XX\xaa\x02\x06\x41\x32\x61.V1\xca\x02\x06\x41\x32\x61\\V1\xe2\x02\x12\x41\x32\x61\\V1\\GPBMetadata\xea\x02\x07\x41\x32\x61::V1b\x06proto3')
_globals = globals()
⋮----
# @@protoc_insertion_point(module_scope)

================
File: src/a2a/grpc/a2a_pb2.pyi
================
import datetime

from google.api import annotations_pb2 as _annotations_pb2
from google.api import client_pb2 as _client_pb2
from google.api import field_behavior_pb2 as _field_behavior_pb2
from google.protobuf import empty_pb2 as _empty_pb2
from google.protobuf import struct_pb2 as _struct_pb2
from google.protobuf import timestamp_pb2 as _timestamp_pb2
from google.protobuf.internal import containers as _containers
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union

DESCRIPTOR: _descriptor.FileDescriptor

class TaskState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
    __slots__ = ()
    TASK_STATE_UNSPECIFIED: _ClassVar[TaskState]
    TASK_STATE_SUBMITTED: _ClassVar[TaskState]
    TASK_STATE_WORKING: _ClassVar[TaskState]
    TASK_STATE_COMPLETED: _ClassVar[TaskState]
    TASK_STATE_FAILED: _ClassVar[TaskState]
    TASK_STATE_CANCELLED: _ClassVar[TaskState]
    TASK_STATE_INPUT_REQUIRED: _ClassVar[TaskState]
    TASK_STATE_REJECTED: _ClassVar[TaskState]
    TASK_STATE_AUTH_REQUIRED: _ClassVar[TaskState]

class Role(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
    __slots__ = ()
    ROLE_UNSPECIFIED: _ClassVar[Role]
    ROLE_USER: _ClassVar[Role]
    ROLE_AGENT: _ClassVar[Role]
TASK_STATE_UNSPECIFIED: TaskState
TASK_STATE_SUBMITTED: TaskState
TASK_STATE_WORKING: TaskState
TASK_STATE_COMPLETED: TaskState
TASK_STATE_FAILED: TaskState
TASK_STATE_CANCELLED: TaskState
TASK_STATE_INPUT_REQUIRED: TaskState
TASK_STATE_REJECTED: TaskState
TASK_STATE_AUTH_REQUIRED: TaskState
ROLE_UNSPECIFIED: Role
ROLE_USER: Role
ROLE_AGENT: Role

class SendMessageConfiguration(_message.Message):
    __slots__ = ("accepted_output_modes", "push_notification", "history_length", "blocking")
    ACCEPTED_OUTPUT_MODES_FIELD_NUMBER: _ClassVar[int]
    PUSH_NOTIFICATION_FIELD_NUMBER: _ClassVar[int]
    HISTORY_LENGTH_FIELD_NUMBER: _ClassVar[int]
    BLOCKING_FIELD_NUMBER: _ClassVar[int]
    accepted_output_modes: _containers.RepeatedScalarFieldContainer[str]
    push_notification: PushNotificationConfig
    history_length: int
    blocking: bool
    def __init__(self, accepted_output_modes: _Optional[_Iterable[str]] = ..., push_notification: _Optional[_Union[PushNotificationConfig, _Mapping]] = ..., history_length: _Optional[int] = ..., blocking: bool = ...) -> None: ...

class Task(_message.Message):
    __slots__ = ("id", "context_id", "status", "artifacts", "history", "metadata")
    ID_FIELD_NUMBER: _ClassVar[int]
    CONTEXT_ID_FIELD_NUMBER: _ClassVar[int]
    STATUS_FIELD_NUMBER: _ClassVar[int]
    ARTIFACTS_FIELD_NUMBER: _ClassVar[int]
    HISTORY_FIELD_NUMBER: _ClassVar[int]
    METADATA_FIELD_NUMBER: _ClassVar[int]
    id: str
    context_id: str
    status: TaskStatus
    artifacts: _containers.RepeatedCompositeFieldContainer[Artifact]
    history: _containers.RepeatedCompositeFieldContainer[Message]
    metadata: _struct_pb2.Struct
    def __init__(self, id: _Optional[str] = ..., context_id: _Optional[str] = ..., status: _Optional[_Union[TaskStatus, _Mapping]] = ..., artifacts: _Optional[_Iterable[_Union[Artifact, _Mapping]]] = ..., history: _Optional[_Iterable[_Union[Message, _Mapping]]] = ..., metadata: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ...

class TaskStatus(_message.Message):
    __slots__ = ("state", "update", "timestamp")
    STATE_FIELD_NUMBER: _ClassVar[int]
    UPDATE_FIELD_NUMBER: _ClassVar[int]
    TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
    state: TaskState
    update: Message
    timestamp: _timestamp_pb2.Timestamp
    def __init__(self, state: _Optional[_Union[TaskState, str]] = ..., update: _Optional[_Union[Message, _Mapping]] = ..., timestamp: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...

class Part(_message.Message):
    __slots__ = ("text", "file", "data")
    TEXT_FIELD_NUMBER: _ClassVar[int]
    FILE_FIELD_NUMBER: _ClassVar[int]
    DATA_FIELD_NUMBER: _ClassVar[int]
    text: str
    file: FilePart
    data: DataPart
    def __init__(self, text: _Optional[str] = ..., file: _Optional[_Union[FilePart, _Mapping]] = ..., data: _Optional[_Union[DataPart, _Mapping]] = ...) -> None: ...

class FilePart(_message.Message):
    __slots__ = ("file_with_uri", "file_with_bytes", "mime_type")
    FILE_WITH_URI_FIELD_NUMBER: _ClassVar[int]
    FILE_WITH_BYTES_FIELD_NUMBER: _ClassVar[int]
    MIME_TYPE_FIELD_NUMBER: _ClassVar[int]
    file_with_uri: str
    file_with_bytes: bytes
    mime_type: str
    def __init__(self, file_with_uri: _Optional[str] = ..., file_with_bytes: _Optional[bytes] = ..., mime_type: _Optional[str] = ...) -> None: ...

class DataPart(_message.Message):
    __slots__ = ("data",)
    DATA_FIELD_NUMBER: _ClassVar[int]
    data: _struct_pb2.Struct
    def __init__(self, data: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ...

class Message(_message.Message):
    __slots__ = ("message_id", "context_id", "task_id", "role", "content", "metadata", "extensions")
    MESSAGE_ID_FIELD_NUMBER: _ClassVar[int]
    CONTEXT_ID_FIELD_NUMBER: _ClassVar[int]
    TASK_ID_FIELD_NUMBER: _ClassVar[int]
    ROLE_FIELD_NUMBER: _ClassVar[int]
    CONTENT_FIELD_NUMBER: _ClassVar[int]
    METADATA_FIELD_NUMBER: _ClassVar[int]
    EXTENSIONS_FIELD_NUMBER: _ClassVar[int]
    message_id: str
    context_id: str
    task_id: str
    role: Role
    content: _containers.RepeatedCompositeFieldContainer[Part]
    metadata: _struct_pb2.Struct
    extensions: _containers.RepeatedScalarFieldContainer[str]
    def __init__(self, message_id: _Optional[str] = ..., context_id: _Optional[str] = ..., task_id: _Optional[str] = ..., role: _Optional[_Union[Role, str]] = ..., content: _Optional[_Iterable[_Union[Part, _Mapping]]] = ..., metadata: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., extensions: _Optional[_Iterable[str]] = ...) -> None: ...

class Artifact(_message.Message):
    __slots__ = ("artifact_id", "name", "description", "parts", "metadata", "extensions")
    ARTIFACT_ID_FIELD_NUMBER: _ClassVar[int]
    NAME_FIELD_NUMBER: _ClassVar[int]
    DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
    PARTS_FIELD_NUMBER: _ClassVar[int]
    METADATA_FIELD_NUMBER: _ClassVar[int]
    EXTENSIONS_FIELD_NUMBER: _ClassVar[int]
    artifact_id: str
    name: str
    description: str
    parts: _containers.RepeatedCompositeFieldContainer[Part]
    metadata: _struct_pb2.Struct
    extensions: _containers.RepeatedScalarFieldContainer[str]
    def __init__(self, artifact_id: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., parts: _Optional[_Iterable[_Union[Part, _Mapping]]] = ..., metadata: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., extensions: _Optional[_Iterable[str]] = ...) -> None: ...

class TaskStatusUpdateEvent(_message.Message):
    __slots__ = ("task_id", "context_id", "status", "final", "metadata")
    TASK_ID_FIELD_NUMBER: _ClassVar[int]
    CONTEXT_ID_FIELD_NUMBER: _ClassVar[int]
    STATUS_FIELD_NUMBER: _ClassVar[int]
    FINAL_FIELD_NUMBER: _ClassVar[int]
    METADATA_FIELD_NUMBER: _ClassVar[int]
    task_id: str
    context_id: str
    status: TaskStatus
    final: bool
    metadata: _struct_pb2.Struct
    def __init__(self, task_id: _Optional[str] = ..., context_id: _Optional[str] = ..., status: _Optional[_Union[TaskStatus, _Mapping]] = ..., final: bool = ..., metadata: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ...

class TaskArtifactUpdateEvent(_message.Message):
    __slots__ = ("task_id", "context_id", "artifact", "append", "last_chunk", "metadata")
    TASK_ID_FIELD_NUMBER: _ClassVar[int]
    CONTEXT_ID_FIELD_NUMBER: _ClassVar[int]
    ARTIFACT_FIELD_NUMBER: _ClassVar[int]
    APPEND_FIELD_NUMBER: _ClassVar[int]
    LAST_CHUNK_FIELD_NUMBER: _ClassVar[int]
    METADATA_FIELD_NUMBER: _ClassVar[int]
    task_id: str
    context_id: str
    artifact: Artifact
    append: bool
    last_chunk: bool
    metadata: _struct_pb2.Struct
    def __init__(self, task_id: _Optional[str] = ..., context_id: _Optional[str] = ..., artifact: _Optional[_Union[Artifact, _Mapping]] = ..., append: bool = ..., last_chunk: bool = ..., metadata: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ...

class PushNotificationConfig(_message.Message):
    __slots__ = ("id", "url", "token", "authentication")
    ID_FIELD_NUMBER: _ClassVar[int]
    URL_FIELD_NUMBER: _ClassVar[int]
    TOKEN_FIELD_NUMBER: _ClassVar[int]
    AUTHENTICATION_FIELD_NUMBER: _ClassVar[int]
    id: str
    url: str
    token: str
    authentication: AuthenticationInfo
    def __init__(self, id: _Optional[str] = ..., url: _Optional[str] = ..., token: _Optional[str] = ..., authentication: _Optional[_Union[AuthenticationInfo, _Mapping]] = ...) -> None: ...

class AuthenticationInfo(_message.Message):
    __slots__ = ("schemes", "credentials")
    SCHEMES_FIELD_NUMBER: _ClassVar[int]
    CREDENTIALS_FIELD_NUMBER: _ClassVar[int]
    schemes: _containers.RepeatedScalarFieldContainer[str]
    credentials: str
    def __init__(self, schemes: _Optional[_Iterable[str]] = ..., credentials: _Optional[str] = ...) -> None: ...

class AgentInterface(_message.Message):
    __slots__ = ("url", "transport")
    URL_FIELD_NUMBER: _ClassVar[int]
    TRANSPORT_FIELD_NUMBER: _ClassVar[int]
    url: str
    transport: str
    def __init__(self, url: _Optional[str] = ..., transport: _Optional[str] = ...) -> None: ...

class AgentCard(_message.Message):
    __slots__ = ("name", "description", "url", "preferred_transport", "additional_interfaces", "provider", "version", "documentation_url", "capabilities", "security_schemes", "security", "default_input_modes", "default_output_modes", "skills", "supports_authenticated_extended_card")
    class SecuritySchemesEntry(_message.Message):
        __slots__ = ("key", "value")
        KEY_FIELD_NUMBER: _ClassVar[int]
        VALUE_FIELD_NUMBER: _ClassVar[int]
        key: str
        value: SecurityScheme
        def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[SecurityScheme, _Mapping]] = ...) -> None: ...
    NAME_FIELD_NUMBER: _ClassVar[int]
    DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
    URL_FIELD_NUMBER: _ClassVar[int]
    PREFERRED_TRANSPORT_FIELD_NUMBER: _ClassVar[int]
    ADDITIONAL_INTERFACES_FIELD_NUMBER: _ClassVar[int]
    PROVIDER_FIELD_NUMBER: _ClassVar[int]
    VERSION_FIELD_NUMBER: _ClassVar[int]
    DOCUMENTATION_URL_FIELD_NUMBER: _ClassVar[int]
    CAPABILITIES_FIELD_NUMBER: _ClassVar[int]
    SECURITY_SCHEMES_FIELD_NUMBER: _ClassVar[int]
    SECURITY_FIELD_NUMBER: _ClassVar[int]
    DEFAULT_INPUT_MODES_FIELD_NUMBER: _ClassVar[int]
    DEFAULT_OUTPUT_MODES_FIELD_NUMBER: _ClassVar[int]
    SKILLS_FIELD_NUMBER: _ClassVar[int]
    SUPPORTS_AUTHENTICATED_EXTENDED_CARD_FIELD_NUMBER: _ClassVar[int]
    name: str
    description: str
    url: str
    preferred_transport: str
    additional_interfaces: _containers.RepeatedCompositeFieldContainer[AgentInterface]
    provider: AgentProvider
    version: str
    documentation_url: str
    capabilities: AgentCapabilities
    security_schemes: _containers.MessageMap[str, SecurityScheme]
    security: _containers.RepeatedCompositeFieldContainer[Security]
    default_input_modes: _containers.RepeatedScalarFieldContainer[str]
    default_output_modes: _containers.RepeatedScalarFieldContainer[str]
    skills: _containers.RepeatedCompositeFieldContainer[AgentSkill]
    supports_authenticated_extended_card: bool
    def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., url: _Optional[str] = ..., preferred_transport: _Optional[str] = ..., additional_interfaces: _Optional[_Iterable[_Union[AgentInterface, _Mapping]]] = ..., provider: _Optional[_Union[AgentProvider, _Mapping]] = ..., version: _Optional[str] = ..., documentation_url: _Optional[str] = ..., capabilities: _Optional[_Union[AgentCapabilities, _Mapping]] = ..., security_schemes: _Optional[_Mapping[str, SecurityScheme]] = ..., security: _Optional[_Iterable[_Union[Security, _Mapping]]] = ..., default_input_modes: _Optional[_Iterable[str]] = ..., default_output_modes: _Optional[_Iterable[str]] = ..., skills: _Optional[_Iterable[_Union[AgentSkill, _Mapping]]] = ..., supports_authenticated_extended_card: bool = ...) -> None: ...

class AgentProvider(_message.Message):
    __slots__ = ("url", "organization")
    URL_FIELD_NUMBER: _ClassVar[int]
    ORGANIZATION_FIELD_NUMBER: _ClassVar[int]
    url: str
    organization: str
    def __init__(self, url: _Optional[str] = ..., organization: _Optional[str] = ...) -> None: ...

class AgentCapabilities(_message.Message):
    __slots__ = ("streaming", "push_notifications", "extensions")
    STREAMING_FIELD_NUMBER: _ClassVar[int]
    PUSH_NOTIFICATIONS_FIELD_NUMBER: _ClassVar[int]
    EXTENSIONS_FIELD_NUMBER: _ClassVar[int]
    streaming: bool
    push_notifications: bool
    extensions: _containers.RepeatedCompositeFieldContainer[AgentExtension]
    def __init__(self, streaming: bool = ..., push_notifications: bool = ..., extensions: _Optional[_Iterable[_Union[AgentExtension, _Mapping]]] = ...) -> None: ...

class AgentExtension(_message.Message):
    __slots__ = ("uri", "description", "required", "params")
    URI_FIELD_NUMBER: _ClassVar[int]
    DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
    REQUIRED_FIELD_NUMBER: _ClassVar[int]
    PARAMS_FIELD_NUMBER: _ClassVar[int]
    uri: str
    description: str
    required: bool
    params: _struct_pb2.Struct
    def __init__(self, uri: _Optional[str] = ..., description: _Optional[str] = ..., required: bool = ..., params: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ...

class AgentSkill(_message.Message):
    __slots__ = ("id", "name", "description", "tags", "examples", "input_modes", "output_modes")
    ID_FIELD_NUMBER: _ClassVar[int]
    NAME_FIELD_NUMBER: _ClassVar[int]
    DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
    TAGS_FIELD_NUMBER: _ClassVar[int]
    EXAMPLES_FIELD_NUMBER: _ClassVar[int]
    INPUT_MODES_FIELD_NUMBER: _ClassVar[int]
    OUTPUT_MODES_FIELD_NUMBER: _ClassVar[int]
    id: str
    name: str
    description: str
    tags: _containers.RepeatedScalarFieldContainer[str]
    examples: _containers.RepeatedScalarFieldContainer[str]
    input_modes: _containers.RepeatedScalarFieldContainer[str]
    output_modes: _containers.RepeatedScalarFieldContainer[str]
    def __init__(self, id: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., tags: _Optional[_Iterable[str]] = ..., examples: _Optional[_Iterable[str]] = ..., input_modes: _Optional[_Iterable[str]] = ..., output_modes: _Optional[_Iterable[str]] = ...) -> None: ...

class TaskPushNotificationConfig(_message.Message):
    __slots__ = ("name", "push_notification_config")
    NAME_FIELD_NUMBER: _ClassVar[int]
    PUSH_NOTIFICATION_CONFIG_FIELD_NUMBER: _ClassVar[int]
    name: str
    push_notification_config: PushNotificationConfig
    def __init__(self, name: _Optional[str] = ..., push_notification_config: _Optional[_Union[PushNotificationConfig, _Mapping]] = ...) -> None: ...

class StringList(_message.Message):
    __slots__ = ("list",)
    LIST_FIELD_NUMBER: _ClassVar[int]
    list: _containers.RepeatedScalarFieldContainer[str]
    def __init__(self, list: _Optional[_Iterable[str]] = ...) -> None: ...

class Security(_message.Message):
    __slots__ = ("schemes",)
    class SchemesEntry(_message.Message):
        __slots__ = ("key", "value")
        KEY_FIELD_NUMBER: _ClassVar[int]
        VALUE_FIELD_NUMBER: _ClassVar[int]
        key: str
        value: StringList
        def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[StringList, _Mapping]] = ...) -> None: ...
    SCHEMES_FIELD_NUMBER: _ClassVar[int]
    schemes: _containers.MessageMap[str, StringList]
    def __init__(self, schemes: _Optional[_Mapping[str, StringList]] = ...) -> None: ...

class SecurityScheme(_message.Message):
    __slots__ = ("api_key_security_scheme", "http_auth_security_scheme", "oauth2_security_scheme", "open_id_connect_security_scheme")
    API_KEY_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
    HTTP_AUTH_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
    OAUTH2_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
    OPEN_ID_CONNECT_SECURITY_SCHEME_FIELD_NUMBER: _ClassVar[int]
    api_key_security_scheme: APIKeySecurityScheme
    http_auth_security_scheme: HTTPAuthSecurityScheme
    oauth2_security_scheme: OAuth2SecurityScheme
    open_id_connect_security_scheme: OpenIdConnectSecurityScheme
    def __init__(self, api_key_security_scheme: _Optional[_Union[APIKeySecurityScheme, _Mapping]] = ..., http_auth_security_scheme: _Optional[_Union[HTTPAuthSecurityScheme, _Mapping]] = ..., oauth2_security_scheme: _Optional[_Union[OAuth2SecurityScheme, _Mapping]] = ..., open_id_connect_security_scheme: _Optional[_Union[OpenIdConnectSecurityScheme, _Mapping]] = ...) -> None: ...

class APIKeySecurityScheme(_message.Message):
    __slots__ = ("description", "location", "name")
    DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
    LOCATION_FIELD_NUMBER: _ClassVar[int]
    NAME_FIELD_NUMBER: _ClassVar[int]
    description: str
    location: str
    name: str
    def __init__(self, description: _Optional[str] = ..., location: _Optional[str] = ..., name: _Optional[str] = ...) -> None: ...

class HTTPAuthSecurityScheme(_message.Message):
    __slots__ = ("description", "scheme", "bearer_format")
    DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
    SCHEME_FIELD_NUMBER: _ClassVar[int]
    BEARER_FORMAT_FIELD_NUMBER: _ClassVar[int]
    description: str
    scheme: str
    bearer_format: str
    def __init__(self, description: _Optional[str] = ..., scheme: _Optional[str] = ..., bearer_format: _Optional[str] = ...) -> None: ...

class OAuth2SecurityScheme(_message.Message):
    __slots__ = ("description", "flows")
    DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
    FLOWS_FIELD_NUMBER: _ClassVar[int]
    description: str
    flows: OAuthFlows
    def __init__(self, description: _Optional[str] = ..., flows: _Optional[_Union[OAuthFlows, _Mapping]] = ...) -> None: ...

class OpenIdConnectSecurityScheme(_message.Message):
    __slots__ = ("description", "open_id_connect_url")
    DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
    OPEN_ID_CONNECT_URL_FIELD_NUMBER: _ClassVar[int]
    description: str
    open_id_connect_url: str
    def __init__(self, description: _Optional[str] = ..., open_id_connect_url: _Optional[str] = ...) -> None: ...

class OAuthFlows(_message.Message):
    __slots__ = ("authorization_code", "client_credentials", "implicit", "password")
    AUTHORIZATION_CODE_FIELD_NUMBER: _ClassVar[int]
    CLIENT_CREDENTIALS_FIELD_NUMBER: _ClassVar[int]
    IMPLICIT_FIELD_NUMBER: _ClassVar[int]
    PASSWORD_FIELD_NUMBER: _ClassVar[int]
    authorization_code: AuthorizationCodeOAuthFlow
    client_credentials: ClientCredentialsOAuthFlow
    implicit: ImplicitOAuthFlow
    password: PasswordOAuthFlow
    def __init__(self, authorization_code: _Optional[_Union[AuthorizationCodeOAuthFlow, _Mapping]] = ..., client_credentials: _Optional[_Union[ClientCredentialsOAuthFlow, _Mapping]] = ..., implicit: _Optional[_Union[ImplicitOAuthFlow, _Mapping]] = ..., password: _Optional[_Union[PasswordOAuthFlow, _Mapping]] = ...) -> None: ...

class AuthorizationCodeOAuthFlow(_message.Message):
    __slots__ = ("authorization_url", "token_url", "refresh_url", "scopes")
    class ScopesEntry(_message.Message):
        __slots__ = ("key", "value")
        KEY_FIELD_NUMBER: _ClassVar[int]
        VALUE_FIELD_NUMBER: _ClassVar[int]
        key: str
        value: str
        def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ...
    AUTHORIZATION_URL_FIELD_NUMBER: _ClassVar[int]
    TOKEN_URL_FIELD_NUMBER: _ClassVar[int]
    REFRESH_URL_FIELD_NUMBER: _ClassVar[int]
    SCOPES_FIELD_NUMBER: _ClassVar[int]
    authorization_url: str
    token_url: str
    refresh_url: str
    scopes: _containers.ScalarMap[str, str]
    def __init__(self, authorization_url: _Optional[str] = ..., token_url: _Optional[str] = ..., refresh_url: _Optional[str] = ..., scopes: _Optional[_Mapping[str, str]] = ...) -> None: ...

class ClientCredentialsOAuthFlow(_message.Message):
    __slots__ = ("token_url", "refresh_url", "scopes")
    class ScopesEntry(_message.Message):
        __slots__ = ("key", "value")
        KEY_FIELD_NUMBER: _ClassVar[int]
        VALUE_FIELD_NUMBER: _ClassVar[int]
        key: str
        value: str
        def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ...
    TOKEN_URL_FIELD_NUMBER: _ClassVar[int]
    REFRESH_URL_FIELD_NUMBER: _ClassVar[int]
    SCOPES_FIELD_NUMBER: _ClassVar[int]
    token_url: str
    refresh_url: str
    scopes: _containers.ScalarMap[str, str]
    def __init__(self, token_url: _Optional[str] = ..., refresh_url: _Optional[str] = ..., scopes: _Optional[_Mapping[str, str]] = ...) -> None: ...

class ImplicitOAuthFlow(_message.Message):
    __slots__ = ("authorization_url", "refresh_url", "scopes")
    class ScopesEntry(_message.Message):
        __slots__ = ("key", "value")
        KEY_FIELD_NUMBER: _ClassVar[int]
        VALUE_FIELD_NUMBER: _ClassVar[int]
        key: str
        value: str
        def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ...
    AUTHORIZATION_URL_FIELD_NUMBER: _ClassVar[int]
    REFRESH_URL_FIELD_NUMBER: _ClassVar[int]
    SCOPES_FIELD_NUMBER: _ClassVar[int]
    authorization_url: str
    refresh_url: str
    scopes: _containers.ScalarMap[str, str]
    def __init__(self, authorization_url: _Optional[str] = ..., refresh_url: _Optional[str] = ..., scopes: _Optional[_Mapping[str, str]] = ...) -> None: ...

class PasswordOAuthFlow(_message.Message):
    __slots__ = ("token_url", "refresh_url", "scopes")
    class ScopesEntry(_message.Message):
        __slots__ = ("key", "value")
        KEY_FIELD_NUMBER: _ClassVar[int]
        VALUE_FIELD_NUMBER: _ClassVar[int]
        key: str
        value: str
        def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ...
    TOKEN_URL_FIELD_NUMBER: _ClassVar[int]
    REFRESH_URL_FIELD_NUMBER: _ClassVar[int]
    SCOPES_FIELD_NUMBER: _ClassVar[int]
    token_url: str
    refresh_url: str
    scopes: _containers.ScalarMap[str, str]
    def __init__(self, token_url: _Optional[str] = ..., refresh_url: _Optional[str] = ..., scopes: _Optional[_Mapping[str, str]] = ...) -> None: ...

class SendMessageRequest(_message.Message):
    __slots__ = ("request", "configuration", "metadata")
    REQUEST_FIELD_NUMBER: _ClassVar[int]
    CONFIGURATION_FIELD_NUMBER: _ClassVar[int]
    METADATA_FIELD_NUMBER: _ClassVar[int]
    request: Message
    configuration: SendMessageConfiguration
    metadata: _struct_pb2.Struct
    def __init__(self, request: _Optional[_Union[Message, _Mapping]] = ..., configuration: _Optional[_Union[SendMessageConfiguration, _Mapping]] = ..., metadata: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ...

class GetTaskRequest(_message.Message):
    __slots__ = ("name", "history_length")
    NAME_FIELD_NUMBER: _ClassVar[int]
    HISTORY_LENGTH_FIELD_NUMBER: _ClassVar[int]
    name: str
    history_length: int
    def __init__(self, name: _Optional[str] = ..., history_length: _Optional[int] = ...) -> None: ...

class CancelTaskRequest(_message.Message):
    __slots__ = ("name",)
    NAME_FIELD_NUMBER: _ClassVar[int]
    name: str
    def __init__(self, name: _Optional[str] = ...) -> None: ...

class GetTaskPushNotificationConfigRequest(_message.Message):
    __slots__ = ("name",)
    NAME_FIELD_NUMBER: _ClassVar[int]
    name: str
    def __init__(self, name: _Optional[str] = ...) -> None: ...

class DeleteTaskPushNotificationConfigRequest(_message.Message):
    __slots__ = ("name",)
    NAME_FIELD_NUMBER: _ClassVar[int]
    name: str
    def __init__(self, name: _Optional[str] = ...) -> None: ...

class CreateTaskPushNotificationConfigRequest(_message.Message):
    __slots__ = ("parent", "config_id", "config")
    PARENT_FIELD_NUMBER: _ClassVar[int]
    CONFIG_ID_FIELD_NUMBER: _ClassVar[int]
    CONFIG_FIELD_NUMBER: _ClassVar[int]
    parent: str
    config_id: str
    config: TaskPushNotificationConfig
    def __init__(self, parent: _Optional[str] = ..., config_id: _Optional[str] = ..., config: _Optional[_Union[TaskPushNotificationConfig, _Mapping]] = ...) -> None: ...

class TaskSubscriptionRequest(_message.Message):
    __slots__ = ("name",)
    NAME_FIELD_NUMBER: _ClassVar[int]
    name: str
    def __init__(self, name: _Optional[str] = ...) -> None: ...

class ListTaskPushNotificationConfigRequest(_message.Message):
    __slots__ = ("parent", "page_size", "page_token")
    PARENT_FIELD_NUMBER: _ClassVar[int]
    PAGE_SIZE_FIELD_NUMBER: _ClassVar[int]
    PAGE_TOKEN_FIELD_NUMBER: _ClassVar[int]
    parent: str
    page_size: int
    page_token: str
    def __init__(self, parent: _Optional[str] = ..., page_size: _Optional[int] = ..., page_token: _Optional[str] = ...) -> None: ...

class GetAgentCardRequest(_message.Message):
    __slots__ = ()
    def __init__(self) -> None: ...

class SendMessageResponse(_message.Message):
    __slots__ = ("task", "msg")
    TASK_FIELD_NUMBER: _ClassVar[int]
    MSG_FIELD_NUMBER: _ClassVar[int]
    task: Task
    msg: Message
    def __init__(self, task: _Optional[_Union[Task, _Mapping]] = ..., msg: _Optional[_Union[Message, _Mapping]] = ...) -> None: ...

class StreamResponse(_message.Message):
    __slots__ = ("task", "msg", "status_update", "artifact_update")
    TASK_FIELD_NUMBER: _ClassVar[int]
    MSG_FIELD_NUMBER: _ClassVar[int]
    STATUS_UPDATE_FIELD_NUMBER: _ClassVar[int]
    ARTIFACT_UPDATE_FIELD_NUMBER: _ClassVar[int]
    task: Task
    msg: Message
    status_update: TaskStatusUpdateEvent
    artifact_update: TaskArtifactUpdateEvent
    def __init__(self, task: _Optional[_Union[Task, _Mapping]] = ..., msg: _Optional[_Union[Message, _Mapping]] = ..., status_update: _Optional[_Union[TaskStatusUpdateEvent, _Mapping]] = ..., artifact_update: _Optional[_Union[TaskArtifactUpdateEvent, _Mapping]] = ...) -> None: ...

class ListTaskPushNotificationConfigResponse(_message.Message):
    __slots__ = ("configs", "next_page_token")
    CONFIGS_FIELD_NUMBER: _ClassVar[int]
    NEXT_PAGE_TOKEN_FIELD_NUMBER: _ClassVar[int]
    configs: _containers.RepeatedCompositeFieldContainer[TaskPushNotificationConfig]
    next_page_token: str
    def __init__(self, configs: _Optional[_Iterable[_Union[TaskPushNotificationConfig, _Mapping]]] = ..., next_page_token: _Optional[str] = ...) -> None: ...

================
File: src/a2a/server/agent_execution/__init__.py
================
"""Components for executing agent logic within the A2A server."""
⋮----
__all__ = [

================
File: src/a2a/server/agent_execution/agent_executor.py
================
class AgentExecutor(ABC)
⋮----
"""Agent Executor interface.
    Implementations of this interface contain the core logic of the agent,
    executing tasks based on requests and publishing updates to an event queue.
    """
⋮----
"""Execute the agent's logic for a given request context.
        The agent should read necessary information from the `context` and
        publish `Task` or `Message` events, or `TaskStatusUpdateEvent` /
        `TaskArtifactUpdateEvent` to the `event_queue`. This method should
        return once the agent's execution for this request is complete or
        yields control (e.g., enters an input-required state).
        Args:
            context: The request context containing the message, task ID, etc.
            event_queue: The queue to publish events to.
        """
⋮----
"""Request the agent to cancel an ongoing task.
        The agent should attempt to stop the task identified by the task_id
        in the context and publish a `TaskStatusUpdateEvent` with state
        `TaskState.canceled` to the `event_queue`.
        Args:
            context: The request context containing the task ID to cancel.
            event_queue: The queue to publish the cancellation status update to.
        """

================
File: src/a2a/server/agent_execution/context.py
================
class RequestContext
⋮----
"""Request Context.
    Holds information about the current request being processed by the server,
    including the incoming message, task and context identifiers, and related
    tasks.
    """
def __init__(  # noqa: PLR0913
⋮----
"""Initializes the RequestContext.
        Args:
            request: The incoming `MessageSendParams` request payload.
            task_id: The ID of the task explicitly provided in the request or path.
            context_id: The ID of the context explicitly provided in the request or path.
            task: The existing `Task` object retrieved from the store, if any.
            related_tasks: A list of other tasks related to the current request (e.g., for tool use).
            call_context: The server call context associated with this request.
        """
⋮----
related_tasks = []
⋮----
# If the task id and context id were provided, make sure they
# match the request. Otherwise, create them
⋮----
def get_user_input(self, delimiter: str = '\n') -> str
⋮----
"""Extracts text content from the user's message parts.
        Args:
            delimiter: The string to use when joining multiple text parts.
        Returns:
            A single string containing all text content from the user message,
            joined by the specified delimiter. Returns an empty string if no
            user message is present or if it contains no text parts.
        """
⋮----
def attach_related_task(self, task: Task) -> None
⋮----
"""Attaches a related task to the context.
        This is useful for scenarios like tool execution where a new task
        might be spawned.
        Args:
            task: The `Task` object to attach.
        """
⋮----
@property
    def message(self) -> Message | None
⋮----
"""The incoming `Message` object from the request, if available."""
⋮----
@property
    def related_tasks(self) -> list[Task]
⋮----
"""A list of tasks related to the current request."""
⋮----
@property
    def current_task(self) -> Task | None
⋮----
"""The current `Task` object being processed."""
⋮----
@current_task.setter
    def current_task(self, task: Task) -> None
⋮----
"""Sets the current task object."""
⋮----
@property
    def task_id(self) -> str | None
⋮----
"""The ID of the task associated with this context."""
⋮----
@property
    def context_id(self) -> str | None
⋮----
"""The ID of the conversation context associated with this task."""
⋮----
@property
    def configuration(self) -> MessageSendConfiguration | None
⋮----
"""The `MessageSendConfiguration` from the request, if available."""
⋮----
@property
    def call_context(self) -> ServerCallContext | None
⋮----
"""The server call context associated with this request."""
⋮----
def _check_or_generate_task_id(self) -> None
⋮----
"""Ensures a task ID is present, generating one if necessary."""
⋮----
def _check_or_generate_context_id(self) -> None
⋮----
"""Ensures a context ID is present, generating one if necessary."""

================
File: src/a2a/server/agent_execution/request_context_builder.py
================
class RequestContextBuilder(ABC)
⋮----
"""Builds request context to be supplied to agent executor."""

================
File: src/a2a/server/agent_execution/simple_request_context_builder.py
================
class SimpleRequestContextBuilder(RequestContextBuilder)
⋮----
"""Builds request context and populates referred tasks."""
⋮----
"""Initializes the SimpleRequestContextBuilder.
        Args:
            should_populate_referred_tasks: If True, the builder will fetch tasks
                referenced in `params.message.referenceTaskIds` and populate the
                `related_tasks` field in the RequestContext. Defaults to False.
            task_store: The TaskStore instance to use for fetching referred tasks.
                Required if `should_populate_referred_tasks` is True.
        """
⋮----
"""Builds the request context for an agent execution.
        This method assembles the RequestContext object. If the builder was
        initialized with `should_populate_referred_tasks=True`, it fetches all tasks
        referenced in `params.message.referenceTaskIds` from the `task_store`.
        Args:
            params: The parameters of the incoming message send request.
            task_id: The ID of the task being executed.
            context_id: The ID of the current execution context.
            task: The primary task object associated with the request.
            context: The server call context, containing metadata about the call.
        Returns:
            An instance of RequestContext populated with the provided information
            and potentially a list of related tasks.
        """
related_tasks: list[Task] | None = None
⋮----
tasks = await asyncio.gather(
related_tasks = [x for x in tasks if x is not None]

================
File: src/a2a/server/apps/jsonrpc/__init__.py
================
"""A2A JSON-RPC Applications."""
⋮----
__all__ = [

================
File: src/a2a/server/apps/jsonrpc/fastapi_app.py
================
logger = logging.getLogger(__name__)
class A2AFastAPIApplication(JSONRPCApplication)
⋮----
"""A FastAPI application implementing the A2A protocol server endpoints.
    Handles incoming JSON-RPC requests, routes them to the appropriate
    handler methods, and manages response generation including Server-Sent Events
    (SSE).
    """
⋮----
"""Initializes the A2AStarletteApplication.
        Args:
            agent_card: The AgentCard describing the agent's capabilities.
            http_handler: The handler instance responsible for processing A2A
              requests via http.
            extended_agent_card: An optional, distinct AgentCard to be served
              at the authenticated extended card endpoint.
            context_builder: The CallContextBuilder used to construct the
              ServerCallContext passed to the http_handler. If None, no
              ServerCallContext is passed.
        """
⋮----
"""Adds the routes to the FastAPI application.
        Args:
            app: The FastAPI application to add the routes to.
            agent_card_url: The URL for the agent card endpoint.
            rpc_url: The URL for the A2A JSON-RPC endpoint.
            extended_agent_card_url: The URL for the authenticated extended agent card endpoint.
        """
⋮----
@app.post(rpc_url)
        async def handle_a2a_request(request: Request) -> Response
⋮----
@app.get(agent_card_url)
        async def get_agent_card(request: Request) -> Response
⋮----
@app.get(extended_agent_card_url)
            async def get_extended_agent_card(request: Request) -> Response
⋮----
"""Builds and returns the FastAPI application instance.
        Args:
            agent_card_url: The URL for the agent card endpoint.
            rpc_url: The URL for the A2A JSON-RPC endpoint.
            extended_agent_card_url: The URL for the authenticated extended agent card endpoint.
            **kwargs: Additional keyword arguments to pass to the FastAPI constructor.
        Returns:
            A configured FastAPI application instance.
        """
app = FastAPI(**kwargs)

================
File: src/a2a/server/apps/jsonrpc/jsonrpc_app.py
================
logger = logging.getLogger(__name__)
class StarletteUserProxy(A2AUser)
⋮----
"""Adapts the Starlette User class to the A2A user representation."""
def __init__(self, user: BaseUser)
⋮----
@property
    def is_authenticated(self) -> bool
⋮----
"""Returns whether the current user is authenticated."""
⋮----
@property
    def user_name(self) -> str
⋮----
"""Returns the user name of the current user."""
⋮----
class CallContextBuilder(ABC)
⋮----
"""A class for building ServerCallContexts using the Starlette Request."""
⋮----
@abstractmethod
    def build(self, request: Request) -> ServerCallContext
⋮----
"""Builds a ServerCallContext from a Starlette Request."""
class DefaultCallContextBuilder(CallContextBuilder)
⋮----
"""A default implementation of CallContextBuilder."""
def build(self, request: Request) -> ServerCallContext
⋮----
"""Builds a ServerCallContext from a Starlette Request.
        Args:
            request: The incoming Starlette Request object.
        Returns:
            A ServerCallContext instance populated with user and state
            information from the request.
        """
user: A2AUser = UnauthenticatedUser()
state = {}
⋮----
user = StarletteUserProxy(request.user)
⋮----
class JSONRPCApplication(ABC)
⋮----
"""Base class for A2A JSONRPC applications.
    Handles incoming JSON-RPC requests, routes them to the appropriate
    handler methods, and manages response generation including Server-Sent Events
    (SSE).
    """
⋮----
"""Initializes the A2AStarletteApplication.
        Args:
            agent_card: The AgentCard describing the agent's capabilities.
            http_handler: The handler instance responsible for processing A2A
              requests via http.
            extended_agent_card: An optional, distinct AgentCard to be served
              at the authenticated extended card endpoint.
            context_builder: The CallContextBuilder used to construct the
              ServerCallContext passed to the http_handler. If None, no
              ServerCallContext is passed.
        """
⋮----
"""Creates a Starlette JSONResponse for a JSON-RPC error.
        Logs the error based on its type.
        Args:
            request_id: The ID of the request that caused the error.
            error: The `JSONRPCError` or `A2AError` object.
        Returns:
            A `JSONResponse` object formatted as a JSON-RPC error response.
        """
error_resp = JSONRPCErrorResponse(
log_level = (
⋮----
async def _handle_requests(self, request: Request) -> Response
⋮----
"""Handles incoming POST requests to the main A2A endpoint.
        Parses the request body as JSON, validates it against A2A request types,
        dispatches it to the appropriate handler method, and returns the response.
        Handles JSON parsing errors, validation errors, and other exceptions,
        returning appropriate JSON-RPC error responses.
        Args:
            request: The incoming Starlette Request object.
        Returns:
            A Starlette Response object (JSONResponse or EventSourceResponse).
        Raises:
            (Implicitly handled): Various exceptions are caught and converted
            into JSON-RPC error responses by this method.
        """
request_id = None
body = None
⋮----
body = await request.json()
a2a_request = A2ARequest.model_validate(body)
call_context = self._context_builder.build(request)
request_id = a2a_request.root.id
request_obj = a2a_request.root
⋮----
"""Processes streaming requests (message/stream or tasks/resubscribe).
        Args:
            request_id: The ID of the request.
            a2a_request: The validated A2ARequest object.
            context: The ServerCallContext for the request.
        Returns:
            An `EventSourceResponse` object to stream results to the client.
        """
⋮----
handler_result: Any = None
⋮----
handler_result = self.handler.on_message_send_stream(
⋮----
handler_result = self.handler.on_resubscribe_to_task(
⋮----
"""Processes non-streaming requests (message/send, tasks/get, tasks/cancel, tasks/pushNotificationConfig/*).
        Args:
            request_id: The ID of the request.
            a2a_request: The validated A2ARequest object.
            context: The ServerCallContext for the request.
        Returns:
            A `JSONResponse` object containing the result or error.
        """
⋮----
handler_result = await self.handler.on_message_send(
⋮----
handler_result = await self.handler.on_cancel_task(
⋮----
handler_result = await self.handler.on_get_task(
⋮----
handler_result = await self.handler.set_push_notification(
⋮----
handler_result = await self.handler.get_push_notification(
⋮----
error = UnsupportedOperationError(
handler_result = JSONRPCErrorResponse(
⋮----
"""Creates a Starlette Response based on the result from the request handler.
        Handles:
        - AsyncGenerator for Server-Sent Events (SSE).
        - JSONRPCErrorResponse for explicit errors returned by handlers.
        - Pydantic RootModels (like GetTaskResponse) containing success or error
        payloads.
        Args:
            handler_result: The result from a request handler method. Can be an
                async generator for streaming or a Pydantic model for non-streaming.
        Returns:
            A Starlette JSONResponse or EventSourceResponse.
        """
⋮----
# Result is a stream of SendStreamingMessageResponse objects
⋮----
async def _handle_get_agent_card(self, request: Request) -> JSONResponse
⋮----
"""Handles GET requests for the agent card endpoint.
        Args:
            request: The incoming Starlette Request object.
        Returns:
            A JSONResponse containing the agent card data.
        """
# The public agent card is a direct serialization of the agent_card
# provided at initialization.
⋮----
"""Handles GET requests for the authenticated extended agent card."""
⋮----
# If an explicit extended_agent_card is provided, serve that.
⋮----
# If supportsAuthenticatedExtendedCard is true, but no specific
# extended_agent_card was provided during server initialization,
# return a 404
⋮----
"""Builds and returns the JSONRPC application instance.
        Args:
            agent_card_url: The URL for the agent card endpoint.
            rpc_url: The URL for the A2A JSON-RPC endpoint
            **kwargs: Additional keyword arguments to pass to the FastAPI constructor.
        Returns:
            A configured JSONRPC application instance.
        """

================
File: src/a2a/server/apps/jsonrpc/starlette_app.py
================
logger = logging.getLogger(__name__)
class A2AStarletteApplication(JSONRPCApplication)
⋮----
"""A Starlette application implementing the A2A protocol server endpoints.
    Handles incoming JSON-RPC requests, routes them to the appropriate
    handler methods, and manages response generation including Server-Sent Events
    (SSE).
    """
⋮----
"""Initializes the A2AStarletteApplication.
        Args:
            agent_card: The AgentCard describing the agent's capabilities.
            http_handler: The handler instance responsible for processing A2A
              requests via http.
            extended_agent_card: An optional, distinct AgentCard to be served
              at the authenticated extended card endpoint.
            context_builder: The CallContextBuilder used to construct the
              ServerCallContext passed to the http_handler. If None, no
              ServerCallContext is passed.
        """
⋮----
"""Returns the Starlette Routes for handling A2A requests.
        Args:
            agent_card_url: The URL path for the agent card endpoint.
            rpc_url: The URL path for the A2A JSON-RPC endpoint (POST requests).
            extended_agent_card_url: The URL for the authenticated extended agent card endpoint.
        Returns:
            A list of Starlette Route objects.
        """
app_routes = [
⋮----
"""Adds the routes to the Starlette application.
        Args:
            app: The Starlette application to add the routes to.
            agent_card_url: The URL path for the agent card endpoint.
            rpc_url: The URL path for the A2A JSON-RPC endpoint (POST requests).
            extended_agent_card_url: The URL for the authenticated extended agent card endpoint.
        """
routes = self.routes(
⋮----
"""Builds and returns the Starlette application instance.
        Args:
            agent_card_url: The URL path for the agent card endpoint.
            rpc_url: The URL path for the A2A JSON-RPC endpoint (POST requests).
            extended_agent_card_url: The URL for the authenticated extended agent card endpoint.
            **kwargs: Additional keyword arguments to pass to the Starlette constructor.
        Returns:
            A configured Starlette application instance.
        """
app = Starlette(**kwargs)

================
File: src/a2a/server/apps/__init__.py
================
"""HTTP application components for the A2A server."""
⋮----
__all__ = [

================
File: src/a2a/server/events/__init__.py
================
"""Event handling components for the A2A server."""
⋮----
__all__ = [

================
File: src/a2a/server/events/event_consumer.py
================
# This is an alias to the exception for closed queue
QueueClosed: type[Exception] = asyncio.QueueEmpty
# When using python 3.13 or higher, the closed queue signal is QueueShutdown
⋮----
QueueClosed = asyncio.QueueShutDown
logger = logging.getLogger(__name__)
⋮----
@trace_class(kind=SpanKind.SERVER)
class EventConsumer
⋮----
"""Consumer to read events from the agent event queue."""
def __init__(self, queue: EventQueue)
⋮----
"""Initializes the EventConsumer.
        Args:
            queue: The `EventQueue` instance to consume events from.
        """
⋮----
async def consume_one(self) -> Event
⋮----
"""Consume one event from the agent event queue non-blocking.
        Returns:
            The next event from the queue.
        Raises:
            ServerError: If the queue is empty when attempting to dequeue
                immediately.
        """
⋮----
event = await self.queue.dequeue_event(no_wait=True)
⋮----
async def consume_all(self) -> AsyncGenerator[Event]
⋮----
"""Consume all the generated streaming events from the agent.
        This method yields events as they become available from the queue
        until a final event is received or the queue is closed. It also
        monitors for exceptions set by the `agent_task_callback`.
        Yields:
            Events dequeued from the queue.
        Raises:
            BaseException: If an exception was set by the `agent_task_callback`.
        """
⋮----
# We use a timeout when waiting for an event from the queue.
# This is required because it allows the loop to check if
# `self._exception` has been set by the `agent_task_callback`.
# Without the timeout, loop might hang indefinitely if no events are
# enqueued by the agent and the agent simply threw an exception
event = await asyncio.wait_for(
⋮----
is_final_event = (
# Make sure the yield is after the close events, otherwise
# the caller may end up in a blocked state where this
# generator isn't called again to close things out and the
# other part is waiting for an event or a closed queue.
⋮----
# continue polling until there is a final event
⋮----
except asyncio.TimeoutError:  # pyright: ignore [reportUnusedExcept]
# This class was made an alias of build-in TimeoutError after 3.11
⋮----
# Confirm that the queue is closed, e.g. we aren't on
# python 3.12 and get a queue empty error on an open queue
⋮----
def agent_task_callback(self, agent_task: asyncio.Task[None]) -> None
⋮----
"""Callback to handle exceptions from the agent's execution task.
        If the agent's asyncio task raises an exception, this callback is
        invoked, and the exception is stored to be re-raised by the consumer loop.
        Args:
            agent_task: The asyncio.Task that completed.
        """

================
File: src/a2a/server/events/event_queue.py
================
logger = logging.getLogger(__name__)
Event = Message | Task | TaskStatusUpdateEvent | TaskArtifactUpdateEvent
"""Type alias for events that can be enqueued."""
DEFAULT_MAX_QUEUE_SIZE = 1024
⋮----
@trace_class(kind=SpanKind.SERVER)
class EventQueue
⋮----
"""Event queue for A2A responses from agent.
    Acts as a buffer between the agent's asynchronous execution and the
    server's response handling (e.g., streaming via SSE). Supports tapping
    to create child queues that receive the same events.
    """
def __init__(self, max_queue_size: int = DEFAULT_MAX_QUEUE_SIZE) -> None
⋮----
"""Initializes the EventQueue."""
# Make sure the `asyncio.Queue` is bounded.
# If it's unbounded (maxsize=0), then `queue.put()` never needs to wait,
# and so the streaming won't work correctly.
⋮----
async def enqueue_event(self, event: Event) -> None
⋮----
"""Enqueues an event to this queue and all its children.
        Args:
            event: The event object to enqueue.
        """
⋮----
# Make sure to use put instead of put_nowait to avoid blocking the event loop.
⋮----
async def dequeue_event(self, no_wait: bool = False) -> Event
⋮----
"""Dequeues an event from the queue.
        This implementation expects that dequeue to raise an exception when
        the queue has been closed. In python 3.13+ this is naturally provided
        by the QueueShutDown exception generated when the queue has closed and
        the user is awaiting the queue.get method. Python<=3.12 this needs to
        manage this lifecycle itself. The current implementation can lead to
        blocking if the dequeue_event is called before the EventQueue has been
        closed but when there are no events on the queue. Two ways to avoid this
        are to call this with no_wait = True which won't block, but is the
        callers responsibility to retry as appropriate. Alternatively, one can
        use a async Task management solution to cancel the get task if the queue
        has closed or some other condition is met. The implementation of the
        EventConsumer uses an async.wait with a timeout to abort the
        dequeue_event call and retry, when it will return with a closed error.
        Args:
            no_wait: If True, retrieve an event immediately or raise `asyncio.QueueEmpty`.
                     If False (default), wait until an event is available.
        Returns:
            The next event from the queue.
        Raises:
            asyncio.QueueEmpty: If `no_wait` is True and the queue is empty.
            asyncio.QueueShutDown: If the queue has been closed and is empty.
        """
⋮----
event = self.queue.get_nowait()
⋮----
event = await self.queue.get()
⋮----
def task_done(self) -> None
⋮----
"""Signals that a formerly enqueued task is complete.
        Used in conjunction with `dequeue_event` to track processed items.
        """
⋮----
def tap(self) -> 'EventQueue'
⋮----
"""Taps the event queue to create a new child queue that receives all future events.
        Returns:
            A new `EventQueue` instance that will receive all events enqueued
            to this parent queue from this point forward.
        """
⋮----
queue = EventQueue()
⋮----
async def close(self) -> None
⋮----
"""Closes the queue for future push events.
        Once closed, `dequeue_event` will eventually raise `asyncio.QueueShutDown`
        when the queue is empty. Also closes all child queues.
        """
⋮----
# If already closed, just return.
⋮----
# If using python 3.13 or higher, use the shutdown method
⋮----
# Otherwise, join the queue
⋮----
tasks = [asyncio.create_task(self.queue.join())]
⋮----
def is_closed(self) -> bool
⋮----
"""Checks if the queue is closed."""

================
File: src/a2a/server/events/in_memory_queue_manager.py
================
@trace_class(kind=SpanKind.SERVER)
class InMemoryQueueManager(QueueManager)
⋮----
"""InMemoryQueueManager is used for a single binary management.
    This implements the `QueueManager` interface using in-memory storage for event
    queues. It requires all incoming interactions for a given task ID to hit the
    same binary instance.
    This implementation is suitable for single-instance deployments but needs
    a distributed approach for scalable deployments.
    """
def __init__(self) -> None
⋮----
"""Initializes the InMemoryQueueManager."""
⋮----
async def add(self, task_id: str, queue: EventQueue) -> None
⋮----
"""Adds a new event queue for a task ID.
        Raises:
            TaskQueueExists: If a queue for the given `task_id` already exists.
        """
⋮----
async def get(self, task_id: str) -> EventQueue | None
⋮----
"""Retrieves the event queue for a task ID.
        Returns:
            The `EventQueue` instance for the `task_id`, or `None` if not found.
        """
⋮----
async def tap(self, task_id: str) -> EventQueue | None
⋮----
"""Taps the event queue for a task ID to create a child queue.
        Returns:
            A new child `EventQueue` instance, or `None` if the task ID is not found.
        """
⋮----
async def close(self, task_id: str) -> None
⋮----
"""Closes and removes the event queue for a task ID.
        Raises:
            NoTaskQueue: If no queue exists for the given `task_id`.
        """
⋮----
queue = self._task_queue.pop(task_id)
⋮----
async def create_or_tap(self, task_id: str) -> EventQueue
⋮----
"""Creates a new event queue for a task ID if one doesn't exist, otherwise taps the existing one.
        Returns:
            A new or child `EventQueue` instance for the `task_id`.
        """
⋮----
queue = EventQueue()

================
File: src/a2a/server/events/queue_manager.py
================
class QueueManager(ABC)
⋮----
"""Interface for managing the event queue lifecycles per task."""
⋮----
@abstractmethod
    async def add(self, task_id: str, queue: EventQueue) -> None
⋮----
"""Adds a new event queue associated with a task ID."""
⋮----
@abstractmethod
    async def get(self, task_id: str) -> EventQueue | None
⋮----
"""Retrieves the event queue for a task ID."""
⋮----
@abstractmethod
    async def tap(self, task_id: str) -> EventQueue | None
⋮----
"""Creates a child event queue (tap) for an existing task ID."""
⋮----
@abstractmethod
    async def close(self, task_id: str) -> None
⋮----
"""Closes and removes the event queue for a task ID."""
⋮----
@abstractmethod
    async def create_or_tap(self, task_id: str) -> EventQueue
⋮----
"""Creates a queue if one doesn't exist, otherwise taps the existing one."""
class TaskQueueExists(Exception):  # noqa: N818
⋮----
"""Exception raised when attempting to add a queue for a task ID that already exists."""
class NoTaskQueue(Exception):  # noqa: N818
⋮----
"""Exception raised when attempting to access or close a queue for a task ID that does not exist."""

================
File: src/a2a/server/request_handlers/__init__.py
================
"""Request handler components for the A2A server."""
⋮----
__all__ = [

================
File: src/a2a/server/request_handlers/default_request_handler.py
================
logger = logging.getLogger(__name__)
TERMINAL_TASK_STATES = {
⋮----
@trace_class(kind=SpanKind.SERVER)
class DefaultRequestHandler(RequestHandler)
⋮----
"""Default request handler for all incoming requests.
    This handler provides default implementations for all A2A JSON-RPC methods,
    coordinating between the `AgentExecutor`, `TaskStore`, `QueueManager`,
    and optional `PushNotifier`.
    """
_running_agents: dict[str, asyncio.Task]
⋮----
"""Initializes the DefaultRequestHandler.
        Args:
            agent_executor: The `AgentExecutor` instance to run agent logic.
            task_store: The `TaskStore` instance to manage task persistence.
            queue_manager: The `QueueManager` instance to manage event queues. Defaults to `InMemoryQueueManager`.
            push_notifier: The `PushNotifier` instance for sending push notifications. Defaults to None.
            request_context_builder: The `RequestContextBuilder` instance used
              to build request contexts. Defaults to `SimpleRequestContextBuilder`.
        """
⋮----
# TODO: Likely want an interface for managing this, like AgentExecutionManager.
⋮----
"""Default handler for 'tasks/get'."""
task: Task | None = await self.task_store.get(params.id)
⋮----
"""Default handler for 'tasks/cancel'.
        Attempts to cancel the task managed by the `AgentExecutor`.
        """
⋮----
task_manager = TaskManager(
result_aggregator = ResultAggregator(task_manager)
queue = await self._queue_manager.tap(task.id)
⋮----
queue = EventQueue()
⋮----
# Cancel the ongoing task, if one exists.
⋮----
consumer = EventConsumer(queue)
result = await result_aggregator.consume_all(consumer)
⋮----
"""Runs the agent's `execute` method and closes the queue afterwards.
        Args:
            request: The request context for the agent.
            queue: The event queue for the agent to publish to.
        """
⋮----
"""Common setup logic for both streaming and non-streaming message handling.
        Returns:
            A tuple of (task_manager, task_id, queue, result_aggregator, producer_task)
        """
# Create task manager and validate existing task
⋮----
task: Task | None = await task_manager.get_task()
⋮----
task = task_manager.update_with_message(params.message, task)
⋮----
# Build request context
request_context = await self._request_context_builder.build(
task_id = cast('str', request_context.task_id)
# Always assign a task ID. We may not actually upgrade to a task, but
# dictating the task ID at this layer is useful for tracking running
# agents.
queue = await self._queue_manager.create_or_tap(task_id)
⋮----
# TODO: to manage the non-blocking flows.
producer_task = asyncio.create_task(
⋮----
def _validate_task_id_match(self, task_id: str, event_task_id: str) -> None
⋮----
"""Validates that agent-generated task ID matches the expected task ID."""
⋮----
"""Sends push notification if configured and task is available."""
⋮----
latest_task = await result_aggregator.current_result
⋮----
"""Default handler for 'message/send' interface (non-streaming).
        Starts the agent execution for the message and waits for the final
        result (Task or Message).
        """
⋮----
interrupted = False
⋮----
# TODO: Track this disconnected cleanup task.
asyncio.create_task(  # noqa: RUF006
⋮----
"""Default handler for 'message/stream' (streaming).
        Starts the agent execution and yields events as they are produced
        by the agent.
        """
⋮----
"""Registers the agent execution task with the handler."""
⋮----
"""Cleans up the agent execution task and queue manager entry."""
⋮----
"""Default handler for 'tasks/pushNotificationConfig/set'.
        Requires a `PushNotifier` to be configured.
        """
⋮----
task: Task | None = await self.task_store.get(params.taskId)
⋮----
# Generate a unique id for the notification
⋮----
"""Default handler for 'tasks/pushNotificationConfig/get'.
        Requires a `PushNotifier` to be configured.
        """
⋮----
push_notification_config = await self._push_notifier.get_info(params.id)
⋮----
"""Default handler for 'tasks/resubscribe'.
        Allows a client to re-attach to a running streaming task's event stream.
        Requires the task and its queue to still be active.
        """
⋮----
def should_add_push_info(self, params: MessageSendParams) -> bool
⋮----
"""Determines if push notification info should be set for a task."""

================
File: src/a2a/server/request_handlers/grpc_handler.py
================
# ruff: noqa: N802
⋮----
logger = logging.getLogger(__name__)
# For now we use a trivial wrapper on the grpc context object
class CallContextBuilder(ABC)
⋮----
"""A class for building ServerCallContexts using the Starlette Request."""
⋮----
@abstractmethod
    def build(self, context: grpc.aio.ServicerContext) -> ServerCallContext
⋮----
"""Builds a ServerCallContext from a gRPC Request."""
class DefaultCallContextBuilder(CallContextBuilder)
⋮----
"""A default implementation of CallContextBuilder."""
def build(self, context: grpc.aio.ServicerContext) -> ServerCallContext
⋮----
"""Builds the ServerCallContext."""
user = UnauthenticatedUser()
state = {}
⋮----
class GrpcHandler(a2a_grpc.A2AServiceServicer)
⋮----
"""Maps incoming gRPC requests to the appropriate request handler method."""
⋮----
"""Initializes the GrpcHandler.
        Args:
            agent_card: The AgentCard describing the agent's capabilities.
            request_handler: The underlying `RequestHandler` instance to
                             delegate requests to.
            context_builder: The CallContextBuilder object. If none the
                             DefaultCallContextBuilder is used.
        """
⋮----
"""Handles the 'SendMessage' gRPC method.
        Args:
            request: The incoming `SendMessageRequest` object.
            context: Context provided by the server.
        Returns:
            A `SendMessageResponse` object containing the result (Task or
            Message) or throws an error response if a `ServerError` is raised
            by the handler.
        """
⋮----
# Construct the server context object
server_context = self.context_builder.build(context)
# Transform the proto object to the python internal objects
a2a_request = proto_utils.FromProto.message_send_params(
task_or_message = await self.request_handler.on_message_send(
⋮----
"""Handles the 'StreamMessage' gRPC method.
        Yields response objects as they are produced by the underlying handler's
        stream.
        Args:
            request: The incoming `SendMessageRequest` object.
            context: Context provided by the server.
        Yields:
            `StreamResponse` objects containing streaming events
            (Task, Message, TaskStatusUpdateEvent, TaskArtifactUpdateEvent)
            or gRPC error responses if a `ServerError` is raised.
        """
⋮----
# Transform the proto object to the python internal objects
⋮----
"""Handles the 'CancelTask' gRPC method.
        Args:
            request: The incoming `CancelTaskRequest` object.
            context: Context provided by the server.
        Returns:
            A `Task` object containing the updated Task or a gRPC error.
        """
⋮----
task_id_params = proto_utils.FromProto.task_id_params(request)
task = await self.request_handler.on_cancel_task(
⋮----
"""Handles the 'TaskSubscription' gRPC method.
        Yields response objects as they are produced by the underlying handler's
        stream.
        Args:
            request: The incoming `TaskSubscriptionRequest` object.
            context: Context provided by the server.
        Yields:
            `StreamResponse` objects containing streaming events
        """
⋮----
"""Handles the 'GetTaskPushNotificationConfig' gRPC method.
        Args:
            request: The incoming `GetTaskPushNotificationConfigRequest` object.
            context: Context provided by the server.
        Returns:
            A `TaskPushNotificationConfig` object containing the config.
        """
⋮----
config = (
⋮----
"""Handles the 'CreateTaskPushNotificationConfig' gRPC method.
        Requires the agent to support push notifications.
        Args:
            request: The incoming `CreateTaskPushNotificationConfigRequest` object.
            context: Context provided by the server.
        Returns:
            A `TaskPushNotificationConfig` object
        Raises:
            ServerError: If push notifications are not supported by the agent
                (due to the `@validate` decorator).
        """
⋮----
"""Handles the 'GetTask' gRPC method.
        Args:
            request: The incoming `GetTaskRequest` object.
            context: Context provided by the server.
        Returns:
            A `Task` object.
        """
⋮----
task = await self.request_handler.on_get_task(
⋮----
"""Get the agent card for the agent served."""
⋮----
"""Sets the grpc errors appropriately in the context."""

================
File: src/a2a/server/request_handlers/jsonrpc_handler.py
================
logger = logging.getLogger(__name__)
⋮----
@trace_class(kind=SpanKind.SERVER)
class JSONRPCHandler
⋮----
"""Maps incoming JSON-RPC requests to the appropriate request handler method and formats responses."""
⋮----
"""Initializes the JSONRPCHandler.
        Args:
            agent_card: The AgentCard describing the agent's capabilities.
            request_handler: The underlying `RequestHandler` instance to delegate requests to.
        """
⋮----
"""Handles the 'message/send' JSON-RPC method.
        Args:
            request: The incoming `SendMessageRequest` object.
            context: Context provided by the server.
        Returns:
            A `SendMessageResponse` object containing the result (Task or Message)
            or a JSON-RPC error response if a `ServerError` is raised by the handler.
        """
# TODO: Wrap in error handler to return error states
⋮----
task_or_message = await self.request_handler.on_message_send(
⋮----
"""Handles the 'message/stream' JSON-RPC method.
        Yields response objects as they are produced by the underlying handler's stream.
        Args:
            request: The incoming `SendStreamingMessageRequest` object.
            context: Context provided by the server.
        Yields:
            `SendStreamingMessageResponse` objects containing streaming events
            (Task, Message, TaskStatusUpdateEvent, TaskArtifactUpdateEvent)
            or JSON-RPC error responses if a `ServerError` is raised.
        """
⋮----
"""Handles the 'tasks/cancel' JSON-RPC method.
        Args:
            request: The incoming `CancelTaskRequest` object.
            context: Context provided by the server.
        Returns:
            A `CancelTaskResponse` object containing the updated Task or a JSON-RPC error.
        """
⋮----
task = await self.request_handler.on_cancel_task(
⋮----
"""Handles the 'tasks/resubscribe' JSON-RPC method.
        Yields response objects as they are produced by the underlying handler's stream.
        Args:
            request: The incoming `TaskResubscriptionRequest` object.
            context: Context provided by the server.
        Yields:
            `SendStreamingMessageResponse` objects containing streaming events
            or JSON-RPC error responses if a `ServerError` is raised.
        """
⋮----
"""Handles the 'tasks/pushNotificationConfig/get' JSON-RPC method.
        Args:
            request: The incoming `GetTaskPushNotificationConfigRequest` object.
            context: Context provided by the server.
        Returns:
            A `GetTaskPushNotificationConfigResponse` object containing the config or a JSON-RPC error.
        """
⋮----
config = (
⋮----
"""Handles the 'tasks/pushNotificationConfig/set' JSON-RPC method.
        Requires the agent to support push notifications.
        Args:
            request: The incoming `SetTaskPushNotificationConfigRequest` object.
            context: Context provided by the server.
        Returns:
            A `SetTaskPushNotificationConfigResponse` object containing the config or a JSON-RPC error.
        Raises:
            ServerError: If push notifications are not supported by the agent
                (due to the `@validate` decorator).
        """
⋮----
"""Handles the 'tasks/get' JSON-RPC method.
        Args:
            request: The incoming `GetTaskRequest` object.
            context: Context provided by the server.
        Returns:
            A `GetTaskResponse` object containing the Task or a JSON-RPC error.
        """
⋮----
task = await self.request_handler.on_get_task(

================
File: src/a2a/server/request_handlers/request_handler.py
================
class RequestHandler(ABC)
⋮----
"""A2A request handler interface.
    This interface defines the methods that an A2A server implementation must
    provide to handle incoming JSON-RPC requests.
    """
⋮----
"""Handles the 'tasks/get' method.
        Retrieves the state and history of a specific task.
        Args:
            params: Parameters specifying the task ID and optionally history length.
            context: Context provided by the server.
        Returns:
            The `Task` object if found, otherwise `None`.
        """
⋮----
"""Handles the 'tasks/cancel' method.
        Requests the agent to cancel an ongoing task.
        Args:
            params: Parameters specifying the task ID.
            context: Context provided by the server.
        Returns:
            The `Task` object with its status updated to canceled, or `None` if the task was not found.
        """
⋮----
"""Handles the 'message/send' method (non-streaming).
        Sends a message to the agent to create, continue, or restart a task,
        and waits for the final result (Task or Message).
        Args:
            params: Parameters including the message and configuration.
            context: Context provided by the server.
        Returns:
            The final `Task` object or a final `Message` object.
        """
⋮----
"""Handles the 'message/stream' method (streaming).
        Sends a message to the agent and yields stream events as they are
        produced (Task updates, Message chunks, Artifact updates).
        Args:
            params: Parameters including the message and configuration.
            context: Context provided by the server.
        Yields:
            `Event` objects from the agent's execution.
        Raises:
             ServerError(UnsupportedOperationError): By default, if not implemented.
        """
⋮----
"""Handles the 'tasks/pushNotificationConfig/set' method.
        Sets or updates the push notification configuration for a task.
        Args:
            params: Parameters including the task ID and push notification configuration.
            context: Context provided by the server.
        Returns:
            The provided `TaskPushNotificationConfig` upon success.
        """
⋮----
"""Handles the 'tasks/pushNotificationConfig/get' method.
        Retrieves the current push notification configuration for a task.
        Args:
            params: Parameters including the task ID.
            context: Context provided by the server.
        Returns:
            The `TaskPushNotificationConfig` for the task.
        """
⋮----
"""Handles the 'tasks/resubscribe' method.
        Allows a client to re-subscribe to a running streaming task's event stream.
        Args:
            params: Parameters including the task ID.
            context: Context provided by the server.
        Yields:
             `Event` objects from the agent's ongoing execution for the specified task.
        Raises:
             ServerError(UnsupportedOperationError): By default, if not implemented.
        """

================
File: src/a2a/server/request_handlers/response_helpers.py
================
"""Helper functions for building A2A JSON-RPC responses."""
# response types
⋮----
RT = TypeVar(
"""Type variable for RootModel response types."""
# success types
SPT = TypeVar(
"""Type variable for SuccessResponse types."""
# result types
EventTypes = (
"""Type alias for possible event types produced by handlers."""
⋮----
"""Helper method to build a JSONRPCErrorResponse wrapped in the appropriate response type.
    Args:
        request_id: The ID of the request that caused the error.
        error: The A2AError or JSONRPCError object.
        response_wrapper_type: The Pydantic RootModel type that wraps the response
                                for the specific RPC method (e.g., `SendMessageResponse`).
    Returns:
        A Pydantic model representing the JSON-RPC error response,
        wrapped in the specified response type.
    """
⋮----
"""Helper method to build appropriate JSONRPCResponse object for RPC methods.
    Based on the type of the `response` object received from the handler,
    it constructs either a success response wrapped in the appropriate payload type
    or an error response.
    Args:
        request_id: The ID of the request.
        response: The object received from the request handler.
        success_response_types: A tuple of expected Pydantic model types for a successful result.
        success_payload_type: The Pydantic model type for the success payload
                                (e.g., `SendMessageSuccessResponse`).
        response_type: The Pydantic RootModel type that wraps the final response
                       (e.g., `SendMessageResponse`).
    Returns:
        A Pydantic model representing the final JSON-RPC response (success or error).
    """
⋮----
root=success_payload_type(id=request_id, result=response)  # type:ignore
⋮----
# If consumer_data is not an expected success type and not an error,
# it's an invalid type of response from the agent for this specific method.
response = A2AError(

================
File: src/a2a/server/tasks/__init__.py
================
"""Components for managing tasks within the A2A server."""
⋮----
__all__ = [

================
File: src/a2a/server/tasks/inmemory_push_notifier.py
================
logger = logging.getLogger(__name__)
class InMemoryPushNotifier(PushNotifier)
⋮----
"""In-memory implementation of PushNotifier interface.
    Stores push notification configurations in memory and uses an httpx client
    to send notifications.
    """
def __init__(self, httpx_client: httpx.AsyncClient) -> None
⋮----
"""Initializes the InMemoryPushNotifier.
        Args:
            httpx_client: An async HTTP client instance to send notifications.
        """
⋮----
"""Sets or updates the push notification configuration for a task in memory."""
⋮----
async def get_info(self, task_id: str) -> PushNotificationConfig | None
⋮----
"""Retrieves the push notification configuration for a task from memory."""
⋮----
async def delete_info(self, task_id: str) -> None
⋮----
"""Deletes the push notification configuration for a task from memory."""
⋮----
async def send_notification(self, task: Task) -> None
⋮----
"""Sends a push notification for a task if configuration exists."""
push_info = await self.get_info(task.id)
⋮----
url = push_info.url
⋮----
response = await self._client.post(

================
File: src/a2a/server/tasks/inmemory_task_store.py
================
logger = logging.getLogger(__name__)
class InMemoryTaskStore(TaskStore)
⋮----
"""In-memory implementation of TaskStore.
    Stores task objects in a dictionary in memory. Task data is lost when the
    server process stops.
    """
def __init__(self) -> None
⋮----
"""Initializes the InMemoryTaskStore."""
⋮----
async def save(self, task: Task) -> None
⋮----
"""Saves or updates a task in the in-memory store."""
⋮----
async def get(self, task_id: str) -> Task | None
⋮----
"""Retrieves a task from the in-memory store by ID."""
⋮----
task = self.tasks.get(task_id)
⋮----
async def delete(self, task_id: str) -> None
⋮----
"""Deletes a task from the in-memory store by ID."""

================
File: src/a2a/server/tasks/push_notifier.py
================
class PushNotifier(ABC)
⋮----
"""PushNotifier interface to store, retrieve push notification for tasks and send push notifications."""
⋮----
"""Sets or updates the push notification configuration for a task."""
⋮----
@abstractmethod
    async def get_info(self, task_id: str) -> PushNotificationConfig | None
⋮----
"""Retrieves the push notification configuration for a task."""
⋮----
@abstractmethod
    async def delete_info(self, task_id: str) -> None
⋮----
"""Deletes the push notification configuration for a task."""
⋮----
@abstractmethod
    async def send_notification(self, task: Task) -> None
⋮----
"""Sends a push notification containing the latest task state."""

================
File: src/a2a/server/tasks/result_aggregator.py
================
logger = logging.getLogger(__name__)
class ResultAggregator
⋮----
"""ResultAggregator is used to process the event streams from an AgentExecutor.
    There are three main ways to use the ResultAggregator:
    1) As part of a processing pipe. consume_and_emit will construct the updated
       task as the events arrive, and re-emit those events for another consumer
    2) As part of a blocking call. consume_all will process the entire stream and
       return the final Task or Message object
    3) As part of a push solution where the latest Task is emitted after processing an event.
       consume_and_emit_task will consume the Event stream, process the events to the current
       Task object and emit that Task object.
    """
def __init__(self, task_manager: TaskManager)
⋮----
"""Initializes the ResultAggregator.
        Args:
            task_manager: The `TaskManager` instance to use for processing events
                          and managing the task state.
        """
⋮----
@property
    async def current_result(self) -> Task | Message | None
⋮----
"""Returns the current aggregated result (Task or Message).
        This is the latest state processed from the event stream.
        Returns:
            The current `Task` object managed by the `TaskManager`, or the final
            `Message` if one was received, or `None` if no result has been produced yet.
        """
⋮----
"""Processes the event stream from the consumer, updates the task state, and re-emits the same events.
        Useful for streaming scenarios where the server needs to observe and
        process events (e.g., save task state, send push notifications) while
        forwarding them to the client.
        Args:
            consumer: The `EventConsumer` to read events from.
        Yields:
            The `Event` objects consumed from the `EventConsumer`.
        """
⋮----
"""Processes the entire event stream from the consumer and returns the final result.
        Blocks until the event stream ends (queue is closed after final event or exception).
        Args:
            consumer: The `EventConsumer` to read events from.
        Returns:
            The final `Task` object or `Message` object after the stream is exhausted.
            Returns `None` if the stream ends without producing a final result.
        Raises:
            BaseException: If the `EventConsumer` raises an exception during consumption.
        """
⋮----
"""Processes the event stream until completion or an interruptable state is encountered.
        Interruptable states currently include `TaskState.auth_required`.
        If interrupted, consumption continues in a background task.
        Args:
            consumer: The `EventConsumer` to read events from.
        Returns:
            A tuple containing:
            - The current aggregated result (`Task` or `Message`) at the point of completion or interruption.
            - A boolean indicating whether the consumption was interrupted (`True`) or completed naturally (`False`).
        Raises:
            BaseException: If the `EventConsumer` raises an exception during consumption.
        """
event_stream = consumer.consume_all()
interrupted = False
⋮----
# auth-required is a special state: the message should be
# escalated back to the caller, but the agent is expected to
# continue producing events once the authorization is received
# out-of-band. This is in contrast to input-required, where a
# new request is expected in order for the agent to make progress,
# so the agent should exit.
⋮----
# TODO: We should track all outstanding tasks to ensure they eventually complete.
asyncio.create_task(self._continue_consuming(event_stream))  # noqa: RUF006
interrupted = True
⋮----
"""Continues processing an event stream in a background task.
        Used after an interruptable state (like auth_required) is encountered
        in the synchronous consumption flow.
        Args:
            event_stream: The remaining `AsyncIterator` of events from the consumer.
        """

================
File: src/a2a/server/tasks/task_manager.py
================
logger = logging.getLogger(__name__)
class TaskManager
⋮----
"""Helps manage a task's lifecycle during execution of a request.
    Responsible for retrieving, saving, and updating the `Task` object based on
    events received from the agent.
    """
⋮----
"""Initializes the TaskManager.
        Args:
            task_id: The ID of the task, if known from the request.
            context_id: The ID of the context, if known from the request.
            task_store: The `TaskStore` instance for persistence.
            initial_message: The `Message` that initiated the task, if any.
                             Used when creating a new task object.
        """
⋮----
async def get_task(self) -> Task | None
⋮----
"""Retrieves the current task object, either from memory or the store.
        If `task_id` is set, it first checks the in-memory `_current_task`,
        then attempts to load it from the `task_store`.
        Returns:
            The `Task` object if found, otherwise `None`.
        """
⋮----
"""Processes a task-related event (Task, Status, Artifact) and saves the updated task state.
        Ensures task and context IDs match or are set from the event.
        Args:
            event: The task-related event (`Task`, `TaskStatusUpdateEvent`, or `TaskArtifactUpdateEvent`).
        Returns:
            The updated `Task` object after processing the event.
        Raises:
            ServerError: If the task ID in the event conflicts with the TaskManager's ID
                         when the TaskManager's ID is already set.
        """
task_id_from_event = (
# If task id is known, make sure it is matched
⋮----
task: Task = await self.ensure_task(event)
⋮----
"""Ensures a Task object exists in memory, loading from store or creating new if needed.
        Args:
            event: The task-related event triggering the need for a Task object.
        Returns:
            An existing or newly created `Task` object.
        """
task: Task | None = self._current_task
⋮----
task = await self.task_store.get(self.task_id)
⋮----
# streaming agent did not previously stream task object.
# Create a task object with the available information and persist the event
task = self._init_task_obj(event.taskId, event.contextId)
⋮----
async def process(self, event: Event) -> Event
⋮----
"""Processes an event, updates the task state if applicable, stores it, and returns the event.
        If the event is task-related (`Task`, `TaskStatusUpdateEvent`, `TaskArtifactUpdateEvent`),
        the internal task state is updated and persisted.
        Args:
            event: The event object received from the agent.
        Returns:
            The same event object that was processed.
        """
⋮----
def _init_task_obj(self, task_id: str, context_id: str) -> Task
⋮----
"""Initializes a new task object in memory.
        Args:
            task_id: The ID for the new task.
            context_id: The context ID for the new task.
        Returns:
            A new `Task` object with initial status and potentially the initial message in history.
        """
⋮----
history = [self._initial_message] if self._initial_message else []
⋮----
async def _save_task(self, task: Task) -> None
⋮----
"""Saves the given task to the task store and updates the in-memory `_current_task`.
        Args:
            task: The `Task` object to save.
        """
⋮----
def update_with_message(self, message: Message, task: Task) -> Task
⋮----
"""Updates a task object in memory by adding a new message to its history.
        If the task has a message in its current status, that message is moved
        to the history first.
        Args:
            message: The new `Message` to add to the history.
            task: The `Task` object to update.
        Returns:
            The updated `Task` object (updated in-place).
        """

================
File: src/a2a/server/tasks/task_store.py
================
class TaskStore(ABC)
⋮----
"""Agent Task Store interface.
    Defines the methods for persisting and retrieving `Task` objects.
    """
⋮----
@abstractmethod
    async def save(self, task: Task) -> None
⋮----
"""Saves or updates a task in the store."""
⋮----
@abstractmethod
    async def get(self, task_id: str) -> Task | None
⋮----
"""Retrieves a task from the store by ID."""
⋮----
@abstractmethod
    async def delete(self, task_id: str) -> None
⋮----
"""Deletes a task from the store by ID."""

================
File: src/a2a/server/tasks/task_updater.py
================
class TaskUpdater
⋮----
"""Helper class for agents to publish updates to a task's event queue.
    Simplifies the process of creating and enqueueing standard task events.
    """
def __init__(self, event_queue: EventQueue, task_id: str, context_id: str)
⋮----
"""Initializes the TaskUpdater.
        Args:
            event_queue: The `EventQueue` associated with the task.
            task_id: The ID of the task.
            context_id: The context ID of the task.
        """
⋮----
"""Updates the status of the task and publishes a `TaskStatusUpdateEvent`.
        Args:
            state: The new state of the task.
            message: An optional message associated with the status update.
            final: If True, indicates this is the final status update for the task.
            timestamp: Optional ISO 8601 datetime string. Defaults to current time.
        """
current_timestamp = (
⋮----
async def add_artifact(  # noqa: PLR0913
⋮----
"""Adds an artifact chunk to the task and publishes a `TaskArtifactUpdateEvent`.
        Args:
            parts: A list of `Part` objects forming the artifact chunk.
            artifact_id: The ID of the artifact. A new UUID is generated if not provided.
            name: Optional name for the artifact.
            metadata: Optional metadata for the artifact.
            append: Optional boolean indicating if this chunk appends to a previous one.
            last_chunk: Optional boolean indicating if this is the last chunk.
        """
⋮----
artifact_id = str(uuid.uuid4())
⋮----
async def complete(self, message: Message | None = None) -> None
⋮----
"""Marks the task as completed and publishes a final status update."""
⋮----
async def failed(self, message: Message | None = None) -> None
⋮----
"""Marks the task as failed and publishes a final status update."""
⋮----
async def reject(self, message: Message | None = None) -> None
⋮----
"""Marks the task as rejected and publishes a final status update."""
⋮----
async def submit(self, message: Message | None = None) -> None
⋮----
"""Marks the task as submitted and publishes a status update."""
⋮----
async def start_work(self, message: Message | None = None) -> None
⋮----
"""Marks the task as working and publishes a status update."""
⋮----
async def cancel(self, message: Message | None = None) -> None
⋮----
"""Marks the task as cancelled and publishes a finalstatus update."""
⋮----
"""Marks the task as input required and publishes a status update."""
⋮----
"""Marks the task as auth required and publishes a status update."""
⋮----
"""Creates a new message object sent by the agent for this task/context.
        Note: This method only *creates* the message object. It does not
              automatically enqueue it.
        Args:
            parts: A list of `Part` objects for the message content.
            metadata: Optional metadata for the message.
        Returns:
            A new `Message` object.
        """

================
File: src/a2a/server/__init__.py
================
"""Server-side components for implementing an A2A agent."""

================
File: src/a2a/server/context.py
================
"""Defines the ServerCallContext class."""
⋮----
State = collections.abc.MutableMapping[str, typing.Any]
class ServerCallContext(BaseModel)
⋮----
"""A context passed when calling a server method.
    This class allows storing arbitrary user data in the state attribute.
    """
model_config = ConfigDict(arbitrary_types_allowed=True)
state: State = Field(default={})
user: User = Field(default=UnauthenticatedUser())

================
File: src/a2a/utils/__init__.py
================
"""Utility functions for the A2A Python SDK."""
⋮----
__all__ = [

================
File: src/a2a/utils/artifact.py
================
"""Utility functions for creating A2A Artifact objects."""
⋮----
"""Creates a new Artifact object.
    Args:
        parts: The list of `Part` objects forming the artifact's content.
        name: The human-readable name of the artifact.
        description: An optional description of the artifact.
    Returns:
        A new `Artifact` object with a generated artifactId.
    """
⋮----
"""Creates a new Artifact object containing only a single TextPart.
    Args:
        name: The human-readable name of the artifact.
        text: The text content of the artifact.
        description: An optional description of the artifact.
    Returns:
        A new `Artifact` object with a generated artifactId.
    """
⋮----
"""Creates a new Artifact object containing only a single DataPart.
    Args:
        name: The human-readable name of the artifact.
        data: The structured data content of the artifact.
        description: An optional description of the artifact.
    Returns:
        A new `Artifact` object with a generated artifactId.
    """

================
File: src/a2a/utils/errors.py
================
"""Custom exceptions for A2A server-side errors."""
⋮----
class A2AServerError(Exception)
⋮----
"""Base exception for A2A Server errors."""
class MethodNotImplementedError(A2AServerError)
⋮----
"""Exception raised for methods that are not implemented by the server handler."""
⋮----
"""Initializes the MethodNotImplementedError.
        Args:
            message: A descriptive error message.
        """
⋮----
class ServerError(Exception)
⋮----
"""Wrapper exception for A2A or JSON-RPC errors originating from the server's logic.
    This exception is used internally by request handlers and other server components
    to signal a specific error that should be formatted as a JSON-RPC error response.
    """
⋮----
"""Initializes the ServerError.
        Args:
            error: The specific A2A or JSON-RPC error model instance.
                   If None, an `InternalError` will be used when formatting the response.
        """

================
File: src/a2a/utils/helpers.py
================
"""General utility functions for the A2A Python SDK."""
⋮----
logger = logging.getLogger(__name__)
⋮----
@trace_function()
def create_task_obj(message_send_params: MessageSendParams) -> Task
⋮----
"""Create a new task object from message send params.
    Generates UUIDs for task and context IDs if they are not already present in the message.
    Args:
        message_send_params: The `MessageSendParams` object containing the initial message.
    Returns:
        A new `Task` object initialized with 'submitted' status and the input message in history.
    """
⋮----
@trace_function()
def append_artifact_to_task(task: Task, event: TaskArtifactUpdateEvent) -> None
⋮----
"""Helper method for updating a Task object with new artifact data from an event.
    Handles creating the artifacts list if it doesn't exist, adding new artifacts,
    and appending parts to existing artifacts based on the `append` flag in the event.
    Args:
        task: The `Task` object to modify.
        event: The `TaskArtifactUpdateEvent` containing the artifact data.
    """
⋮----
new_artifact_data: Artifact = event.artifact
artifact_id: str = new_artifact_data.artifactId
append_parts: bool = event.append or False
existing_artifact: Artifact | None = None
existing_artifact_list_index: int | None = None
# Find existing artifact by its id
⋮----
existing_artifact = art
existing_artifact_list_index = i
⋮----
# This represents the first chunk for this artifact index.
⋮----
# Replace the existing artifact entirely with the new data
⋮----
# Append the new artifact since no artifact with this index exists yet
⋮----
# Append new parts to the existing artifact's part list
⋮----
# We received a chunk to append, but we don't have an existing artifact.
# we will ignore this chunk
⋮----
def build_text_artifact(text: str, artifact_id: str) -> Artifact
⋮----
"""Helper to create a text artifact.
    Args:
        text: The text content for the artifact.
        artifact_id: The ID for the artifact.
    Returns:
        An `Artifact` object containing a single `TextPart`.
    """
text_part = TextPart(text=text)
part = Part(root=text_part)
⋮----
"""Decorator that validates if a given expression evaluates to True.
    Typically used on class methods to check capabilities or configuration
    before executing the method's logic. If the expression is False,
    a `ServerError` with an `UnsupportedOperationError` is raised.
    Args:
        expression: A callable that takes the instance (`self`) as its argument
                    and returns a boolean.
        error_message: An optional custom error message for the `UnsupportedOperationError`.
                       If None, the string representation of the expression will be used.
    """
def decorator(function: Callable) -> Callable
⋮----
def wrapper(self: Any, *args, **kwargs) -> Any
⋮----
final_message = error_message or str(expression)
⋮----
def decorator(function)
⋮----
@functools.wraps(function)
        async def wrapper(self, *args, **kwargs)
⋮----
"""Checks if server and client output modalities (MIME types) are compatible.
    Modalities are compatible if:
    1. The client specifies no preferred output modes (client_output_modes is None or empty).
    2. The server specifies no supported output modes (server_output_modes is None or empty).
    3. There is at least one common modality between the server's supported list and the client's preferred list.
    Args:
        server_output_modes: A list of MIME types supported by the server/agent for output.
                             Can be None or empty if the server doesn't specify.
        client_output_modes: A list of MIME types preferred by the client for output.
                             Can be None or empty if the client accepts any.
    Returns:
        True if the modalities are compatible, False otherwise.
    """

================
File: src/a2a/utils/message.py
================
"""Utility functions for creating and handling A2A Message objects."""
⋮----
"""Creates a new agent message containing a single TextPart.
    Args:
        text: The text content of the message.
        context_id: The context ID for the message.
        task_id: The task ID for the message.
    Returns:
        A new `Message` object with role 'agent'.
    """
⋮----
"""Creates a new agent message containing a list of Parts.
    Args:
        parts: The list of `Part` objects for the message content.
        context_id: The context ID for the message.
        task_id: The task ID for the message.
    Returns:
        A new `Message` object with role 'agent'.
    """
⋮----
def get_text_parts(parts: list[Part]) -> list[str]
⋮----
"""Extracts text content from all TextPart objects in a list of Parts.
    Args:
        parts: A list of `Part` objects.
    Returns:
        A list of strings containing the text content from any `TextPart` objects found.
    """
⋮----
def get_message_text(message: Message, delimiter: str = '\n') -> str
⋮----
"""Extracts and joins all text content from a Message's parts.
    Args:
        message: The `Message` object.
        delimiter: The string to use when joining text from multiple TextParts.
    Returns:
        A single string containing all text content, or an empty string if no text parts are found.
    """

================
File: src/a2a/utils/proto_utils.py
================
# mypy: disable-error-code="arg-type"
"""Utils for converting between proto and Python types."""
⋮----
# Regexp patterns for matching
_TASK_NAME_MATCH = r'tasks/(\w+)'
_TASK_PUSH_CONFIG_NAME_MATCH = r'tasks/(\w+)/pushNotificationConfigs/(\w+)'
class ToProto
⋮----
"""Converts Python types to proto types."""
⋮----
@classmethod
    def message(cls, message: types.Message | None) -> a2a_pb2.Message | None
⋮----
# TODO: Add support for other types.
⋮----
@classmethod
    def part(cls, part: types.Part) -> a2a_pb2.Part
⋮----
@classmethod
    def data(cls, data: dict[str, Any]) -> a2a_pb2.DataPart
⋮----
json_data = json.dumps(data)
⋮----
@classmethod
    def task(cls, task: types.Task) -> a2a_pb2.Task
⋮----
[ToProto.message(h) for h in task.history]  # type: ignore[misc]
⋮----
@classmethod
    def task_status(cls, status: types.TaskStatus) -> a2a_pb2.TaskStatus
⋮----
@classmethod
    def task_state(cls, state: types.TaskState) -> a2a_pb2.TaskState
⋮----
@classmethod
    def artifact(cls, artifact: types.Artifact) -> a2a_pb2.Artifact
⋮----
auth_info = (
⋮----
"""Converts a task, message, or task update event to a StreamResponse."""
⋮----
rval: list[a2a_pb2.Security] = []
⋮----
@classmethod
    def oauth2_flows(cls, flows: types.OAuthFlows) -> a2a_pb2.OAuthFlows
⋮----
@classmethod
    def skill(cls, skill: types.AgentSkill) -> a2a_pb2.AgentSkill
⋮----
@classmethod
    def role(cls, role: types.Role) -> a2a_pb2.Role
class FromProto
⋮----
"""Converts proto types to Python types."""
⋮----
@classmethod
    def message(cls, message: a2a_pb2.Message) -> types.Message
⋮----
@classmethod
    def metadata(cls, metadata: struct_pb2.Struct) -> dict[str, Any]
⋮----
@classmethod
    def part(cls, part: a2a_pb2.Part) -> types.Part
⋮----
@classmethod
    def data(cls, data: a2a_pb2.DataPart) -> dict[str, Any]
⋮----
json_data = json_format.MessageToJson(data.data)
⋮----
@classmethod
    def task(cls, task: a2a_pb2.Task) -> types.Task
⋮----
@classmethod
    def task_status(cls, status: a2a_pb2.TaskStatus) -> types.TaskStatus
⋮----
@classmethod
    def task_state(cls, state: a2a_pb2.TaskState) -> types.TaskState
⋮----
@classmethod
    def artifact(cls, artifact: a2a_pb2.Artifact) -> types.Artifact
⋮----
# This is currently incomplete until the core sdk supports multiple
# configs for a single task.
⋮----
m = re.match(_TASK_PUSH_CONFIG_NAME_MATCH, request.name)
⋮----
m = re.match(_TASK_NAME_MATCH, request.name)
⋮----
m = re.match(_TASK_NAME_MATCH, request.parent)
⋮----
rval: list[dict[str, list[str]]] = []
⋮----
in_=types.In(scheme.api_key_security_scheme.location),  # type: ignore[call-arg]
⋮----
@classmethod
    def oauth2_flows(cls, flows: a2a_pb2.OAuthFlows) -> types.OAuthFlows
⋮----
@classmethod
    def skill(cls, skill: a2a_pb2.AgentSkill) -> types.AgentSkill
⋮----
@classmethod
    def role(cls, role: a2a_pb2.Role) -> types.Role

================
File: src/a2a/utils/task.py
================
"""Utility functions for creating A2A Task objects."""
⋮----
def new_task(request: Message) -> Task
⋮----
"""Creates a new Task object from an initial user message.
    Generates task and context IDs if not provided in the message.
    Args:
        request: The initial `Message` object from the user.
    Returns:
        A new `Task` object initialized with 'submitted' status and the input message in history.
    """
⋮----
"""Creates a Task object in the 'completed' state.
    Useful for constructing a final Task representation when the agent
    finishes and produces artifacts.
    Args:
        task_id: The ID of the task.
        context_id: The context ID of the task.
        artifacts: A list of `Artifact` objects produced by the task.
        history: An optional list of `Message` objects representing the task history.
    Returns:
        A `Task` object with status set to 'completed'.
    """
⋮----
history = []

================
File: src/a2a/utils/telemetry.py
================
"""OpenTelemetry Tracing Utilities for A2A Python SDK.
This module provides decorators to simplify the integration of OpenTelemetry
tracing into Python applications. It offers `trace_function` for instrumenting
individual functions (both synchronous and asynchronous) and `trace_class`
for instrumenting multiple methods within a class.
The tracer is initialized with the module name and version defined by
`INSTRUMENTING_MODULE_NAME` ('a2a-python-sdk') and
`INSTRUMENTING_MODULE_VERSION` ('1.0.0').
Features:
- Automatic span creation for decorated functions/methods.
- Support for both synchronous and asynchronous functions.
- Default span naming based on module and function/class/method name.
- Customizable span names, kinds, and static attributes.
- Dynamic attribute setting via an `attribute_extractor` callback.
- Automatic recording of exceptions and setting of span status.
- Selective method tracing in classes using include/exclude lists.
Usage:
    For a single function:
    ```python
    from your_module import trace_function
    @trace_function
    def my_function():
        # ...
        pass
    @trace_function(span_name='custom.op', kind=SpanKind.CLIENT)
    async def my_async_function():
        # ...
        pass
    ```
    For a class:
    ```python
    from your_module import trace_class
    @trace_class(exclude_list=['internal_method'])
    class MyService:
        def public_api(self, user_id):
            # This method will be traced
            pass
        def internal_method(self):
            # This method will not be traced
            pass
    ```
"""
⋮----
SpanKind: TypeAlias = _SpanKind
__all__ = ['SpanKind']
INSTRUMENTING_MODULE_NAME = 'a2a-python-sdk'
INSTRUMENTING_MODULE_VERSION = '1.0.0'
logger = logging.getLogger(__name__)
def trace_function(  # noqa: PLR0915
⋮----
"""A decorator to automatically trace a function call with OpenTelemetry.
    This decorator can be used to wrap both sync and async functions.
    When applied, it creates a new span for each call to the decorated function.
    The span will record the execution time, status (OK or ERROR), and any
    exceptions that occur.
    It can be used in two ways:
    1. As a direct decorator: `@trace_function`
    2. As a decorator factory to provide arguments: `@trace_function(span_name="custom.name")`
    Args:
        func (callable, optional): The function to be decorated. If None,
            the decorator returns a partial function, allowing it to be called
            with arguments. Defaults to None.
        span_name (str, optional): Custom name for the span. If None,
            it defaults to ``f'{func.__module__}.{func.__name__}'``.
            Defaults to None.
        kind (SpanKind, optional): The ``opentelemetry.trace.SpanKind`` for the
            created span. Defaults to ``SpanKind.INTERNAL``.
        attributes (dict, optional): A dictionary of static attributes to be
            set on the span. Keys are attribute names (str) and values are
            the corresponding attribute values. Defaults to None.
        attribute_extractor (callable, optional): A function that can be used
            to dynamically extract and set attributes on the span.
            It is called within a ``finally`` block, ensuring it runs even if
            the decorated function raises an exception.
            The function signature should be:
            ``attribute_extractor(span, args, kwargs, result, exception)``
            where:
                - ``span`` : the OpenTelemetry ``Span`` object.
                - ``args`` : a tuple of positional arguments passed
                - ``kwargs`` : a dictionary of keyword arguments passed
                - ``result`` : return value (None if an exception occurred)
                - ``exception`` : exception object if raised (None otherwise).
            Any exception raised by the ``attribute_extractor`` itself will be
            caught and logged. Defaults to None.
    Returns:
        callable: The wrapped function that includes tracing, or a partial
            decorator if ``func`` is None.
    """
⋮----
actual_span_name = span_name or f'{func.__module__}.{func.__name__}'
is_async_func = inspect.iscoroutinefunction(func)
⋮----
@functools.wraps(func)
    async def async_wrapper(*args, **kwargs) -> Any
⋮----
"""Async Wrapper for the decorator."""
⋮----
tracer = trace.get_tracer(
⋮----
result = None
exception = None
⋮----
# Async wrapper, await for the function call to complete.
result = await func(*args, **kwargs)
⋮----
exception = e
⋮----
@functools.wraps(func)
    def sync_wrapper(*args, **kwargs) -> Any
⋮----
"""Sync Wrapper for the decorator."""
tracer = trace.get_tracer(INSTRUMENTING_MODULE_NAME)
⋮----
# Sync wrapper, execute the function call.
result = func(*args, **kwargs)
⋮----
"""A class decorator to automatically trace specified methods of a class.
    This decorator iterates over the methods of a class and applies the
    `trace_function` decorator to them, based on the `include_list` and
    `exclude_list` criteria. Methods starting or ending with double underscores
    (dunder methods, e.g., `__init__`, `__call__`) are always excluded by default.
    Args:
        include_list (list[str], optional): A list of method names to
            explicitly include for tracing. If provided, only methods in this
            list (that are not dunder methods) will be traced.
            Defaults to None (trace all non-dunder methods).
        exclude_list (list[str], optional): A list of method names to exclude
            from tracing. This is only considered if `include_list` is not
            provided. Dunder methods are implicitly excluded.
            Defaults to an empty list.
        kind (SpanKind, optional): The `opentelemetry.trace.SpanKind` for the
            created spans on the methods. Defaults to `SpanKind.INTERNAL`.
    Returns:
        callable: A decorator function that, when applied to a class,
                  modifies the class to wrap its specified methods with tracing.
    Example:
        To trace all methods except 'internal_method':
        ```python
        @trace_class(exclude_list=['internal_method'])
        class MyService:
            def public_api(self):
                pass
            def internal_method(self):
                pass
        ```
        To trace only 'method_one' and 'method_two':
        ```python
        @trace_class(include_list=['method_one', 'method_two'])
        class AnotherService:
            def method_one(self):
                pass
            def method_two(self):
                pass
            def not_traced_method(self):
                pass
        ```
    """
⋮----
exclude_list = exclude_list or []
def decorator(cls: Any) -> Any
⋮----
all_methods = {}
⋮----
# Skip Dunders
⋮----
# Skip if include list is defined but the method not included.
⋮----
# Skip if include list is not defined but the method is in excludes.
⋮----
span_name = f'{cls.__module__}.{cls.__name__}.{name}'
# Set the decorator on the method.

================
File: src/a2a/__init__.py
================
"""The A2A Python SDK."""

================
File: src/a2a/types.py
================
# generated by datamodel-codegen:
#   filename:  https://raw.githubusercontent.com/a2aproject/A2A/refs/heads/main/specification/json/a2a.json
⋮----
class A2A(RootModel[Any])
⋮----
root: Any
class In(str, Enum)
⋮----
"""
    The location of the API key. Valid values are "query", "header", or "cookie".
    """
cookie = 'cookie'
header = 'header'
query = 'query'
class APIKeySecurityScheme(BaseModel)
⋮----
"""
    API Key security scheme.
    """
description: str | None = None
"""
    Description of this security scheme.
    """
in_: In = Field(..., alias='in')
⋮----
name: str
"""
    The name of the header, query or cookie parameter to be used.
    """
type: Literal['apiKey'] = 'apiKey'
class AgentExtension(BaseModel)
⋮----
"""
    A declaration of an extension supported by an Agent.
    """
⋮----
"""
    A description of how this agent uses this extension.
    """
params: dict[str, Any] | None = None
"""
    Optional configuration for the extension.
    """
required: bool | None = None
"""
    Whether the client must follow specific requirements of the extension.
    """
uri: str
"""
    The URI of the extension.
    """
class AgentInterface(BaseModel)
⋮----
"""
    AgentInterface provides a declaration of a combination of the
    target url and the supported transport to interact with the agent.
    """
transport: str
"""
    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.
    """
url: str
class AgentProvider(BaseModel)
⋮----
"""
    Represents the service provider of an agent.
    """
organization: str
"""
    Agent provider's organization name.
    """
⋮----
"""
    Agent provider's URL.
    """
class AgentSkill(BaseModel)
⋮----
"""
    Represents a unit of capability that an agent can perform.
    """
description: str
"""
    Description of the skill - will be used by the client or a human
    as a hint to understand what the skill does.
    """
examples: list[str] | None = None
"""
    The set of example scenarios that the skill can perform.
    Will be used by the client as a hint to understand how the skill can be used.
    """
id: str
"""
    Unique identifier for the agent's skill.
    """
inputModes: list[str] | None = None
"""
    The set of interaction modes that the skill supports
    (if different than the default).
    Supported media types for input.
    """
⋮----
"""
    Human readable name of the skill.
    """
outputModes: list[str] | None = None
"""
    Supported media types for output.
    """
tags: list[str]
"""
    Set of tagwords describing classes of capabilities for this specific skill.
    """
class AuthorizationCodeOAuthFlow(BaseModel)
⋮----
"""
    Configuration details for a supported OAuth Flow
    """
authorizationUrl: str
"""
    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
    """
refreshUrl: str | None = None
"""
    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.
    """
scopes: dict[str, str]
"""
    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.
    """
tokenUrl: str
"""
    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.
    """
class ClientCredentialsOAuthFlow(BaseModel)
class ContentTypeNotSupportedError(BaseModel)
⋮----
"""
    A2A specific error indicating incompatible content types between request and agent capabilities.
    """
code: Literal[-32005] = -32005
"""
    A Number that indicates the error type that occurred.
    """
data: Any | None = None
"""
    A Primitive or Structured value that contains additional information about the error.
    This may be omitted.
    """
message: str | None = 'Incompatible content types'
"""
    A String providing a short description of the error.
    """
class DataPart(BaseModel)
⋮----
"""
    Represents a structured data segment within a message part.
    """
data: dict[str, Any]
"""
    Structured data content
    """
kind: Literal['data'] = 'data'
"""
    Part type - data for DataParts
    """
metadata: dict[str, Any] | None = None
"""
    Optional metadata associated with the part.
    """
class DeleteTaskPushNotificationConfigParams(BaseModel)
⋮----
"""
    Parameters for removing pushNotificationConfiguration associated with a Task
    """
⋮----
"""
    Task id.
    """
⋮----
pushNotificationConfigId: str
class DeleteTaskPushNotificationConfigRequest(BaseModel)
⋮----
"""
    JSON-RPC request model for the 'tasks/pushNotificationConfig/delete' method.
    """
id: str | int
"""
    An identifier established by the Client that MUST contain a String, Number.
    Numbers SHOULD NOT contain fractional parts.
    """
jsonrpc: Literal['2.0'] = '2.0'
"""
    Specifies the version of the JSON-RPC protocol. MUST be exactly "2.0".
    """
method: Literal['tasks/pushNotificationConfig/delete'] = (
"""
    A String containing the name of the method to be invoked.
    """
params: DeleteTaskPushNotificationConfigParams
"""
    A Structured value that holds the parameter values to be used during the invocation of the method.
    """
class DeleteTaskPushNotificationConfigSuccessResponse(BaseModel)
⋮----
"""
    JSON-RPC success response model for the 'tasks/pushNotificationConfig/delete' method.
    """
id: str | int | None = None
⋮----
result: None
"""
    The result object on success.
    """
class FileBase(BaseModel)
⋮----
"""
    Represents the base entity for FileParts
    """
mimeType: str | None = None
"""
    Optional mimeType for the file
    """
name: str | None = None
"""
    Optional name for the file
    """
class FileWithBytes(BaseModel)
⋮----
"""
    Define the variant where 'bytes' is present and 'uri' is absent
    """
bytes: str
"""
    base64 encoded content of the file
    """
⋮----
class FileWithUri(BaseModel)
⋮----
"""
    Define the variant where 'uri' is present and 'bytes' is absent
    """
⋮----
"""
    URL for the File content
    """
class GetTaskPushNotificationConfigParams(BaseModel)
⋮----
"""
    Parameters for fetching a pushNotificationConfiguration associated with a Task
    """
⋮----
pushNotificationConfigId: str | None = None
class HTTPAuthSecurityScheme(BaseModel)
⋮----
"""
    HTTP Authentication security scheme.
    """
bearerFormat: str | None = None
"""
    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.
    """
⋮----
scheme: str
"""
    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.
    """
type: Literal['http'] = 'http'
class ImplicitOAuthFlow(BaseModel)
class InternalError(BaseModel)
⋮----
"""
    JSON-RPC error indicating an internal JSON-RPC error on the server.
    """
code: Literal[-32603] = -32603
⋮----
message: str | None = 'Internal error'
⋮----
class InvalidAgentResponseError(BaseModel)
⋮----
"""
    A2A specific error indicating agent returned invalid response for the current method
    """
code: Literal[-32006] = -32006
⋮----
message: str | None = 'Invalid agent response'
⋮----
class InvalidParamsError(BaseModel)
⋮----
"""
    JSON-RPC error indicating invalid method parameter(s).
    """
code: Literal[-32602] = -32602
⋮----
message: str | None = 'Invalid parameters'
⋮----
class InvalidRequestError(BaseModel)
⋮----
"""
    JSON-RPC error indicating the JSON sent is not a valid Request object.
    """
code: Literal[-32600] = -32600
⋮----
message: str | None = 'Request payload validation error'
⋮----
class JSONParseError(BaseModel)
⋮----
"""
    JSON-RPC error indicating invalid JSON was received by the server.
    """
code: Literal[-32700] = -32700
⋮----
message: str | None = 'Invalid JSON payload'
⋮----
class JSONRPCError(BaseModel)
⋮----
"""
    Represents a JSON-RPC 2.0 Error object.
    This is typically included in a JSONRPCErrorResponse when an error occurs.
    """
code: int
⋮----
message: str
⋮----
class JSONRPCMessage(BaseModel)
⋮----
"""
    Base interface for any JSON-RPC 2.0 request or response.
    """
⋮----
class JSONRPCRequest(BaseModel)
⋮----
"""
    Represents a JSON-RPC 2.0 Request object.
    """
⋮----
method: str
⋮----
class JSONRPCSuccessResponse(BaseModel)
⋮----
"""
    Represents a JSON-RPC 2.0 Success Response object.
    """
⋮----
result: Any
"""
    The result object on success
    """
class ListTaskPushNotificationConfigParams(BaseModel)
⋮----
"""
    Parameters for getting list of pushNotificationConfigurations associated with a Task
    """
⋮----
class ListTaskPushNotificationConfigRequest(BaseModel)
⋮----
"""
    JSON-RPC request model for the 'tasks/pushNotificationConfig/list' method.
    """
⋮----
method: Literal['tasks/pushNotificationConfig/list'] = (
⋮----
params: ListTaskPushNotificationConfigParams
⋮----
class Role(str, Enum)
⋮----
"""
    Message sender's role
    """
agent = 'agent'
user = 'user'
class MethodNotFoundError(BaseModel)
⋮----
"""
    JSON-RPC error indicating the method does not exist or is not available.
    """
code: Literal[-32601] = -32601
⋮----
message: str | None = 'Method not found'
⋮----
class OpenIdConnectSecurityScheme(BaseModel)
⋮----
"""
    OpenID Connect security scheme configuration.
    """
⋮----
openIdConnectUrl: str
"""
    Well-known URL to discover the [[OpenID-Connect-Discovery]] provider metadata.
    """
type: Literal['openIdConnect'] = 'openIdConnect'
class PartBase(BaseModel)
⋮----
"""
    Base properties common to all message parts.
    """
⋮----
class PasswordOAuthFlow(BaseModel)
class PushNotificationAuthenticationInfo(BaseModel)
⋮----
"""
    Defines authentication details for push notifications.
    """
credentials: str | None = None
"""
    Optional credentials
    """
schemes: list[str]
"""
    Supported authentication schemes - e.g. Basic, Bearer
    """
class PushNotificationConfig(BaseModel)
⋮----
"""
    Configuration for setting up push notifications for task updates.
    """
authentication: PushNotificationAuthenticationInfo | None = None
id: str | None = None
"""
    Push Notification ID - created by server to support multiple callbacks
    """
token: str | None = None
"""
    Token unique to this task/session.
    """
⋮----
"""
    URL for sending the push notifications.
    """
class PushNotificationNotSupportedError(BaseModel)
⋮----
"""
    A2A specific error indicating the agent does not support push notifications.
    """
code: Literal[-32003] = -32003
⋮----
message: str | None = 'Push Notification is not supported'
⋮----
class SecuritySchemeBase(BaseModel)
⋮----
"""
    Base properties shared by all security schemes.
    """
⋮----
class TaskIdParams(BaseModel)
⋮----
"""
    Parameters containing only a task ID, used for simple task operations.
    """
⋮----
class TaskNotCancelableError(BaseModel)
⋮----
"""
    A2A specific error indicating the task is in a state where it cannot be canceled.
    """
code: Literal[-32002] = -32002
⋮----
message: str | None = 'Task cannot be canceled'
⋮----
class TaskNotFoundError(BaseModel)
⋮----
"""
    A2A specific error indicating the requested task ID was not found.
    """
code: Literal[-32001] = -32001
⋮----
message: str | None = 'Task not found'
⋮----
class TaskPushNotificationConfig(BaseModel)
⋮----
"""
    Parameters for setting or getting push notification configuration for a task
    """
pushNotificationConfig: PushNotificationConfig
"""
    Push notification configuration.
    """
taskId: str
⋮----
class TaskQueryParams(BaseModel)
⋮----
"""
    Parameters for querying a task, including optional history length.
    """
historyLength: int | None = None
"""
    Number of recent messages to be retrieved.
    """
⋮----
class TaskResubscriptionRequest(BaseModel)
⋮----
"""
    JSON-RPC request model for the 'tasks/resubscribe' method.
    """
⋮----
method: Literal['tasks/resubscribe'] = 'tasks/resubscribe'
⋮----
params: TaskIdParams
⋮----
class TaskState(str, Enum)
⋮----
"""
    Represents the possible states of a Task.
    """
submitted = 'submitted'
working = 'working'
input_required = 'input-required'
completed = 'completed'
canceled = 'canceled'
failed = 'failed'
rejected = 'rejected'
auth_required = 'auth-required'
unknown = 'unknown'
class TextPart(BaseModel)
⋮----
"""
    Represents a text segment within parts.
    """
kind: Literal['text'] = 'text'
"""
    Part type - text for TextParts
    """
⋮----
text: str
"""
    Text content
    """
class UnsupportedOperationError(BaseModel)
⋮----
"""
    A2A specific error indicating the requested operation is not supported by the agent.
    """
code: Literal[-32004] = -32004
⋮----
message: str | None = 'This operation is not supported'
⋮----
class A2AError(
⋮----
root: (
class AgentCapabilities(BaseModel)
⋮----
"""
    Defines optional capabilities supported by an agent.
    """
extensions: list[AgentExtension] | None = None
"""
    extensions supported by this agent.
    """
pushNotifications: bool | None = None
"""
    true if the agent can notify updates to client.
    """
stateTransitionHistory: bool | None = None
"""
    true if the agent exposes status change history for tasks.
    """
streaming: bool | None = None
"""
    true if the agent supports SSE.
    """
class CancelTaskRequest(BaseModel)
⋮----
"""
    JSON-RPC request model for the 'tasks/cancel' method.
    """
⋮----
method: Literal['tasks/cancel'] = 'tasks/cancel'
⋮----
class FilePart(BaseModel)
⋮----
"""
    Represents a File segment within parts.
    """
file: FileWithBytes | FileWithUri
"""
    File content either as url or bytes
    """
kind: Literal['file'] = 'file'
"""
    Part type - file for FileParts
    """
⋮----
class GetTaskPushNotificationConfigRequest(BaseModel)
⋮----
"""
    JSON-RPC request model for the 'tasks/pushNotificationConfig/get' method.
    """
⋮----
method: Literal['tasks/pushNotificationConfig/get'] = (
⋮----
params: TaskIdParams | GetTaskPushNotificationConfigParams
"""
    A Structured value that holds the parameter values to be used during the invocation of the method.
    TaskIdParams type is deprecated for this method
    """
class GetTaskPushNotificationConfigSuccessResponse(BaseModel)
⋮----
"""
    JSON-RPC success response model for the 'tasks/pushNotificationConfig/get' method.
    """
⋮----
result: TaskPushNotificationConfig
⋮----
class GetTaskRequest(BaseModel)
⋮----
"""
    JSON-RPC request model for the 'tasks/get' method.
    """
⋮----
method: Literal['tasks/get'] = 'tasks/get'
⋮----
params: TaskQueryParams
⋮----
class JSONRPCErrorResponse(BaseModel)
⋮----
"""
    Represents a JSON-RPC 2.0 Error Response object.
    """
error: (
⋮----
class ListTaskPushNotificationConfigSuccessResponse(BaseModel)
⋮----
"""
    JSON-RPC success response model for the 'tasks/pushNotificationConfig/list' method.
    """
⋮----
result: list[TaskPushNotificationConfig]
⋮----
class MessageSendConfiguration(BaseModel)
⋮----
"""
    Configuration for the send message request.
    """
acceptedOutputModes: list[str]
"""
    Accepted output modalities by the client.
    """
blocking: bool | None = None
"""
    If the server should treat the client as a blocking request.
    """
⋮----
pushNotificationConfig: PushNotificationConfig | None = None
"""
    Where the server should send notifications when disconnected.
    """
class OAuthFlows(BaseModel)
⋮----
"""
    Allows configuration of the supported OAuth Flows
    """
authorizationCode: AuthorizationCodeOAuthFlow | None = None
"""
    Configuration for the OAuth Authorization Code flow. Previously called accessCode in OpenAPI 2.0.
    """
clientCredentials: ClientCredentialsOAuthFlow | None = None
"""
    Configuration for the OAuth Client Credentials flow. Previously called application in OpenAPI 2.0
    """
implicit: ImplicitOAuthFlow | None = None
"""
    Configuration for the OAuth Implicit flow
    """
password: PasswordOAuthFlow | None = None
"""
    Configuration for the OAuth Resource Owner Password flow
    """
class Part(RootModel[TextPart | FilePart | DataPart])
⋮----
root: TextPart | FilePart | DataPart
"""
    Represents a part of a message, which can be text, a file, or structured data.
    """
class SetTaskPushNotificationConfigRequest(BaseModel)
⋮----
"""
    JSON-RPC request model for the 'tasks/pushNotificationConfig/set' method.
    """
⋮----
method: Literal['tasks/pushNotificationConfig/set'] = (
⋮----
params: TaskPushNotificationConfig
⋮----
class SetTaskPushNotificationConfigSuccessResponse(BaseModel)
⋮----
"""
    JSON-RPC success response model for the 'tasks/pushNotificationConfig/set' method.
    """
⋮----
class Artifact(BaseModel)
⋮----
"""
    Represents an artifact generated for a task.
    """
artifactId: str
"""
    Unique identifier for the artifact.
    """
⋮----
"""
    Optional description for the artifact.
    """
extensions: list[str] | None = None
"""
    The URIs of extensions that are present or contributed to this Artifact.
    """
⋮----
"""
    Extension metadata.
    """
⋮----
"""
    Optional name for the artifact.
    """
parts: list[Part]
"""
    Artifact parts.
    """
class DeleteTaskPushNotificationConfigResponse(
⋮----
root: JSONRPCErrorResponse | DeleteTaskPushNotificationConfigSuccessResponse
"""
    JSON-RPC response for the 'tasks/pushNotificationConfig/delete' method.
    """
class GetTaskPushNotificationConfigResponse(
⋮----
root: JSONRPCErrorResponse | GetTaskPushNotificationConfigSuccessResponse
"""
    JSON-RPC response for the 'tasks/pushNotificationConfig/set' method.
    """
class ListTaskPushNotificationConfigResponse(
⋮----
root: JSONRPCErrorResponse | ListTaskPushNotificationConfigSuccessResponse
"""
    JSON-RPC response for the 'tasks/pushNotificationConfig/list' method.
    """
class Message(BaseModel)
⋮----
"""
    Represents a single message exchanged between user and agent.
    """
contextId: str | None = None
"""
    The context the message is associated with
    """
⋮----
"""
    The URIs of extensions that are present or contributed to this Message.
    """
kind: Literal['message'] = 'message'
"""
    Event type
    """
messageId: str
"""
    Identifier created by the message creator
    """
⋮----
"""
    Message content
    """
referenceTaskIds: list[str] | None = None
"""
    List of tasks referenced as context by this message.
    """
role: Role
⋮----
taskId: str | None = None
"""
    Identifier of task the message is related to
    """
class MessageSendParams(BaseModel)
⋮----
"""
    Sent by the client to the agent as a request. May create, continue or restart a task.
    """
configuration: MessageSendConfiguration | None = None
"""
    Send message configuration.
    """
message: Message
"""
    The message being sent to the server.
    """
⋮----
class OAuth2SecurityScheme(BaseModel)
⋮----
"""
    OAuth2.0 security scheme configuration.
    """
⋮----
flows: OAuthFlows
"""
    An object containing configuration information for the flow types supported.
    """
type: Literal['oauth2'] = 'oauth2'
class SecurityScheme(
⋮----
"""
    Mirrors the OpenAPI Security Scheme Object
    (https://swagger.io/specification/#security-scheme-object)
    """
class SendMessageRequest(BaseModel)
⋮----
"""
    JSON-RPC request model for the 'message/send' method.
    """
⋮----
method: Literal['message/send'] = 'message/send'
⋮----
params: MessageSendParams
⋮----
class SendStreamingMessageRequest(BaseModel)
⋮----
"""
    JSON-RPC request model for the 'message/stream' method.
    """
⋮----
method: Literal['message/stream'] = 'message/stream'
⋮----
class SetTaskPushNotificationConfigResponse(
⋮----
root: JSONRPCErrorResponse | SetTaskPushNotificationConfigSuccessResponse
⋮----
class TaskArtifactUpdateEvent(BaseModel)
⋮----
"""
    Sent by server during sendStream or subscribe requests
    """
append: bool | None = None
"""
    Indicates if this artifact appends to a previous one
    """
artifact: Artifact
"""
    Generated artifact
    """
contextId: str
"""
    The context the task is associated with
    """
kind: Literal['artifact-update'] = 'artifact-update'
⋮----
lastChunk: bool | None = None
"""
    Indicates if this is the last chunk of the artifact
    """
⋮----
"""
    Task id
    """
class TaskStatus(BaseModel)
⋮----
"""
    TaskState and accompanying message.
    """
message: Message | None = None
"""
    Additional status updates for client
    """
state: TaskState
timestamp: str | None = None
"""
    ISO 8601 datetime string when the status was recorded.
    """
class TaskStatusUpdateEvent(BaseModel)
⋮----
final: bool
"""
    Indicates the end of the event stream
    """
kind: Literal['status-update'] = 'status-update'
⋮----
status: TaskStatus
"""
    Current status of the task
    """
⋮----
class A2ARequest(
⋮----
"""
    A2A supported request types
    """
class AgentCard(BaseModel)
⋮----
"""
    An AgentCard conveys key information:
    - Overall details (version, name, description, uses)
    - Skills: A set of capabilities the agent can perform
    - Default modalities/content types supported by the agent.
    - Authentication requirements
    """
additionalInterfaces: list[AgentInterface] | None = None
"""
    Announcement of additional supported transports. Client can use any of
    the supported transports.
    """
capabilities: AgentCapabilities
"""
    Optional capabilities supported by the agent.
    """
defaultInputModes: list[str]
"""
    The set of interaction modes that the agent supports across all skills. This can be overridden per-skill.
    Supported media types for input.
    """
defaultOutputModes: list[str]
⋮----
"""
    A human-readable description of the agent. Used to assist users and
    other agents in understanding what the agent can do.
    """
documentationUrl: str | None = None
"""
    A URL to documentation for the agent.
    """
iconUrl: str | None = None
"""
    A URL to an icon for the agent.
    """
⋮----
"""
    Human readable name of the agent.
    """
preferredTransport: str | None = None
"""
    The transport of the preferred endpoint. If empty, defaults to JSONRPC.
    """
provider: AgentProvider | None = None
"""
    The service provider of the agent
    """
security: list[dict[str, list[str]]] | None = None
"""
    Security requirements for contacting the agent.
    """
securitySchemes: dict[str, SecurityScheme] | None = None
"""
    Security scheme details used for authenticating with this agent.
    """
skills: list[AgentSkill]
"""
    Skills are a unit of capability that an agent can perform.
    """
supportsAuthenticatedExtendedCard: bool | None = None
"""
    true if the agent supports providing an extended agent card when the user is authenticated.
    Defaults to false if not specified.
    """
⋮----
"""
    A URL to the address the agent is hosted at. This represents the
    preferred endpoint as declared by the agent.
    """
version: str
"""
    The version of the agent - format is up to the provider.
    """
class Task(BaseModel)
⋮----
artifacts: list[Artifact] | None = None
"""
    Collection of artifacts created by the agent.
    """
⋮----
"""
    Server-generated id for contextual alignment across interactions
    """
history: list[Message] | None = None
⋮----
"""
    Unique identifier for the task
    """
kind: Literal['task'] = 'task'
⋮----
class CancelTaskSuccessResponse(BaseModel)
⋮----
"""
    JSON-RPC success response model for the 'tasks/cancel' method.
    """
⋮----
result: Task
⋮----
class GetTaskSuccessResponse(BaseModel)
⋮----
"""
    JSON-RPC success response for the 'tasks/get' method.
    """
⋮----
class SendMessageSuccessResponse(BaseModel)
⋮----
"""
    JSON-RPC success response model for the 'message/send' method.
    """
⋮----
result: Task | Message
⋮----
class SendStreamingMessageSuccessResponse(BaseModel)
⋮----
"""
    JSON-RPC success response model for the 'message/stream' method.
    """
⋮----
result: Task | Message | TaskStatusUpdateEvent | TaskArtifactUpdateEvent
⋮----
class CancelTaskResponse(RootModel[JSONRPCErrorResponse | CancelTaskSuccessResponse])
⋮----
root: JSONRPCErrorResponse | CancelTaskSuccessResponse
"""
    JSON-RPC response for the 'tasks/cancel' method.
    """
class GetTaskResponse(RootModel[JSONRPCErrorResponse | GetTaskSuccessResponse])
⋮----
root: JSONRPCErrorResponse | GetTaskSuccessResponse
"""
    JSON-RPC response for the 'tasks/get' method.
    """
class JSONRPCResponse(
⋮----
"""
    Represents a JSON-RPC 2.0 Response object.
    """
class SendMessageResponse(RootModel[JSONRPCErrorResponse | SendMessageSuccessResponse])
⋮----
root: JSONRPCErrorResponse | SendMessageSuccessResponse
"""
    JSON-RPC response model for the 'message/send' method.
    """
class SendStreamingMessageResponse(
⋮----
root: JSONRPCErrorResponse | SendStreamingMessageSuccessResponse
"""
    JSON-RPC response model for the 'message/stream' method.
    """

================
File: tests/auth/test_user.py
================
class TestUnauthenticatedUser(unittest.TestCase)
⋮----
def test_is_authenticated_returns_false(self)
⋮----
user = UnauthenticatedUser()
⋮----
def test_user_name_returns_empty_string(self)

================
File: tests/client/test_auth_middleware.py
================
# A simple mock interceptor for testing basic middleware functionality
class HeaderInterceptor(ClientCallInterceptor)
⋮----
def __init__(self, header_name: str, header_value: str)
⋮----
headers = http_kwargs.get('headers', {})
⋮----
@pytest.mark.asyncio
@respx.mock
async def test_client_with_simple_interceptor()
⋮----
"""
    Tests that a basic interceptor is called and successfully
    modifies the outgoing request headers.
    """
# Arrange
test_url = 'http://fake-agent.com/rpc'
header_interceptor = HeaderInterceptor('X-Test-Header', 'Test-Value-123')
⋮----
client = A2AClient(
# Mock the HTTP response with a minimal valid success response
minimal_success_response = {
⋮----
# Act
⋮----
# Assert
⋮----
request = respx.calls.last.request
⋮----
@pytest.mark.asyncio
async def test_in_memory_context_credential_store()
⋮----
"""
    Tests the functionality of the InMemoryContextCredentialStore to ensure
    it correctly stores and retrieves credentials based on sessionId.
    """
⋮----
store = InMemoryContextCredentialStore()
session_id = 'test-session-123'
scheme_name = 'test-scheme'
credential = 'test-token'
# Act
⋮----
# Assert: Successful retrieval
context = ClientCallContext(state={'sessionId': session_id})
retrieved_credential = await store.get_credentials(scheme_name, context)
⋮----
# Assert: Retrieval with wrong session ID returns None
wrong_context = ClientCallContext(state={'sessionId': 'wrong-session'})
retrieved_credential_wrong = await store.get_credentials(
⋮----
# Assert: Retrieval with no context returns None
retrieved_credential_none = await store.get_credentials(scheme_name, None)
⋮----
# Assert: Retrieval with context but no sessionId returns None
empty_context = ClientCallContext(state={})
retrieved_credential_empty = await store.get_credentials(
⋮----
@pytest.mark.asyncio
@respx.mock
async def test_auth_interceptor_with_api_key()
⋮----
"""
    Tests the authentication flow with an API key in the header.
    """
⋮----
test_url = 'http://apikey-agent.com/rpc'
session_id = 'user-session-2'
scheme_name = 'apiKeyAuth'
api_key = 'secret-api-key'
cred_store = InMemoryContextCredentialStore()
⋮----
auth_interceptor = AuthInterceptor(credential_service=cred_store)
api_key_scheme_params = {
agent_card = AgentCard(
⋮----
@pytest.mark.asyncio
@respx.mock
async def test_auth_interceptor_with_oauth2_scheme()
⋮----
"""
    Tests the AuthInterceptor with an OAuth2 security scheme defined in AgentCard.
    Ensures it correctly sets the Authorization: Bearer <token> header.
    """
test_url = 'http://oauth-agent.com/rpc'
session_id = 'user-session-oauth'
scheme_name = 'myOAuthScheme'
access_token = 'secret-oauth-access-token'
⋮----
oauth_flows = OAuthFlows(
⋮----
request_sent = respx.calls.last.request
⋮----
@pytest.mark.asyncio
@respx.mock
async def test_auth_interceptor_with_oidc_scheme()
⋮----
"""
    Tests the AuthInterceptor with an OpenIdConnectSecurityScheme.
    Ensures it correctly sets the Authorization: Bearer <token> header.
    """
⋮----
test_url = 'http://oidc-agent.com/rpc'
session_id = 'user-session-oidc'
scheme_name = 'myOidcScheme'
id_token = 'secret-oidc-id-token'

================
File: tests/client/test_client.py
================
AGENT_CARD = AgentCard(
AGENT_CARD_EXTENDED = AGENT_CARD.model_copy(
AGENT_CARD_SUPPORTS_EXTENDED = AGENT_CARD.model_copy(
AGENT_CARD_NO_URL_SUPPORTS_EXTENDED = AGENT_CARD_SUPPORTS_EXTENDED.model_copy(
MINIMAL_TASK: dict[str, Any] = {
MINIMAL_CANCELLED_TASK: dict[str, Any] = {
⋮----
@pytest.fixture
def mock_httpx_client() -> AsyncMock
⋮----
@pytest.fixture
def mock_agent_card() -> MagicMock
⋮----
"""Helper to create an async iterable from a list."""
⋮----
class TestA2ACardResolver
⋮----
BASE_URL = 'http://example.com'
AGENT_CARD_PATH = '/.well-known/agent.json'
FULL_AGENT_CARD_URL = f'{BASE_URL}{AGENT_CARD_PATH}'
EXTENDED_AGENT_CARD_PATH = (
⋮----
'/agent/authenticatedExtendedCard'  # Default path
⋮----
base_url = 'http://example.com'
custom_path = '/custom/agent-card.json'
resolver = A2ACardResolver(
⋮----
# Test default agent_card_path
resolver_default_path = A2ACardResolver(
⋮----
@pytest.mark.asyncio
    async def test_init_strips_slashes(self, mock_httpx_client: AsyncMock)
⋮----
base_url='http://example.com/',  # With trailing slash
agent_card_path='/.well-known/agent.json/',  # With leading/trailing slash
⋮----
)  # Trailing slash stripped
# constructor lstrips agent_card_path, but keeps trailing if provided
⋮----
mock_response = AsyncMock(spec=httpx.Response)
⋮----
agent_card = await resolver.get_agent_card(http_kwargs={'timeout': 10})
⋮----
# Ensure only one call was made (for the public card)
⋮----
extended_card_response = AsyncMock(spec=httpx.Response)
⋮----
# Mock the single call for the extended card
⋮----
# Fetch the extended card by providing its relative path and example auth
auth_kwargs = {'headers': {'Authorization': 'Bearer test token'}}
agent_card_result = await resolver.get_agent_card(
expected_extended_url = (
⋮----
)  # Should return the extended card
⋮----
# Data that will cause a Pydantic ValidationError
⋮----
# The call that is expected to raise an error should be within pytest.raises
⋮----
await resolver.get_agent_card()  # Fetches from default path
⋮----
)  # Check if Pydantic error details are present
⋮----
)  # Should only be called once
⋮----
mock_response = MagicMock(
⋮----
)  # Use MagicMock for response attribute
⋮----
http_status_error = httpx.HTTPStatusError(
⋮----
# Define json_error before using it
json_error = json.JSONDecodeError('Expecting value', 'doc', 0)
⋮----
# Assertions using exc_info must be after the with block
⋮----
request_error = httpx.RequestError('Network issue', request=MagicMock())
⋮----
class TestA2AClient
⋮----
AGENT_URL = 'http://agent.example.com/api'
⋮----
client = A2AClient(
⋮----
def test_init_with_url(self, mock_httpx_client: AsyncMock)
⋮----
client = A2AClient(httpx_client=mock_httpx_client, url=self.AGENT_URL)
⋮----
)  # Agent card URL should be used
⋮----
agent_card_path = '/.well-known/custom-agent.json'
resolver_kwargs = {'timeout': 30}
mock_resolver_instance = AsyncMock(spec=A2ACardResolver)
⋮----
client = await A2AClient.get_client_from_agent_card_url(
⋮----
# relative_card_path=None is implied by not passing it
⋮----
error_to_raise = A2AClientHTTPError(404, 'Agent card not found')
⋮----
params = MessageSendParams(
request = SendMessageRequest(id=123, params=params)
success_response = create_text_message_object(
rpc_response: dict[str, Any] = {
⋮----
response = await client.send_message(
⋮----
assert not called_kwargs  # no kwargs to _send_request
⋮----
json_rpc_request: dict[str, Any] = called_args[0]
⋮----
http_kwargs: dict[str, Any] = called_args[1]
⋮----
a2a_request_arg = A2ARequest.model_validate(json_rpc_request)
⋮----
error_response = InvalidParamsError()
⋮----
response = await client.send_message(request=request)
⋮----
request = SendStreamingMessageRequest(id=123, params=params)
mock_stream_response_1_dict: dict[str, Any] = {
mock_stream_response_2_dict: dict[str, Any] = {
sse_event_1 = ServerSentEvent(
sse_event_2 = ServerSentEvent(
mock_event_source = AsyncMock(spec=EventSource)
⋮----
results: list[Any] = []
⋮----
# Assuming SendStreamingMessageResponse is a RootModel like SendMessageResponse
⋮----
results[0].root.result.model_dump(  # type: ignore
⋮----
results[1].root.result.model_dump(  # type: ignore
⋮----
sent_json_payload = call_kwargs['json']
⋮----
)  # Default timeout for streaming
⋮----
request = SendStreamingMessageRequest(id='kwarg_req', params=params)
custom_kwargs = {
# Setup mock_aconnect_sse to behave minimally
⋮----
)  # No events needed for this test
⋮----
pass  # We just want to check the call to aconnect_sse
⋮----
)  # Ensure custom timeout is used
⋮----
request = SendStreamingMessageRequest(
# Configure the mock aconnect_sse to raise SSEError when aiter_sse is called
⋮----
assert exc_info.value.status_code == 400  # As per client implementation
⋮----
# Malformed JSON event
malformed_sse_event = ServerSentEvent(data='not valid json')
⋮----
# json.loads will be called on "not valid json" and raise JSONDecodeError
⋮----
)  # Example of JSONDecodeError message
⋮----
# Configure aconnect_sse itself to raise httpx.RequestError (e.g., during connection)
# This needs to be raised when aconnect_sse is entered or iterated.
# One way is to make the context manager's __aenter__ raise it, or aiter_sse.
# For simplicity, let's make aiter_sse raise it, as if the error occurs after connection.
⋮----
assert exc_info.value.status_code == 503  # As per client implementation
⋮----
mock_response = MagicMock(spec=httpx.Response)
⋮----
http_error = httpx.HTTPStatusError(
⋮----
task_id_val = 'task_set_cb_001'
# Correctly create the PushNotificationConfig (inner model)
push_config_payload = PushNotificationConfig(
# Correctly create the TaskPushNotificationConfig (outer model)
params_model = TaskPushNotificationConfig(
# request.id will be generated by the client method if not provided
request = SetTaskPushNotificationConfigRequest(
⋮----
)  # Test ID auto-generation
# The result for a successful set operation is the same config
rpc_response_payload: dict[str, Any] = {
⋮----
'id': ANY,  # Will be checked against generated ID
⋮----
# Capture the generated ID for assertion
generated_id = str(mock_uuid.return_value)
⋮----
generated_id  # Ensure mock response uses the generated ID
⋮----
response = await client.set_task_callback(request=request)
⋮----
sent_json_payload = called_args[0]
⋮----
req_id = 'set_cb_err_req'
push_config_payload = PushNotificationConfig(url='https://errors.com')
⋮----
error_details = InvalidParamsError(message='Invalid callback URL')
⋮----
push_config_payload = PushNotificationConfig(url='https://kwargs.com')
⋮----
custom_kwargs = {'headers': {'X-Callback-Token': 'secret'}}
# Minimal successful response
⋮----
called_args, _ = mock_send_req.call_args  # Correctly unpack args
⋮----
)  # http_kwargs is the second positional arg
⋮----
task_id_val = 'task_get_cb_001'
params_model = TaskIdParams(
⋮----
)  # Params for get is just TaskIdParams
request = GetTaskPushNotificationConfigRequest(
⋮----
)  # ID is empty string for auto-generation test
# Expected result for a successful get operation
⋮----
expected_callback_config = TaskPushNotificationConfig(
⋮----
response = await client.get_task_callback(request=request)
⋮----
req_id = 'get_cb_err_req'
params_model = TaskIdParams(id='task_get_err_cb')
⋮----
error_details = TaskNotCancelableError(
⋮----
)  # Example error
⋮----
params_model = TaskIdParams(id='task_get_cb_kwargs')
⋮----
custom_kwargs = {'headers': {'X-Tenant-ID': 'tenant-x'}}
# Correctly create the nested PushNotificationConfig
push_config_payload_for_expected = PushNotificationConfig(
⋮----
task_id_val = 'task_for_req_obj'
params_model = TaskQueryParams(id=task_id_val)
request_obj_id = 789
request = GetTaskRequest(id=request_obj_id, params=params_model)
⋮----
response = await client.get_task(
⋮----
json_rpc_request_sent: dict[str, Any] = called_args[0]
assert not called_kwargs  # no extra kwargs to _send_request
⋮----
response.root.result.model_dump(mode='json', exclude_none=True)  # type: ignore
⋮----
params_model = TaskQueryParams(id='task_error_case')
request = GetTaskRequest(id='err_req_id', params=params_model)
error_details = InvalidParamsError()
⋮----
response = await client.get_task(request=request)
⋮----
task_id_val = MINIMAL_CANCELLED_TASK['id']
params_model = TaskIdParams(id=task_id_val)
request_obj_id = 'cancel_req_obj_id_001'
request = CancelTaskRequest(id=request_obj_id, params=params_model)
⋮----
response = await client.cancel_task(
⋮----
params_model = TaskIdParams(id='task_cancel_error_case')
request = CancelTaskRequest(id='err_cancel_req', params=params_model)
error_details = TaskNotCancelableError()
⋮----
response = await client.cancel_task(request=request)

================
File: tests/client/test_errors.py
================
class TestA2AClientError
⋮----
"""Test cases for the base A2AClientError class."""
def test_instantiation(self)
⋮----
"""Test that A2AClientError can be instantiated."""
error = A2AClientError('Test error message')
⋮----
def test_inheritance(self)
⋮----
"""Test that A2AClientError inherits from Exception."""
error = A2AClientError()
⋮----
class TestA2AClientHTTPError
⋮----
"""Test cases for A2AClientHTTPError class."""
⋮----
"""Test that A2AClientHTTPError can be instantiated with status_code and message."""
error = A2AClientHTTPError(404, 'Not Found')
⋮----
def test_message_formatting(self)
⋮----
"""Test that the error message is formatted correctly."""
error = A2AClientHTTPError(500, 'Internal Server Error')
⋮----
"""Test that A2AClientHTTPError inherits from A2AClientError."""
error = A2AClientHTTPError(400, 'Bad Request')
⋮----
def test_with_empty_message(self)
⋮----
"""Test behavior with an empty message."""
error = A2AClientHTTPError(403, '')
⋮----
def test_with_various_status_codes(self)
⋮----
"""Test with different HTTP status codes."""
test_cases = [
⋮----
error = A2AClientHTTPError(status_code, message)
⋮----
class TestA2AClientJSONError
⋮----
"""Test cases for A2AClientJSONError class."""
⋮----
"""Test that A2AClientJSONError can be instantiated with a message."""
error = A2AClientJSONError('Invalid JSON format')
⋮----
error = A2AClientJSONError('Missing required field')
⋮----
"""Test that A2AClientJSONError inherits from A2AClientError."""
error = A2AClientJSONError('Parsing error')
⋮----
error = A2AClientJSONError('')
⋮----
def test_with_various_messages(self)
⋮----
"""Test with different error messages."""
test_messages = [
⋮----
error = A2AClientJSONError(message)
⋮----
class TestExceptionHierarchy
⋮----
"""Test the exception hierarchy and relationships."""
def test_exception_hierarchy(self)
⋮----
"""Test that the exception hierarchy is correct."""
⋮----
def test_catch_specific_exception(self)
⋮----
"""Test that specific exceptions can be caught."""
⋮----
def test_catch_base_exception(self)
⋮----
"""Test that derived exceptions can be caught as base exception."""
exceptions = [
⋮----
class TestExceptionRaising
⋮----
"""Test cases for raising and handling the exceptions."""
def test_raising_http_error(self)
⋮----
"""Test raising an HTTP error and checking its properties."""
⋮----
error = excinfo.value
⋮----
def test_raising_json_error(self)
⋮----
"""Test raising a JSON error and checking its properties."""
⋮----
def test_raising_base_error(self)
⋮----
"""Test raising the base error."""
⋮----
# Additional parametrized tests for more comprehensive coverage
⋮----
def test_http_error_parametrized(status_code, message, expected)
⋮----
"""Parametrized test for HTTP errors with different status codes."""
⋮----
def test_json_error_parametrized(message, expected)
⋮----
"""Parametrized test for JSON errors with different messages."""

================
File: tests/client/test_grpc_client.py
================
# Fixtures
⋮----
@pytest.fixture
def mock_grpc_stub() -> AsyncMock
⋮----
"""Provides a mock gRPC stub with methods mocked."""
stub = AsyncMock(spec=a2a_pb2_grpc.A2AServiceStub)
⋮----
@pytest.fixture
def sample_agent_card() -> AgentCard
⋮----
"""Provides a minimal agent card for initialization."""
⋮----
"""Provides an A2AGrpcClient instance."""
⋮----
@pytest.fixture
def sample_message_send_params() -> MessageSendParams
⋮----
"""Provides a sample MessageSendParams object."""
⋮----
@pytest.fixture
def sample_task() -> Task
⋮----
"""Provides a sample Task object."""
⋮----
@pytest.fixture
def sample_message() -> Message
⋮----
"""Provides a sample Message object."""
⋮----
"""Test send_message that returns a Task."""
⋮----
response = await grpc_client.send_message(sample_message_send_params)
⋮----
"""Test retrieving a task."""
⋮----
params = TaskQueryParams(id=sample_task.id)
response = await grpc_client.get_task(params)
⋮----
"""Test cancelling a task."""
cancelled_task = sample_task.model_copy()
⋮----
params = TaskIdParams(id=sample_task.id)
response = await grpc_client.cancel_task(params)

================
File: tests/server/agent_execution/test_context.py
================
class TestRequestContext
⋮----
"""Tests for the RequestContext class."""
⋮----
@pytest.fixture
    def mock_message(self)
⋮----
"""Fixture for a mock Message."""
⋮----
@pytest.fixture
    def mock_params(self, mock_message)
⋮----
"""Fixture for a mock MessageSendParams."""
⋮----
@pytest.fixture
    def mock_task(self)
⋮----
"""Fixture for a mock Task."""
⋮----
def test_init_without_params(self)
⋮----
"""Test initialization without parameters."""
context = RequestContext()
⋮----
def test_init_with_params_no_ids(self, mock_params)
⋮----
"""Test initialization with params but no task or context IDs."""
⋮----
context = RequestContext(request=mock_params)
⋮----
def test_init_with_task_id(self, mock_params)
⋮----
"""Test initialization with task ID provided."""
task_id = 'task-123'
context = RequestContext(request=mock_params, task_id=task_id)
⋮----
def test_init_with_context_id(self, mock_params)
⋮----
"""Test initialization with context ID provided."""
context_id = 'context-456'
context = RequestContext(request=mock_params, context_id=context_id)
⋮----
def test_init_with_both_ids(self, mock_params)
⋮----
"""Test initialization with both task and context IDs provided."""
⋮----
context = RequestContext(
⋮----
def test_init_with_task(self, mock_params, mock_task)
⋮----
"""Test initialization with a task object."""
context = RequestContext(request=mock_params, task=mock_task)
⋮----
def test_get_user_input_no_params(self)
⋮----
"""Test get_user_input with no params returns empty string."""
⋮----
def test_attach_related_task(self, mock_task)
⋮----
"""Test attach_related_task adds a task to related_tasks."""
⋮----
# Test adding multiple tasks
another_task = Mock(spec=Task)
⋮----
def test_current_task_property(self, mock_task)
⋮----
"""Test current_task getter and setter."""
⋮----
# Change current task
new_task = Mock(spec=Task)
⋮----
def test_check_or_generate_task_id_no_params(self)
⋮----
"""Test _check_or_generate_task_id with no params does nothing."""
⋮----
def test_check_or_generate_task_id_with_existing_task_id(self, mock_params)
⋮----
"""Test _check_or_generate_task_id with existing task ID."""
existing_id = 'existing-task-id'
⋮----
# The method is called during initialization
⋮----
def test_check_or_generate_context_id_no_params(self)
⋮----
"""Test _check_or_generate_context_id with no params does nothing."""
⋮----
"""Test _check_or_generate_context_id with existing context ID."""
existing_id = 'existing-context-id'
⋮----
"""Test that an error is raised if provided task_id mismatches task.id."""
⋮----
"""Test that an error is raised if provided context_id mismatches task.contextId."""
# Set a valid task_id to avoid that error
⋮----
def test_with_related_tasks_provided(self, mock_task)
⋮----
"""Test initialization with related tasks provided."""
related_tasks = [mock_task, Mock(spec=Task)]
context = RequestContext(related_tasks=related_tasks)
⋮----
def test_message_property_without_params(self)
⋮----
"""Test message property returns None when no params are provided."""
⋮----
def test_message_property_with_params(self, mock_params)
⋮----
"""Test message property returns the message from params."""
⋮----
def test_init_with_existing_ids_in_message(self, mock_message, mock_params)
⋮----
"""Test initialization with existing IDs in the message."""
⋮----
# No new UUIDs should be generated
⋮----
"""Test initialization succeeds when task_id matches task.id."""
⋮----
"""Test initialization succeeds when context_id matches task.contextId."""
mock_params.message.taskId = mock_task.id  # Set matching task ID

================
File: tests/server/agent_execution/test_simple_request_context_builder.py
================
from a2a.auth.user import UnauthenticatedUser  # Import User types
⋮----
RequestContext,  # Corrected import path
⋮----
# ServerCallContext, # Removed from a2a.types
⋮----
# Helper to create a simple message
⋮----
# Helper to create a simple task
⋮----
class TestSimpleRequestContextBuilder(unittest.IsolatedAsyncioTestCase)
⋮----
def setUp(self)
def test_init_with_populate_true_and_task_store(self)
⋮----
builder = SimpleRequestContextBuilder(
⋮----
def test_init_with_populate_false_task_store_none(self)
def test_init_with_populate_false_task_store_provided(self)
⋮----
# Even if populate is false, task_store might still be provided (though not used by build for related_tasks)
⋮----
async def test_build_basic_context_no_populate(self)
⋮----
params = MessageSendParams(message=create_sample_message())
task_id = 'test_task_id_1'
context_id = 'test_context_id_1'
current_task = create_sample_task(
# Pass a valid User instance, e.g., UnauthenticatedUser or a mock spec'd as User
server_call_context = ServerCallContext(
request_context = await builder.build(
⋮----
# Access params via its properties message and configuration
⋮----
)  # Property is current_task
⋮----
)  # Property is call_context
self.assertEqual(request_context.related_tasks, [])  # Initialized to []
⋮----
async def test_build_populate_true_with_reference_task_ids(self)
⋮----
ref_task_id1 = 'ref_task1'
ref_task_id2 = 'ref_task2_missing'
ref_task_id3 = 'ref_task3'
mock_ref_task1 = create_sample_task(task_id=ref_task_id1)
mock_ref_task3 = create_sample_task(task_id=ref_task_id3)
# Configure task_store.get mock
# Note: AsyncMock side_effect needs to handle multiple calls if they have different args.
# A simple way is a list of return values, or a function.
async def get_side_effect(task_id)
⋮----
params = MessageSendParams(
server_call_context = ServerCallContext(user=UnauthenticatedUser())
⋮----
)  # Only non-None tasks
⋮----
async def test_build_populate_true_params_none(self)
async def test_build_populate_true_reference_ids_empty_or_none(self)
⋮----
# Test with empty list
params_empty_refs = MessageSendParams(
request_context_empty = await builder.build(
⋮----
)  # Should be [] if list is empty
⋮----
self.mock_task_store.get.reset_mock()  # Reset for next call
# Test with referenceTaskIds=None (Pydantic model might default it to empty list or handle it)
# create_sample_message defaults to [] if None is passed, so this tests the same as above.
# To explicitly test None in Message, we'd have to bypass Pydantic default or modify helper.
# For now, this covers the "no IDs to process" case.
msg_with_no_refs = Message(
params_none_refs = MessageSendParams(message=msg_with_no_refs)
request_context_none = await builder.build(
⋮----
async def test_build_populate_true_task_store_none(self)
⋮----
# This scenario might be prevented by constructor logic if should_populate_referred_tasks is True,
# but testing defensively. The builder might allow task_store=None if it's set post-init,
# or if constructor logic changes. Current SimpleRequestContextBuilder takes it at init.
# If task_store is None, it should not attempt to call get.
⋮----
task_store=None,  # Explicitly None
⋮----
# Expect related_tasks to be an empty list as task_store is None
⋮----
# No mock_task_store to check calls on, this test is mostly for graceful handling.
async def test_build_populate_false_with_reference_task_ids(self)

================
File: tests/server/apps/jsonrpc/test_jsonrpc_app.py
================
# Attempt to import StarletteBaseUser, fallback to MagicMock if not available
⋮----
StarletteBaseUser = MagicMock()  # type: ignore
⋮----
JSONRPCApplication,  # Still needed for JSONRPCApplication default constructor arg
⋮----
RequestHandler,  # For mock spec
⋮----
from a2a.types import AgentCard  # For mock spec
# --- StarletteUserProxy Tests ---
class TestStarletteUserProxy
⋮----
def test_starlette_user_proxy_is_authenticated_true(self)
⋮----
starlette_user_mock = MagicMock(spec=StarletteBaseUser)
⋮----
proxy = StarletteUserProxy(starlette_user_mock)
⋮----
def test_starlette_user_proxy_is_authenticated_false(self)
def test_starlette_user_proxy_user_name(self)
def test_starlette_user_proxy_user_name_raises_attribute_error(self)
⋮----
"""
        Tests that if the underlying starlette user object is missing the
        display_name attribute, the proxy currently raises an AttributeError.
        """
⋮----
# Ensure display_name is not present on the mock to trigger AttributeError
⋮----
_ = proxy.user_name
# --- JSONRPCApplication Tests (Selected) ---
class TestJSONRPCApplicationSetup:  # Renamed to avoid conflict
⋮----
):  # Renamed test
mock_handler = MagicMock(spec=RequestHandler)
# Mock agent_card with essential attributes accessed in JSONRPCApplication.__init__
mock_agent_card = MagicMock(spec=AgentCard)
# Ensure 'url' attribute exists on the mock_agent_card, as it's accessed in __init__
⋮----
# Ensure 'supportsAuthenticatedExtendedCard' attribute exists
⋮----
# This will fail at definition time if an abstract method is not implemented
⋮----
class IncompleteJSONRPCApp(JSONRPCApplication)
⋮----
# Intentionally not implementing 'build'
def some_other_method(self)

================
File: tests/server/apps/jsonrpc/test_serialization.py
================
@pytest.fixture
def agent_card_with_api_key()
⋮----
"""Provides an AgentCard with an APIKeySecurityScheme for testing serialization."""
# This data uses the alias 'in', which is correct for creating the model.
api_key_scheme_data = {
api_key_scheme = APIKeySecurityScheme.model_validate(api_key_scheme_data)
agent_card = AgentCard(
⋮----
"""
    Tests that the A2AStarletteApplication endpoint correctly serializes aliased fields.
    This verifies the fix for `APIKeySecurityScheme.in_` being serialized as `in_` instead of `in`.
    """
handler = mock.AsyncMock()
app_instance = A2AStarletteApplication(agent_card_with_api_key, handler)
client = TestClient(app_instance.build())
response = client.get('/.well-known/agent.json')
⋮----
response_data = response.json()
security_scheme_json = response_data['securitySchemes']['api_key_auth']
⋮----
parsed_card = AgentCard.model_validate(response_data)
parsed_scheme_wrapper = parsed_card.securitySchemes['api_key_auth']
⋮----
"""
    Tests that the A2AFastAPIApplication endpoint correctly serializes aliased fields.
    This verifies the fix for `APIKeySecurityScheme.in_` being serialized as `in_` instead of `in`.
    """
⋮----
app_instance = A2AFastAPIApplication(agent_card_with_api_key, handler)

================
File: tests/server/events/test_event_consumer.py
================
MINIMAL_TASK: dict[str, Any] = {
MESSAGE_PAYLOAD: dict[str, Any] = {
⋮----
@pytest.fixture
def mock_event_queue()
⋮----
@pytest.fixture
def event_consumer(mock_event_queue: EventQueue)
def test_init_logs_debug_message(mock_event_queue: EventQueue)
⋮----
"""Test that __init__ logs a debug message."""
# Patch the logger instance within the module where EventConsumer is defined
⋮----
EventConsumer(queue=mock_event_queue)  # Instantiate to trigger __init__
⋮----
task_event = Task(**MINIMAL_TASK)
⋮----
result = await event_consumer.consume_one()
⋮----
message_event = Message(**MESSAGE_PAYLOAD)
⋮----
error_event = A2AError(InternalError())
⋮----
error_event = JSONRPCError(code=123, message='Some Error')
⋮----
events: list[Any] = [
cursor = 0
async def mock_dequeue() -> Any
⋮----
event = events[cursor]
⋮----
consumed_events: list[Any] = []
⋮----
events = [
⋮----
# Upon first Message the stream is closed.
⋮----
"""Test that consume_all raises an exception if _exception is set."""
sample_exception = RuntimeError('Simulated agent error')
⋮----
pass  # Should not reach here
⋮----
"""Test consume_all stops if QueueClosed is raised and queue.is_closed() is True."""
# Simulate the queue raising QueueClosed (which is asyncio.QueueEmpty or QueueShutdown)
⋮----
# Simulate the queue confirming it's closed
⋮----
consumed_events = []
⋮----
consumed_events.append(event)  # Should not happen
⋮----
)  # No events should be consumed as it breaks on QueueClosed
mock_event_queue.dequeue_event.assert_called_once()  # Should attempt to dequeue once
mock_event_queue.is_closed.assert_called_once()  # Should check if closed
⋮----
"""Test that QueueClosed with is_closed=False allows loop to continue via timeout."""
payload = MESSAGE_PAYLOAD.copy()
⋮----
final_event = Message(**payload)
# Setup dequeue_event behavior:
# 1. Raise QueueClosed (e.g., asyncio.QueueEmpty)
# 2. Return the final_event
# 3. Raise QueueClosed again (to terminate after final_event)
dequeue_effects = [
⋮----
# Setup is_closed behavior:
# 1. False when QueueClosed is first raised (so loop doesn't break)
# 2. True after final_event is processed and QueueClosed is raised again
is_closed_effects = [False, True]
⋮----
# Patch asyncio.wait_for used inside consume_all
# The goal is that the first QueueClosed leads to a TimeoutError inside consume_all,
# the loop continues, and then the final_event is fetched.
# To reliably test the timeout behavior within consume_all, we adjust the consumer's
# internal timeout to be very short for the test.
⋮----
# Dequeue attempts:
# 1. Raises QueueClosed (is_closed=False, leads to TimeoutError, loop continues)
# 2. Returns final_event (which is a Message, causing consume_all to break)
⋮----
)  # Only two calls needed
# is_closed calls:
# 1. After first QueueClosed (returns False)
# The second QueueClosed is not reached because Message breaks the loop.
⋮----
def test_agent_task_callback_sets_exception(event_consumer: EventConsumer)
⋮----
"""Test that agent_task_callback sets _exception if the task had one."""
mock_task = MagicMock(spec=asyncio.Task)
sample_exception = ValueError('Task failed')
⋮----
# mock_task.exception.assert_called_once() # Removing this, as exception() might be called internally by the check
def test_agent_task_callback_no_exception(event_consumer: EventConsumer)
⋮----
"""Test that agent_task_callback does nothing if the task has no exception."""
⋮----
mock_task.exception.return_value = None  # No exception
⋮----
assert event_consumer._exception is None  # Should remain None

================
File: tests/server/events/test_event_queue.py
================
MINIMAL_TASK: dict[str, Any] = {
MESSAGE_PAYLOAD: dict[str, Any] = {
⋮----
@pytest.fixture
def event_queue() -> EventQueue
def test_constructor_default_max_queue_size()
⋮----
"""Test that the queue is created with the default max size."""
eq = EventQueue()
⋮----
def test_constructor_max_queue_size()
⋮----
"""Test that the asyncio.Queue is created with the specified max_queue_size."""
custom_size = 123
eq = EventQueue(max_queue_size=custom_size)
⋮----
def test_constructor_invalid_max_queue_size()
⋮----
"""Test that a ValueError is raised for non-positive max_queue_size."""
⋮----
@pytest.mark.asyncio
async def test_enqueue_and_dequeue_event(event_queue: EventQueue) -> None
⋮----
"""Test that an event can be enqueued and dequeued."""
event = Message(**MESSAGE_PAYLOAD)
⋮----
dequeued_event = await event_queue.dequeue_event()
⋮----
@pytest.mark.asyncio
async def test_dequeue_event_no_wait(event_queue: EventQueue) -> None
⋮----
"""Test dequeue_event with no_wait=True."""
event = Task(**MINIMAL_TASK)
⋮----
dequeued_event = await event_queue.dequeue_event(no_wait=True)
⋮----
"""Test dequeue_event with no_wait=True when the queue is empty."""
⋮----
@pytest.mark.asyncio
async def test_dequeue_event_wait(event_queue: EventQueue) -> None
⋮----
"""Test dequeue_event with the default wait behavior."""
event = TaskStatusUpdateEvent(
⋮----
@pytest.mark.asyncio
async def test_task_done(event_queue: EventQueue) -> None
⋮----
"""Test the task_done method."""
event = TaskArtifactUpdateEvent(
⋮----
_ = await event_queue.dequeue_event()
⋮----
"""Test enqueuing different types of events."""
events: list[Any] = [
⋮----
"""Test that events are enqueued to tapped child queues."""
child_queue1 = event_queue.tap()
child_queue2 = event_queue.tap()
event1 = Message(**MESSAGE_PAYLOAD)
event2 = Task(**MINIMAL_TASK)
⋮----
# Check parent queue
⋮----
# Check child queue 1
⋮----
# Check child queue 2
⋮----
@pytest.mark.asyncio
async def test_enqueue_event_when_closed(event_queue: EventQueue) -> None
⋮----
"""Test that no event is enqueued if the parent queue is closed."""
await event_queue.close()  # Close the queue first
⋮----
# Attempt to enqueue, should do nothing or log a warning as per implementation
⋮----
# Verify the queue is still empty
⋮----
# Also verify child queues are not affected directly by parent's enqueue attempt when closed
# (though they would be closed too by propagation)
child_queue = (
⋮----
)  # Tap after close might be weird, but let's see
# The current implementation would add it to _children
# and then child.close() would be called.
# A more robust test for child propagation is in test_close_propagates
⋮----
)  # ensure child is also seen as closed for this test's purpose
⋮----
"""Test dequeue_event raises QueueEmpty when closed, empty, and no_wait=True."""
⋮----
# Ensure queue is actually empty (e.g. by trying a non-blocking get on internal queue)
⋮----
"""Test dequeue_event raises QueueEmpty eventually when closed, empty, and no_wait=False."""
⋮----
):  # Should still raise QueueEmpty as per current implementation
event_queue.queue.get_nowait()  # verify internal queue is empty
# This test is tricky because await event_queue.dequeue_event() would hang if not for the close check.
# The current implementation's dequeue_event checks `is_closed` first.
# If closed and empty, it raises QueueEmpty immediately.
# The "waits_then_raises" scenario described in the subtask implies the `get()` might wait.
# However, the current code:
# async with self._lock:
#     if self._is_closed and self.queue.empty():
#         logger.warning('Queue is closed. Event will not be dequeued.')
#         raise asyncio.QueueEmpty('Queue is closed.')
# event = await self.queue.get() -> this line is not reached if closed and empty.
# So, for the current implementation, it will raise QueueEmpty immediately.
⋮----
# If the implementation were to change to allow `await self.queue.get()`
# to be called even when closed (to drain it), then a timeout test would be needed.
# For now, testing the current behavior.
# Example of a timeout test if it were to wait:
# with pytest.raises(asyncio.TimeoutError): # Or QueueEmpty if that's what join/shutdown causes get() to raise
#     await asyncio.wait_for(event_queue.dequeue_event(no_wait=False), timeout=0.01)
⋮----
@pytest.mark.asyncio
async def test_tap_creates_child_queue(event_queue: EventQueue) -> None
⋮----
"""Test that tap creates a new EventQueue and adds it to children."""
initial_children_count = len(event_queue._children)
child_queue = event_queue.tap()
⋮----
assert child_queue != event_queue  # Ensure it's a new instance
⋮----
# Test that the new child queue has the default max size (or specific if tap could configure it)
⋮----
)  # To monitor calls to asyncio.wait for older Python versions
⋮----
)  # To monitor calls to asyncio.create_task for older Python versions
⋮----
"""Test close behavior on Python < 3.13 (using queue.join)."""
with patch('sys.version_info', (3, 12, 0)):  # Simulate older Python
# Mock queue.join as it's called in older versions
⋮----
event_queue.queue.join.assert_called_once()  # specific to <3.13
mock_create_task.assert_called_once()  # create_task for join
mock_asyncio_wait.assert_called_once()  # wait for join
⋮----
"""Test close behavior on Python >= 3.13 (using queue.shutdown)."""
with patch('sys.version_info', (3, 13, 0)):  # Simulate Python 3.13+
# Mock queue.shutdown as it's called in newer versions
event_queue.queue.shutdown = MagicMock()  # shutdown is not async
⋮----
event_queue.queue.shutdown.assert_called_once()  # specific to >=3.13
⋮----
@pytest.mark.asyncio
async def test_close_propagates_to_children(event_queue: EventQueue) -> None
⋮----
"""Test that close() is called on all child queues."""
⋮----
# Mock the close method of children to verify they are called
⋮----
@pytest.mark.asyncio
async def test_close_idempotent(event_queue: EventQueue) -> None
⋮----
"""Test that calling close() multiple times doesn't cause errors and only acts once."""
# Mock the internal queue's join or shutdown to see how many times it's effectively called
⋮----
):  # Test with older version logic first
⋮----
event_queue.queue.join.assert_called_once()  # Called first time
# Call close again
⋮----
event_queue.queue.join.assert_called_once()  # Still only called once
# Reset for new Python version test
event_queue_new = EventQueue()  # New queue for fresh state
with patch('sys.version_info', (3, 13, 0)):  # Test with newer version logic
⋮----
event_queue_new.queue.shutdown.assert_called_once()  # Still only called once
⋮----
@pytest.mark.asyncio
async def test_is_closed_reflects_state(event_queue: EventQueue) -> None
⋮----
"""Test that is_closed() returns the correct state before and after closing."""
assert event_queue.is_closed() is False  # Initially open
⋮----
assert event_queue.is_closed() is True  # Closed after calling close()

================
File: tests/server/events/test_inmemory_queue_manager.py
================
class TestInMemoryQueueManager
⋮----
@pytest.fixture
    def queue_manager(self)
⋮----
"""Fixture to create a fresh InMemoryQueueManager for each test."""
⋮----
@pytest.fixture
    def event_queue(self)
⋮----
"""Fixture to create a mock EventQueue."""
queue = MagicMock(spec=EventQueue)
# Mock the tap method to return itself
⋮----
@pytest.mark.asyncio
    async def test_init(self, queue_manager)
⋮----
"""Test that the InMemoryQueueManager initializes with empty task queue and a lock."""
⋮----
@pytest.mark.asyncio
    async def test_add_new_queue(self, queue_manager, event_queue)
⋮----
"""Test adding a new queue to the manager."""
task_id = 'test_task_id'
⋮----
@pytest.mark.asyncio
    async def test_add_existing_queue(self, queue_manager, event_queue)
⋮----
"""Test adding a queue with an existing task_id raises TaskQueueExists."""
⋮----
@pytest.mark.asyncio
    async def test_get_existing_queue(self, queue_manager, event_queue)
⋮----
"""Test getting an existing queue returns the queue."""
⋮----
result = await queue_manager.get(task_id)
⋮----
@pytest.mark.asyncio
    async def test_get_nonexistent_queue(self, queue_manager)
⋮----
"""Test getting a nonexistent queue returns None."""
result = await queue_manager.get('nonexistent_task_id')
⋮----
@pytest.mark.asyncio
    async def test_tap_existing_queue(self, queue_manager, event_queue)
⋮----
"""Test tapping an existing queue returns the tapped queue."""
⋮----
result = await queue_manager.tap(task_id)
⋮----
@pytest.mark.asyncio
    async def test_tap_nonexistent_queue(self, queue_manager)
⋮----
"""Test tapping a nonexistent queue returns None."""
result = await queue_manager.tap('nonexistent_task_id')
⋮----
@pytest.mark.asyncio
    async def test_close_existing_queue(self, queue_manager, event_queue)
⋮----
"""Test closing an existing queue removes it from the manager."""
⋮----
@pytest.mark.asyncio
    async def test_close_nonexistent_queue(self, queue_manager)
⋮----
"""Test closing a nonexistent queue raises NoTaskQueue."""
⋮----
@pytest.mark.asyncio
    async def test_create_or_tap_new_queue(self, queue_manager)
⋮----
"""Test create_or_tap with a new task_id creates and returns a new queue."""
⋮----
result = await queue_manager.create_or_tap(task_id)
⋮----
"""Test create_or_tap with an existing task_id taps and returns the existing queue."""
⋮----
@pytest.mark.asyncio
    async def test_concurrency(self, queue_manager)
⋮----
"""Test concurrent access to the queue manager."""
async def add_task(task_id)
⋮----
queue = EventQueue()
⋮----
async def get_task(task_id)
# Create 10 different task IDs
task_ids = [f'task_{i}' for i in range(10)]
# Add tasks concurrently
add_tasks = [add_task(task_id) for task_id in task_ids]
added_task_ids = await asyncio.gather(*add_tasks)
# Verify all tasks were added
⋮----
# Get tasks concurrently
get_tasks = [get_task(task_id) for task_id in task_ids]
queues = await asyncio.gather(*get_tasks)
# Verify all queues are not None
⋮----
# Verify all tasks are in the manager

================
File: tests/server/request_handlers/test_default_request_handler.py
================
class DummyAgentExecutor(AgentExecutor)
⋮----
async def execute(self, context: RequestContext, event_queue: EventQueue)
⋮----
task_updater = TaskUpdater(
⋮----
parts = [Part(root=TextPart(text=f'Event {i}'))]
⋮----
# Stop processing when the event loop is closed
⋮----
async def _run(self)
⋮----
for i in range(1_000_000):  # Simulate a long-running stream
⋮----
async def cancel(self, context: RequestContext, event_queue: EventQueue)
# Helper to create a simple task for tests
⋮----
# Helper to create ServerCallContext
def create_server_call_context() -> ServerCallContext
⋮----
# Assuming UnauthenticatedUser is available or can be imported
⋮----
def test_init_default_dependencies()
⋮----
"""Test that default dependencies are created if not provided."""
agent_executor = DummyAgentExecutor()
task_store = InMemoryTaskStore()
handler = DefaultRequestHandler(
⋮----
@pytest.mark.asyncio
async def test_on_get_task_not_found()
⋮----
"""Test on_get_task when task_store.get returns None."""
mock_task_store = AsyncMock(spec=TaskStore)
⋮----
request_handler = DefaultRequestHandler(
params = TaskQueryParams(id='non_existent_task')
from a2a.utils.errors import ServerError  # Local import for ServerError
⋮----
@pytest.mark.asyncio
async def test_on_cancel_task_task_not_found()
⋮----
"""Test on_cancel_task when the task is not found."""
⋮----
params = TaskIdParams(id='task_not_found_for_cancel')
from a2a.utils.errors import ServerError  # Local import
⋮----
@pytest.mark.asyncio
async def test_on_cancel_task_queue_tap_returns_none()
⋮----
"""Test on_cancel_task when queue_manager.tap returns None."""
⋮----
sample_task = create_sample_task(task_id='tap_none_task')
⋮----
mock_queue_manager = AsyncMock(spec=QueueManager)
⋮----
None  # Simulate queue not found / tap returns None
⋮----
mock_agent_executor = AsyncMock(
⋮----
)  # Use AsyncMock for agent_executor
# Mock ResultAggregator and its consume_all method
mock_result_aggregator_instance = AsyncMock(spec=ResultAggregator)
⋮----
status_state=TaskState.canceled,  # Expected final state
⋮----
params = TaskIdParams(id='tap_none_task')
result_task = await request_handler.on_cancel_task(
⋮----
# agent_executor.cancel should be called with a new EventQueue if tap returned None
⋮----
# Verify the EventQueue passed to cancel was a new one
call_args_list = mock_agent_executor.cancel.call_args_list
⋮----
)  # args[1] is the event_queue argument
⋮----
@pytest.mark.asyncio
async def test_on_cancel_task_cancels_running_agent()
⋮----
"""Test on_cancel_task cancels a running agent task."""
task_id = 'running_agent_task_to_cancel'
sample_task = create_sample_task(task_id=task_id)
⋮----
mock_event_queue = AsyncMock(spec=EventQueue)
⋮----
mock_agent_executor = AsyncMock(spec=AgentExecutor)
# Mock ResultAggregator
⋮----
# Simulate a running agent task
mock_producer_task = AsyncMock(spec=asyncio.Task)
⋮----
params = TaskIdParams(id=task_id)
⋮----
@pytest.mark.asyncio
async def test_on_cancel_task_invalid_result_type()
⋮----
"""Test on_cancel_task when result_aggregator returns a Message instead of a Task."""
task_id = 'cancel_invalid_result_task'
⋮----
# Mock ResultAggregator to return a Message
⋮----
@pytest.mark.asyncio
async def test_on_message_send_with_push_notification()
⋮----
"""Test on_message_send sets push notification info if provided."""
⋮----
mock_push_notifier = AsyncMock(spec=PushNotifier)
⋮----
mock_request_context_builder = AsyncMock(spec=RequestContextBuilder)
task_id = 'push_task_1'
context_id = 'push_ctx_1'
sample_initial_task = create_sample_task(
# TaskManager will be created inside on_message_send.
# We need to mock task_store.get to return None initially for TaskManager to create a new task.
# Then, TaskManager.update_with_message will be called.
# For simplicity in this unit test, let's assume TaskManager correctly sets up the task
# and the task object (with IDs) is available for _request_context_builder.build
⋮----
None  # Simulate new task scenario for TaskManager
⋮----
# Mock _request_context_builder.build to return a context with the generated/confirmed IDs
mock_request_context = MagicMock(spec=RequestContext)
⋮----
push_config = PushNotificationConfig(url='http://callback.com/push')
message_config = MessageSendConfiguration(
⋮----
acceptedOutputModes=['text/plain'],  # Added required field
⋮----
params = MessageSendParams(
# Mock ResultAggregator and its consume_and_break_on_interrupt
⋮----
final_task_result = create_sample_task(
⋮----
# Mock the current_result property to return the final task result
async def get_current_result()
# Configure the 'current_result' property on the type of the mock instance
⋮----
):  # Ensure task object is returned
⋮----
# Other assertions for full flow if needed (e.g., agent execution)
⋮----
@pytest.mark.asyncio
async def test_on_message_send_no_result_from_aggregator()
⋮----
"""Test on_message_send when aggregator returns (None, False)."""
⋮----
task_id = 'no_result_task'
# Mock _request_context_builder.build
⋮----
):  # TaskManager.get_task for initial task
⋮----
@pytest.mark.asyncio
async def test_on_message_send_task_id_mismatch()
⋮----
"""Test on_message_send when result task ID doesn't match request context task ID."""
⋮----
context_task_id = 'context_task_id_1'
result_task_id = 'DIFFERENT_task_id_1'  # Mismatch
⋮----
mismatched_task = create_sample_task(task_id=result_task_id)
⋮----
@pytest.mark.asyncio
async def test_on_message_send_interrupted_flow()
⋮----
"""Test on_message_send when flow is interrupted (e.g., auth_required)."""
⋮----
task_id = 'interrupted_task_1'
⋮----
interrupt_task_result = create_sample_task(
⋮----
)  # Interrupted = True
# Patch asyncio.create_task to verify _cleanup_producer is scheduled
⋮----
result = await request_handler.on_message_send(
⋮----
)  # First for _run_event_stream, second for _cleanup_producer
# Check that the second call to create_task was for _cleanup_producer
found_cleanup_call = False
⋮----
created_coro = call_args_tuple[0][0]
⋮----
found_cleanup_call = True
⋮----
@pytest.mark.asyncio
async def test_on_message_send_stream_with_push_notification()
⋮----
"""Test on_message_send_stream sets and uses push notification info."""
⋮----
task_id = 'stream_push_task_1'
context_id = 'stream_push_ctx_1'
# Initial task state for TaskManager
initial_task_for_tm = create_sample_task(
# Task state for RequestContext
task_for_rc = create_sample_task(
⋮----
)  # Example state after message update
mock_task_store.get.return_value = None  # New task for TaskManager
⋮----
push_config = PushNotificationConfig(url='http://callback.stream.com/push')
⋮----
# Mock ResultAggregator and its consume_and_emit
mock_result_aggregator_instance = MagicMock(
⋮----
)  # Use MagicMock for easier property mocking
# Events to be yielded by consume_and_emit
event1_task_update = create_sample_task(
event2_final_task = create_sample_task(
async def event_stream_gen()
# consume_and_emit is called by `async for ... in result_aggregator.consume_and_emit(consumer)`
# This means result_aggregator.consume_and_emit(consumer) must directly return an async iterable.
# If consume_and_emit is an async method, this is problematic in the product code.
# For the test, we make the mock of consume_and_emit a synchronous method
# that returns the async generator object.
def sync_get_event_stream_gen(*args, **kwargs)
⋮----
# Mock current_result property to return appropriate awaitables
# Coroutines that will be returned by successive accesses to current_result
async def current_result_coro1()
async def current_result_coro2()
# Use unittest.mock.PropertyMock for async property
# We need to patch 'ResultAggregator.current_result' when this instance is used.
# This is complex because ResultAggregator is instantiated inside the handler.
# Easier: If mock_result_aggregator_instance is a MagicMock, we can assign a callable.
# This part is tricky. Let's assume current_result is an async method for easier mocking first.
# If it's truly a property, the mocking is harder with instance mocks.
# Let's adjust the mock_result_aggregator_instance.current_result to be an AsyncMock directly
# This means the code would call `await result_aggregator.current_result()`
# But the actual code is `await result_aggregator.current_result`
# This implies `result_aggregator.current_result` IS an awaitable.
# So, we can mock it with a side_effect that returns awaitables (coroutines).
# Create simple awaitables (coroutines) for side_effect
async def get_event1()
async def get_event2()
# Make the current_result attribute of the mock instance itself an awaitable
# This still means current_result is not callable.
# For an async property, the mock needs to have current_result as a non-AsyncMock attribute
# that is itself an awaitable.
# Let's try to mock the property at the type level for ResultAggregator temporarily
# This is not ideal as it affects all instances.
# Alternative: Configure the AsyncMock for current_result to return a coroutine
# when it's awaited. This is not directly supported by AsyncMock for property access.
# Simplest for now: Assume `current_result` attribute of the mocked `ResultAggregator` instance
# can be sequentially awaited if it's a list of awaitables that a test runner can handle.
# This is likely to fail again but will clarify the exact point of await.
# The error "TypeError: object AsyncMock can't be used in 'await' expression" means
# `mock_result_aggregator_instance.current_result` is an AsyncMock, and that's what's awaited.
# This AsyncMock needs to have a __await__ method.
# Let's make the side_effect of the AsyncMock `current_result` provide the values.
# This assumes that `await mock.property` somehow triggers a call to the mock.
# This is not how AsyncMock works.
# The code is `await result_aggregator.current_result`.
# `result_aggregator` is an instance of `ResultAggregator`.
# `current_result` is an async property.
# So `result_aggregator.current_result` evaluates to a coroutine.
# We need `mock_result_aggregator_instance.current_result` to be a coroutine,
# or a list of coroutines if accessed multiple times.
# This is best done by mocking the property itself.
# Let's assume it's called twice.
# We will patch ResultAggregator to be our mock_result_aggregator_instance
# Then, we need to control what its `current_result` property returns.
# We can use a PropertyMock for this, attached to the type of mock_result_aggregator_instance.
# For this specific test, let's make current_result a simple async def method on the mock instance
# This means we are slightly diverging from the "property" nature just for this mock.
# Mock current_result property to return appropriate awaitables (coroutines) sequentially.
async def get_event1_coro()
async def get_event2_coro()
⋮----
# This makes accessing `instance.current_result` call the side_effect function,
# which then cycles through our list of coroutines.
# We need a new PropertyMock for each instance, or patch the class.
# Since mock_result_aggregator_instance is already created, we attach to its type.
# This can be tricky. A more direct way is to ensure the instance's attribute `current_result`
# behaves as desired. If `mock_result_aggregator_instance` is a `MagicMock`, its attributes are also mocks.
# Let's make `current_result` a MagicMock whose side_effect returns the coroutines.
# This means when `result_aggregator.current_result` is accessed, this mock is "called".
# This isn't quite right for a property. A property isn't "called" on access.
# Correct approach for mocking an async property on an instance mock:
# Set the attribute `current_result` on the instance `mock_result_aggregator_instance`
# to be a `PropertyMock` if we were patching the class.
# Since we have the instance, we can try to replace its `current_result` attribute.
# The instance `mock_result_aggregator_instance` is a `MagicMock`.
# We can make `mock_result_aggregator_instance.current_result` a `PropertyMock`
# that returns a coroutine. For multiple calls, `side_effect` on `PropertyMock` is a list of return_values.
# Create a PropertyMock that will cycle through coroutines
# This requires Python 3.8+ for PropertyMock to be directly usable with side_effect list for properties.
# For older versions or for clarity with async properties, directly mocking the attribute
# to be a series of awaitables is hard.
# The easiest is to ensure `current_result` is an AsyncMock that returns the values.
# The product code `await result_aggregator.current_result` means `current_result` must be an awaitable.
# Let's make current_result an AsyncMock whose __call__ returns the sequence.
# Mock current_result as an async property
# Create coroutines that will be the "result" of awaiting the property
async def get_current_result_coro1()
async def get_current_result_coro2()
# Configure the 'current_result' property on the mock_result_aggregator_instance
# using PropertyMock attached to its type. This makes instance.current_result return
# items from side_effect sequentially on each access.
# Since current_result is an async property, these items should be coroutines.
# We need to ensure that mock_result_aggregator_instance itself is the one patched.
# The patch for ResultAggregator returns this instance.
# So, we configure PropertyMock on the type of this specific mock instance.
# This is slightly unusual; typically PropertyMock is used when patching a class.
# A more straightforward approach for an instance is if its type is already a mock.
# As mock_result_aggregator_instance is a MagicMock, we can configure its 'current_result'
# attribute to be a PropertyMock.
# Let's directly assign a PropertyMock to the type of the instance for `current_result`
# This ensures that when `instance.current_result` is accessed, the PropertyMock's logic is triggered.
# However, PropertyMock is usually used with `patch.object` or by setting it on the class.
#
# A simpler way for MagicMock instance:
# `mock_result_aggregator_instance.current_result` is already a MagicMock (or AsyncMock if spec'd).
# We need to make it return a coroutine upon access.
# The most direct way to mock an async property on a MagicMock instance
# such that it returns a sequence of awaitables:
async def side_effect_current_result()
# Create an async generator from the side effect
current_result_gen = side_effect_current_result()
# Make current_result return the next item from this generator (wrapped in a coroutine)
# each time it's accessed.
async def get_next_current_result()
⋮----
# Handle case where it's awaited more times than values provided
return None  # Or raise an error
# Since current_result is a property, accessing it should return a coroutine.
# We can achieve this by making mock_result_aggregator_instance.current_result
# a MagicMock whose side_effect returns these coroutines.
# This is still tricky because it's a property access.
# Let's use the PropertyMock on the class being mocked via the patch.
# Setup for consume_and_emit
def sync_get_event_stream_gen_for_prop_test(*args, **kwargs)
⋮----
# Configure current_result on the type of the mock_result_aggregator_instance
# This makes it behave like a property that returns items from side_effect on access.
⋮----
# Consume the stream
⋮----
# Assertions
# 1. set_info called once at the beginning if task exists (or after task is created from message)
⋮----
# 2. send_notification called for each task event yielded by aggregator
⋮----
@pytest.mark.asyncio
async def test_on_message_send_stream_task_id_mismatch()
⋮----
"""Test on_message_send_stream raises error if yielded task ID mismatches."""
⋮----
)  # Only need a basic mock
⋮----
context_task_id = 'stream_task_id_ctx'
mismatched_task_id = 'DIFFERENT_stream_task_id'
⋮----
mismatched_task_event = create_sample_task(
⋮----
)  # Task with different ID
async def event_stream_gen_mismatch()
⋮----
pass  # Consume the stream to trigger the error
⋮----
@pytest.mark.asyncio
async def test_cleanup_producer_task_id_not_in_running_agents()
⋮----
"""Test _cleanup_producer when task_id is not in _running_agents (e.g., already cleaned up)."""
⋮----
task_id = 'task_already_cleaned'
# Create a real, completed asyncio.Task for the test
async def dummy_coro_for_task()
mock_producer_task = asyncio.create_task(dummy_coro_for_task())
⋮----
)  # Ensure the task has a chance to complete/be scheduled
# Call cleanup directly, ensuring task_id is NOT in _running_agents
# This simulates a race condition or double cleanup.
⋮----
del request_handler._running_agents[task_id]  # Ensure it's not there
⋮----
# Verify queue_manager.close was still called
⋮----
# No error should be raised by pop if key is missing and default is None.
⋮----
@pytest.mark.asyncio
async def test_set_task_push_notification_config_no_notifier()
⋮----
"""Test on_set_task_push_notification_config when _push_notifier is None."""
⋮----
push_notifier=None,  # Explicitly None
⋮----
params = TaskPushNotificationConfig(
⋮----
@pytest.mark.asyncio
async def test_set_task_push_notification_config_task_not_found()
⋮----
"""Test on_set_task_push_notification_config when task is not found."""
⋮----
mock_task_store.get.return_value = None  # Task not found
⋮----
@pytest.mark.asyncio
async def test_get_task_push_notification_config_no_notifier()
⋮----
"""Test on_get_task_push_notification_config when _push_notifier is None."""
⋮----
params = TaskIdParams(id='task1')
⋮----
@pytest.mark.asyncio
async def test_get_task_push_notification_config_task_not_found()
⋮----
"""Test on_get_task_push_notification_config when task is not found."""
⋮----
params = TaskIdParams(id='non_existent_task')
⋮----
@pytest.mark.asyncio
async def test_get_task_push_notification_config_info_not_found()
⋮----
"""Test on_get_task_push_notification_config when push_notifier.get_info returns None."""
⋮----
sample_task = create_sample_task(task_id='task_info_not_found')
⋮----
mock_push_notifier.get_info.return_value = None  # Info not found
⋮----
params = TaskIdParams(id='task_info_not_found')
⋮----
)  # Current code raises InternalError
⋮----
@pytest.mark.asyncio
async def test_on_resubscribe_to_task_task_not_found()
⋮----
"""Test on_resubscribe_to_task when the task is not found."""
⋮----
params = TaskIdParams(id='resub_task_not_found')
⋮----
# Need to consume the async generator to trigger the error
⋮----
@pytest.mark.asyncio
async def test_on_resubscribe_to_task_queue_not_found()
⋮----
"""Test on_resubscribe_to_task when the queue is not found by queue_manager.tap."""
⋮----
sample_task = create_sample_task(task_id='resub_queue_not_found')
⋮----
mock_queue_manager.tap.return_value = None  # Queue not found
⋮----
params = TaskIdParams(id='resub_queue_not_found')
⋮----
)  # Should be TaskNotFoundError as per spec
⋮----
@pytest.mark.asyncio
async def test_on_message_send_stream()
⋮----
message_params = MessageSendParams(
async def consume_stream()
⋮----
events = []
⋮----
break  # Stop after a few events
⋮----
# Consume first 3 events from the stream and measure time
start = time.perf_counter()
events = await consume_stream()
elapsed = time.perf_counter() - start
# Assert we received events quickly
⋮----
texts = [p.root.text for e in events for p in e.status.message.parts]
⋮----
TERMINAL_TASK_STATES = {
⋮----
@pytest.mark.asyncio
@pytest.mark.parametrize('terminal_state', TERMINAL_TASK_STATES)
async def test_on_message_send_task_in_terminal_state(terminal_state)
⋮----
"""Test on_message_send when task is already in a terminal state."""
task_id = f'terminal_task_{terminal_state.value}'
terminal_task = create_sample_task(
⋮----
# The get method of TaskManager calls task_store.get.
# We mock TaskManager.get_task which is an async method.
# So we should patch that instead.
⋮----
# Patch the TaskManager's get_task method to return our terminal task
⋮----
@pytest.mark.asyncio
@pytest.mark.parametrize('terminal_state', TERMINAL_TASK_STATES)
async def test_on_message_send_stream_task_in_terminal_state(terminal_state)
⋮----
"""Test on_message_send_stream when task is already in a terminal state."""
task_id = f'terminal_stream_task_{terminal_state.value}'
⋮----
pass  # pragma: no cover
⋮----
@pytest.mark.asyncio
@pytest.mark.parametrize('terminal_state', TERMINAL_TASK_STATES)
async def test_on_resubscribe_to_task_in_terminal_state(terminal_state)
⋮----
"""Test on_resubscribe_to_task when task is in a terminal state."""
task_id = f'resub_terminal_task_{terminal_state.value}'
⋮----
pass  # pragma: no cover

================
File: tests/server/request_handlers/test_grpc_handler.py
================
# --- Fixtures ---
⋮----
@pytest.fixture
def mock_request_handler() -> AsyncMock
⋮----
@pytest.fixture
def mock_grpc_context() -> AsyncMock
⋮----
context = AsyncMock(spec=grpc.aio.ServicerContext)
⋮----
@pytest.fixture
def sample_agent_card() -> types.AgentCard
⋮----
# --- Test Cases ---
⋮----
"""Test successful SendMessage call."""
request_proto = a2a_pb2.SendMessageRequest(
response_model = types.Task(
⋮----
response = await grpc_handler.SendMessage(request_proto, mock_grpc_context)
⋮----
"""Test SendMessage call when handler raises a ServerError."""
request_proto = a2a_pb2.SendMessageRequest()
error = ServerError(error=types.InvalidParamsError(message='Bad params'))
⋮----
"""Test successful GetTask call."""
request_proto = a2a_pb2.GetTaskRequest(name='tasks/task-1')
⋮----
response = await grpc_handler.GetTask(request_proto, mock_grpc_context)
⋮----
"""Test GetTask call when task is not found."""
⋮----
"""Test CancelTask call when handler raises ServerError."""
request_proto = a2a_pb2.CancelTaskRequest(name='tasks/task-1')
error = ServerError(error=types.TaskNotCancelableError())
⋮----
"""Test successful SendStreamingMessage call."""
async def mock_stream()
⋮----
results = [
⋮----
"""Test GetAgentCard call."""
request_proto = a2a_pb2.GetAgentCardRequest()
response = await grpc_handler.GetAgentCard(request_proto, mock_grpc_context)
⋮----
request_proto = a2a_pb2.GetTaskRequest(name='tasks/any')

================
File: tests/server/request_handlers/test_jsonrpc_handler.py
================
MINIMAL_TASK: dict[str, Any] = {
MESSAGE_PAYLOAD: dict[str, Any] = {
class TestJSONRPCtHandler(unittest.async_case.IsolatedAsyncioTestCase)
⋮----
@pytest.fixture(autouse=True)
    def init_fixtures(self) -> None
async def test_on_get_task_success(self) -> None
⋮----
mock_agent_executor = AsyncMock(spec=AgentExecutor)
mock_task_store = AsyncMock(spec=TaskStore)
request_handler = DefaultRequestHandler(
call_context = ServerCallContext(state={'foo': 'bar'})
handler = JSONRPCHandler(self.mock_agent_card, request_handler)
task_id = 'test_task_id'
mock_task = Task(**MINIMAL_TASK)
⋮----
request = GetTaskRequest(id='1', params=TaskQueryParams(id=task_id))
response: GetTaskResponse = await handler.on_get_task(
⋮----
assert response.root.result == mock_task  # type: ignore
⋮----
async def test_on_get_task_not_found(self) -> None
⋮----
request = GetTaskRequest(
⋮----
assert response.root.error == TaskNotFoundError()  # type: ignore
async def test_on_cancel_task_success(self) -> None
⋮----
async def streaming_coro()
⋮----
request = CancelTaskRequest(id='1', params=TaskIdParams(id=task_id))
response = await handler.on_cancel_task(request, call_context)
⋮----
assert response.root.result == mock_task  # type: ignore
⋮----
async def test_on_cancel_task_not_supported(self) -> None
⋮----
assert response.root.error == UnsupportedOperationError()  # type: ignore
⋮----
async def test_on_cancel_task_not_found(self) -> None
⋮----
request = CancelTaskRequest(
response = await handler.on_cancel_task(request)
⋮----
request = SendMessageRequest(
response = await handler.on_message_send(request)
⋮----
async def test_on_message_error(self) -> None
⋮----
events: list[Any] = [
⋮----
request = SendStreamingMessageRequest(
response = handler.on_message_send_stream(request)
⋮----
collected_events: list[Any] = []
⋮----
mock_task = Task(**MINIMAL_TASK, history=[])
⋮----
collected_events = [item async for item in response]
⋮----
async def test_set_push_notification_success(self) -> None
⋮----
mock_push_notifier = AsyncMock(spec=PushNotifier)
⋮----
task_push_config = TaskPushNotificationConfig(
request = SetTaskPushNotificationConfigRequest(
response: SetTaskPushNotificationConfigResponse = (
⋮----
assert response.root.result == task_push_config  # type: ignore
⋮----
async def test_get_push_notification_success(self) -> None
⋮----
mock_httpx_client = AsyncMock(spec=httpx.AsyncClient)
push_notifier = InMemoryPushNotifier(httpx_client=mock_httpx_client)
⋮----
get_request: GetTaskPushNotificationConfigRequest = (
get_response: GetTaskPushNotificationConfigResponse = (
⋮----
assert get_response.root.result == task_push_config  # type: ignore
⋮----
calls = [
⋮----
mock_queue_manager = AsyncMock(spec=QueueManager)
⋮----
request = TaskResubscriptionRequest(
response = handler.on_resubscribe_to_task(request)
⋮----
async def test_on_resubscribe_no_existing_task_error(self) -> None
⋮----
"""Test that on_message_send_stream raises an error when streaming not supported."""
# Arrange
⋮----
# Create agent card with streaming capability disabled
⋮----
# Act & Assert
⋮----
# Should raise ServerError about streaming not supported
⋮----
async def test_push_notifications_not_supported_error(self) -> None
⋮----
"""Test that set_push_notification raises an error when push notifications not supported."""
⋮----
# Create agent card with push notifications capability disabled
⋮----
# Should raise ServerError about push notifications not supported
⋮----
async def test_on_get_push_notification_no_push_notifier(self) -> None
⋮----
"""Test get_push_notification with no push notifier configured."""
⋮----
# Create request handler without a push notifier
⋮----
# Act
get_request = GetTaskPushNotificationConfigRequest(
response = await handler.get_push_notification(get_request)
# Assert
⋮----
self.assertEqual(response.root.error, UnsupportedOperationError())  # type: ignore
async def test_on_set_push_notification_no_push_notifier(self) -> None
⋮----
"""Test set_push_notification with no push notifier configured."""
⋮----
response = await handler.set_push_notification(request)
⋮----
async def test_on_message_send_internal_error(self) -> None
⋮----
"""Test on_message_send with an internal error."""
⋮----
# Make the request handler raise an Internal error without specifying an error type
async def raise_server_error(*args, **kwargs) -> NoReturn
# Patch the method to raise an error
⋮----
# Act
⋮----
# Assert
⋮----
self.assertIsInstance(response.root.error, InternalError)  # type: ignore
async def test_on_message_stream_internal_error(self) -> None
⋮----
"""Test on_message_send_stream with an internal error."""
⋮----
async def raise_server_error(*args, **kwargs)
⋮----
yield  # Need this to make it an async generator
⋮----
# Get the single error response
responses = []
⋮----
async def test_default_request_handler_with_custom_components(self) -> None
⋮----
"""Test DefaultRequestHandler initialization with custom components."""
⋮----
mock_request_context_builder = AsyncMock(spec=RequestContextBuilder)
⋮----
handler = DefaultRequestHandler(
⋮----
async def test_on_message_send_error_handling(self) -> None
⋮----
"""Test error handling in on_message_send when consuming raises ServerError."""
⋮----
# Let task exist
⋮----
# Set up consume_and_break_on_interrupt to raise ServerError
async def consume_raises_error(*args, **kwargs) -> NoReturn
⋮----
async def test_on_message_send_task_id_mismatch(self) -> None
async def test_on_message_stream_task_id_mismatch(self) -> None
⋮----
events: list[Any] = [Task(**MINIMAL_TASK)]

================
File: tests/server/request_handlers/test_response_helpers.py
================
class TestResponseHelpers(unittest.TestCase)
⋮----
def test_build_error_response_with_a2a_error(self)
⋮----
request_id = 'req1'
specific_error = TaskNotFoundError()
a2a_error = A2AError(root=specific_error)  # Correctly wrap
response_wrapper = build_error_response(
⋮----
)  # build_error_response unwraps A2AError
def test_build_error_response_with_jsonrpc_error(self)
⋮----
request_id = 123
json_rpc_error = InvalidParamsError(
⋮----
)  # This is a specific error, not A2AError wrapped
⋮----
)  # No .root access for json_rpc_error
def test_build_error_response_with_a2a_wrapping_jsonrpc_error(self)
⋮----
request_id = 'req_wrap'
specific_jsonrpc_error = InvalidParamsError(message='Detail error')
a2a_error_wrapping = A2AError(
⋮----
)  # Correctly wrap
⋮----
def test_build_error_response_with_request_id_string(self)
⋮----
request_id = 'string_id_test'
# Pass an A2AError-wrapped specific error for consistency with how build_error_response handles A2AError
error = A2AError(root=TaskNotFoundError())
⋮----
def test_build_error_response_with_request_id_int(self)
⋮----
request_id = 456
⋮----
def test_build_error_response_with_request_id_none(self)
⋮----
request_id = None
⋮----
def _create_sample_task(self, task_id='task123', context_id='ctx456')
def test_prepare_response_object_successful_response(self)
⋮----
request_id = 'req_success'
task_result = self._create_sample_task()
response_wrapper = prepare_response_object(
⋮----
request_id = 'req_a2a_err'
⋮----
a2a_error_instance = A2AError(
⋮----
)  # Correctly wrapped A2AError
# This is what build_error_response (when called by prepare_response_object) will return
mock_wrapped_error_response = GetTaskResponse(
⋮----
response=a2a_error_instance,  # Pass the A2AError instance
⋮----
# prepare_response_object should identify A2AError and call build_error_response
⋮----
request_id = 789
# Use the base JSONRPCError class instance
json_rpc_base_error = JSONRPCError(
⋮----
response=json_rpc_base_error,  # Pass the JSONRPCError instance
⋮----
# prepare_response_object should identify JSONRPCError and call build_error_response
⋮----
request_id = 'req_specific_unexpected'
# Pass a specific error model (like TaskNotFoundError) directly, NOT wrapped in A2AError
# This should be treated as an "unexpected" type by prepare_response_object's current logic
specific_error_direct = TaskNotFoundError()
# This is the InvalidAgentResponseError that prepare_response_object will generate
generated_error_wrapper = A2AError(
# This is what build_error_response will be called with (the generated error)
# And this is what it will return (the generated error, wrapped in GetTaskResponse)
mock_final_wrapped_response = GetTaskResponse(
⋮----
response=specific_error_direct,  # Pass TaskNotFoundError() directly
⋮----
# Check that the error passed to build_error_response is the generated A2AError(InvalidAgentResponseError)
⋮----
def test_prepare_response_object_with_request_id_string(self)
⋮----
request_id = 'string_id_prep'
⋮----
def test_prepare_response_object_with_request_id_int(self)
⋮----
request_id = 101112
⋮----
def test_prepare_response_object_with_request_id_none(self)

================
File: tests/server/tasks/test_inmemory_push_notifier.py
================
# Suppress logging for cleaner test output, can be enabled for debugging
# logging.disable(logging.CRITICAL)
def create_sample_task(task_id='task123', status_state=TaskState.completed)
⋮----
class TestInMemoryPushNotifier(unittest.IsolatedAsyncioTestCase)
⋮----
def setUp(self)
⋮----
)  # Corrected argument name
def test_constructor_stores_client(self)
async def test_set_info_adds_new_config(self)
⋮----
task_id = 'task_new'
config = create_sample_push_config(url='http://new.url/callback')
⋮----
async def test_set_info_updates_existing_config(self)
⋮----
task_id = 'task_update'
initial_config = create_sample_push_config(
⋮----
updated_config = create_sample_push_config(
⋮----
async def test_get_info_existing_config(self)
⋮----
task_id = 'task_get_exist'
config = create_sample_push_config(url='http://get.this/callback')
⋮----
retrieved_config = await self.notifier.get_info(task_id)
⋮----
async def test_get_info_non_existent_config(self)
⋮----
task_id = 'task_get_non_exist'
⋮----
async def test_delete_info_existing_config(self)
⋮----
task_id = 'task_delete_exist'
config = create_sample_push_config(url='http://delete.this/callback')
⋮----
async def test_delete_info_non_existent_config(self)
⋮----
task_id = 'task_delete_non_exist'
# Ensure it doesn't raise an error
⋮----
)  # Should still not be there
async def test_send_notification_success(self)
⋮----
task_id = 'task_send_success'
task_data = create_sample_task(task_id=task_id)
config = create_sample_push_config(url='http://notify.me/here')
⋮----
# Mock the post call to simulate success
mock_response = AsyncMock(spec=httpx.Response)
⋮----
await self.notifier.send_notification(task_data)  # Pass only task_data
⋮----
)  # auth is not passed by current implementation
⋮----
async def test_send_notification_no_config(self)
⋮----
task_id = 'task_send_no_config'
⋮----
task_id = 'task_send_http_err'
⋮----
config = create_sample_push_config(url='http://notify.me/http_error')
⋮----
mock_response = MagicMock(
⋮----
)  # Use MagicMock for status_code attribute
⋮----
http_error = httpx.HTTPStatusError(
⋮----
# The method should catch the error and log it, not re-raise
⋮----
# Check that the error message contains the generic part and the specific exception string
⋮----
task_id = 'task_send_req_err'
⋮----
config = create_sample_push_config(url='http://notify.me/req_error')
⋮----
request_error = httpx.RequestError('Network issue', request=MagicMock())
⋮----
@patch('a2a.server.tasks.inmemory_push_notifier.logger')
    async def test_send_notification_with_auth(self, mock_logger: MagicMock)
⋮----
task_id = 'task_send_auth'
⋮----
auth_info = ('user', 'pass')
config = create_sample_push_config(url='http://notify.me/auth')
config.authentication = MagicMock()  # Mocking the structure for auth
config.authentication.schemes = ['basic']  # Assume basic for simplicity
⋮----
auth_info  # This might need to be a specific model
⋮----
# For now, let's assume it's a tuple for basic auth
# The actual PushNotificationAuthenticationInfo is more complex
# For this test, we'll simplify and assume InMemoryPushNotifier
# directly uses tuple for httpx's `auth` param if basic.
# A more accurate test would construct the real auth model.
# Given the current implementation of InMemoryPushNotifier,
# it only supports basic auth via tuple.

================
File: tests/server/tasks/test_inmemory_task_store.py
================
MINIMAL_TASK: dict[str, Any] = {
⋮----
@pytest.mark.asyncio
async def test_in_memory_task_store_save_and_get() -> None
⋮----
"""Test saving and retrieving a task from the in-memory store."""
store = InMemoryTaskStore()
task = Task(**MINIMAL_TASK)
⋮----
retrieved_task = await store.get(MINIMAL_TASK['id'])
⋮----
@pytest.mark.asyncio
async def test_in_memory_task_store_get_nonexistent() -> None
⋮----
"""Test retrieving a nonexistent task."""
⋮----
retrieved_task = await store.get('nonexistent')
⋮----
@pytest.mark.asyncio
async def test_in_memory_task_store_delete() -> None
⋮----
"""Test deleting a task from the store."""
⋮----
@pytest.mark.asyncio
async def test_in_memory_task_store_delete_nonexistent() -> None
⋮----
"""Test deleting a nonexistent task."""

================
File: tests/server/tasks/test_result_aggregator.py
================
# Helper to create a simple message
⋮----
# Helper to create a simple task
⋮----
# Helper to create a TaskStatusUpdateEvent
⋮----
final=False,  # Typically false unless it's the very last update
⋮----
class TestResultAggregator(unittest.IsolatedAsyncioTestCase)
⋮----
def setUp(self)
⋮----
# event_consumer is not passed to constructor
⋮----
def test_init_stores_task_manager(self)
⋮----
# event_consumer is also stored, can be tested if needed, but focus is on task_manager per req.
async def test_current_result_property_with_message_set(self)
⋮----
sample_message = create_sample_message(content='hola')
⋮----
async def test_current_result_property_with_message_none(self)
⋮----
expected_task = create_sample_task(task_id='task_from_tm')
⋮----
current_res = await self.aggregator.current_result
⋮----
async def test_consume_and_emit(self)
⋮----
event1 = create_sample_message(content='event one', msg_id='e1')
event2 = create_sample_task(
event3 = create_sample_status_update(
# Mock event_consumer.consume() to be an async generator
async def mock_consume_generator()
⋮----
# To store yielded events
yielded_events = []
⋮----
# Assert that all events were yielded
⋮----
# Assert that task_manager.process was called for each event
⋮----
async def test_consume_all_only_message_event(self)
⋮----
sample_message = create_sample_message(content='final message')
⋮----
result = await self.aggregator.consume_all(self.mock_event_consumer)
⋮----
self.mock_task_manager.process.assert_not_called()  # Process is not called if message is returned directly
self.mock_task_manager.get_task.assert_not_called()  # Should not be called if message is returned
async def test_consume_all_other_event_types(self)
⋮----
task_event = create_sample_task(task_id='task_other_event')
status_update_event = create_sample_status_update(
final_task_state = create_sample_task(
⋮----
async def test_consume_all_empty_stream(self)
⋮----
empty_task_state = create_sample_task(task_id='empty_stream_task')
⋮----
if False:  # Will not yield anything
⋮----
async def test_consume_all_event_consumer_exception(self)
⋮----
class TestException(Exception)
⋮----
)  # Re-mock to make it an async generator that raises
async def raiser_gen()
⋮----
# Yield a non-Message event first to ensure process is called
⋮----
# Ensure process was called for the event before the exception
⋮----
unittest.mock.ANY  # Check it was called, arg is the task
⋮----
async def test_consume_and_break_on_message(self)
⋮----
sample_message = create_sample_message(content='interrupt message')
event_after = create_sample_task('task_after_msg')
⋮----
yield event_after  # This should not be processed by task_manager in this call
⋮----
self.mock_task_manager.process.assert_not_called()  # Process is not called for the Message if returned directly
# _continue_consuming should not be called if it's a message interrupt
# and no auth_required state.
⋮----
auth_task = create_sample_task(
event_after_auth = create_sample_message('after auth')
⋮----
yield event_after_auth  # This event will be handled by _continue_consuming
⋮----
auth_task  # current_result after auth_task processing
⋮----
# Mock _continue_consuming to check if it's called by create_task
⋮----
mock_create_task.assert_called_once()  # Check that create_task was called
# self.aggregator._continue_consuming is an AsyncMock.
# The actual call in product code is create_task(self._continue_consuming(event_stream_arg))
# So, we check that our mock _continue_consuming was called with an AsyncIterator arg.
⋮----
# Manually run the mocked _continue_consuming to check its behavior
# This requires the generator to be re-setup or passed if stateful.
# For simplicity, let's assume _continue_consuming uses the same generator instance.
# In a real scenario, the generator's state would be an issue.
# However, ResultAggregator re-assigns self.mock_event_consumer.consume()
# to self.aggregator._event_stream in the actual code.
# The test setup for _continue_consuming needs to be more robust if we want to test its internal loop.
# For now, we've verified it's called.
⋮----
auth_status_update = create_sample_status_update(
current_task_state_after_update = create_sample_task(
⋮----
# When current_result is called after processing auth_status_update
⋮----
async def test_consume_and_break_completes_normally(self)
⋮----
event1 = create_sample_message('event one normal', msg_id='n1')
event2 = create_sample_task('normal_task')
⋮----
final_task_state  # For the end of stream
⋮----
# If the first event is a Message, it's returned directly.
⋮----
# process() is NOT called for the Message if it's the one causing the return
⋮----
async def test_consume_and_break_event_consumer_exception(self)
⋮----
class TestInterruptException(Exception)
⋮----
async def raiser_gen_interrupt()
⋮----
# Yield a non-Message event first
⋮----
@patch('asyncio.create_task')  # To verify _continue_consuming is called
⋮----
# This test focuses on verifying that if an interrupt occurs,
# the events *after* the interrupting one are processed by _continue_consuming.
auth_event = create_sample_task(
event_after_auth1 = create_sample_message(
event_after_auth2 = create_sample_task('task_after_auth_2')
# This generator will be iterated first by consume_and_break_on_interrupt,
# then by _continue_consuming.
# We need a way to simulate this shared iterator state or provide a new one for _continue_consuming.
# The actual implementation uses self.aggregator._event_stream
# Let's simulate the state after consume_and_break_on_interrupt has consumed auth_event
# and _event_stream is now the rest of the generator.
# Initial stream for consume_and_break_on_interrupt
async def initial_consume_generator()
⋮----
# These should be consumed by _continue_consuming
⋮----
auth_event  # Task state at interrupt
⋮----
# Call the main method that triggers _continue_consuming via create_task
⋮----
# Now, we need to actually execute the coroutine passed to create_task
# to test the behavior of _continue_consuming
continue_consuming_coro = mock_create_task.call_args[0][0]
# Reset process mock to only count calls from _continue_consuming
⋮----
# Verify process was called for events after the interrupt

================
File: tests/server/tasks/test_task_manager.py
================
MINIMAL_TASK: dict[str, Any] = {
⋮----
@pytest.fixture
def mock_task_store() -> AsyncMock
⋮----
"""Fixture for a mock TaskStore."""
⋮----
@pytest.fixture
def task_manager(mock_task_store: AsyncMock) -> TaskManager
⋮----
"""Fixture for a TaskManager with a mock TaskStore."""
⋮----
"""Test getting an existing task."""
expected_task = Task(**MINIMAL_TASK)
⋮----
retrieved_task = await task_manager.get_task()
⋮----
"""Test getting a nonexistent task."""
⋮----
"""Test saving a new task."""
task = Task(**MINIMAL_TASK)
⋮----
"""Test saving a status update for an existing task."""
initial_task = Task(**MINIMAL_TASK)
⋮----
new_status = TaskStatus(
event = TaskStatusUpdateEvent(
⋮----
updated_task = initial_task
⋮----
"""Test saving an artifact update for an existing task."""
⋮----
new_artifact = Artifact(
event = TaskArtifactUpdateEvent(
⋮----
"""Test saving an updated metadata for an existing task."""
⋮----
new_metadata = {'meta_key_test': 'meta_value_test'}
⋮----
updated_task = mock_task_store.save.call_args.args[0]
⋮----
"""Test ensuring an existing task."""
⋮----
retrieved_task = await task_manager.ensure_task(event)
⋮----
"""Test ensuring a nonexistent task (creates a new one)."""
⋮----
task_manager_without_id = TaskManager(
⋮----
new_task = await task_manager_without_id.ensure_task(event)
⋮----
def test_init_task_obj(task_manager: TaskManager) -> None
⋮----
"""Test initializing a new task object."""
new_task = task_manager._init_task_obj('new-task', 'new-context')  # type: ignore
⋮----
"""Test saving a task."""
⋮----
await task_manager._save_task(task)  # type: ignore
⋮----
"""Test that save_task_event raises ServerError on task ID mismatch."""
# The task_manager is initialized with 'task-abc'
mismatched_task = Task(
⋮----
"""Test saving a task event without task id in TaskManager."""
⋮----
task_data: dict[str, Any] = {
task = Task(**task_data)
⋮----
# initial submit should be updated to working
⋮----
"""Test getting a task when task_id is not set in TaskManager."""
⋮----
retrieved_task = await task_manager_without_id.get_task()
⋮----
"""Test saving an event when no task exists and task_id is not set."""
⋮----
# Check if a new task was created and saved
call_args = mock_task_store.save.call_args
⋮----
saved_task = call_args[0][0]

================
File: tests/server/tasks/test_task_updater.py
================
@pytest.fixture
def event_queue()
⋮----
"""Create a mock event queue for testing."""
⋮----
@pytest.fixture
def task_updater(event_queue)
⋮----
"""Create a TaskUpdater instance for testing."""
⋮----
@pytest.fixture
def sample_message()
⋮----
"""Create a sample message for testing."""
⋮----
@pytest.fixture
def sample_parts()
⋮----
"""Create sample parts for testing."""
⋮----
def test_init(event_queue)
⋮----
"""Test that TaskUpdater initializes correctly."""
task_updater = TaskUpdater(
⋮----
@pytest.mark.asyncio
async def test_update_status_without_message(task_updater, event_queue)
⋮----
"""Test updating status without a message."""
⋮----
event = event_queue.enqueue_event.call_args[0][0]
⋮----
"""Test updating status with a message."""
⋮----
@pytest.mark.asyncio
async def test_update_status_final(task_updater, event_queue)
⋮----
"""Test updating status with final=True."""
⋮----
"""Test adding an artifact with a custom ID and name."""
⋮----
"""Test add_artifact generates an ID if artifact_id is None."""
known_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678')
⋮----
"""Test add_artifact with append and last_chunk flags."""
⋮----
@pytest.mark.asyncio
async def test_complete_without_message(task_updater, event_queue)
⋮----
"""Test marking a task as completed without a message."""
⋮----
@pytest.mark.asyncio
async def test_complete_with_message(task_updater, event_queue, sample_message)
⋮----
"""Test marking a task as completed with a message."""
⋮----
@pytest.mark.asyncio
async def test_submit_without_message(task_updater, event_queue)
⋮----
"""Test marking a task as submitted without a message."""
⋮----
@pytest.mark.asyncio
async def test_submit_with_message(task_updater, event_queue, sample_message)
⋮----
"""Test marking a task as submitted with a message."""
⋮----
@pytest.mark.asyncio
async def test_start_work_without_message(task_updater, event_queue)
⋮----
"""Test marking a task as working without a message."""
⋮----
"""Test marking a task as working with a message."""
⋮----
def test_new_agent_message(task_updater, sample_parts)
⋮----
"""Test creating a new agent message."""
⋮----
message = task_updater.new_agent_message(parts=sample_parts)
⋮----
def test_new_agent_message_with_metadata(task_updater, sample_parts)
⋮----
"""Test creating a new agent message with metadata and final=True."""
metadata = {'key': 'value'}
⋮----
message = task_updater.new_agent_message(
⋮----
@pytest.mark.asyncio
async def test_failed_without_message(task_updater, event_queue)
⋮----
"""Test marking a task as failed without a message."""
⋮----
@pytest.mark.asyncio
async def test_failed_with_message(task_updater, event_queue, sample_message)
⋮----
"""Test marking a task as failed with a message."""
⋮----
@pytest.mark.asyncio
async def test_reject_without_message(task_updater, event_queue)
⋮----
"""Test marking a task as rejected without a message."""
⋮----
@pytest.mark.asyncio
async def test_reject_with_message(task_updater, event_queue, sample_message)
⋮----
"""Test marking a task as rejected with a message."""
⋮----
@pytest.mark.asyncio
async def test_requires_input_without_message(task_updater, event_queue)
⋮----
"""Test marking a task as input required without a message."""
⋮----
"""Test marking a task as input required with a message."""
⋮----
@pytest.mark.asyncio
async def test_requires_input_final_true(task_updater, event_queue)
⋮----
"""Test marking a task as input required with final=True."""
⋮----
"""Test marking a task as input required with message and final=True."""
⋮----
@pytest.mark.asyncio
async def test_requires_auth_without_message(task_updater, event_queue)
⋮----
"""Test marking a task as auth required without a message."""
⋮----
"""Test marking a task as auth required with a message."""
⋮----
@pytest.mark.asyncio
async def test_requires_auth_final_true(task_updater, event_queue)
⋮----
"""Test marking a task as auth required with final=True."""
⋮----
"""Test marking a task as auth required with message and final=True."""
⋮----
@pytest.mark.asyncio
async def test_cancel_without_message(task_updater, event_queue)
⋮----
"""Test marking a task as cancelled without a message."""
⋮----
@pytest.mark.asyncio
async def test_cancel_with_message(task_updater, event_queue, sample_message)
⋮----
"""Test marking a task as cancelled with a message."""

================
File: tests/server/test_integration.py
================
# === TEST SETUP ===
MINIMAL_AGENT_SKILL: dict[str, Any] = {
MINIMAL_AGENT_AUTH: dict[str, Any] = {'schemes': ['Bearer']}
AGENT_CAPS = AgentCapabilities(
MINIMAL_AGENT_CARD: dict[str, Any] = {
⋮----
'capabilities': AGENT_CAPS,  # AgentCapabilities is required but can be empty
⋮----
EXTENDED_AGENT_CARD_DATA: dict[str, Any] = {
TEXT_PART_DATA: dict[str, Any] = {'kind': 'text', 'text': 'Hello'}
DATA_PART_DATA: dict[str, Any] = {'kind': 'data', 'data': {'key': 'value'}}
MINIMAL_MESSAGE_USER: dict[str, Any] = {
MINIMAL_TASK_STATUS: dict[str, Any] = {'state': 'submitted'}
FULL_TASK_STATUS: dict[str, Any] = {
⋮----
@pytest.fixture
def agent_card()
⋮----
@pytest.fixture
def extended_agent_card_fixture()
⋮----
@pytest.fixture
def handler()
⋮----
handler = mock.AsyncMock()
⋮----
@pytest.fixture
def app(agent_card: AgentCard, handler: mock.AsyncMock)
⋮----
@pytest.fixture
def client(app: A2AStarletteApplication, **kwargs)
⋮----
"""Create a test client with the Starlette app."""
⋮----
# === BASIC FUNCTIONALITY TESTS ===
def test_agent_card_endpoint(client: TestClient, agent_card: AgentCard)
⋮----
"""Test the agent card endpoint returns expected data."""
response = client.get('/.well-known/agent.json')
⋮----
data = response.json()
⋮----
"""Test extended card endpoint returns 404 if not supported by main card."""
# Ensure supportsAuthenticatedExtendedCard is False or None
⋮----
app_instance = A2AStarletteApplication(agent_card, handler)
# The route should not even be added if supportsAuthenticatedExtendedCard is false
# So, building the app and trying to hit it should result in 404 from Starlette itself
client = TestClient(app_instance.build())
response = client.get('/agent/authenticatedExtendedCard')
assert response.status_code == 404  # Starlette's default for no route
⋮----
app_instance = A2AFastAPIApplication(agent_card, handler)
⋮----
# So, building the app and trying to hit it should result in 404 from FastAPI itself
⋮----
assert response.status_code == 404  # FastAPI's default for no route
⋮----
"""Test extended card endpoint returns the specific extended card when provided."""
⋮----
True  # Main card must support it
⋮----
app_instance = A2AStarletteApplication(
⋮----
# Verify it's the extended card's data
⋮----
app_instance = A2AFastAPIApplication(
⋮----
"""Test the agent card endpoint with a custom URL."""
client = TestClient(app.build(agent_card_url='/my-agent'))
response = client.get('/my-agent')
⋮----
"""Test the RPC endpoint with a custom URL."""
# Provide a valid Task object as the return value
task_status = TaskStatus(**MINIMAL_TASK_STATUS)
task = Task(
⋮----
client = TestClient(app.build(rpc_url='/api/rpc'))
response = client.post(
⋮----
"""Test building the app with additional routes."""
def custom_handler(request)
extra_route = Route('/hello', custom_handler, methods=['GET'])
test_app = app.build(routes=[extra_route])
client = TestClient(test_app)
# Test the added route
response = client.get('/hello')
⋮----
# Ensure default routes still work
⋮----
# === REQUEST METHODS TESTS ===
def test_send_message(client: TestClient, handler: mock.AsyncMock)
⋮----
"""Test sending a message."""
# Prepare mock response
⋮----
mock_task = Task(
⋮----
# Send request
⋮----
# Verify response
⋮----
# Verify handler was called
⋮----
def test_cancel_task(client: TestClient, handler: mock.AsyncMock)
⋮----
"""Test cancelling a task."""
# Setup mock response
⋮----
task_status.state = TaskState.canceled  # 'cancelled' #
⋮----
def test_get_task(client: TestClient, handler: mock.AsyncMock)
⋮----
"""Test getting a task."""
⋮----
handler.on_get_task.return_value = task  # JSONRPCResponse(root=task)
⋮----
"""Test setting push notification configuration."""
⋮----
task_push_config = TaskPushNotificationConfig(
⋮----
"""Test getting push notification configuration."""
⋮----
def test_server_auth(app: A2AStarletteApplication, handler: mock.AsyncMock)
⋮----
class TestAuthMiddleware(AuthenticationBackend)
⋮----
# For the purposes of this test, all requests are authenticated!
⋮----
client = TestClient(
# Set the output message to be the authenticated user name
⋮----
result = SendMessageResponse.model_validate(response.json())
⋮----
message = result.root.result
⋮----
# === STREAMING TESTS ===
⋮----
"""Test streaming message sending."""
# Setup mock streaming response
async def stream_generator()
⋮----
text_part = TextPart(**TEXT_PART_DATA)
data_part = DataPart(**DATA_PART_DATA)
artifact = Artifact(
last = [False, False, True]
task_artifact_update_event_data: dict[str, Any] = {
⋮----
client = None
⋮----
# Create client
client = TestClient(app.build(), raise_server_exceptions=False)
# Send request
⋮----
# Verify response is a stream
⋮----
# Read some content to verify streaming works
content = b''
event_count = 0
⋮----
if b'data' in chunk:  # Naive check for SSE data lines
⋮----
# Check content has event data (e.g., part of the first event)
⋮----
)  # Check for the actual JSON payload
⋮----
# Ensure the client is closed
⋮----
# Allow event loop to process any pending callbacks
⋮----
"""Test task resubscription streaming."""
⋮----
# Create client
⋮----
# Send request using client.stream() context manager
⋮----
'id': '123',  # This ID is used in the success_event above
⋮----
# A more robust check would be to parse each SSE event
if b'data:' in chunk:  # Naive check for SSE data lines
⋮----
):  # Ensure we've processed at least one event
⋮----
# === ERROR HANDLING TESTS ===
def test_invalid_json(client: TestClient)
⋮----
"""Test handling invalid JSON."""
response = client.post('/', content=b'This is not JSON')  # Use bytes
assert response.status_code == 200  # JSON-RPC errors still return 200
⋮----
def test_invalid_request_structure(client: TestClient)
⋮----
"""Test handling an invalid request structure."""
⋮----
# Missing required fields
⋮----
def test_method_not_implemented(client: TestClient, handler: mock.AsyncMock)
⋮----
"""Test handling MethodNotImplementedError."""
⋮----
def test_unknown_method(client: TestClient)
⋮----
"""Test handling unknown method."""
⋮----
# This should produce an UnsupportedOperationError error code
⋮----
def test_validation_error(client: TestClient)
⋮----
"""Test handling validation error."""
# Missing required fields in the message
⋮----
# Missing required fields
⋮----
def test_unhandled_exception(client: TestClient, handler: mock.AsyncMock)
⋮----
"""Test handling unhandled exception."""
⋮----
def test_get_method_to_rpc_endpoint(client: TestClient)
⋮----
"""Test sending GET request to RPC endpoint."""
response = client.get('/')
# Should return 405 Method Not Allowed
⋮----
def test_non_dict_json(client: TestClient)
⋮----
"""Test handling JSON that's not a dict."""
response = client.post('/', json=['not', 'a', 'dict'])

================
File: tests/utils/test_artifact.py
================
class TestArtifact(unittest.TestCase)
⋮----
@patch('uuid.uuid4')
    def test_new_artifact_generates_id(self, mock_uuid4)
⋮----
mock_uuid = uuid.UUID('abcdef12-1234-5678-1234-567812345678')
⋮----
artifact = new_artifact(parts=[], name='test_artifact')
⋮----
def test_new_artifact_assigns_parts_name_description(self)
⋮----
parts = [Part(root=TextPart(text='Sample text'))]
name = 'My Artifact'
description = 'This is a test artifact.'
artifact = new_artifact(parts=parts, name=name, description=description)
⋮----
def test_new_artifact_empty_description_if_not_provided(self)
⋮----
parts = [Part(root=TextPart(text='Another sample'))]
name = 'Artifact_No_Desc'
artifact = new_artifact(parts=parts, name=name)
⋮----
def test_new_text_artifact_creates_single_text_part(self)
⋮----
text = 'This is a text artifact.'
name = 'Text_Artifact'
artifact = new_text_artifact(text=text, name=name)
⋮----
def test_new_text_artifact_part_contains_provided_text(self)
⋮----
text = 'Hello, world!'
name = 'Greeting_Artifact'
⋮----
def test_new_text_artifact_assigns_name_description(self)
⋮----
text = 'Some content.'
name = 'Named_Text_Artifact'
description = 'Description for text artifact.'
artifact = new_text_artifact(
⋮----
def test_new_data_artifact_creates_single_data_part(self)
⋮----
sample_data = {'key': 'value', 'number': 123}
name = 'Data_Artifact'
artifact = new_data_artifact(data=sample_data, name=name)
⋮----
def test_new_data_artifact_part_contains_provided_data(self)
⋮----
sample_data = {'content': 'test_data', 'is_valid': True}
name = 'Structured_Data_Artifact'
⋮----
# Ensure the 'data' attribute of DataPart is accessed for comparison
⋮----
def test_new_data_artifact_assigns_name_description(self)
⋮----
sample_data = {'info': 'some details'}
name = 'Named_Data_Artifact'
description = 'Description for data artifact.'
artifact = new_data_artifact(

================
File: tests/utils/test_helpers.py
================
# --- Helper Data ---
TEXT_PART_DATA: dict[str, Any] = {'type': 'text', 'text': 'Hello'}
MINIMAL_MESSAGE_USER: dict[str, Any] = {
MINIMAL_TASK_STATUS: dict[str, Any] = {'state': 'submitted'}
MINIMAL_TASK: dict[str, Any] = {
# Test create_task_obj
def test_create_task_obj()
⋮----
message = Message(**MINIMAL_MESSAGE_USER)
send_params = MessageSendParams(message=message)
task = create_task_obj(send_params)
⋮----
def test_create_task_obj_generates_context_id()
⋮----
"""Test that create_task_obj generates contextId if not present and uses it for the task."""
# Message without contextId
message_no_context_id = Message(
⋮----
taskId='task-from-msg',  # Provide a taskId to differentiate from generated task.id
⋮----
send_params = MessageSendParams(message=message_no_context_id)
# Ensure message.contextId is None initially
⋮----
known_task_uuid = uuid.UUID('11111111-1111-1111-1111-111111111111')
known_context_uuid = uuid.UUID('22222222-2222-2222-2222-222222222222')
# Patch uuid.uuid4 to return specific UUIDs in sequence
# The first call will be for message.contextId (if None), the second for task.id.
⋮----
# Assert that uuid4 was called twice (once for contextId, once for task.id)
⋮----
# Assert that message.contextId was set to the first generated UUID
⋮----
# Assert that task.contextId is the same generated UUID
⋮----
# Assert that task.id is the second generated UUID
⋮----
# Ensure the original message in history also has the updated contextId
⋮----
# Test append_artifact_to_task
def test_append_artifact_to_task()
⋮----
# Prepare base task
task = Task(**MINIMAL_TASK)
⋮----
# Prepare appending artifact and event
artifact_1 = Artifact(
append_event_1 = TaskArtifactUpdateEvent(
# Test adding a new artifact (not appending)
⋮----
# Test replacing the artifact
artifact_2 = Artifact(
append_event_2 = TaskArtifactUpdateEvent(
⋮----
assert len(task.artifacts) == 1  # Should still have one artifact
⋮----
# Test appending parts to an existing artifact
artifact_with_parts = Artifact(
append_event_3 = TaskArtifactUpdateEvent(
⋮----
# Test adding another new artifact
another_artifact_with_parts = Artifact(
append_event_4 = TaskArtifactUpdateEvent(
⋮----
# Test appending part to a task that does not have a matching artifact
non_existing_artifact_with_parts = Artifact(
append_event_5 = TaskArtifactUpdateEvent(
⋮----
# Test build_text_artifact
def test_build_text_artifact()
⋮----
artifact_id = 'text_artifact'
text = 'This is a sample text'
artifact = build_text_artifact(text, artifact_id)
⋮----
# Test validate decorator
def test_validate_decorator()
⋮----
class TestClass
⋮----
condition = True
⋮----
@validate(lambda self: self.condition, 'Condition not met')
        def test_method(self) -> str
obj = TestClass()
# Test passing condition
⋮----
# Test failing condition
⋮----
# Tests for are_modalities_compatible
def test_are_modalities_compatible_client_none()
def test_are_modalities_compatible_client_empty()
def test_are_modalities_compatible_server_none()
def test_are_modalities_compatible_server_empty()
def test_are_modalities_compatible_common_mode()
def test_are_modalities_compatible_no_common_modes()
def test_are_modalities_compatible_exact_match()
def test_are_modalities_compatible_server_more_but_common()
def test_are_modalities_compatible_client_more_but_common()
def test_are_modalities_compatible_both_none()
def test_are_modalities_compatible_both_empty()

================
File: tests/utils/test_message.py
================
class TestNewAgentTextMessage
⋮----
def test_new_agent_text_message_basic(self)
⋮----
# Setup
text = "Hello, I'm an agent"
# Exercise - with a fixed uuid for testing
⋮----
message = new_agent_text_message(text)
# Verify
⋮----
def test_new_agent_text_message_with_context_id(self)
⋮----
text = 'Message with context'
context_id = 'test-context-id'
# Exercise
⋮----
message = new_agent_text_message(text, context_id=context_id)
⋮----
def test_new_agent_text_message_with_task_id(self)
⋮----
text = 'Message with task id'
task_id = 'test-task-id'
⋮----
message = new_agent_text_message(text, task_id=task_id)
⋮----
def test_new_agent_text_message_with_both_ids(self)
⋮----
text = 'Message with both ids'
⋮----
message = new_agent_text_message(
⋮----
def test_new_agent_text_message_empty_text(self)
⋮----
text = ''
⋮----
class TestNewAgentPartsMessage
⋮----
def test_new_agent_parts_message(self)
⋮----
"""Test creating an agent message with multiple, mixed parts."""
⋮----
parts = [
context_id = 'ctx-multi-part'
task_id = 'task-multi-part'
⋮----
message = new_agent_parts_message(
⋮----
class TestGetTextParts
⋮----
def test_get_text_parts_single_text_part(self)
⋮----
parts = [Part(root=TextPart(text='Hello world'))]
⋮----
result = get_text_parts(parts)
⋮----
def test_get_text_parts_multiple_text_parts(self)
def test_get_text_parts_empty_list(self)
⋮----
parts = []
⋮----
class TestGetMessageText
⋮----
def test_get_message_text_single_part(self)
⋮----
message = Message(
⋮----
result = get_message_text(message)
⋮----
def test_get_message_text_multiple_parts(self)
⋮----
# Verify - default delimiter is newline
⋮----
def test_get_message_text_custom_delimiter(self)
⋮----
result = get_message_text(message, delimiter=' | ')
⋮----
def test_get_message_text_empty_parts(self)

================
File: tests/utils/test_proto_utils.py
================
# --- Test Data ---
⋮----
@pytest.fixture
def sample_message() -> types.Message
⋮----
@pytest.fixture
def sample_task(sample_message: types.Message) -> types.Task
⋮----
@pytest.fixture
def sample_agent_card() -> types.AgentCard
# --- Test Cases ---
class TestToProto
⋮----
def test_part_unsupported_type(self)
⋮----
"""Test that ToProto.part raises ValueError for an unsupported Part type."""
class FakePartType
⋮----
kind = 'fake'
# Create a mock Part object that has a .root attribute pointing to the fake type
mock_part = mock.MagicMock(spec=types.Part)
⋮----
class TestFromProto
⋮----
"""Test that FromProto.part raises ValueError for an unsupported part type in proto."""
unsupported_proto_part = (
⋮----
)  # An empty part with no oneof field set
⋮----
def test_task_query_params_invalid_name(self)
⋮----
request = a2a_pb2.GetTaskRequest(name='invalid-name-format')
⋮----
class TestProtoUtils
⋮----
def test_roundtrip_message(self, sample_message: types.Message)
⋮----
"""Test conversion of Message to proto and back."""
proto_msg = proto_utils.ToProto.message(sample_message)
⋮----
# Test file part handling
⋮----
roundtrip_msg = proto_utils.FromProto.message(proto_msg)
⋮----
def test_enum_conversions(self)
⋮----
"""Test conversions for all enum types."""
⋮----
proto_state = proto_utils.ToProto.task_state(state)
⋮----
# Test unknown state case
⋮----
def test_oauth_flows_conversion(self)
⋮----
"""Test conversion of different OAuth2 flows."""
# Test password flow
password_flow = types.OAuthFlows(
proto_password_flow = proto_utils.ToProto.oauth2_flows(password_flow)
⋮----
# Test implicit flow
implicit_flow = types.OAuthFlows(
proto_implicit_flow = proto_utils.ToProto.oauth2_flows(implicit_flow)
⋮----
# Test authorization code flow
auth_code_flow = types.OAuthFlows(
proto_auth_code_flow = proto_utils.ToProto.oauth2_flows(auth_code_flow)
⋮----
# Test invalid flow
⋮----
# Test FromProto
roundtrip_password = proto_utils.FromProto.oauth2_flows(
⋮----
roundtrip_implicit = proto_utils.FromProto.oauth2_flows(
⋮----
def test_task_id_params_from_proto_invalid_name(self)
⋮----
request = a2a_pb2.CancelTaskRequest(name='invalid-name-format')
⋮----
def test_task_push_config_from_proto_invalid_parent(self)
⋮----
request = a2a_pb2.CreateTaskPushNotificationConfigRequest(
⋮----
def test_none_handling(self)
⋮----
"""Test that None inputs are handled gracefully."""

================
File: tests/utils/test_task.py
================
class TestTask(unittest.TestCase)
⋮----
def test_new_task_status(self)
⋮----
message = Message(
task = new_task(message)
⋮----
@patch('uuid.uuid4')
    def test_new_task_generates_ids(self, mock_uuid4)
⋮----
mock_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678')
⋮----
def test_new_task_uses_provided_ids(self)
⋮----
task_id = str(uuid.uuid4())
context_id = str(uuid.uuid4())
⋮----
def test_new_task_initial_message_in_history(self)
def test_completed_task_status(self)
⋮----
artifacts = []  # Artifacts should be of type Artifact
task = completed_task(
⋮----
def test_completed_task_assigns_ids_and_artifacts(self)
def test_completed_task_empty_history_if_not_provided(self)
def test_completed_task_uses_provided_history(self)
⋮----
history = [

================
File: tests/utils/test_telemetry.py
================
@pytest.fixture
def mock_span()
⋮----
@pytest.fixture
def mock_tracer(mock_span)
⋮----
tracer = mock.MagicMock()
⋮----
@pytest.fixture(autouse=True)
def patch_trace_get_tracer(mock_tracer)
def test_trace_function_sync_success(mock_span)
⋮----
@trace_function
    def foo(x, y)
result = foo(2, 3)
⋮----
def test_trace_function_sync_exception(mock_span)
⋮----
@trace_function
    def bar() -> NoReturn
⋮----
def test_trace_function_sync_attribute_extractor_called(mock_span)
⋮----
called = {}
def attr_extractor(span, args, kwargs, result, exception) -> None
⋮----
@trace_function(attribute_extractor=attr_extractor)
    def foo() -> int
⋮----
def test_trace_function_sync_attribute_extractor_error_logged(mock_span)
⋮----
def attr_extractor(span, args, kwargs, result, exception) -> NoReturn
⋮----
@trace_function(attribute_extractor=attr_extractor)
        def foo() -> int
⋮----
@pytest.mark.asyncio
async def test_trace_function_async_success(mock_span)
⋮----
@trace_function
    async def foo(x)
result = await foo(4)
⋮----
@pytest.mark.asyncio
async def test_trace_function_async_exception(mock_span)
⋮----
@trace_function
    async def bar() -> NoReturn
⋮----
@pytest.mark.asyncio
async def test_trace_function_async_attribute_extractor_called(mock_span)
⋮----
@trace_function(attribute_extractor=attr_extractor)
    async def foo() -> int
⋮----
def test_trace_function_with_args_and_attributes(mock_span)
⋮----
@trace_function(span_name='custom.span', attributes={'foo': 'bar'})
    def foo() -> int
⋮----
def test_trace_class_exclude_list(mock_span)
⋮----
@trace_class(exclude_list=['skip_me'])
    class MyClass
⋮----
def a(self) -> str
def skip_me(self) -> str
def __str__(self)
obj = MyClass()
⋮----
# Only 'a' is traced, not 'skip_me' or dunder
⋮----
def test_trace_class_include_list(mock_span)
⋮----
@trace_class(include_list=['only_this'])
    class MyClass
⋮----
def only_this(self) -> str
def not_this(self) -> str
⋮----
def test_trace_class_dunder_not_traced(mock_span)
⋮----
@trace_class()
    class MyClass
⋮----
def __init__(self)
def foo(self) -> str

================
File: tests/README.md
================
## Running the tests

1. Run the tests
    ```bash
    uv run pytest -v -s client/test_client.py
    ```

In case of failures, you can cleanup the cache:

1. `uv clean`
2. `rm -fR .pytest_cache .venv __pycache__`

================
File: tests/test_types.py
================
# --- Helper Data ---
MINIMAL_AGENT_SECURITY_SCHEME: dict[str, Any] = {
MINIMAL_AGENT_SKILL: dict[str, Any] = {
FULL_AGENT_SKILL: dict[str, Any] = {
MINIMAL_AGENT_CARD: dict[str, Any] = {
⋮----
'capabilities': {},  # AgentCapabilities is required but can be empty
⋮----
TEXT_PART_DATA: dict[str, Any] = {'kind': 'text', 'text': 'Hello'}
FILE_URI_PART_DATA: dict[str, Any] = {
FILE_BYTES_PART_DATA: dict[str, Any] = {
⋮----
'file': {'bytes': 'aGVsbG8=', 'name': 'hello.txt'},  # base64 for "hello"
⋮----
DATA_PART_DATA: dict[str, Any] = {'kind': 'data', 'data': {'key': 'value'}}
MINIMAL_MESSAGE_USER: dict[str, Any] = {
AGENT_MESSAGE_WITH_FILE: dict[str, Any] = {
MINIMAL_TASK_STATUS: dict[str, Any] = {'state': 'submitted'}
FULL_TASK_STATUS: dict[str, Any] = {
MINIMAL_TASK: dict[str, Any] = {
FULL_TASK: dict[str, Any] = {
MINIMAL_TASK_ID_PARAMS: dict[str, Any] = {'id': 'task-123'}
FULL_TASK_ID_PARAMS: dict[str, Any] = {
JSONRPC_ERROR_DATA: dict[str, Any] = {
JSONRPC_SUCCESS_RESULT: dict[str, Any] = {'status': 'ok', 'data': [1, 2, 3]}
# --- Test Functions ---
def test_security_scheme_valid()
⋮----
scheme = SecurityScheme.model_validate(MINIMAL_AGENT_SECURITY_SCHEME)
⋮----
def test_security_scheme_invalid()
⋮----
)  # Missing "in"  # type: ignore
⋮----
)  # Missing "flows"
def test_agent_capabilities()
⋮----
caps = AgentCapabilities(
⋮----
)  # All optional
⋮----
caps_full = AgentCapabilities(
⋮----
def test_agent_provider()
⋮----
provider = AgentProvider(organization='Test Org', url='http://test.org')
⋮----
AgentProvider(organization='Test Org')  # Missing url  # type: ignore
def test_agent_skill_valid()
⋮----
skill = AgentSkill(**MINIMAL_AGENT_SKILL)
⋮----
skill_full = AgentSkill(**FULL_AGENT_SKILL)
⋮----
def test_agent_skill_invalid()
⋮----
)  # Missing tags  # type: ignore
⋮----
invalid_extra='foo',  # type: ignore
)  # Extra field
def test_agent_card_valid()
⋮----
card = AgentCard(**MINIMAL_AGENT_CARD)
⋮----
assert card.provider is None  # Optional
def test_agent_card_invalid()
⋮----
bad_card_data = MINIMAL_AGENT_CARD.copy()
⋮----
AgentCard(**bad_card_data)  # Missing name
# --- Test Parts ---
def test_text_part()
⋮----
part = TextPart(**TEXT_PART_DATA)
⋮----
TextPart(type='text')  # Missing text # type: ignore
⋮----
kind='file',  # type: ignore
⋮----
)  # Wrong type literal
def test_file_part_variants()
⋮----
# URI variant
file_uri = FileWithUri(
part_uri = FilePart(kind='file', file=file_uri)
⋮----
# Bytes variant
file_bytes = FileWithBytes(bytes='aGVsbG8=', name='hello.txt')
part_bytes = FilePart(kind='file', file=file_bytes)
⋮----
# Test deserialization directly
part_uri_deserialized = FilePart.model_validate(FILE_URI_PART_DATA)
⋮----
part_bytes_deserialized = FilePart.model_validate(FILE_BYTES_PART_DATA)
⋮----
# Invalid - wrong type literal
⋮----
FilePart(kind='text', file=file_uri)  # type: ignore
FilePart(**FILE_URI_PART_DATA, extra='extra')  # type: ignore
def test_data_part()
⋮----
part = DataPart(**DATA_PART_DATA)
⋮----
DataPart(type='data')  # Missing data  # type: ignore
def test_part_root_model()
⋮----
# Test deserialization of the Union RootModel
part_text = Part.model_validate(TEXT_PART_DATA)
⋮----
part_file = Part.model_validate(FILE_URI_PART_DATA)
⋮----
part_data = Part.model_validate(DATA_PART_DATA)
⋮----
# Test serialization
⋮----
# --- Test Message and Task ---
def test_message()
⋮----
msg = Message(**MINIMAL_MESSAGE_USER)
⋮----
)  # Access root for RootModel Part
⋮----
msg_agent = Message(**AGENT_MESSAGE_WITH_FILE)
⋮----
role='invalid_role',  # type: ignore
parts=[TEXT_PART_DATA],  # type: ignore
)  # Invalid enum
⋮----
Message(role=Role.user)  # Missing parts  # type: ignore
def test_task_status()
⋮----
status = TaskStatus(**MINIMAL_TASK_STATUS)
⋮----
status_full = TaskStatus(**FULL_TASK_STATUS)
⋮----
TaskStatus(state='invalid_state')  # Invalid enum  # type: ignore
def test_task()
⋮----
task = Task(**MINIMAL_TASK)
⋮----
task_full = Task(**FULL_TASK)
⋮----
Task(id='abc', sessionId='xyz')  # Missing status # type: ignore
# --- Test JSON-RPC Structures ---
def test_jsonrpc_error()
⋮----
err = JSONRPCError(code=-32600, message='Invalid Request')
⋮----
err_data = JSONRPCError(
⋮----
def test_jsonrpc_request()
⋮----
req = JSONRPCRequest(jsonrpc='2.0', method='test_method', id=1)
⋮----
req_params = JSONRPCRequest(
⋮----
jsonrpc='1.0',  # type: ignore
⋮----
)  # Wrong version
⋮----
JSONRPCRequest(jsonrpc='2.0', id=1)  # Missing method  # type: ignore
def test_jsonrpc_error_response()
⋮----
err_obj = JSONRPCError(**JSONRPC_ERROR_DATA)
resp = JSONRPCErrorResponse(jsonrpc='2.0', error=err_obj, id='err-1')
⋮----
)  # Missing error # type: ignore
def test_jsonrpc_response_root_model() -> None
⋮----
# Success case
success_data: dict[str, Any] = {
resp_success = JSONRPCResponse.model_validate(success_data)
⋮----
# Error case
error_data: dict[str, Any] = {
resp_error = JSONRPCResponse.model_validate(error_data)
⋮----
# Note: .model_dump() might serialize the nested error model
⋮----
# Invalid case (neither success nor error structure)
⋮----
# --- Test Request/Response Wrappers ---
def test_send_message_request() -> None
⋮----
params = MessageSendParams(message=Message(**MINIMAL_MESSAGE_USER))
req_data: dict[str, Any] = {
req = SendMessageRequest.model_validate(req_data)
⋮----
with pytest.raises(ValidationError):  # Wrong method literal
⋮----
def test_send_subscribe_request() -> None
⋮----
req = SendStreamingMessageRequest.model_validate(req_data)
⋮----
def test_get_task_request() -> None
⋮----
params = TaskQueryParams(id='task-1', historyLength=2)
⋮----
req = GetTaskRequest.model_validate(req_data)
⋮----
def test_cancel_task_request() -> None
⋮----
params = TaskIdParams(id='task-1')
⋮----
req = CancelTaskRequest.model_validate(req_data)
⋮----
def test_get_task_response() -> None
⋮----
resp_data: dict[str, Any] = {
resp = GetTaskResponse.model_validate(resp_data)
⋮----
with pytest.raises(ValidationError):  # Result is not a Task
⋮----
resp_data_err: dict[str, Any] = {
resp_err = GetTaskResponse.model_validate(resp_data_err)
⋮----
def test_send_message_response() -> None
⋮----
resp = SendMessageResponse.model_validate(resp_data)
⋮----
resp_err = SendMessageResponse.model_validate(resp_data_err)
⋮----
def test_cancel_task_response() -> None
⋮----
resp = CancelTaskResponse.model_validate(resp_data)
⋮----
resp_err = CancelTaskResponse.model_validate(resp_data_err)
⋮----
def test_send_message_streaming_status_update_response() -> None
⋮----
task_status_update_event_data: dict[str, Any] = {
event_data: dict[str, Any] = {
response = SendStreamingMessageResponse.model_validate(event_data)
⋮----
):  # Result is not a TaskStatusUpdateEvent
⋮----
event_data = {
⋮----
resp_err = SendStreamingMessageResponse.model_validate(resp_data_err)
⋮----
def test_send_message_streaming_artifact_update_response() -> None
⋮----
text_part = TextPart(**TEXT_PART_DATA)
data_part = DataPart(**DATA_PART_DATA)
artifact = Artifact(
task_artifact_update_event_data: dict[str, Any] = {
⋮----
def test_set_task_push_notification_response() -> None
⋮----
task_push_config = TaskPushNotificationConfig(
⋮----
resp = SetTaskPushNotificationConfigResponse.model_validate(resp_data)
⋮----
auth_info_dict: dict[str, Any] = {
⋮----
resp_data = {
⋮----
resp_err = SetTaskPushNotificationConfigResponse.model_validate(
⋮----
def test_get_task_push_notification_response() -> None
⋮----
resp = GetTaskPushNotificationConfigResponse.model_validate(resp_data)
⋮----
resp_err = GetTaskPushNotificationConfigResponse.model_validate(
⋮----
# --- Test A2ARequest Root Model ---
def test_a2a_request_root_model() -> None
⋮----
# SendMessageRequest case
send_params = MessageSendParams(message=Message(**MINIMAL_MESSAGE_USER))
send_req_data: dict[str, Any] = {
a2a_req_send = A2ARequest.model_validate(send_req_data)
⋮----
# SendStreamingMessageRequest case
send_subs_req_data: dict[str, Any] = {
a2a_req_send_subs = A2ARequest.model_validate(send_subs_req_data)
⋮----
# GetTaskRequest case
get_params = TaskQueryParams(id='t2')
get_req_data: dict[str, Any] = {
a2a_req_get = A2ARequest.model_validate(get_req_data)
⋮----
# CancelTaskRequest case
id_params = TaskIdParams(id='t2')
cancel_req_data: dict[str, Any] = {
a2a_req_cancel = A2ARequest.model_validate(cancel_req_data)
⋮----
# SetTaskPushNotificationConfigRequest
⋮----
set_push_notif_req_data: dict[str, Any] = {
a2a_req_set_push_req = A2ARequest.model_validate(set_push_notif_req_data)
⋮----
# GetTaskPushNotificationConfigRequest
⋮----
get_push_notif_req_data: dict[str, Any] = {
a2a_req_get_push_req = A2ARequest.model_validate(get_push_notif_req_data)
⋮----
# TaskResubscriptionRequest
task_resubscribe_req_data: dict[str, Any] = {
a2a_req_task_resubscribe_req = A2ARequest.model_validate(
⋮----
# Invalid method case
invalid_req_data: dict[str, Any] = {
⋮----
def test_a2a_request_root_model_id_validation() -> None
⋮----
A2ARequest.model_validate(send_req_data)  # missing id
⋮----
A2ARequest.model_validate(send_subs_req_data)  # missing id
⋮----
A2ARequest.model_validate(get_req_data)  # missing id
⋮----
A2ARequest.model_validate(cancel_req_data)  # missing id
⋮----
A2ARequest.model_validate(set_push_notif_req_data)  # missing id
⋮----
def test_content_type_not_supported_error()
⋮----
# Test ContentTypeNotSupportedError
err = ContentTypeNotSupportedError(
⋮----
with pytest.raises(ValidationError):  # Wrong code
⋮----
code=-32000,  # type: ignore
⋮----
extra='extra',  # type: ignore
⋮----
def test_task_not_found_error()
⋮----
# Test TaskNotFoundError
err2 = TaskNotFoundError(
⋮----
TaskNotFoundError(code=-32000, message='Task not found')  # type: ignore
TaskNotFoundError(code=-32001, message='Task not found', extra='extra')  # type: ignore
def test_push_notification_not_supported_error()
⋮----
# Test PushNotificationNotSupportedError
err3 = PushNotificationNotSupportedError(data={'taskId': 'abc'})
⋮----
with pytest.raises(ValidationError):  # Extra field
⋮----
extra='extra',  # type: ignore
⋮----
def test_internal_error()
⋮----
# Test InternalError
err_internal = InternalError()
⋮----
err_internal_data = InternalError(
⋮----
InternalError(code=-32000, message='Internal error')  # type: ignore
InternalError(code=-32603, message='Internal error', extra='extra')  # type: ignore
def test_invalid_params_error()
⋮----
# Test InvalidParamsError
err_params = InvalidParamsError()
⋮----
err_params_data = InvalidParamsError(
⋮----
InvalidParamsError(code=-32000, message='Invalid parameters')  # type: ignore
⋮----
def test_invalid_request_error()
⋮----
# Test InvalidRequestError
err_request = InvalidRequestError()
⋮----
err_request_data = InvalidRequestError(data={'field': 'missing'})
⋮----
)  # type: ignore
def test_json_parse_error()
⋮----
# Test JSONParseError
err_parse = JSONParseError(code=-32700, message='Invalid JSON payload')
⋮----
err_parse_data = JSONParseError(data={'foo': 'bar'})  # Explicit None data
⋮----
JSONParseError(code=-32000, message='Invalid JSON payload')  # type: ignore
JSONParseError(code=-32700, message='Invalid JSON payload', extra='extra')  # type: ignore
def test_method_not_found_error()
⋮----
# Test MethodNotFoundError
err_parse = MethodNotFoundError()
⋮----
err_parse_data = JSONParseError(data={'foo': 'bar'})
⋮----
def test_task_not_cancelable_error()
⋮----
# Test TaskNotCancelableError
err_parse = TaskNotCancelableError()
⋮----
err_parse_data = JSONParseError(
⋮----
JSONParseError(code=-32000, message='Task cannot be canceled')  # type: ignore
⋮----
def test_unsupported_operation_error()
⋮----
# Test UnsupportedOperationError
err_parse = UnsupportedOperationError()
⋮----
JSONParseError(code=-32000, message='Unsupported')  # type: ignore
JSONParseError(code=-32700, message='Unsupported', extra='extra')  # type: ignore
# --- Test TaskIdParams ---
def test_task_id_params_valid()
⋮----
"""Tests successful validation of TaskIdParams."""
# Minimal valid data
params_min = TaskIdParams(**MINIMAL_TASK_ID_PARAMS)
⋮----
# Full valid data
params_full = TaskIdParams(**FULL_TASK_ID_PARAMS)
⋮----
def test_task_id_params_invalid()
⋮----
"""Tests validation errors for TaskIdParams."""
# Missing required 'id' field
⋮----
TaskIdParams()  # type: ignore
⋮----
)  # Check that 'id' is mentioned in the error
invalid_data = MINIMAL_TASK_ID_PARAMS.copy()
⋮----
TaskIdParams(**invalid_data)  # type: ignore
# Incorrect type for metadata (should be dict)
invalid_metadata_type = {'id': 'task-789', 'metadata': 'not_a_dict'}
⋮----
TaskIdParams(**invalid_metadata_type)  # type: ignore
⋮----
)  # Check that 'metadata' is mentioned
def test_task_push_notification_config() -> None
⋮----
"""Tests successful validation of TaskPushNotificationConfig."""
⋮----
auth_info = PushNotificationAuthenticationInfo(**auth_info_dict)
push_notification_config = PushNotificationConfig(
⋮----
task_push_notification_config = TaskPushNotificationConfig(
⋮----
def test_jsonrpc_message_valid()
⋮----
"""Tests successful validation of JSONRPCMessage."""
# With string ID
msg_str_id = JSONRPCMessage(jsonrpc='2.0', id='req-1')
⋮----
# With integer ID (will be coerced to float by Pydantic for JSON number compatibility)
msg_int_id = JSONRPCMessage(jsonrpc='2.0', id=1)
⋮----
)  # Pydantic v2 keeps int if possible, but float is in type hint
rpc_message = JSONRPCMessage(id=1)
⋮----
def test_jsonrpc_message_invalid()
⋮----
"""Tests validation errors for JSONRPCMessage."""
# Incorrect jsonrpc version
⋮----
JSONRPCMessage(jsonrpc='1.0', id=1)  # type: ignore
JSONRPCMessage(jsonrpc='2.0', id=1, extra_field='extra')  # type: ignore
# Invalid ID type (e.g., list) - Pydantic should catch this based on type hints
⋮----
JSONRPCMessage(jsonrpc='2.0', id=[1, 2])  # type: ignore
def test_file_base_valid()
⋮----
"""Tests successful validation of FileBase."""
# No optional fields
base1 = FileBase()
⋮----
# With mimeType only
base2 = FileBase(mimeType='image/png')
⋮----
# With name only
base3 = FileBase(name='document.pdf')
⋮----
# With both fields
base4 = FileBase(mimeType='application/json', name='data.json')
⋮----
def test_file_base_invalid()
⋮----
"""Tests validation errors for FileBase."""
FileBase(extra_field='allowed')  # type: ignore
# Incorrect type for mimeType
⋮----
FileBase(mimeType=123)  # type: ignore
⋮----
# Incorrect type for name
⋮----
FileBase(name=['list', 'is', 'wrong'])  # type: ignore
⋮----
def test_part_base_valid() -> None
⋮----
"""Tests successful validation of PartBase."""
# No optional fields (metadata is None)
base1 = PartBase()
⋮----
# With metadata
meta_data: dict[str, Any] = {'source': 'test', 'timestamp': 12345}
base2 = PartBase(metadata=meta_data)
⋮----
def test_part_base_invalid()
⋮----
"""Tests validation errors for PartBase."""
PartBase(extra_field='allowed')  # type: ignore
⋮----
PartBase(metadata='not_a_dict')  # type: ignore
⋮----
def test_a2a_error_validation_and_serialization() -> None
⋮----
"""Tests validation and serialization of the A2AError RootModel."""
# 1. Test JSONParseError
json_parse_instance = JSONParseError()
json_parse_data = json_parse_instance.model_dump(exclude_none=True)
a2a_err_parse = A2AError.model_validate(json_parse_data)
⋮----
# 2. Test InvalidRequestError
invalid_req_instance = InvalidRequestError()
invalid_req_data = invalid_req_instance.model_dump(exclude_none=True)
a2a_err_invalid_req = A2AError.model_validate(invalid_req_data)
⋮----
# 3. Test MethodNotFoundError
method_not_found_instance = MethodNotFoundError()
method_not_found_data = method_not_found_instance.model_dump(
a2a_err_method = A2AError.model_validate(method_not_found_data)
⋮----
# 4. Test InvalidParamsError
invalid_params_instance = InvalidParamsError()
invalid_params_data = invalid_params_instance.model_dump(exclude_none=True)
a2a_err_params = A2AError.model_validate(invalid_params_data)
⋮----
# 5. Test InternalError
internal_err_instance = InternalError()
internal_err_data = internal_err_instance.model_dump(exclude_none=True)
a2a_err_internal = A2AError.model_validate(internal_err_data)
⋮----
# 6. Test TaskNotFoundError
task_not_found_instance = TaskNotFoundError(data={'taskId': 't1'})
task_not_found_data = task_not_found_instance.model_dump(exclude_none=True)
a2a_err_task_nf = A2AError.model_validate(task_not_found_data)
⋮----
# 7. Test TaskNotCancelableError
task_not_cancelable_instance = TaskNotCancelableError()
task_not_cancelable_data = task_not_cancelable_instance.model_dump(
a2a_err_task_nc = A2AError.model_validate(task_not_cancelable_data)
⋮----
# 8. Test PushNotificationNotSupportedError
push_not_supported_instance = PushNotificationNotSupportedError()
push_not_supported_data = push_not_supported_instance.model_dump(
a2a_err_push_ns = A2AError.model_validate(push_not_supported_data)
⋮----
# 9. Test UnsupportedOperationError
unsupported_op_instance = UnsupportedOperationError()
unsupported_op_data = unsupported_op_instance.model_dump(exclude_none=True)
a2a_err_unsupported = A2AError.model_validate(unsupported_op_data)
⋮----
# 10. Test ContentTypeNotSupportedError
content_type_err_instance = ContentTypeNotSupportedError()
content_type_err_data = content_type_err_instance.model_dump(
a2a_err_content = A2AError.model_validate(content_type_err_data)
⋮----
# 11. Test invalid data (doesn't match any known error code/structure)
invalid_data: dict[str, Any] = {'code': -99999, 'message': 'Unknown error'}
⋮----
def test_subclass_enums() -> None
⋮----
"""validate subtype enum types"""

================
File: .coveragerc
================
[run]
branch = True
omit = 
    */tests/*
    */site-packages/*
    */__init__.py
    */noxfile.py*
    src/a2a/grpc/*

[report]
exclude_lines =
    pragma: no cover
    import
    def __repr__
    raise NotImplementedError
    if TYPE_CHECKING
    @abstractmethod
    pass
    raise ImportError

================
File: .git-blame-ignore-revs
================
# Template taken from https://github.com/v8/v8/blob/master/.git-blame-ignore-revs.
#
# This file contains a list of git hashes of revisions to be ignored by git blame. These
# revisions are considered "unimportant" in that they are unlikely to be what you are
# interested in when blaming. Most of these will probably be commits related to linting
# and code formatting.
#
# Instructions:
# - Only large (generally automated) reformatting or renaming CLs should be
#   added to this list. Do not put things here just because you feel they are
#   trivial or unimportant. If in doubt, do not put it on this list.
# - Precede each revision with a comment containing the PR title and number.
#   For bulk work over many commits, place all commits in a block with a single
#   comment at the top describing the work done in those commits.
# - Only put full 40-character hashes on this list (not short hashes or any
#   other revision reference).
# - Append to the bottom of the file (revisions should be in chronological order
#   from oldest to newest).
# - Because you must use a hash, you need to append to this list in a follow-up
#   PR to the actual reformatting PR that you are trying to ignore.
193693836e1ed8cd361e139668323d2e267a9eaa

================
File: .gitignore
================
.DS_Store
__pycache__
.env
.coverage
.mypy_cache
.pytest_cache
.ruff_cache
.venv
coverage.xml
.nox
spec.json

================
File: .jscpd.json
================
{
  "ignore": ["**/.github/**", "**/.git/**", "**/tests/**", "**/src/a2a/grpc/**", "**/.nox/**", "**/.venv/**"],
  "threshold": 3,
  "reporters": ["html", "markdown"]
}

================
File: .mypy.ini
================
[mypy]
exclude = src/a2a/grpc/
disable_error_code = import-not-found,annotation-unchecked,import-untyped

[mypy-examples.*]
follow_imports = skip

================
File: .pre-commit-config.yaml
================
---
repos:
  # ===============================================
  # Pre-commit standard hooks (general file cleanup)
  # ===============================================
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace  # Removes extra whitespace at the end of lines
      - id: end-of-file-fixer  # Ensures files end with a newline
      - id: check-yaml  # Checks YAML file syntax (before formatting)
      - id: check-toml  # Checks TOML file syntax (before formatting)
      - id: check-added-large-files  # Prevents committing large files
        args: [--maxkb=500]  # Example: Limit to 500KB
      - id: check-merge-conflict  # Checks for merge conflict strings
      - id: detect-private-key  # Detects accidental private key commits
  # Formatter and linter for TOML files
  - repo: https://github.com/ComPWA/taplo-pre-commit
    rev: v0.9.3
    hooks:
      - id: taplo-format
      - id: taplo-lint
  # YAML files
  - repo: https://github.com/lyz-code/yamlfix
    rev: 1.17.0
    hooks:
      - id: yamlfix
  # ===============================================
  # Python Hooks
  # ===============================================
  # no_implicit_optional for ensuring explicit Optional types
  - repo: https://github.com/hauntsaninja/no_implicit_optional
    rev: '1.4'
    hooks:
      - id: no_implicit_optional
        args: [--use-union-or]
  # Pyupgrade for upgrading Python syntax to newer versions
  - repo: https://github.com/asottile/pyupgrade
    rev: v3.20.0
    hooks:
      - id: pyupgrade
        args: [--py310-plus]  # Target Python 3.10+ syntax, matching project's target
  # Autoflake for removing unused imports and variables
  - repo: https://github.com/pycqa/autoflake
    rev: v2.3.1
    hooks:
      - id: autoflake
        args: [--in-place, --remove-all-unused-imports]
  # Ruff for linting and formatting
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.12.0
    hooks:
      - id: ruff
        args: [--fix, --exit-zero]  # Apply fixes, and exit with 0 even if files were modified
        exclude: ^src/a2a/grpc/
      - id: ruff-format
        exclude: ^src/a2a/grpc/
  # Keep uv.lock in sync
  - repo: https://github.com/astral-sh/uv-pre-commit
    rev: 0.7.13
    hooks:
      - id: uv-lock
  # Commitzen for conventional commit messages
  - repo: https://github.com/commitizen-tools/commitizen
    rev: v4.8.3
    hooks:
      - id: commitizen
        stages: [commit-msg]
  # Gitleaks
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.27.2
    hooks:
      - id: gitleaks

================
File: .python-version
================
3.10

================
File: .ruff.toml
================
#################################################################################
#
# Ruff linter and code formatter for A2A
#
# This file follows the standards in Google Python Style Guide
# https://google.github.io/styleguide/pyguide.html
#

line-length = 80 # Google Style Guide §3.2: 80 columns
indent-width = 4 # Google Style Guide §3.4: 4 spaces

target-version = "py310" # Minimum Python version

[lint]
ignore = [
    "COM812", # Trailing comma missing.
    "FBT001", # Boolean positional arg in function definition
    "FBT002", # Boolean default value in function definition
    "D203",   # 1 blank line required before class docstring (Google: 0)
    "D213",   # Multi-line docstring summary should start at the second line (Google: first line)
    "D100",   # Ignore Missing docstring in public module (often desired at top level __init__.py)
    "D104",   # Ignore Missing docstring in public package (often desired at top level __init__.py)
    "D107",   # Ignore Missing docstring in __init__ (use class docstring)
    "TD002",  # Ignore Missing author in TODOs (often not required)
    "TD003",  # Ignore Missing issue link in TODOs (often not required/available)
    "T201",   # Ignore print presence
    "RUF012", # Ignore Mutable class attributes should be annotated with `typing.ClassVar`
    "E501",   # Ignore line length (handled by Ruff's dynamic line length)
    "ANN002",
    "ANN003",
    "ANN401",
]

select = [
    "E",  # pycodestyle errors (PEP 8)
    "W",  # pycodestyle warnings (PEP 8)
    "F",  # Pyflakes (logical errors, unused imports/variables)
    "I",  # isort (import sorting - Google Style §3.1.2)
    "D",  # pydocstyle (docstring conventions - Google Style §3.8)
    "N",  # pep8-naming (naming conventions - Google Style §3.16)
    "UP", # pyupgrade (use modern Python syntax)
    "ANN",# flake8-annotations (type hint usage/style - Google Style §2.22)
    "A",  # flake8-builtins (avoid shadowing builtins)
    "B",  # flake8-bugbear (potential logic errors & style issues - incl. mutable defaults B006, B008)
    "C4", # flake8-comprehensions (unnecessary list/set/dict comprehensions)
    "ISC",# flake8-implicit-str-concat (disallow implicit string concatenation across lines)
    "T20",# flake8-print (discourage `print` - prefer logging)
    "SIM",# flake8-simplify (simplify code, e.g., `if cond: return True else: return False`)
    "PTH",# flake8-use-pathlib (use pathlib instead of os.path where possible)
    "PL", # Pylint rules ported to Ruff (PLC, PLE, PLR, PLW)
    "PIE",# flake8-pie (misc code improvements, e.g., no-unnecessary-pass)
    "RUF",# Ruff-specific rules (e.g., RUF001-003 ambiguous unicode, RUF013 implicit optional)
    "RET",# flake8-return (consistency in return statements)
    "SLF",# flake8-self (check for private member access via `self`)
    "TID",# flake8-tidy-imports (relative imports, banned imports - configure if needed)
    "YTT",# flake8-boolean-trap (checks for boolean positional arguments, truthiness tests - Google Style §3.10)
    "TD", # flake8-todos (check TODO format - Google Style §3.7)
    "TCH",# flake8-type-checking (helps manage TYPE_CHECKING blocks and imports)
    "PYI",# flake8-pyi (best practices for .pyi stub files, some rules are useful for .py too)
]

exclude = [
    ".bzr",
    ".direnv",
    ".eggs",
    ".git",
    ".hg",
    ".mypy_cache",
    ".nox",
    ".pants.d",
    ".pytype",
    ".ruff_cache",
    ".svn",
    ".tox",
    ".venv",
    "__pypackages__",
    "_build",
    "buck-out",
    "build",
    "dist",
    "node_modules",
    "venv",
    "*/migrations/*",
    "noxfile.py",
    "src/a2a/grpc/**",
    "tests/**",
]

[lint.isort]
#force-sort-within-sections = true
#combine-as-imports = true
case-sensitive = true
#force-single-line = false
#known-first-party = []
#known-third-party = []
lines-after-imports = 2
lines-between-types = 1
#no-lines-before = ["LOCALFOLDER"]
#required-imports = []
#section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]

[lint.pydocstyle]
convention = "google"
ignore-decorators = ["typing.overload", "abc.abstractmethod"]

[lint.flake8-annotations]
mypy-init-return = true
allow-star-arg-any = true

[lint.pep8-naming]
ignore-names = ["test_*", "setUp", "tearDown", "mock_*"]
classmethod-decorators = ["classmethod", "pydantic.validator", "pydantic.root_validator"]
staticmethod-decorators = ["staticmethod"]

[lint.flake8-tidy-imports]
ban-relative-imports = "all" # Google generally prefers absolute imports (§3.1.2)

[lint.flake8-quotes]
docstring-quotes = "double"
inline-quotes = "single"

[lint.per-file-ignores]
"__init__.py" = ["F401", "D", "ANN"]  # Ignore unused imports in __init__.py
"*_test.py" = [
    "D",      # All pydocstyle rules
    "ANN",    # Missing type annotation for function argument
    "RUF013", # Implicit optional type in test function signatures
    "S101",   # Use of `assert` detected (expected in tests)
    "PLR2004",
    "SLF001",
]
"test_*.py" = [
    "D",
    "ANN",
    "RUF013",
    "S101",
    "PLR2004",
    "SLF001",
]
"types.py" = ["D", "E501", "N815"]  # Ignore docstring and annotation issues in types.py
"proto_utils.py" = ["D102", "PLR0911"]
"helpers.py" = ["ANN001", "ANN201", "ANN202"]

[format]
exclude = [
    "types.py",
    "src/a2a/grpc/**",
]
docstring-code-format = true
docstring-code-line-length = "dynamic" # Or set to 80
quote-style = "single"
indent-style = "space"

================
File: buf.gen.yaml
================
---
version: v2
inputs:
  - git_repo: https://github.com/a2aproject/A2A.git
    ref: main
    subdir: specification/grpc
managed:
  enabled: true
# Python Generation
# Using remote plugins. To use local plugins replace remote with local
# pip install protobuf grpcio-tools
# Optionally, install plugin to generate stubs for grpc services
# pip install mypy-protobuf
# Generate python protobuf code
# - local: protoc-gen-python
# - out: src/python
# Generate gRPC stubs
# - local: protoc-gen-grpc-python
# - out: src/python
plugins:
  # Generate python protobuf related code
  # Generates *_pb2.py files, one for each .proto
  - remote: buf.build/protocolbuffers/python:v29.3
    out: src/a2a/grpc
  # Generate python service code.
  # Generates *_pb2_grpc.py
  - remote: buf.build/grpc/python
    out: src/a2a/grpc
  # Generates *_pb2.pyi files.
  - remote: buf.build/protocolbuffers/pyi
    out: src/a2a/grpc

================
File: CHANGELOG.md
================
# Changelog

## [0.2.8](https://github.com/a2aproject/a2a-python/compare/v0.2.7...v0.2.8) (2025-06-12)


### Features

* Add HTTP Headers to ServerCallContext for Improved Handler Access ([#182](https://github.com/a2aproject/a2a-python/issues/182)) ([d5e5f5f](https://github.com/a2aproject/a2a-python/commit/d5e5f5f7e7a3cab7de13cff545a874fc58d85e46))
* Update A2A types from specification 🤖 ([#191](https://github.com/a2aproject/a2a-python/issues/191)) ([174230b](https://github.com/a2aproject/a2a-python/commit/174230bf6dfb6bf287d233a101b98cc4c79cad19))


### Bug Fixes

* Add `protobuf==6.31.1` to dependencies ([#189](https://github.com/a2aproject/a2a-python/issues/189)) ([ae1c31c](https://github.com/a2aproject/a2a-python/commit/ae1c31c1da47f6965c02e0564dc7d3791dd03e2c)), closes [#185](https://github.com/a2aproject/a2a-python/issues/185)

## [0.2.7](https://github.com/a2aproject/a2a-python/compare/v0.2.6...v0.2.7) (2025-06-11)


### Features

* Update A2A types from specification 🤖 ([#179](https://github.com/a2aproject/a2a-python/issues/179)) ([3ef4240](https://github.com/a2aproject/a2a-python/commit/3ef42405f6096281fe90b1df399731bd009bde12))

## [0.2.6](https://github.com/a2aproject/a2a-python/compare/v0.2.5...v0.2.6) (2025-06-09)


### ⚠ BREAKING CHANGES

* Add FastAPI JSONRPC Application ([#104](https://github.com/a2aproject/a2a-python/issues/104))

### Features

* Add FastAPI JSONRPC Application ([#104](https://github.com/a2aproject/a2a-python/issues/104)) ([0e66e1f](https://github.com/a2aproject/a2a-python/commit/0e66e1f81f98d7e2cf50b1c100e35d13ad7149dc))
* Add gRPC server and client support ([#162](https://github.com/a2aproject/a2a-python/issues/162)) ([a981605](https://github.com/a2aproject/a2a-python/commit/a981605dbb32e87bd241b64bf2e9bb52831514d1))
* add reject method to task_updater ([#147](https://github.com/a2aproject/a2a-python/issues/147)) ([2a6ef10](https://github.com/a2aproject/a2a-python/commit/2a6ef109f8b743f8eb53d29090cdec7df143b0b4))
* Add timestamp to `TaskStatus` updates on `TaskUpdater` ([#140](https://github.com/a2aproject/a2a-python/issues/140)) ([0c9df12](https://github.com/a2aproject/a2a-python/commit/0c9df125b740b947b0e4001421256491b5f87920))
* **spec:** Add an optional iconUrl field to the AgentCard 🤖 ([a1025f4](https://github.com/a2aproject/a2a-python/commit/a1025f406acd88e7485a5c0f4dd8a42488c41fa2))


### Bug Fixes

* Correctly adapt starlette BaseUser to A2A User ([#133](https://github.com/a2aproject/a2a-python/issues/133)) ([88d45eb](https://github.com/a2aproject/a2a-python/commit/88d45ebd935724e6c3ad614bf503defae4de5d85))
* Event consumer should stop on input_required ([#167](https://github.com/a2aproject/a2a-python/issues/167)) ([51c2d8a](https://github.com/a2aproject/a2a-python/commit/51c2d8addf9e89a86a6834e16deb9f4ac0e05cc3))
* Fix Release Version ([#161](https://github.com/a2aproject/a2a-python/issues/161)) ([011d632](https://github.com/a2aproject/a2a-python/commit/011d632b27b201193813ce24cf25e28d1335d18e))
* generate StrEnum types for enums ([#134](https://github.com/a2aproject/a2a-python/issues/134)) ([0c49dab](https://github.com/a2aproject/a2a-python/commit/0c49dabcdb9d62de49fda53d7ce5c691b8c1591c))
* library should released as 0.2.6 ([d8187e8](https://github.com/a2aproject/a2a-python/commit/d8187e812d6ac01caedf61d4edaca522e583d7da))
* remove error types from enqueable events ([#138](https://github.com/a2aproject/a2a-python/issues/138)) ([511992f](https://github.com/a2aproject/a2a-python/commit/511992fe585bd15e956921daeab4046dc4a50a0a))
* **stream:** don't block event loop in EventQueue ([#151](https://github.com/a2aproject/a2a-python/issues/151)) ([efd9080](https://github.com/a2aproject/a2a-python/commit/efd9080b917c51d6e945572fd123b07f20974a64))
* **task_updater:** fix potential duplicate artifact_id from default v… ([#156](https://github.com/a2aproject/a2a-python/issues/156)) ([1f0a769](https://github.com/a2aproject/a2a-python/commit/1f0a769c1027797b2f252e4c894352f9f78257ca))


### Documentation

* remove final and metadata fields from docstring ([#66](https://github.com/a2aproject/a2a-python/issues/66)) ([3c50ee1](https://github.com/a2aproject/a2a-python/commit/3c50ee1f64c103a543c8afb6d2ac3a11063b0f43))
* Update Links to Documentation Site ([5e7d418](https://github.com/a2aproject/a2a-python/commit/5e7d4180f7ae0ebeb76d976caa5ef68b4277ce54))

## [0.2.5](https://github.com/a2aproject/a2a-python/compare/v0.2.4...v0.2.5) (2025-05-27)


### Features

* Add a User representation to ServerCallContext ([#116](https://github.com/a2aproject/a2a-python/issues/116)) ([2cc2a0d](https://github.com/a2aproject/a2a-python/commit/2cc2a0de93631aa162823d43fe488173ed8754dc))
* Add functionality for extended agent card.  ([#31](https://github.com/a2aproject/a2a-python/issues/31)) ([20f0826](https://github.com/a2aproject/a2a-python/commit/20f0826a2cb9b77b89b85189fd91e7cd62318a30))
* Introduce a ServerCallContext ([#94](https://github.com/a2aproject/a2a-python/issues/94)) ([85b521d](https://github.com/a2aproject/a2a-python/commit/85b521d8a790dacb775ef764a66fbdd57b180da3))


### Bug Fixes

* fix hello world example for python 3.12 ([#98](https://github.com/a2aproject/a2a-python/issues/98)) ([536e4a1](https://github.com/a2aproject/a2a-python/commit/536e4a11f2f32332968a06e7d0bc4615e047a56c))
* Remove unused dependencies and update py version ([#119](https://github.com/a2aproject/a2a-python/issues/119)) ([9f8bc02](https://github.com/a2aproject/a2a-python/commit/9f8bc023b45544942583818968f3d320e5ff1c3b))
* Update hello world test client to match sdk behavior. Also down-level required python version ([#117](https://github.com/a2aproject/a2a-python/issues/117)) ([04c7c45](https://github.com/a2aproject/a2a-python/commit/04c7c452f5001d69524d94095d11971c1e857f75))
* Update the google adk demos to use ADK v1.0 ([#95](https://github.com/a2aproject/a2a-python/issues/95)) ([c351656](https://github.com/a2aproject/a2a-python/commit/c351656a91c37338668b0cd0c4db5fedd152d743))


### Documentation

* Update README for Python 3.10+ support ([#90](https://github.com/a2aproject/a2a-python/issues/90)) ([e0db20f](https://github.com/a2aproject/a2a-python/commit/e0db20ffc20aa09ee68304cc7e2a67c32ecdd6a8))

## [0.2.4](https://github.com/a2aproject/a2a-python/compare/v0.2.3...v0.2.4) (2025-05-22)

### Features

* Update to support python 3.10 ([#85](https://github.com/a2aproject/a2a-python/issues/85)) ([fd9c3b5](https://github.com/a2aproject/a2a-python/commit/fd9c3b5b0bbef509789a701171d95f690c84750b))


### Bug Fixes

* Throw exception for task_id mismatches ([#70](https://github.com/a2aproject/a2a-python/issues/70)) ([a9781b5](https://github.com/a2aproject/a2a-python/commit/a9781b589075280bfaaab5742d8b950916c9de74))

## [0.2.3](https://github.com/a2aproject/a2a-python/compare/v0.2.2...v0.2.3) (2025-05-20)


### Features

* Add request context builder with referenceTasks ([#56](https://github.com/a2aproject/a2a-python/issues/56)) ([f20bfe7](https://github.com/a2aproject/a2a-python/commit/f20bfe74b8cc854c9c29720b2ea3859aff8f509e))

## [0.2.2](https://github.com/a2aproject/a2a-python/compare/v0.2.1...v0.2.2) (2025-05-20)


### Documentation

* Write/Update Docstrings for Classes/Methods ([#59](https://github.com/a2aproject/a2a-python/issues/59)) ([9f773ef](https://github.com/a2aproject/a2a-python/commit/9f773eff4dddc4eec723d519d0050f21b9ccc042))

================
File: CODE_OF_CONDUCT.md
================
# Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of
experience, education, socio-economic status, nationality, personal appearance,
race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

- The use of sexualized language or imagery and unwelcome sexual attention or
  advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
  address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, or to ban temporarily or permanently any
contributor for other behaviors that they deem inappropriate, threatening,
offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project email
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

This Code of Conduct also applies outside the project spaces when the Project
Steward has a reasonable belief that an individual's behavior may have a
negative impact on the project or its community.

## Conflict Resolution

We do not believe that all conflict is bad; healthy debate and disagreement
often yield positive results. However, it is never okay to be disrespectful or
to engage in behavior that violates the project's code of conduct.

If you see someone violating the code of conduct, you are encouraged to address
the behavior directly with those involved. Many issues can be resolved quickly
and easily, and this gives people more control over the outcome of their
dispute. If you are unable to resolve the matter for any reason, or if the
behavior is threatening or harassing, report it. We are dedicated to providing
an environment where participants feel welcome and safe.

Reports should be directed to _[PROJECT STEWARD NAME(s) AND EMAIL(s)]_, the
Project Steward(s) for _[PROJECT NAME]_. It is the Project Steward's duty to
receive and address reported violations of the code of conduct. They will then
work with a committee consisting of representatives from the Open Source
Programs Office and the Google Open Source Strategy team. If for any reason you
are uncomfortable reaching out to the Project Steward, please email
opensource@google.com.

We will investigate every complaint, but you may not receive a direct response.
We will use our discretion in determining when and how to follow up on reported
incidents, which may range from not taking action to permanent expulsion from
the project and project-sponsored spaces. We will notify the accused of the
report and provide them an opportunity to discuss it before any action is taken.
The identity of the reporter will be omitted from the details of the report
supplied to the accused. In potentially harmful situations, such as ongoing
harassment or threats to anyone's safety, we may take action without notice.

## Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
available at
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

Note: A version of this file is also available in the
[New Project repository](https://github.com/google/new-project/blob/master/docs/code-of-conduct.md).

================
File: CONTRIBUTING.md
================
# How to contribute

We'd love to accept your patches and contributions to this project.

## Before you begin

### Review our community guidelines

This project follows
[Google's Open Source Community Guidelines](https://opensource.google/conduct/).

## Contribution process

### Code reviews

All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.

### Contributor Guide

You may follow these steps to contribute:

1. **Fork the official repository.** This will create a copy of the official repository in your own account.
2. **Sync the branches.** This will ensure that your copy of the repository is up-to-date with the latest changes from the official repository.
3. **Work on your forked repository's feature branch.** This is where you will make your changes to the code.
4. **Commit your updates on your forked repository's feature branch.** This will save your changes to your copy of the repository.
5. **Submit a pull request to the official repository's main branch.** This will request that your changes be merged into the official repository.
6. **Resolve any linting errors.** This will ensure that your changes are formatted correctly.

Here are some additional things to keep in mind during the process:

- **Test your changes.** Before you submit a pull request, make sure that your changes work as expected.
- **Be patient.** It may take some time for your pull request to be reviewed and merged.

---

## For Google Employees

Complete the following steps to register your GitHub account and be added as a contributor to this repository.

1. Register your GitHub account at [go/GitHub](http://go/github).

================
File: LICENSE
================
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.

================
File: noxfile.py
================
# pylint: skip-file
# type: ignore
# -*- coding: utf-8 -*-
#
# Copyright 2025 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
⋮----
#     https://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.
⋮----
DEFAULT_PYTHON_VERSION = '3.10'
CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute()
⋮----
# Error if a python version is missing
⋮----
@nox.session(python=DEFAULT_PYTHON_VERSION)
def format(session) -> None
⋮----
"""Format Python code using autoflake, pyupgrade, and ruff."""
# Sort Spelling Allowlist
spelling_allow_file = '.github/actions/spelling/allow.txt'
⋮----
unique_words = sorted(set(file))
⋮----
format_all = False
⋮----
lint_paths_py = ['.']
⋮----
target_branch = 'origin/main'
unstaged_files = subprocess.run(
staged_files = subprocess.run(
committed_files = subprocess.run(
changed_files = sorted(
lint_paths_py = [

================
File: pyproject.toml
================
[project]
name = "a2a-sdk"
dynamic = ["version"]
description = "A2A Python SDK"
readme = "README.md"
license = { file = "LICENSE" }
authors = [{ name = "Google LLC", email = "googleapis-packages@google.com" }]
requires-python = ">=3.10"
keywords = ["A2A", "A2A SDK", "A2A Protocol", "Agent2Agent"]
dependencies = [
  "fastapi>=0.115.2",
  "httpx>=0.28.1",
  "httpx-sse>=0.4.0",
  "google-api-core>=1.26.0",
  "opentelemetry-api>=1.33.0",
  "opentelemetry-sdk>=1.33.0",
  "pydantic>=2.11.3",
  "sse-starlette",
  "starlette",
  "grpcio>=1.60",
  "grpcio-tools>=1.60",
  "grpcio_reflection>=1.7.0",
  "protobuf==5.29.5",
]

classifiers = [
  "Intended Audience :: Developers",
  "Programming Language :: Python",
  "Programming Language :: Python :: 3",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: 3.13",
  "Operating System :: OS Independent",
  "Topic :: Software Development :: Libraries :: Python Modules",
  "License :: OSI Approved :: Apache Software License",
]

[project.urls]
homepage = "https://a2aproject.github.io/A2A/"
repository = "https://github.com/a2aproject/a2a-python"
changelog = "https://github.com/a2aproject/a2a-python/blob/main/CHANGELOG.md"
documentation = "https://a2aproject.github.io/A2A/sdk/python/"

[tool.hatch.build.targets.wheel]
packages = ["src/a2a"]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_functions = "test_*"
addopts = "-ra --strict-markers"
markers = [
  "asyncio: mark a test as a coroutine that should be run by pytest-asyncio",
]

[tool.pytest-asyncio]
mode = "strict"

[build-system]
requires = ["hatchling", "uv-dynamic-versioning"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "uv-dynamic-versioning"

[tool.hatch.build.targets.sdist]
exclude = ["tests/"]

[tool.uv-dynamic-versioning]
vcs = "git"
style = "pep440"

[dependency-groups]
dev = [
  "datamodel-code-generator>=0.30.0",
  "mypy>=1.15.0",
  "pytest>=8.3.5",
  "pytest-asyncio>=0.26.0",
  "pytest-cov>=6.1.1",
  "pytest-mock>=3.14.0",
  "respx>=0.20.2",
  "ruff>=0.11.6",
  "uv-dynamic-versioning>=0.8.2",
  "types-protobuf",
  "types-requests",
  "pre-commit",
]

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true

[tool.pyright]
include = ["src"]
exclude = [
  "**/__pycache__",
  "**/dist",
  "**/build",
  "**/node_modules",
  "**/venv",
  "**/.venv",
  "src/a2a/grpc/",
]
reportMissingImports = "none"
reportMissingModuleSource = "none"

================
File: README.md
================
# A2A Python SDK

[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
[![PyPI version](https://img.shields.io/pypi/v/a2a-sdk)](https://pypi.org/project/a2a-sdk/)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/a2a-sdk)
[![PyPI - Downloads](https://img.shields.io/pypi/dw/a2a-sdk)](https://pypistats.org/packages/a2a-sdk)

<!-- markdownlint-disable no-inline-html -->

<html>
   <h2 align="center">
   <img src="https://raw.githubusercontent.com/a2aproject/A2A/refs/heads/main/docs/assets/a2a-logo-black.svg" width="256" alt="A2A Logo"/>
   </h2>
   <h3 align="center">A Python library that helps run agentic applications as A2AServers following the <a href="https://a2aproject.github.io/A2A">Agent2Agent (A2A) Protocol</a>.</h3>
</html>

<!-- markdownlint-enable no-inline-html -->

## Installation

You can install the A2A SDK using either `uv` or `pip`.

## Prerequisites

- Python 3.10+
- `uv` (optional, but recommended) or `pip`

### Using `uv`

When you're working within a uv project or a virtual environment managed by uv, the preferred way to add packages is using uv add.

```bash
uv add a2a-sdk
```

### Using `pip`

If you prefer to use pip, the standard Python package installer, you can install `a2a-sdk` as follows

```bash
pip install a2a-sdk
```

## Examples

### [Helloworld Example](https://github.com/a2aproject/a2a-samples/tree/main/samples/python/agents/helloworld)

1. Run Remote Agent

   ```bash
   git clone https://github.com/a2aproject/a2a-samples.git
   cd a2a-samples/samples/python/agents/helloworld
   uv run .
   ```

2. In another terminal, run the client

   ```bash
   cd a2a-samples/samples/python/agents/helloworld
   uv run test_client.py
   ```

3. You can validate your agent using the agent inspector. Follow the instructions at the [a2a-inspector](https://github.com/a2aproject/a2a-inspector) repo. 

You can also find more Python samples [here](https://github.com/a2aproject/a2a-samples/tree/main/samples/python) and JavaScript samples [here](https://github.com/a2aproject/a2a-samples/tree/main/samples/js).

## License

This project is licensed under the terms of the [Apache 2.0 License](https://raw.githubusercontent.com/a2aproject/a2a-python/refs/heads/main/LICENSE).

## Contributing

See [CONTRIBUTING.md](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md) for contribution guidelines.

================
File: SECURITY.md
================
# Security Policy

To report a security issue, please use [g.co/vulnz](https://g.co/vulnz).

The Google Security Team will respond within 5 working days of your report on g.co/vulnz.

We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue.




================================================================
End of Codebase
================================================================
