Skip to content

fasta2a: bump to 0.6.0 with A2A spec compatibility fixes#1075

Open
jensbodal wants to merge 1 commit intoagent0ai:developmentfrom
jensbodal:local
Open

fasta2a: bump to 0.6.0 with A2A spec compatibility fixes#1075
jensbodal wants to merge 1 commit intoagent0ai:developmentfrom
jensbodal:local

Conversation

@jensbodal
Copy link

  • Bump fasta2a to 0.6.0
  • agent.json -> agent-card.json per A2A spec
  • Use camelCase wire format (messageId, contextId)
  • Set A2A_SERVER_URL to expose agent outside local network (defaults to localhost)
  • Default acceptedOutputModes to ["application/json"] when omitted
  • Return structured error to client on task failure
  • Add A2A compatibility tests

- Bump fasta2a to 0.6.0
- agent.json -> agent-card.json per A2A spec
- Use camelCase wire format (messageId, contextId)
- Set A2A_SERVER_URL to expose agent outside local network (defaults to localhost)
- Default acceptedOutputModes to ["application/json"] when omitted
- Return structured error to client on task failure
- Add A2A compatibility tests
Copilot AI review requested due to automatic review settings February 18, 2026 19:31
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request updates the fasta2a integration to version 0.6.0 and implements several A2A (Agent-to-Agent) specification compliance improvements. The changes ensure compatibility with the official A2A protocol specification, including proper naming conventions, wire format standards, and error handling patterns.

Changes:

  • Bumps fasta2a dependency from 0.5.0 to 0.6.0 and renames agent.json to agent-card.json per A2A spec
  • Implements camelCase wire format for A2A protocol fields (messageId, contextId) to comply with the specification
  • Adds A2A_SERVER_URL environment variable configuration, structured error messages on task failure, and acceptedOutputModes defaulting middleware
  • Introduces comprehensive A2A compatibility tests using the official a2a-python client library

Reviewed changes

Copilot reviewed 5 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
requirements.txt Updates fasta2a from version 0.5.0 to 0.6.0
requirements2.txt No functional change (formatting only)
requirements.dev.txt No functional change (formatting only)
python/helpers/subagents.py Renames agent.json to agent-card.json throughout, removes trailing whitespace
python/helpers/runtime.py Adds get_a2a_server_url() function to construct A2A server URLs with token
python/helpers/fasta2a_server.py Implements camelCase wire format, adds structured error handling, acceptedOutputModes defaulting, and A2A_SERVER_URL support
python/helpers/fasta2a_client.py Updates to use agent-card.json paths and camelCase wire format with backward compatibility
tests/test_fasta2a_client.py Replaces basic connectivity test with comprehensive A2A compatibility tests using official client library

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +579 to +624
# A2A allows message/send.configuration.acceptedOutputModes to be optional.
# fasta2a 0.6.0 rejects configuration without it, so default to ["application/json"].
original_receive = receive
request_buffered = False

async def receive_wrapper():
nonlocal request_buffered
if request_buffered:
return await original_receive()

body_parts: list[bytes] = []
while True:
message = await original_receive()
if message['type'] != 'http.request':
return message
body_parts.append(message.get('body', b''))
if not message.get('more_body', False):
break

request_buffered = True
full_body = b''.join(body_parts)
if not full_body:
return {'type': 'http.request', 'body': b'', 'more_body': False}

try:
data = json.loads(full_body)
changed = False
if isinstance(data, dict) and data.get('method') == 'message/send':
params = data.get('params')
if isinstance(params, dict):
configuration = params.get('configuration')
if (
isinstance(configuration, dict)
and not configuration.get('acceptedOutputModes')
):
configuration['acceptedOutputModes'] = ['application/json']
changed = True

if changed:
full_body = json.dumps(data).encode('utf-8')
except Exception as e:
_PRINTER.print(f"[A2A] Failed to normalize request payload: {e}")

return {'type': 'http.request', 'body': full_body, 'more_body': False}

receive = receive_wrapper
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two independent receive wrappers buffer the request body sequentially, creating inefficiency. The project injection wrapper (lines 498-543) buffers and modifies the body, then the acceptedOutputModes wrapper (lines 579-624) receives that already-buffered body and buffers it again before potentially modifying it further. While this works functionally, consider combining both modifications in a single wrapper to avoid double-buffering the request body and improve performance.

Copilot uses AI. Check for mistakes.
Comment on lines +183 to +186
a2a_server_url = (
str(dotenv.get_dotenv_value("A2A_SERVER_URL", 'localhost'))
)

Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_a2a_server_url function returns a URL that doesn't include the protocol scheme (http:// or https://). The function gets 'localhost' as the default value for A2A_SERVER_URL, but a valid URL should include the scheme. For example, if A2A_SERVER_URL is set to just 'example.com', the returned URL would be 'example.com/a2a/t-token', which is not a valid complete URL. Consider either requiring the scheme in the environment variable or adding logic to prepend it if missing, similar to what is done in the _get_rfc_url function at line 136.

Suggested change
a2a_server_url = (
str(dotenv.get_dotenv_value("A2A_SERVER_URL", 'localhost'))
)
a2a_server_url = str(
dotenv.get_dotenv_value("A2A_SERVER_URL", "http://localhost")
)
if "://" not in a2a_server_url:
a2a_server_url = "http://" + a2a_server_url
if a2a_server_url.endswith("/"):
a2a_server_url = a2a_server_url[:-1]

Copilot uses AI. Check for mistakes.
Comment on lines +263 to +266
a2a_server_url = (
str(dotenv.get_dotenv_value("A2A_SERVER_URL", 'localhost'))
)
a2a_url = f"{a2a_server_url}/a2a/t-{self.token}"
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URL construction logic is duplicated between runtime.py (lines 182-187) and fasta2a_server.py (lines 263-266). This duplication creates a maintenance issue where changes to the URL construction logic would need to be made in both places. Consider having fasta2a_server.py use the runtime.get_a2a_server_url function instead of duplicating the logic.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments