-
Notifications
You must be signed in to change notification settings - Fork 0
fix(a2a): serve agent-card discovery at the standard well-known path #319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
37be358
ada0cff
5bd11af
4349727
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,6 +62,13 @@ function extractWorkspaceHint(c: Context<AppEnv>): string | null { | |
| const explicit = c.req.query('workspace'); | ||
| if (explicit) return explicit; | ||
|
|
||
| // An explicit path selector must beat host inference. Previously the host | ||
| // branch was consulted first, so on any authority with three or more labels | ||
| // — which the Relay identifier profile requires — the documented | ||
| // `/:workspace/.well-known/agent-card.json` route could never take effect. | ||
| const pathWorkspace = c.req.param('workspace'); | ||
| if (pathWorkspace) return pathWorkspace; | ||
|
|
||
| const host = c.req.header('Host') ?? new URL(c.req.url).host; | ||
| const hostname = host.split(':')[0] ?? ''; | ||
| const hostSegments = hostname.split('.').filter(Boolean); | ||
|
|
@@ -70,7 +77,7 @@ function extractWorkspaceHint(c: Context<AppEnv>): string | null { | |
| return hostSegments[0]!; | ||
| } | ||
|
|
||
| return c.req.param('workspace') || null; | ||
| return null; | ||
| } | ||
|
|
||
| function extractTargetAgentName(params: Record<string, unknown> | undefined, fallbackContextId?: string): string | null { | ||
|
|
@@ -228,6 +235,38 @@ async function handleWorkspaceAgentCard(c: Context<AppEnv>) { | |
| } | ||
| } | ||
|
|
||
| // A self-hosted, single-tenant deployment must answer the standard bare | ||
| // well-known URL without requiring a Relaycast-specific query parameter. | ||
| // | ||
| // Two guards, and the distinction between them is deliberate: | ||
| // | ||
| // 1. An *explicit* selector is caller intent, so a misspelled `?workspace=` | ||
| // or `/:workspace/` must 404 rather than silently resolving to a | ||
| // different tenant. Host-label inference is not caller intent — it is a | ||
| // hosted workspace-per-subdomain convention, and on any authority with | ||
| // three or more labels it always produces a candidate. Treating it as an | ||
| // explicit selector would mean the fallback never fires on exactly the | ||
| // deployments it exists for. | ||
| // 2. The row cap is what makes that safe: with two or more workspaces the | ||
| // fallback declines rather than guessing, so there is no tenant boundary | ||
| // to cross. It is `limit(2)` rather than a count so a large table is | ||
| // never scanned. | ||
| // | ||
| // Net effect: on a multi-tenant deployment an unresolved host label 404s; | ||
| // on a single-tenant one it serves the only workspace there is. The card is | ||
| // unauthenticated by design (A2A discovery), so this exposes nothing that | ||
| // the standard well-known path is not already meant to publish. | ||
| if ( | ||
| !workspace | ||
| && !c.req.query('workspace') | ||
| && !c.req.param('workspace') | ||
|
Comment on lines
+260
to
+262
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a deployment has exactly one workspace and a caller sends Useful? React with 👍 / 👎. |
||
| ) { | ||
| const candidates = await db.select().from(workspaces).limit(2); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This changes the public agent-card resolution contract by adding sole-workspace fallback and making the path selector authoritative, but AGENTS.md reference: AGENTS.md:L35-L35 Useful? React with 👍 / 👎. |
||
| if (candidates.length === 1) { | ||
| workspace = candidates[0]!; | ||
| } | ||
| } | ||
|
Comment on lines
+259
to
+268
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Discovery endpoint behaviour changed without matching API documentation updates The public agent-card discovery endpoint changed how it picks a workspace (new sole-workspace fallback added at Repository documentation rule and what is missing
Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback.
Comment on lines
+259
to
+268
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟨 Unauthenticated agent-card discovery can leak a workspace's agent roster when host selector is wrong The unauthenticated discovery handler now falls back to the only workspace in the database whenever no query/path selector is present ( Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| if (!workspace) { | ||
| return jsonNotFound(c, 'workspace_not_found', 'Workspace could not be inferred from request. Provide an Authorization header or ?workspace= query param.'); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: An empty
?workspace=selector is treated as absent and returns the sole workspace card rather than failing closed. Check query-key presence in the fallback guard so supplied-but-empty selectors produceworkspace_not_found.Prompt for AI agents