fix(a2a-client): save every file the remote agent returns, per task, with size and type
File parts were only taken from artifacts as inline bytes up to 10 MB; parts in the status message or the history were dropped silently, URL parts were never downloaded, and a plain message reply lost its files. Files now go to inbox/a2a/<taskId>/ from artifacts, status message and recent agent messages (deduplicated), URLs are streamed to disk (agent key only on the agent's origin, else https without it), the cap is the new MaxReceivedFileBytes (200 MB), and results list path, bytes and media type or why a file was not saved. Re-saving a task overwrites, so action=status refetches files.
This commit is contained in:
29
docs/a2a.md
29
docs/a2a.md
@@ -271,7 +271,7 @@ does). Then:
|
||||
|
||||
- **Settled** (`completed`, `failed`, `canceled`, `rejected`, or waiting for us: `input-required`, `auth-required`):
|
||||
the result is `Agent "travel-bot": task <taskId> (context <contextId>) is completed.`, then the answer, then
|
||||
optional lines `Files: ...` (saved files), `Files skipped (over N bytes): ...` and `Links: ...`. The answer is
|
||||
an optional `Files:` list (see "Files out" below). The answer is
|
||||
the `response` artifact, else every text artifact, else the status message. For `input-required`/`auth-required`
|
||||
the question (status message) comes first, followed by the hint to reply with `action=send`, the agent and
|
||||
`taskId=<id>`; the model asks the user first when it doesn't know the answer.
|
||||
@@ -285,9 +285,24 @@ after resolving every symlink; directories and missing files are refused, and al
|
||||
`MaxFileBytes`. The media type comes from the extension (`.png`, `.jpg`, `.pdf`, `.txt`, `.md`, `.json`, `.csv`,
|
||||
else `application/octet-stream`).
|
||||
|
||||
Files out: file parts with inline bytes in a settled task's artifacts are saved to `<workspace>/inbox/a2a/<agent>/`
|
||||
(name from the part, sanitised; `-2`, `-3`, ... on a clash) and listed under `Files:`; a part over `MaxFileBytes` is
|
||||
skipped and listed. URL parts are listed under `Links:` and not downloaded.
|
||||
Files out: every file part of the answer is saved to `<workspace>/inbox/a2a/<taskId>/` (for a plain message reply,
|
||||
`<messageId>`), whether it sits in an artifact, in the status message or in an agent message after our last one in
|
||||
the task history. Inline bytes are written; URL parts are downloaded, streamed, with the agent's key only when the URL
|
||||
is on the agent's own origin (its `agents.json` URL or a card interface), and otherwise only over `https` and without
|
||||
the key. Each file may be at most `MaxReceivedFileBytes` (200 MB); a failed or partial download leaves nothing behind.
|
||||
Names come from the part (else the URL's last segment, else the artifact), sanitised, with `-2`, `-3`, ... when two
|
||||
parts of one answer share a name; the same part found twice (say, artifact and status message) is saved once. The
|
||||
result lists each file with its size and media type, or why it was not saved:
|
||||
|
||||
```
|
||||
Files:
|
||||
- inbox/a2a/eff0e973.../SKILL.md (5222 bytes, text/markdown)
|
||||
- crm.tar.gz (320000000 bytes, application/gzip): not saved, over 209715200 bytes
|
||||
```
|
||||
|
||||
Saving a task again overwrites its folder's files, so `action=status` with the `taskId` of a finished task fetches
|
||||
its files again (as long as the remote agent still returns them). Watch deliveries (`[A2A result from …]`) carry the
|
||||
same list.
|
||||
|
||||
**cancel** stops the watch first, so a `canceled` outcome isn't delivered again later. If the remote refuses (typically
|
||||
because the task already finished), the result says so and carries the task's outcome. If the remote can't be reached
|
||||
@@ -329,7 +344,8 @@ The delivered text is a follow-up prompt:
|
||||
[A2A result from "travel-bot", task 5c1e... (completed)]
|
||||
Context: 7f20...
|
||||
<the agent's answer>
|
||||
Files: inbox/a2a/travel-bot/itinerary.pdf
|
||||
Files:
|
||||
- inbox/a2a/5c1e.../itinerary.pdf (48213 bytes, application/pdf)
|
||||
```
|
||||
|
||||
`[A2A question from ...]` for `input-required`/`auth-required`, with the hint to answer with `send` and the `taskId`.
|
||||
@@ -374,7 +390,8 @@ sessions that also receive A2A.
|
||||
| `WaitSeconds` | `60` | How long `send` waits for a result before a watch takes over. `0` hands over at once |
|
||||
| `WatchTimeout` | `1.00:00:00` | When a watch gives up, counted from its start |
|
||||
| `CardTimeoutSeconds` | `10` | Card (and extended card) fetch timeout per agent |
|
||||
| `MaxFileBytes` | `10485760` (10 MB) | Cap on the files attached to one `send` together, and on each received file part |
|
||||
| `MaxFileBytes` | `10485760` (10 MB) | Cap on the files attached to one `send` together |
|
||||
| `MaxReceivedFileBytes` | `209715200` (200 MB) | Cap per file the remote agent sends back (inline or downloaded) |
|
||||
|
||||
The push base URL isn't a client setting: the Telegram host sets it from `A2A:PublicUrl` while the server is on and
|
||||
ignores any configured value otherwise.
|
||||
|
||||
@@ -72,7 +72,7 @@ settings use the `GoogleCalendar` section, the scheduler the `Scheduler` section
|
||||
| `A2A__Enabled` / `A2A:Enabled` | `false` | Serve other agents over A2A (see [A2A](#a2a) and [a2a.md](a2a.md)). Opens Kestrel: port 8080 in the container, `ASPNETCORE_URLS` or Kestrel's default (`http://localhost:5000`) under `dotnet run` |
|
||||
| `A2A__PublicUrl` / `A2A:PublicUrl` | — | External base URL for the agent card and push webhooks, e.g. `https://bot.example.com`. Required for push to work; see [docker.md](docker.md) |
|
||||
| `A2AClient__Enabled` / `A2AClient:Enabled` | `false` | The A2A client: the `a2a` tool in every allowed chat, for calling other agents listed in the chat's `.volcanic/a2a/agents.json`, and watches for their long tasks (see [A2A](#a2a) and [a2a.md](a2a.md#client)). Independent of `A2A__Enabled` |
|
||||
| `A2AClient__*` | see [a2a.md](a2a.md#client-configuration) | `WaitSeconds`, `WatchTimeout`, `CardTimeoutSeconds`, `MaxFileBytes` |
|
||||
| `A2AClient__*` | see [a2a.md](a2a.md#client-configuration) | `WaitSeconds`, `WatchTimeout`, `CardTimeoutSeconds`, `MaxFileBytes`, `MaxReceivedFileBytes` |
|
||||
| `A2A__*` | see [a2a.md](a2a.md#configuration) | `MaxHops`, `SessionIdleTimeout`, `MaxFileBytes`, `AllowPrivatePushUrls`, `Card:Name`, `Card:Description` |
|
||||
|
||||
Sessions are stored as usual under `~/.volcanic/agent/sessions/telegram-<chatId>/`; after a restart each chat resumes
|
||||
|
||||
Reference in New Issue
Block a user