Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# a2a.catalog

`com.openjiuwen.service.app.a2a.catalog` 提供可供 Runtime 适配器复用的远端 A2A Agent 目录。

## 类型

| Type | Description |
| --- | --- |
| `A2ARemoteAgentCardRegistry` | 线程安全地保存远端 Agent Card、调用超时与调用模式,并发布目录更新事件。 |
| `RemoteAgentEntry` | 保存单个远端 Agent 的名称、Agent Card、调用超时与调用模式。 |
| `RemoteAgentCatalogSnapshot` | 保存版本号和全量远端 Agent 条目的不可变快照。 |
| `RemoteAgentCatalogChangedEvent` | 远端 Agent 目录更新后发布的全量快照事件。 |

## 源码路径

`service/agent-service-app/src/main/java/com/openjiuwen/service/app/a2a/catalog/`
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
| Type | Description |
| --- | --- |
| `A2AAgentCardDiscovery` | 按 `openjiuwen.service.a2a.remote-agents` 拉取远端 Agent Card,失败后定时重试。 |
| `A2ARemoteAgentCardRegistry` | 保存远端 AgentCard、URL 和 timeout。 |
| `A2ARemoteAgentClient` | 调用远端 Agent 的 sync / streaming client。 |

## 调用模式
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
*/

package com.openjiuwen.service.app.a2a.catalog;

import org.a2aproject.sdk.spec.AgentCard;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

/**
* Thread-safe in-memory registry of discovered remote A2A AgentCards.
*
* @since 0.1.0
*/
public class A2ARemoteAgentCardRegistry {
private static final Logger log = LoggerFactory.getLogger(A2ARemoteAgentCardRegistry.class);

/**
* Default timeout in seconds for remote agent calls.
*/
static final int DEFAULT_TIMEOUT_SECONDS = 300;

private final ApplicationEventPublisher eventPublisher;
private final Map<String, RemoteAgentEntry> entries = new ConcurrentHashMap<>();
private final ReentrantLock updateLock = new ReentrantLock();

private long version;

/**
* Creates a registry without event publication.
*
* <p>This constructor preserves direct, non-Spring usage. Runtime auto-configuration
* supplies an {@link ApplicationEventPublisher}.</p>
*/
public A2ARemoteAgentCardRegistry() {
this(event -> {
});
}

/**
* Creates a registry that publishes complete catalog snapshots after updates.
*
* @param eventPublisher the Spring application event publisher
*/
public A2ARemoteAgentCardRegistry(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}

/**
* Registers a remote agent card using the default timeout.
*
* @param name the agent name
* @param card the agent card
*/
public void register(String name, AgentCard card) {
register(name, card, DEFAULT_TIMEOUT_SECONDS, false);
}

/**
* Returns all registered remote agent entries.
*
* @return an unmodifiable copy of all entries
*/
public List<RemoteAgentEntry> getAll() {
return snapshot().entries();
}

/**
* Looks up a remote agent entry by name.
*
* @param name the agent name
* @return the entry, or {@link Optional#empty()} if not found
*/
public Optional<RemoteAgentEntry> get(String name) {
return Optional.ofNullable(entries.get(name));
}

/**
* Resolves the JSON-RPC URL for a registered remote agent.
*
* @param name the agent name
* @return the JSON-RPC URL, or empty string if not found
*/
public String resolveUrl(String name) {
var entry = entries.get(name);
if (entry == null) {
return "";
}
var ifaces = entry.card().supportedInterfaces();
if (ifaces == null || ifaces.isEmpty()) {
return "";
}
return ifaces.get(0).url();
}

/**
* Returns the current complete remote-agent catalog.
*
* @return an immutable, name-sorted catalog snapshot
*/
public RemoteAgentCatalogSnapshot snapshot() {
updateLock.lock();
try {
return createSnapshot();
} finally {
updateLock.unlock();
}
}

/**
* Registers a remote agent card with a specific timeout.
*
* @param name the agent name
* @param card the agent card
* @param timeoutSeconds the call timeout in seconds
*/
public void register(String name, AgentCard card, int timeoutSeconds) {
register(name, card, timeoutSeconds, false);
}

/**
* Registers a remote agent card with timeout and invocation mode.
*
* @param name the agent name
* @param card the agent card
* @param timeoutSeconds the call timeout in seconds
* @param isStreaming whether Runtime should prefer a streaming remote invocation
*/
public void register(String name, AgentCard card, int timeoutSeconds, boolean isStreaming) {
RemoteAgentCatalogSnapshot updatedSnapshot;
updateLock.lock();
try {
entries.put(name, new RemoteAgentEntry(name, card, timeoutSeconds, isStreaming));
version++;
updatedSnapshot = createSnapshot();
} finally {
updateLock.unlock();
}
log.info("Registered remote A2A Agent Card agentName={} catalogVersion={} catalogSize={} streaming={}", name,
updatedSnapshot.version(), updatedSnapshot.entries().size(), isStreaming);
publishCatalogChanged(updatedSnapshot);
}

private RemoteAgentCatalogSnapshot createSnapshot() {
List<RemoteAgentEntry> sortedEntries = entries.values().stream()
.sorted((left, right) -> left.name().compareTo(right.name())).toList();
return new RemoteAgentCatalogSnapshot(version, sortedEntries);
}

private void publishCatalogChanged(RemoteAgentCatalogSnapshot updatedSnapshot) {
try {
eventPublisher.publishEvent(new RemoteAgentCatalogChangedEvent(updatedSnapshot));
log.info("Published remote A2A Agent catalog change catalogVersion={} catalogSize={}",
updatedSnapshot.version(), updatedSnapshot.entries().size());
} catch (RuntimeException exception) {
log.error("Failed to publish remote A2A Agent catalog change catalogVersion={} catalogSize={}",
updatedSnapshot.version(), updatedSnapshot.entries().size(), exception);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
*/

package com.openjiuwen.service.app.a2a.catalog;

/**
* Event published after the remote A2A agent catalog changes.
*
* @param snapshot complete catalog snapshot produced by the registry update
* @since 0.1.1
*/
public record RemoteAgentCatalogChangedEvent(RemoteAgentCatalogSnapshot snapshot) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
*/

package com.openjiuwen.service.app.a2a.catalog;

import java.util.List;

/**
* Immutable versioned snapshot of all discovered remote A2A agents.
*
* @param version monotonically increasing registry version
* @param entries complete remote-agent entries sorted by name
* @since 0.1.1
*/
public record RemoteAgentCatalogSnapshot(long version, List<RemoteAgentEntry> entries) {
/**
* Creates an immutable snapshot.
*/
public RemoteAgentCatalogSnapshot {
entries = List.copyOf(entries);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
*/

package com.openjiuwen.service.app.a2a.catalog;

import org.a2aproject.sdk.spec.AgentCard;

/**
* Immutable remote A2A Agent registration entry.
*
* @param name remote Agent name
* @param card discovered Agent Card
* @param timeoutSeconds remote call timeout in seconds
* @param isStreaming whether Runtime should prefer streaming invocation
* @since 0.1.1
*/
public record RemoteAgentEntry(String name, AgentCard card, int timeoutSeconds, boolean isStreaming) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import com.openjiuwen.service.adapters.common.middleware.MiddlewareProperties;
import com.openjiuwen.service.adapters.common.middleware.redis.RedisMiddlewareAutoConfiguration;
import com.openjiuwen.service.app.a2a.catalog.A2ARemoteAgentCardRegistry;
import com.openjiuwen.service.app.config.A2AProperties;
import com.openjiuwen.service.app.config.SpringEnvironmentConfigProvider;
import com.openjiuwen.service.app.controller.a2a.A2AAgentExecutor;
Expand All @@ -19,7 +20,6 @@
import com.openjiuwen.service.app.controller.a2a.RedisTaskStore;
import com.openjiuwen.service.app.controller.a2a.WriteThrottlingTaskStore;
import com.openjiuwen.service.app.controller.a2a.client.A2AAgentCardDiscovery;
import com.openjiuwen.service.app.controller.a2a.client.A2ARemoteAgentCardRegistry;
import com.openjiuwen.service.app.controller.a2a.client.A2ARemoteAgentClient;
import com.openjiuwen.service.app.controller.a2a.client.RemoteAgentCaller;
import com.openjiuwen.service.app.controller.a2a.client.RemoteAgentCardResolver;
Expand Down Expand Up @@ -50,6 +50,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

Expand Down Expand Up @@ -233,12 +234,13 @@ public A2AAgentExecutor a2aAgentExecutor(ServeOrchestrator orchestrator, A2AProt
/**
* Creates the remote agent card registry bean.
*
* @param eventPublisher the Spring application event publisher
* @return the remote agent card registry
*/
@Bean
@ConditionalOnMissingBean
public A2ARemoteAgentCardRegistry a2aRemoteAgentCardRegistry() {
return new A2ARemoteAgentCardRegistry();
public A2ARemoteAgentCardRegistry a2aRemoteAgentCardRegistry(ApplicationEventPublisher eventPublisher) {
return new A2ARemoteAgentCardRegistry(eventPublisher);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package com.openjiuwen.service.app.controller.a2a.client;

import com.openjiuwen.service.app.a2a.catalog.A2ARemoteAgentCardRegistry;
import com.openjiuwen.service.app.config.A2AProperties;
import com.openjiuwen.service.app.config.A2AProperties.RemoteAgentProperties;
import com.openjiuwen.service.spec.paths.A2AServicePaths;
Expand Down
Loading