Skip to content

fix(a2a): build the status agent card from the request that asked for it - #12918

Merged
diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.51from
ntdatt812:fix/a2a-status-agent-card
Sep 10, 2026
Merged

diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.51from
ntdatt812:fix/a2a-status-agent-card

Conversation

@ntdatt812

Copy link
Copy Markdown
Contributor

What

/api/a2a/status builds its agent-card section by importing the
/.well-known/agent.json route handler and calling it in-process. It calls
GET() with no argument, so the card handler gets request === undefined and
getBaseUrl() cannot read request.nextUrl.origin. The card is then built
against the hardcoded http://localhost:20128 — the exact hostname the S2 fix
removed from that helper.

Effect: a gateway reached at any other host (LAN IP, reverse proxy, container
hostname) serves a status payload whose agent.url points at localhost, and the
A2A dashboard shows that URL. The dedicated /.well-known/agent.json endpoint is
correct, because Next passes it a real request — only the in-process call is wrong.

Fix: forward the status route's own request to the card handler.

Verifying the report first (#12887)

The report says the missing argument makes getBaseUrl() throw a TypeError,
the surrounding try/catch swallows it, and the panel renders
agent: null, skills: []. That half no longer reproduces on the tip.
0023a9ec0 changed the helper to

return request?.nextUrl?.origin ?? "http://localhost:20128";

so there is no throw any more and the card is returned. What that guard did was
mask the missing wiring: instead of a visible null, the endpoint now reports a
confidently wrong URL. That is what this PR fixes; getBaseUrl() is left alone.

Why no test caught it

tests/unit/a2a-enabled-route.test.ts calls statusRoute.GET() with no argument
too — the same shape as the bug — and asserts only status/enabled/online,
never the card. The route file is also outside tsconfig.typecheck-core.json's
files list, so the missing argument was never a type error.

Test

tests/unit/a2a-status-agent-card-12887.test.ts calls the status route with a real
NextRequest for http://gateway.test:9999/api/a2a/status (with
OMNIROUTE_BASE_URL unset) and asserts agent.url === "http://gateway.test:9999/a2a".
The origin is deliberately not localhost, so the hardcoded fallback cannot pass by
accident; the two branches produce different strings.

npx cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm \
  --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts \
  --test --test-force-exit tests/unit/a2a-status-agent-card-12887.test.ts \
  tests/unit/a2a-enabled-route.test.ts

before the fix: 4 pass, 1 fail — the new test, actual: 'http://localhost:20128/a2a'
after the fix: 5 pass, 0 fail

Mutation

Reverting the one wired line — agentModule.GET(request) back to
agentModule.GET() — turns the new test red again with the same
actual: 'http://localhost:20128/a2a'. The assertion is on the status route's
output, not on getBaseUrl() in isolation, so it pins the call site rather than
the helper.

Closes #12887

/api/a2a/status loads the agent-card route handler in-process and calls
GET() with no argument, so the card handler sees `request === undefined`.
getBaseUrl() then cannot read `request.nextUrl.origin` and falls back to
the hardcoded `http://localhost:20128`, which is exactly the hostname the
S2 fix removed. A gateway reached at any other host advertises a card
whose `url` points at localhost, and the dashboard shows that URL.

Forward the status route's own request to the card handler, so the card
is built from the origin the caller actually used.

Reported as cheaper-inference#12887. The crash described there (card `null`) no longer
reproduces on the tip: 0023a9e gave getBaseUrl() a `request?` guard, so
the TypeError is gone. The wiring it masked is still missing, and the
wrong-host URL is what remains.
@diegosouzapw
diegosouzapw merged commit ee21e7d into cheaper-inference:release/v3.8.51 Sep 10, 2026
8 of 16 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
… it (cheaper-inference#12918)

Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings.

Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith.
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.

fix(api): A2A dashboard: Agent Card always "not available" — /api/a2a/status calls card route handler without a request object

2 participants