diff --git a/packages/a2a-server/src/chat-bridge/a2a-bridge-client.ts b/packages/a2a-server/src/chat-bridge/a2a-bridge-client.ts index ba0ab08176..aab777b450 100644 --- a/packages/a2a-server/src/chat-bridge/a2a-bridge-client.ts +++ b/packages/a2a-server/src/chat-bridge/a2a-bridge-client.ts @@ -27,6 +27,7 @@ import { RestTransportFactory, JsonRpcTransportFactory, } from '@a2a-js/sdk/client'; +import { GoogleAuth } from 'google-auth-library'; import { v4 as uuidv4 } from 'uuid'; import { logger } from '../utils/logger.js'; @@ -162,17 +163,37 @@ export class A2ABridgeClient { /** * Initializes the client connection to the A2A server. + * On Cloud Run (K_SERVICE is set), wraps fetch with an identity token + * for service-to-service authentication. */ async initialize(): Promise { if (this.client) return; - const resolver = new DefaultAgentCardResolver({}); + // On Cloud Run, create an authenticated fetch that adds identity tokens + let fetchImpl: typeof fetch = fetch; + if (process.env['K_SERVICE']) { + const auth = new GoogleAuth(); + const idTokenClient = await auth.getIdTokenClient(this.agentUrl); + fetchImpl = async (input, init?) => { + const authHeaders = await idTokenClient.getRequestHeaders(); + const merged = new Headers(init?.headers); + for (const [key, value] of Object.entries(authHeaders)) { + merged.set(key, value); + } + return fetch(input, { ...init, headers: merged }); + }; + logger.info( + '[ChatBridge] Using Cloud Run identity token for A2A server auth', + ); + } + + const resolver = new DefaultAgentCardResolver({ fetchImpl }); const options = ClientFactoryOptions.createFrom( ClientFactoryOptions.default, { transports: [ - new RestTransportFactory({}), - new JsonRpcTransportFactory({}), + new RestTransportFactory({ fetchImpl }), + new JsonRpcTransportFactory({ fetchImpl }), ], cardResolver: resolver, }, diff --git a/packages/a2a-server/src/chat-bridge/handler.ts b/packages/a2a-server/src/chat-bridge/handler.ts index 837c797391..16238599d8 100644 --- a/packages/a2a-server/src/chat-bridge/handler.ts +++ b/packages/a2a-server/src/chat-bridge/handler.ts @@ -100,7 +100,10 @@ export class ChatBridgeHandler { return { text: 'Error: Missing thread information.' }; } - const text = message.argumentText || message.text || ''; + // argumentText has bot mentions stripped (legacy format only). + // For Add-ons format, strip leading @mention manually. + const rawText = message.argumentText || message.text || ''; + const text = rawText.replace(/^@\S+\s*/, ''); if (!text.trim()) { return { text: "I didn't receive any text. Please try again." }; } diff --git a/packages/a2a-server/src/http/app.ts b/packages/a2a-server/src/http/app.ts index 84a3b3ac80..b20d3051db 100644 --- a/packages/a2a-server/src/http/app.ts +++ b/packages/a2a-server/src/http/app.ts @@ -77,7 +77,11 @@ const coderAgentCard: AgentCard = { }; export function updateCoderAgentCardUrl(port: number) { - coderAgentCard.url = `http://localhost:${port}/`; + // On Cloud Run, use the public service URL so remote clients can reach us + const publicUrl = process.env['CODER_AGENT_PUBLIC_URL']; + coderAgentCard.url = publicUrl + ? publicUrl.replace(/\/$/, '') + '/' + : `http://localhost:${port}/`; } async function handleExecuteCommand(