diff --git a/.github/workflows/run-tck.yml b/.github/workflows/run-tck.yml
index 87a16980d..d0a0212be 100644
--- a/.github/workflows/run-tck.yml
+++ b/.github/workflows/run-tck.yml
@@ -1,7 +1,6 @@
name: Run TCK
on:
- # Handle all branches for now
push:
branches:
- main
@@ -12,17 +11,10 @@ on:
env:
# Tag/branch of the TCK
- TCK_VERSION: main
- # Tell the TCK runner to report failure if the quality tests fail
- A2A_TCK_FAIL_ON_QUALITY: 1
- # Tell the TCK runner to report failure if the features tests fail
- A2A_TCK_FAIL_ON_FEATURES: 1
+ TCK_VERSION: 1.0-dev
# Tells uv to not need a venv, and instead use system
UV_SYSTEM_PYTHON: 1
- # SUT_JSONRPC_URL to use for the TCK and the server agent
- SUT_JSONRPC_URL: http://localhost:9999
- # Slow system on CI
- TCK_STREAMING_TIMEOUT: 5.0
+ SUT_URL: http://localhost:9999
# Only run the latest job
concurrency:
@@ -34,142 +26,102 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- java-version: [17, 21, 25]
+ java-version: [17]
steps:
- name: Checkout a2a-java
uses: actions/checkout@v6
- - name: Checkout a2a-tck
- uses: actions/checkout@v6
- with:
- repository: a2aproject/a2a-tck
- path: tck/a2a-tck
- ref: ${{ env.TCK_VERSION }}
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v5
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
cache: maven
- - name: check java_home
- run: echo $JAVA_HOME
+ - name: Build a2a-java SDK
+ run: mvn -B install -DskipTests
+ - name: Extract a2a-java version
+ id: extract-version
+ run: |
+ A2A_JAVA_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
+ echo "version=$A2A_JAVA_VERSION" >> $GITHUB_OUTPUT
+ echo "Detected a2a-java version: $A2A_JAVA_VERSION"
+ - name: Checkout a2a-tck
+ uses: actions/checkout@v6
+ with:
+ repository: a2aproject/a2a-tck
+ path: a2a-tck
+ ref: ${{ env.TCK_VERSION }}
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
- python-version-file: "tck/a2a-tck/pyproject.toml"
+ python-version-file: "a2a-tck/pyproject.toml"
- name: Install uv and Python dependencies
run: |
pip install uv
uv pip install -e .
- working-directory: tck/a2a-tck
- - name: Build with Maven, skipping tests
- run: mvn -B install -DskipTests
+ working-directory: a2a-tck
+ - name: Generate a2a-java SUT
+ run: A2A_JAVA_SDK_VERSION=${{ steps.extract-version.outputs.version }} make codegen-a2a-java-sut
+ working-directory: a2a-tck
- name: Start SUT
- run: SUT_GRPC_URL=${{ env.SUT_JSONRPC_URL }} SUT_REST_URL=${{ env.SUT_JSONRPC_URL }} mvn -B quarkus:dev & #SUT_JSONRPC_URL already set
- working-directory: tck
+ run: mvn -B quarkus:dev -Dquarkus.console.enabled=false &
+ working-directory: a2a-tck/sut/a2a-java
- name: Wait for SUT to start
run: |
- URL="${{ env.SUT_JSONRPC_URL }}/.well-known/agent-card.json"
+ URL="${{ env.SUT_URL }}/.well-known/agent-card.json"
EXPECTED_STATUS=200
TIMEOUT=120
RETRY_INTERVAL=2
START_TIME=$(date +%s)
while true; do
- # Calculate elapsed time
CURRENT_TIME=$(date +%s)
ELAPSED_TIME=$((CURRENT_TIME - START_TIME))
- # Check for timeout
if [ "$ELAPSED_TIME" -ge "$TIMEOUT" ]; then
- echo "❌ Timeout: Server did not respond with status $EXPECTED_STATUS within $TIMEOUT seconds."
+ echo "Timeout: Server did not respond with status $EXPECTED_STATUS within $TIMEOUT seconds."
exit 1
fi
- # Get HTTP status code. || true is to reporting a failure to connect as an error
HTTP_STATUS=$(curl --output /dev/null --silent --write-out "%{http_code}" "$URL") || true
- echo "STATUS: ${HTTP_STATUS}"
- # Check if we got the correct status code
if [ "$HTTP_STATUS" -eq "$EXPECTED_STATUS" ]; then
- echo "✅ Server is up! Received status $HTTP_STATUS after $ELAPSED_TIME seconds."
+ echo "Server is up! Received status $HTTP_STATUS after $ELAPSED_TIME seconds."
break;
fi
- # Wait before retrying
- echo "⏳ Server not ready (status: $HTTP_STATUS). Retrying in $RETRY_INTERVAL seconds..."
+ echo "Server not ready (status: $HTTP_STATUS). Retrying in $RETRY_INTERVAL seconds..."
sleep "$RETRY_INTERVAL"
done
-
- name: Run TCK
id: run-tck
timeout-minutes: 5
run: |
set -o pipefail
- ./run_tck.py --sut-url ${{ env.SUT_JSONRPC_URL }} --category all --transports jsonrpc,grpc,rest --compliance-report report.json 2>&1 | tee tck-output.log
- working-directory: tck/a2a-tck
- - name: Capture Diagnostics on Failure
- if: failure()
+ uv run ./run_tck.py --sut-host ${{ env.SUT_URL }} -v 2>&1 | tee tck-output.log
+ working-directory: a2a-tck
+ - name: TCK Summary
+ if: always() && steps.run-tck.outcome != 'skipped'
run: |
- echo "=== Capturing diagnostic information ==="
-
- # Create diagnostics directory
- mkdir -p tck/target/diagnostics
-
- # Capture process list
- echo "📋 Capturing process list..."
- ps auxww > tck/target/diagnostics/processes.txt
-
- # Find the actual Quarkus JVM (child of Maven process), not the Maven parent
- # Look for the dev.jar process which is the actual application
- QUARKUS_PID=$(pgrep -f "a2a-tck-server-dev.jar" || echo "")
- if [ -n "$QUARKUS_PID" ]; then
- echo "📊 Capturing thread dump for Quarkus JVM PID $QUARKUS_PID"
- jstack $QUARKUS_PID > tck/target/diagnostics/thread-dump.txt || echo "Failed to capture thread dump"
- if [ -f tck/target/diagnostics/thread-dump.txt ]; then
- echo "✅ Thread dump captured ($(wc -l < tck/target/diagnostics/thread-dump.txt) lines)"
+ if [ -f a2a-tck/tck-output.log ]; then
+ # Extract everything after the first ═══ separator line
+ SUMMARY=$(sed -n '/^═══/,$p' a2a-tck/tck-output.log)
+ if [ -n "$SUMMARY" ]; then
+ echo '### TCK Results (Java ${{ matrix.java-version }})' >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
fi
- else
- echo "⚠️ No Quarkus JVM process found for thread dump"
- echo "Available Java processes:"
- ps aux | grep java | tee -a tck/target/diagnostics/processes.txt || true
- fi
-
- # Capture Quarkus application logs (if available)
- echo "📝 Checking for Quarkus logs..."
- if [ -f tck/target/quarkus.log ]; then
- cp tck/target/quarkus.log tck/target/diagnostics/
- echo "✅ Copied quarkus.log ($(wc -l < tck/target/quarkus.log) lines)"
fi
-
- # Copy TCK server logs
- if [ -f tck/target/tck-test.log ]; then
- cp tck/target/tck-test.log tck/target/diagnostics/
- echo "✅ Copied tck-test.log ($(wc -l < tck/target/tck-test.log) lines)"
- fi
-
- echo ""
- echo "=== Diagnostic capture complete ==="
- - name: Stop Quarkus Server
+ - name: Stop SUT
if: always()
run: |
- # Find and kill the Quarkus process to ensure logs are flushed
pkill -f "quarkus:dev" || true
sleep 2
- - name: Upload TCK Diagnostics
- if: failure()
- uses: actions/upload-artifact@v6
- with:
- name: tck-diagnostics-java-${{ matrix.java-version }}
- path: |
- tck/target/diagnostics/
- tck/a2a-tck/tck-output.log
- retention-days: 7
- if-no-files-found: warn
- - name: Upload TCK Compliance Report
+ - name: Upload TCK Reports
if: always()
uses: actions/upload-artifact@v6
with:
- name: tck-compliance-report-java-${{ matrix.java-version }}
- path: tck/a2a-tck/report.json
+ name: tck-reports-java-${{ matrix.java-version }}
+ path: a2a-tck/reports/
retention-days: 14
- if-no-files-found: ignore
+ if-no-files-found: warn
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 8078b339a..31c762137 100644
--- a/pom.xml
+++ b/pom.xml
@@ -570,7 +570,6 @@
server-common
spec
spec-grpc
- tck
test-utils-docker
tests/server-common
transport/jsonrpc
diff --git a/tck/pom.xml b/tck/pom.xml
deleted file mode 100644
index 026715dfb..000000000
--- a/tck/pom.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
- 4.0.0
-
-
- org.a2aproject.sdk
- a2a-java-sdk-parent
- 1.0.0.Beta1-SNAPSHOT
-
-
- a2a-tck-server
-
- Java SDK A2A TCK Server
- Server example to use with the A2A TCK
-
-
-
- ${project.groupId}
- a2a-java-sdk-reference-jsonrpc
-
-
- org.a2aproject.sdk
- a2a-java-sdk-reference-grpc
-
-
- org.a2aproject.sdk
- a2a-java-sdk-reference-rest
-
-
- io.quarkus
- quarkus-rest
- provided
-
-
- jakarta.enterprise
- jakarta.enterprise.cdi-api
- provided
-
-
- jakarta.ws.rs
- jakarta.ws.rs-api
-
-
-
-
-
-
- io.quarkus
- quarkus-maven-plugin
- true
-
-
-
- build
- generate-code
- generate-code-tests
-
-
-
-
-
-
-
diff --git a/tck/src/main/java/org/a2aproject/sdk/tck/server/AgentCardProducer.java b/tck/src/main/java/org/a2aproject/sdk/tck/server/AgentCardProducer.java
deleted file mode 100644
index 15262001f..000000000
--- a/tck/src/main/java/org/a2aproject/sdk/tck/server/AgentCardProducer.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.a2aproject.sdk.tck.server;
-
-
-import java.util.Collections;
-import java.util.List;
-
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.enterprise.inject.Produces;
-
-import org.a2aproject.sdk.server.PublicAgentCard;
-import org.a2aproject.sdk.spec.AgentCapabilities;
-import org.a2aproject.sdk.spec.AgentCard;
-import org.a2aproject.sdk.spec.AgentInterface;
-import org.a2aproject.sdk.spec.AgentSkill;
-import org.a2aproject.sdk.spec.TransportProtocol;
-
-@ApplicationScoped
-public class AgentCardProducer {
-
- private static final String DEFAULT_SUT_URL = "http://localhost:9999";
-
- @Produces
- @PublicAgentCard
- public AgentCard agentCard() {
-
- String sutJsonRpcUrl = getEnvOrDefault("SUT_JSONRPC_URL", DEFAULT_SUT_URL);
- String sutGrpcUrl = getEnvOrDefault("SUT_GRPC_URL", DEFAULT_SUT_URL);
- String sutRestcUrl = getEnvOrDefault("SUT_REST_URL", DEFAULT_SUT_URL);
- return AgentCard.builder()
- .name("Hello World Agent")
- .description("Just a hello world agent")
- .supportedInterfaces(List.of(
- new AgentInterface(TransportProtocol.JSONRPC.asString(), sutJsonRpcUrl),
- new AgentInterface(TransportProtocol.GRPC.asString(), sutGrpcUrl),
- new AgentInterface(TransportProtocol.HTTP_JSON.asString(), sutRestcUrl)))
- .version("1.0.0")
- .documentationUrl("http://example.com/docs")
- .capabilities(AgentCapabilities.builder()
- .streaming(true)
- .pushNotifications(true)
- .build())
- .defaultInputModes(Collections.singletonList("text"))
- .defaultOutputModes(Collections.singletonList("text"))
- .skills(Collections.singletonList(AgentSkill.builder()
- .id("hello_world")
- .name("Returns hello world")
- .description("just returns hello world")
- .tags(Collections.singletonList("hello world"))
- .examples(List.of("hi", "hello world"))
- .build()))
- .build();
- }
-
- private static String getEnvOrDefault(String envVar, String defaultValue) {
- String value = System.getenv(envVar);
- return value == null || value.isBlank() ? defaultValue : value;
- }
-}
-
diff --git a/tck/src/main/java/org/a2aproject/sdk/tck/server/AgentExecutorProducer.java b/tck/src/main/java/org/a2aproject/sdk/tck/server/AgentExecutorProducer.java
deleted file mode 100644
index 55bd4a71b..000000000
--- a/tck/src/main/java/org/a2aproject/sdk/tck/server/AgentExecutorProducer.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package org.a2aproject.sdk.tck.server;
-
-import java.util.List;
-
-import jakarta.annotation.PreDestroy;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.enterprise.inject.Produces;
-
-import org.a2aproject.sdk.server.agentexecution.AgentExecutor;
-import org.a2aproject.sdk.server.agentexecution.RequestContext;
-import org.a2aproject.sdk.server.tasks.AgentEmitter;
-import org.a2aproject.sdk.spec.A2AError;
-import org.a2aproject.sdk.spec.Task;
-import org.a2aproject.sdk.spec.TaskNotCancelableError;
-import org.a2aproject.sdk.spec.TaskState;
-import org.a2aproject.sdk.spec.TaskStatus;
-
-@ApplicationScoped
-public class AgentExecutorProducer {
-
- @Produces
- public AgentExecutor agentExecutor() {
- return new FireAndForgetAgentExecutor();
- }
-
- private static class FireAndForgetAgentExecutor implements AgentExecutor {
-
- @Override
- public void execute(RequestContext context, AgentEmitter agentEmitter) throws A2AError {
- Task task = context.getTask();
-
- if (task == null) {
- if (context == null) {
- throw new IllegalArgumentException("RequestContext may not be null");
- }
- if (context.getTaskId() == null) {
- throw new IllegalArgumentException("Parameter 'id' may not be null");
- }
- if (context.getContextId() == null) {
- throw new IllegalArgumentException("Parameter 'contextId' may not be null");
- }
- task = Task.builder()
- .id(context.getTaskId())
- .contextId(context.getContextId())
- .status(new TaskStatus(TaskState.TASK_STATE_SUBMITTED))
- .history(List.of(context.getMessage()))
- .build();
- agentEmitter.addTask(task);
- }
-
- // Sleep to allow task state persistence before TCK subscribe test
- if (context.getMessage() != null && context.getMessage().messageId().startsWith("test-subscribe-message-id")) {
- int timeoutMs = Integer.parseInt(System.getenv().getOrDefault("RESUBSCRIBE_TIMEOUT_MS", "3000"));
- System.out.println("====> task id starts with test-subscribe-message-id, sleeping for " + timeoutMs + " ms");
- try {
- Thread.sleep(timeoutMs);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
-
- // Immediately set to WORKING state
- agentEmitter.startWork();
- System.out.println("====> task set to WORKING, starting background execution");
-
- // Method returns immediately - task continues in background
- System.out.println("====> execute() method returning immediately, task running in background");
- }
-
- @Override
- public void cancel(RequestContext context, AgentEmitter agentEmitter) throws A2AError {
- System.out.println("====> task cancel request received");
- Task task = context.getTask();
- if (task == null) {
- System.out.println("====> No task found");
- throw new TaskNotCancelableError();
- }
- if (task.status().state() == TaskState.TASK_STATE_CANCELED) {
- System.out.println("====> task already canceled");
- throw new TaskNotCancelableError();
- }
-
- if (task.status().state() == TaskState.TASK_STATE_COMPLETED) {
- System.out.println("====> task already completed");
- throw new TaskNotCancelableError();
- }
-
- agentEmitter.cancel();
- System.out.println("====> task canceled");
- }
-
- /**
- * Cleanup method for proper resource management
- */
- @PreDestroy
- public void cleanup() {
- System.out.println("====> shutting down task executor");
- }
- }
-}
diff --git a/tck/src/main/java/org/a2aproject/sdk/tck/server/package-info.java b/tck/src/main/java/org/a2aproject/sdk/tck/server/package-info.java
deleted file mode 100644
index dc93d6473..000000000
--- a/tck/src/main/java/org/a2aproject/sdk/tck/server/package-info.java
+++ /dev/null
@@ -1,10 +0,0 @@
-@NullMarked
-package org.a2aproject.sdk.tck.server;
-
-import org.jspecify.annotations.NullMarked;
-
-//The following had @Nullable annotation applied from JSpecify
-//AgentCardProducer.java getEnvOrDefault method,
-//AgentExecutorProducer.java execute method
-//
-
diff --git a/tck/src/main/resources/application.properties b/tck/src/main/resources/application.properties
deleted file mode 100644
index b9b442693..000000000
--- a/tck/src/main/resources/application.properties
+++ /dev/null
@@ -1,21 +0,0 @@
-# Use the new gRPC implementation which uses the main HTTP port
-quarkus.grpc.server.use-separate-server=false
-%dev.quarkus.http.port=9999
-
-# Thread pool configuration for TCK testing
-# Limit max threads to prevent resource exhaustion in CI environments
-a2a.executor.core-pool-size=5
-a2a.executor.max-pool-size=15
-a2a.executor.keep-alive-seconds=60
-
-# Enable debug logging for troubleshooting TCK failures
-quarkus.log.category."org.a2aproject.sdk.server.requesthandlers".level=DEBUG
-quarkus.log.category."org.a2aproject.sdk.server.events".level=DEBUG
-quarkus.log.category."org.a2aproject.sdk.server.tasks".level=DEBUG
-org.a2aproject.sdk.server.diagnostics.ThreadStats.level=DEBUG
-
-# Log to file for analysis
-quarkus.log.file.enable=true
-quarkus.log.file.path=target/tck-test.log
-quarkus.log.file.level=DEBUG
-quarkus.log.console.level=INFO