Skip to content

Support for tool auth #574

@newtonnthiga

Description

@newtonnthiga

** Please make sure you read the contribution guide and file the issues in the right place. **
Contribution guide.

🔴 Required Information

Please ensure all items in this section are completed to allow for efficient
triaging. If an item is not applicable to you - please mark it as N/A

Is your feature request related to a specific problem?

adk-go currently lacks first-class support for authenticated tools that require OAuth or other user credentials. The Python ADK provides tool_context.request_credential(auth_config), generate_auth_event() in the flow, and an auth preprocessor for callbacks. adk-go has references to adk_request_credential (e.g. in contents_processor.go) and a stub authPreprocessor, but no working implementation.

As a result:

  • No tool/toolauth package: types (AuthConfig, OAuth2Credential), OAuth URL generation, token exchange, and StateDelta conventions are absent.
  • authPreprocessor is a no-op: the callback FunctionResponse for adk_request_credential is never handled; OAuth code is never exchanged for tokens.
  • REST events lack the correct format: when a tool stores auth config in StateDelta, the runner yields an event with raw tool response + StateDelta. adk-web expects longRunningToolIds and content.parts with a FunctionCall named adk_request_credential containing authConfig.exchangedAuthCredential.oauth2.authUri. The raw event does not satisfy these, so the OAuth popup never opens.

Proposed Solution

Add complete tool authentication support to adk-go, achieving parity with adk-python for the adk_request_credential protocol:

  1. tool/toolauth package — Types (AuthConfig, AuthCredential, OAuth2Credential), constants (StateDeltaKeyPrefix, CredentialStatePrefix), AuthConfigFromResponseMap, ExchangeAndStore, ExchangeAndStoreServiceAccount, GenerateAuthRequest, IsAuthRequired, ExtractAuthRequest, BuildAuthRequestEvent.
  2. authPreprocessor implementation — When a user message contains a FunctionResponse with name == adk_request_credential, parse the response into AuthConfig, call ExchangeAndStore to exchange the OAuth code for tokens and store in session state, find the original tool call by functionCallID, and re-run it via handleFunctionCalls.
  3. REST-layer auth-event transformation — Before sending each event in RunSSEHandler and RunHandler, if IsAuthRequired(event) is true, extract functionCallID and authConfig via ExtractAuthRequest, build an adk_request_credential event via BuildAuthRequestEvent (with Content.Parts = FunctionCall, LongRunningToolIDs set, authConfig in camelCase), and send that instead of the raw event.
  4. tool.Context.SessionState() — Add SessionState() session.ReadonlyState to tool.Context so tools can read credentials from session state after the OAuth callback.

Impact on your work

We run Go ADK agents with the bundled adk-web UI for development and testing. Without this feature, authenticated tools (OAuth2) do not work—the OAuth popup never appears and users cannot complete the authorization flow. This is critical for any deployment using adk-web or similar clients expecting the adk_request_credential event structure. We have a working implementation and would like to upstream it so we can consume the official release instead of maintaining a fork.


🟡 Recommended Information

Alternatives Considered

  1. Implement event emission in the flow (like Python) — Adding generate_auth_event() in base_flow.go would require detecting StateDelta auth keys and yielding an additional event. This is a larger change and could affect A2A and other transports. The REST-layer transformation is localized and achieves the same client-facing result.
  2. Client-side workaround — Modifying adk-web to also detect auth requests via StateDelta would duplicate protocol logic in every client and diverge from the Python-based adk-web.
  3. Agent-only implementation — Agents could implement tool auth in their own code, but this would require duplicating types, OAuth exchange logic, and REST transformation in every agent. Baking it into adk-go keeps behavior consistent and discoverable.

Willingness to contribute

Yes. We have implemented and tested the full feature and are willing to contribute a PR.

Proposed API / Implementation

tool/toolauth package:

// toolauth.go
const FunctionCallName = "adk_request_credential"
type AuthConfig struct {
    RawAuthCredential       *AuthCredential
    ExchangedAuthCredential *AuthCredential
    CredentialKey           string
    AuthType                AuthCredentialType
}
type OAuth2Credential struct { ClientID, AuthURI, TokenURI, RedirectURI, AuthResponseURI, State, Scopes, AccessToken, RefreshToken, ExpiresAt ... }

// constants.go
const CredentialStatePrefix = "temp:"
const StateDeltaKeyPrefix = "adk_auth_request_"

// handler.go
func AuthConfigFromResponseMap(m map[string]any) (AuthConfig, error)
func ExchangeAndStore(ctx context.Context, cfg AuthConfig, state session.State) error
func ExchangeAndStoreServiceAccount(ctx context.Context, cfg AuthConfig, state session.State) error

// auth_request.go
func GenerateAuthRequest(cfg AuthConfig) (AuthConfig, error)
func IsAuthRequired(event *session.Event) bool
func ExtractAuthRequest(event *session.Event) (functionCallID string, authConfig AuthConfig, ok bool)
func BuildAuthRequestEvent(sourceEvent *session.Event, functionCallID string, authConfig AuthConfig) *session.Event

REST controller (server/adkrest/controllers/runtime.go):

// Before sending each event in RunSSEHandler and RunHandler:
if toolauth.IsAuthRequired(event) {
    if fnCallID, authCfg, ok := toolauth.ExtractAuthRequest(event); ok {
        if authEvent := toolauth.BuildAuthRequestEvent(event, fnCallID, authCfg); authEvent != nil {
            event = authEvent
        }
    }
}

tool.Context extension:

// tool/tool.go
SessionState() session.ReadonlyState

authPreprocessor: For each FunctionResponse with name == toolauth.FunctionCallName, parse response → ExchangeAndStore → find original tool call → re-run via handleFunctionCalls.

Additional Context

adk-web expectations: For the OAuth popup to open, adk-web requires (1) e.longRunningToolIds non-empty, (2) a FunctionCall in e.content.parts whose id is in longRunningToolIds, (3) func.args.authConfig.exchangedAuthCredential.oauth2.authUri exists. The transformed event satisfies all of these.

Protocol alignment: This achieves parity with adk-python: tools store auth requests in StateDelta; the REST layer transforms them into the canonical adk_request_credential event format; the auth preprocessor handles the callback and re-runs the tool. Dependencies: golang.org/x/oauth2, google.golang.org/genai.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions