Skip to content
This repository was archived by the owner on Jun 3, 2026. It is now read-only.

feat(a2a): add clientFactory to A2AAgent for auth support - #806

Closed
agent-of-mkmeral wants to merge 2 commits into
strands-agents:mainfrom
agent-of-mkmeral:feat/a2a-client-factory-options
Closed

agent-of-mkmeral wants to merge 2 commits into
strands-agents:mainfrom
agent-of-mkmeral:feat/a2a-client-factory-options

Conversation

@agent-of-mkmeral

@agent-of-mkmeral agent-of-mkmeral commented Apr 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add clientFactory?: ClientFactory to A2AAgentConfig to allow passing a pre-configured A2A SDK ClientFactory for authentication, custom transports, interceptors, and agent card resolution.

Problem

A2AAgent creates a bare new ClientFactory() with no options, which means agent card resolution and message sending use plain unauthenticated fetch calls. This causes 403 errors when connecting to protected endpoints (SigV4, OAuth, bearer tokens).

This is the same auth bug fixed in the Python SDK via PR #2103.

Solution

Accept an optional clientFactory?: ClientFactory — a pre-configured factory instance. The factory already handles card resolution (createFromUrl) and client creation, so we just use it directly.

When not provided, a default new ClientFactory() is created (existing behavior).

Usage

import { A2AAgent } from '@strands-agents/sdk/a2a'
import { ClientFactory, ClientFactoryOptions, DefaultAgentCardResolver, createAuthenticatingFetchWithRetry } from '@a2a-js/sdk/client'

const factory = new ClientFactory(ClientFactoryOptions.createFrom(ClientFactoryOptions.default, {
  cardResolver: new DefaultAgentCardResolver({
    fetchImpl: createAuthenticatingFetchWithRetry(fetch, myAuthHandler),
  }),
}))

const agent = new A2AAgent({
  url: 'https://protected-agent.example.com',
  clientFactory: factory,
})

Changes

File Change
src/a2a/a2a-agent.ts Replace clientFactoryOptions with clientFactory: ClientFactory, simplify _getClient() to this._config.clientFactory ?? new ClientFactory()
src/a2a/__tests__/a2a-agent.test.ts Update tests: verify provided factory is used directly (no extra construction), agentCardPath works with custom factory

Key Design Decision

We accept a ClientFactory instance instead of Partial<ClientFactoryOptions> because:

  • The factory already handles everything (card resolution + client creation)
  • Users configure auth/transports/interceptors on their factory — we just use it
  • Simpler API, no need to re-expose SDK options through our config
  • Factory is reusable across multiple A2AAgent instances

Testing

  • ✅ 32 tests pass (4 factory-specific + 28 existing)
  • ✅ TypeScript build clean (zero errors in src/a2a/)
  • ✅ ESLint clean
  • ✅ Prettier formatted

Related

Add clientFactoryOptions to A2AAgentConfig to allow configuring
authentication, custom transports, interceptors, and agent card
resolvers for the underlying A2A SDK ClientFactory.

Previously, A2AAgent created a bare ClientFactory() with no options,
which meant agent card resolution and message sending used plain
unauthenticated fetch calls. This caused 403 errors when connecting
to protected endpoints (SigV4, OAuth, bearer tokens).

Users can now pass Partial<ClientFactoryOptions> which gets merged
with SDK defaults via ClientFactoryOptions.createFrom(). This enables:

- Custom card resolvers with authenticated fetch
- Per-request interceptors for dynamic auth (token refresh)
- Custom transport factories
- Transport preferences

Example:
  const agent = new A2AAgent({
    url: 'https://protected-agent.example.com',
    clientFactoryOptions: {
      cardResolver: new DefaultAgentCardResolver({
        fetchImpl: createAuthenticatingFetchWithRetry(fetch, handler),
      }),
    },
  })
@mkmeral

mkmeral commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

/strands review

@github-actions github-actions Bot added the strands-running <strands-managed> Whether or not an agent is currently running label Apr 10, 2026
Comment thread src/a2a/a2a-agent.ts Outdated
* })
* ```
*/
clientFactoryOptions?: Partial<ClientFactoryOptions>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: ClientFactoryOptions is not re-exported from src/a2a/index.ts. Users who want to construct clientFactoryOptions values will need to import the type directly from @a2a-js/sdk/client, which is not discoverable and requires knowledge of the underlying dependency.

Suggestion: Re-export ClientFactoryOptions from src/a2a/index.ts so users can import everything they need from @strands-agents/sdk/a2a:

export { ClientFactoryOptions } from '@a2a-js/sdk/client'

This aligns with the "Prefer Flat Namespaces Over Nested Modules" decision record — users shouldn't need to import from transitive dependencies for common functionality.

@github-actions

Copy link
Copy Markdown
Contributor

Issue: The PR description mentions a "Documentation PR" section is missing. This change adds a new public configuration option (clientFactoryOptions) to A2AAgentConfig — a new config parameter that users need to know about when connecting to protected A2A endpoints.

Suggestion: Consider adding a documentation PR to https://github.com/strands-agents/docs covering the clientFactoryOptions config option, especially since authentication is a common use case that users will actively search for.

@github-actions

Copy link
Copy Markdown
Contributor

Issue: The @a2a-js/sdk peer dependency is specified as ^0.3.10 with no upper bound. ClientFactoryOptions (both the interface and the static createFrom/default companion object) was introduced in @a2a-js/sdk, but since this is a 0.x semver range, minor version bumps can include breaking changes. If ClientFactoryOptions.createFrom signature changes in a future 0.4.x release, this code would break silently.

Suggestion: This is a pre-existing concern with the ^0.3.10 range (not introduced by this PR), but worth noting — consider whether the A2A SDK dependency should have a tighter upper bound (e.g., >=0.3.10 <0.4.0) given it's pre-1.0.

Comment thread src/a2a/__tests__/a2a-agent.test.ts Outdated
})
})

it('passes agentCardPath to createFromUrl', async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: The agentCardPath test is in the clientFactoryOptions describe block but doesn't actually test clientFactoryOptions behavior — it tests the existing agentCardPath config passthrough to createFromUrl. This makes the test grouping misleading.

Suggestion: Move this test to a more appropriate describe block (e.g., the identity properties or invoke block), or rename the describe to something broader like client configuration.

@github-actions

Copy link
Copy Markdown
Contributor

Assessment: Comment

Clean, well-scoped change that solves a real auth problem for protected A2A endpoints. The implementation is minimal and follows existing patterns well.

Review Categories
  • API Surface: ClientFactoryOptions should be re-exported from src/a2a/index.ts so users can import everything from @strands-agents/sdk/a2a without reaching into @a2a-js/sdk/client directly (aligns with the "Prefer Flat Namespaces" decision record).
  • Documentation: PR is missing a "Documentation PR" section — the new clientFactoryOptions config option is a user-facing addition that should be documented for discoverability, especially since auth is a common pain point.
  • Testing: One test (agentCardPath) is grouped under the clientFactoryOptions describe block but tests unrelated functionality — minor organizational nit.

The TSDoc with @example, the Partial<> typing, and the use of ClientFactoryOptions.createFrom for SDK-native merging are all well done.

@github-actions github-actions Bot removed the strands-running <strands-managed> Whether or not an agent is currently running label Apr 10, 2026
… options

Simplify the API: instead of accepting Partial<ClientFactoryOptions> and
reconstructing the factory internally, accept a ClientFactory directly.

The factory already handles card resolution and client creation. Users
configure their factory however they want (auth, interceptors, transports)
and pass it in. We just use it.

Before:
  clientFactoryOptions: { cardResolver: myResolver }

After:
  clientFactory: new ClientFactory({ ...myOptions })

- Removes ClientFactoryOptions import (no longer needed in our API surface)
- Simplifies _getClient() to a single nullish coalescing expression
- Updates tests to verify no extra factory is constructed when one is provided
@agent-of-mkmeral agent-of-mkmeral changed the title feat(a2a): add clientFactoryOptions to A2AAgent for auth support feat(a2a): add clientFactory to A2AAgent for auth support Apr 10, 2026
@github-actions github-actions Bot added the strands-running <strands-managed> Whether or not an agent is currently running label Apr 10, 2026
expect(mockCreateFromUrl).toHaveBeenCalledWith('http://localhost:9000', undefined)
})

it('passes agentCardPath to createFromUrl', async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: The agentCardPath test at line 172 (without a custom factory) is grouped under the clientFactory describe block, but it tests the agentCardPath config passthrough to createFromUrl — not the clientFactory feature itself. The test at line 183 that combines agentCardPath + clientFactory does belong here.

Suggestion: Consider moving the standalone agentCardPath test (line 172) to the invoke describe block where connection/URL behavior is tested, keeping only factory-related tests in the clientFactory block.

@github-actions

Copy link
Copy Markdown
Contributor

Assessment: Approve

Well-designed, minimal change that solves a real auth problem. The v2 approach of accepting a ClientFactory instance instead of Partial<ClientFactoryOptions> is a clear improvement — simpler API, no need to re-export third-party types, and the factory is reusable across multiple agents.

Review Details
  • API Design: clientFactory?: ClientFactory is the right abstraction. Users configure auth/transports on their factory instance and hand it over — Strands doesn't need to know the details. This aligns with "Extensible by design" and "Embrace common standards" tenets.
  • Testing: Minor grouping nit — one agentCardPath test doesn't exercise clientFactory but is grouped in the clientFactory describe block.
  • Documentation: This adds a new user-facing config option for auth (a common pain point). Consider adding a documentation PR to https://github.com/strands-agents/docs for discoverability, though the TSDoc @example provides good inline guidance.

The 1-line production change (this._config.clientFactory ?? new ClientFactory()) with comprehensive TSDoc and 4 targeted tests is a model for clean, minimal PRs.

@github-actions github-actions Bot removed the strands-running <strands-managed> Whether or not an agent is currently running label Apr 10, 2026
@mkmeral

mkmeral commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

duplicate of #810

@mkmeral mkmeral closed this Apr 13, 2026

This branch had an error being deployed

1 failed deployment
manual-approval — 80344098 Deployed Apr 10, 2026 by agent-of-mkmeral via Run integration tests #1752
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants