From 665f1e192d57c10ada363ae9d343b303e83fb7a7 Mon Sep 17 00:00:00 2001 From: Pascal Wilbrink Date: Wed, 6 Aug 2025 22:44:31 +0200 Subject: [PATCH 1/8] Initial Java SDK setup --- java-sdk/client/pom.xml | 86 ++++++++++ .../java/com/agui/client/AbstractAgent.java | 151 ++++++++++++++++++ .../com/agui/client/RunAgentParameters.java | 86 ++++++++++ .../java/com/agui/client/RunAgentResult.java | 4 + .../src/main/java/com/agui/client/State.java | 4 + .../java/com/agui/client/Subscription.java | 5 + .../main/java/com/agui/client/ToolCall.java | 4 + .../client/subscriber/AgentStateMutation.java | 13 ++ .../subscriber/AgentSubscriberParams.java | 46 ++++++ .../test/java/com/agui/client/TestAgent.java | 36 +++++ .../subscriber/AgentSubscriberTest.java | 59 +++++++ java-sdk/core/pom.xml | 41 +++++ .../main/java/com/agui/event/BaseEvent.java | 37 +++++ .../main/java/com/agui/event/CustomEvent.java | 10 ++ .../com/agui/event/MessagesSnapshotEvent.java | 10 ++ .../main/java/com/agui/event/RawEvent.java | 10 ++ .../java/com/agui/event/RunErrorEvent.java | 10 ++ .../java/com/agui/event/RunFinishedEvent.java | 34 ++++ .../java/com/agui/event/RunStartedEvent.java | 10 ++ .../java/com/agui/event/StateDeltaEvent.java | 10 ++ .../com/agui/event/StateSnapshotEvent.java | 10 ++ .../com/agui/event/StepFinishedEvent.java | 10 ++ .../java/com/agui/event/StepStartedEvent.java | 10 ++ .../com/agui/event/TextMessageChunkEvent.java | 10 ++ .../agui/event/TextMessageContentEvent.java | 10 ++ .../com/agui/event/TextMessageEndEvent.java | 10 ++ .../com/agui/event/TextMessageStartEvent.java | 10 ++ .../java/com/agui/event/ThinkingEndEvent.java | 10 ++ .../com/agui/event/ThinkingStartEvent.java | 10 ++ .../ThinkingTextMessageContentEvent.java | 10 ++ .../event/ThinkingTextMessageEndEvent.java | 10 ++ .../event/ThinkingTextMessageStartEvent.java | 10 ++ .../com/agui/event/ToolCallArgsEvent.java | 10 ++ .../com/agui/event/ToolCallChunkEvent.java | 10 ++ .../java/com/agui/event/ToolCallEndEvent.java | 10 ++ .../com/agui/event/ToolCallResultEvent.java | 10 ++ .../com/agui/event/ToolCallStartEvent.java | 10 ++ .../com/agui/message/AssistantMessage.java | 12 ++ .../java/com/agui/message/BaseMessage.java | 22 +++ .../com/agui/message/DeveloperMessage.java | 12 ++ .../java/com/agui/message/SystemMessage.java | 12 ++ .../java/com/agui/message/UserMessage.java | 12 ++ .../src/main/java/com/agui/types/Context.java | 15 ++ .../main/java/com/agui/types/EventType.java | 36 +++++ .../java/com/agui/types/RunAgentInput.java | 63 ++++++++ .../src/main/java/com/agui/types/Tool.java | 17 ++ 46 files changed, 1037 insertions(+) create mode 100644 java-sdk/client/pom.xml create mode 100644 java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java create mode 100644 java-sdk/client/src/main/java/com/agui/client/RunAgentParameters.java create mode 100644 java-sdk/client/src/main/java/com/agui/client/RunAgentResult.java create mode 100644 java-sdk/client/src/main/java/com/agui/client/State.java create mode 100644 java-sdk/client/src/main/java/com/agui/client/Subscription.java create mode 100644 java-sdk/client/src/main/java/com/agui/client/ToolCall.java create mode 100644 java-sdk/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java create mode 100644 java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java create mode 100644 java-sdk/client/src/test/java/com/agui/client/TestAgent.java create mode 100644 java-sdk/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java create mode 100644 java-sdk/core/pom.xml create mode 100644 java-sdk/core/src/main/java/com/agui/event/BaseEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/CustomEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/RawEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/RunErrorEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/RunFinishedEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/RunStartedEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/StateDeltaEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/StateSnapshotEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/StepFinishedEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/StepStartedEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/TextMessageChunkEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/TextMessageContentEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/TextMessageEndEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/TextMessageStartEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/ThinkingEndEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/ThinkingStartEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageContentEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageEndEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageStartEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/ToolCallArgsEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/ToolCallChunkEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/ToolCallEndEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/ToolCallResultEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/event/ToolCallStartEvent.java create mode 100644 java-sdk/core/src/main/java/com/agui/message/AssistantMessage.java create mode 100644 java-sdk/core/src/main/java/com/agui/message/BaseMessage.java create mode 100644 java-sdk/core/src/main/java/com/agui/message/DeveloperMessage.java create mode 100644 java-sdk/core/src/main/java/com/agui/message/SystemMessage.java create mode 100644 java-sdk/core/src/main/java/com/agui/message/UserMessage.java create mode 100644 java-sdk/core/src/main/java/com/agui/types/Context.java create mode 100644 java-sdk/core/src/main/java/com/agui/types/EventType.java create mode 100644 java-sdk/core/src/main/java/com/agui/types/RunAgentInput.java create mode 100644 java-sdk/core/src/main/java/com/agui/types/Tool.java diff --git a/java-sdk/client/pom.xml b/java-sdk/client/pom.xml new file mode 100644 index 000000000..661eb48d5 --- /dev/null +++ b/java-sdk/client/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + com.ag-ui + ag-ui + 0.0.1-SNAPSHOT + + + client + + + 18 + 18 + UTF-8 + + + + + Pascal Wilbrink + pascal.wilbrink@gmail.com + https://github.com/pascalwilbrink + + Developer + + + + + + Pascal Wilbrink + pascal.wilbrink@gmail.com + https://github.com/pascalwilbrink + + Maintainer + Developer + + + + + + + com.ag-ui + core + 0.0.1-SNAPSHOT + compile + + + io.reactivex.rxjava2 + rxjava + 2.2.21 + + + org.junit.jupiter + junit-jupiter + 5.12.2 + test + + + org.assertj + assertj-core + 3.24.2 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M9 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 18 + 18 + + + + + \ No newline at end of file diff --git a/java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java b/java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java new file mode 100644 index 000000000..3a43c0cf2 --- /dev/null +++ b/java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java @@ -0,0 +1,151 @@ +package com.agui.client; + +import com.agui.client.subscriber.AgentStateMutation; +import com.agui.client.subscriber.AgentSubscriber; +import com.agui.client.subscriber.AgentSubscriberParams; +import com.agui.event.BaseEvent; +import com.agui.event.RunFinishedEvent; +import com.agui.message.BaseMessage; +import com.agui.types.RunAgentInput; +import io.reactivex.Observable; +import io.reactivex.internal.operators.observable.ObservableAny; + +import javax.swing.plaf.basic.BasicListUI; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static java.util.Arrays.asList; + +public abstract class AbstractAgent { + + private String agentId; + private String description; + private String threadId; + private List messages; + private State state; + private boolean debug = false; + + private List agentSubscribers = new ArrayList<>(); + + public AbstractAgent( + String agentId, + String description, + String threadId, + List messages, + State state, + boolean debug + ) { + this.agentId = agentId; + this.description = Objects.nonNull(description) ? description : ""; + this.threadId = Objects.nonNull(threadId) ? threadId : UUID.randomUUID().toString(); + this.messages = Objects.nonNull(messages) ? messages : new ArrayList<>(); + + this.state = Objects.nonNull(state) ? state : new State(); + this.debug = debug; + } + + public Subscription subscribe(final AgentSubscriber subscriber) { + this.agentSubscribers.add(subscriber); + + return () -> this.agentSubscribers.remove(subscriber); + } + + protected abstract Observable run(final RunAgentInput input); + + + public CompletableFuture runAgent( + RunAgentParameters parameters, + AgentSubscriber subscriber + ) { + this.agentId = Objects.nonNull(this.agentId) ? this.agentId : UUID.randomUUID().toString(); + + var input = this.prepareRunAgentInput(parameters); + Object result = null; + + List subscribers = asList( + new AgentSubscriber() { + @Override + public CompletableFuture onRunFinishedEvent(AgentSubscriberParams params, RunFinishedEvent event) { + //result = event.getResult(); + + return CompletableFuture.completedFuture(null); + } + }, + subscriber + ); + subscribers.addAll(this.agentSubscribers); + + this.onInitialize(input, subscribers); + + return null; + } + + protected void onInitialize( + final RunAgentInput input, + final List subscribers + ) { + subscribers.forEach(subscriber -> subscriber.onRunInitialized( + new AgentSubscriberParams( + this.messages, + this.state, + this, + input + ) + )); + } + + public void addMessage(final BaseMessage message) { + this.messages.add(message); + + this.agentSubscribers + .forEach((subscriber -> { + // On new message + + })); + + // Fire onNewToolCall if the message is from assistant and contains tool calls + + + // Fire onMessagesChanged sequentially + } + + public void addMessages(final List messages) { + this.messages.forEach(this::addMessage); + } + + public void setMessages(final List messages) { + this.messages = messages; + + this.agentSubscribers + .forEach((subscriber -> { + // Fire onMessagesChanged + })); + } + + public void setState(final State state) { + this.state = state; + + this.agentSubscribers + .forEach(subscriber -> { + // Fire onStateChanged + }); + } + + protected RunAgentInput prepareRunAgentInput(RunAgentParameters parameters) { + return new RunAgentInput( + this.threadId, + parameters.getRunId().orElse(UUID.randomUUID().toString()), + this.state, + this.messages, + parameters.getTools().orElse(Collections.emptyList()), + parameters.getContext().orElse(Collections.emptyList()), + parameters.getForwardedProps().orElse(null) + ); + + } + + public State getState() { + return this.state; + } + +} \ No newline at end of file diff --git a/java-sdk/client/src/main/java/com/agui/client/RunAgentParameters.java b/java-sdk/client/src/main/java/com/agui/client/RunAgentParameters.java new file mode 100644 index 000000000..aca26d630 --- /dev/null +++ b/java-sdk/client/src/main/java/com/agui/client/RunAgentParameters.java @@ -0,0 +1,86 @@ +package com.agui.client; + +import com.agui.types.Context; +import com.agui.types.Tool; + +import java.util.List; +import java.util.Optional; + +public class RunAgentParameters { + + private final Optional runId; + private final Optional> tools; + private final Optional> context; + private final Optional forwardedProps; + + // Private constructor for builder pattern + private RunAgentParameters(Builder builder) { + this.runId = Optional.ofNullable(builder.runId); + this.tools = Optional.ofNullable(builder.tools); + this.context = Optional.ofNullable(builder.context); + this.forwardedProps = Optional.ofNullable(builder.forwardedProps); + } + + // Getters + public Optional getRunId() { + return runId; + } + + public Optional> getTools() { + return tools; + } + + public Optional> getContext() { + return context; + } + + public Optional getForwardedProps() { + return forwardedProps; + } + + // Builder pattern for easy construction + public static class Builder { + private String runId; + private List tools; + private List context; + private Object forwardedProps; + + public Builder runId(String runId) { + this.runId = runId; + return this; + } + + public Builder tools(List tools) { + this.tools = tools; + return this; + } + + public Builder context(List context) { + this.context = context; + return this; + } + + public Builder forwardedProps(Object forwardedProps) { + this.forwardedProps = forwardedProps; + return this; + } + + public RunAgentParameters build() { + return new RunAgentParameters(this); + } + } + + // Static factory method + public static Builder builder() { + return new Builder(); + } + + // Convenience factory methods + public static RunAgentParameters empty() { + return new Builder().build(); + } + + public static RunAgentParameters withRunId(String runId) { + return new Builder().runId(runId).build(); + } +} diff --git a/java-sdk/client/src/main/java/com/agui/client/RunAgentResult.java b/java-sdk/client/src/main/java/com/agui/client/RunAgentResult.java new file mode 100644 index 000000000..16f59fb4b --- /dev/null +++ b/java-sdk/client/src/main/java/com/agui/client/RunAgentResult.java @@ -0,0 +1,4 @@ +package com.agui.client; + +public class RunAgentResult { +} diff --git a/java-sdk/client/src/main/java/com/agui/client/State.java b/java-sdk/client/src/main/java/com/agui/client/State.java new file mode 100644 index 000000000..10790ca33 --- /dev/null +++ b/java-sdk/client/src/main/java/com/agui/client/State.java @@ -0,0 +1,4 @@ +package com.agui.client; + +public class State { +} diff --git a/java-sdk/client/src/main/java/com/agui/client/Subscription.java b/java-sdk/client/src/main/java/com/agui/client/Subscription.java new file mode 100644 index 000000000..72de02824 --- /dev/null +++ b/java-sdk/client/src/main/java/com/agui/client/Subscription.java @@ -0,0 +1,5 @@ +package com.agui.client; + +public interface Subscription { + void unsubscribe(); +} diff --git a/java-sdk/client/src/main/java/com/agui/client/ToolCall.java b/java-sdk/client/src/main/java/com/agui/client/ToolCall.java new file mode 100644 index 000000000..1661ec612 --- /dev/null +++ b/java-sdk/client/src/main/java/com/agui/client/ToolCall.java @@ -0,0 +1,4 @@ +package com.agui.client; + +public class ToolCall { +} diff --git a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java new file mode 100644 index 000000000..0e29b96ff --- /dev/null +++ b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java @@ -0,0 +1,13 @@ +package com.agui.client.subscriber; + +import com.agui.client.State; +import com.agui.message.BaseMessage; + +import java.util.List; + +public class AgentStateMutation { + + private List messages; + private State state; + private boolean stopPropagation; +} diff --git a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java new file mode 100644 index 000000000..5c4b40c7a --- /dev/null +++ b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java @@ -0,0 +1,46 @@ +package com.agui.client.subscriber; + +import com.agui.client.AbstractAgent; + +import com.agui.client.State; +import com.agui.message.BaseMessage; +import com.agui.types.RunAgentInput; + +import java.util.List; + +public class AgentSubscriberParams { + + private List messages; + private State state; + private AbstractAgent agent; + private RunAgentInput input; + + public AgentSubscriberParams( + final List messages, + final State state, + final AbstractAgent agent, + final RunAgentInput input + ) { + this.messages = messages; + this.state = state; + this.agent = agent; + this.input = input; + } + + public List getMessages() { + return this.messages; + } + + public State getState() { + return this.state; + } + + public AbstractAgent getAgent() { + return this.agent; + } + + public RunAgentInput getInput() { + return this.input; + } +} + diff --git a/java-sdk/client/src/test/java/com/agui/client/TestAgent.java b/java-sdk/client/src/test/java/com/agui/client/TestAgent.java new file mode 100644 index 000000000..692c490e9 --- /dev/null +++ b/java-sdk/client/src/test/java/com/agui/client/TestAgent.java @@ -0,0 +1,36 @@ +package com.agui.client; + +import com.agui.event.BaseEvent; +import com.agui.message.BaseMessage; +import io.reactivex.Observable; +import io.reactivex.subjects.BehaviorSubject; + +import java.util.List; + +public class TestAgent extends AbstractAgent { + + private final BehaviorSubject subject; + + public TestAgent(String agentId, String description, String threadId, List messages, State state, boolean debug) { + super(agentId, description, threadId, messages, state, debug); + + this.subject = BehaviorSubject.create(); + } + + @Override + protected Observable run(com.agui.types.RunAgentInput input) { + return this.subject; + } + + public void emit(final BaseEvent event) { + this.subject.onNext(event); + } + + public void complete() { + this.subject.onComplete(); + } + + public void fail(Throwable t) { + this.subject.onError(t); + } +} diff --git a/java-sdk/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java b/java-sdk/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java new file mode 100644 index 000000000..5175f5a4b --- /dev/null +++ b/java-sdk/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java @@ -0,0 +1,59 @@ +package com.agui.client.subscriber; + + +import com.agui.client.RunAgentParameters; +import com.agui.client.State; +import com.agui.client.TestAgent; +import com.agui.types.RunAgentInput; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.concurrent.CompletableFuture; + +import static org.assertj.core.api.Assertions.as; +import static org.assertj.core.api.Assertions.assertThat; + +class AgentSubscriberTest { + + @Test + public void testOnRunInitialized() { + var agent = new TestAgent( + "agent", + "TESTING", + "THREAD_ID", + Collections.emptyList(), + new State(), + true + ); + + var params = RunAgentParameters + .builder() + .runId("RUN_ID") + .context(Collections.emptyList()) + .tools(Collections.emptyList()) + .build(); + + agent.runAgent( + params, + new AgentSubscriber() { + @Override + public CompletableFuture onRunInitialized(AgentSubscriberParams params) { + assertThat(params.getAgent()).isEqualTo(agent); + assertThat(params.getInput().getThreadId()).isEqualTo("THREAD_ID"); + assertThat(params.getInput().getRunId()).isEqualTo("RUN_ID"); + assertThat(params.getInput().getContext()).hasSize(0); + assertThat(params.getInput().getMessages()).hasSize(0); + assertThat(params.getInput().getTools()).hasSize(0); + + assertThat(params.getMessages()).hasSize(0); + assertThat(params.getState()).isEqualTo(agent.getState()); + + return AgentSubscriber.super.onRunInitialized(params); + } + } + ); + + agent.runAgent(RunAgentParameters.builder() + .build(), new AgentSubscriber() {}); + } +} \ No newline at end of file diff --git a/java-sdk/core/pom.xml b/java-sdk/core/pom.xml new file mode 100644 index 000000000..462f53844 --- /dev/null +++ b/java-sdk/core/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + com.ag-ui + ag-ui + 0.0.1-SNAPSHOT + + + core + + + 18 + 18 + UTF-8 + + + + + Pascal Wilbrink + pascal.wilbrink@gmail.com + https://github.com/pascalwilbrink + + Developer + + + + + + Pascal Wilbrink + pascal.wilbrink@gmail.com + https://github.com/pascalwilbrink + + Maintainer + Developer + + + + \ No newline at end of file diff --git a/java-sdk/core/src/main/java/com/agui/event/BaseEvent.java b/java-sdk/core/src/main/java/com/agui/event/BaseEvent.java new file mode 100644 index 000000000..1f0d9786a --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/BaseEvent.java @@ -0,0 +1,37 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class BaseEvent { + + private final EventType type; + + private int timestamp; + + private Object rawEvent; + + public BaseEvent(final EventType type) { + this.type = type; + } + + public EventType getType() { + return this.type; + } + + public void setTimestamp(final int timestamp) { + this.timestamp = timestamp; + } + + public int getTimestamp() { + return this.timestamp; + } + + public void setRawEvent(final Object rawEvent) { + this.rawEvent = rawEvent; + } + + public Object getRawEvent() { + return this.rawEvent; + } +} + diff --git a/java-sdk/core/src/main/java/com/agui/event/CustomEvent.java b/java-sdk/core/src/main/java/com/agui/event/CustomEvent.java new file mode 100644 index 000000000..887a15dd6 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/CustomEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class CustomEvent extends BaseEvent { + + public CustomEvent() { + super(EventType.CUSTOM); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java b/java-sdk/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java new file mode 100644 index 000000000..82d945c37 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class MessagesSnapshotEvent extends BaseEvent { + + public MessagesSnapshotEvent() { + super(EventType.MESSAGES_SNAPSHOT); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/RawEvent.java b/java-sdk/core/src/main/java/com/agui/event/RawEvent.java new file mode 100644 index 000000000..96a51e1f5 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/RawEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class RawEvent extends BaseEvent { + + public RawEvent() { + super(EventType.RAW); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/RunErrorEvent.java b/java-sdk/core/src/main/java/com/agui/event/RunErrorEvent.java new file mode 100644 index 000000000..5a4255c1e --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/RunErrorEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class RunErrorEvent extends BaseEvent { + + public RunErrorEvent() { + super(EventType.RUN_ERROR); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/RunFinishedEvent.java b/java-sdk/core/src/main/java/com/agui/event/RunFinishedEvent.java new file mode 100644 index 000000000..66ad5f4b6 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/RunFinishedEvent.java @@ -0,0 +1,34 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class RunFinishedEvent extends BaseEvent { + + private final String threadId; + private final String runId; + private final Object result; + + public RunFinishedEvent( + final String threadId, + final String runId, + final Object result + ) { + super(EventType.RUN_FINISHED); + + this.threadId = threadId; + this.runId = runId; + this.result = result; + } + + public String getThreadId() { + return this.threadId; + } + + public String getRunId() { + return this.runId; + } + + public Object getResult() { + return this.result; + } +} \ No newline at end of file diff --git a/java-sdk/core/src/main/java/com/agui/event/RunStartedEvent.java b/java-sdk/core/src/main/java/com/agui/event/RunStartedEvent.java new file mode 100644 index 000000000..08cbf273a --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/RunStartedEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class RunStartedEvent extends BaseEvent { + + public RunStartedEvent() { + super(EventType.RUN_STARTED); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/StateDeltaEvent.java b/java-sdk/core/src/main/java/com/agui/event/StateDeltaEvent.java new file mode 100644 index 000000000..ef50e18b1 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/StateDeltaEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class StateDeltaEvent extends BaseEvent { + + public StateDeltaEvent() { + super(EventType.STATE_DELTA); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/StateSnapshotEvent.java b/java-sdk/core/src/main/java/com/agui/event/StateSnapshotEvent.java new file mode 100644 index 000000000..d0002cebb --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/StateSnapshotEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class StateSnapshotEvent extends BaseEvent { + + public StateSnapshotEvent() { + super(EventType.STATE_SNAPSHOT); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/StepFinishedEvent.java b/java-sdk/core/src/main/java/com/agui/event/StepFinishedEvent.java new file mode 100644 index 000000000..5c2e093b2 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/StepFinishedEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class StepFinishedEvent extends BaseEvent { + + public StepFinishedEvent() { + super(EventType.STEP_FINISHED); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/StepStartedEvent.java b/java-sdk/core/src/main/java/com/agui/event/StepStartedEvent.java new file mode 100644 index 000000000..1f0a25674 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/StepStartedEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class StepStartedEvent extends BaseEvent { + + public StepStartedEvent() { + super(EventType.STEP_STARTED); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageChunkEvent.java b/java-sdk/core/src/main/java/com/agui/event/TextMessageChunkEvent.java new file mode 100644 index 000000000..b17971f07 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/TextMessageChunkEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class TextMessageChunkEvent extends BaseEvent { + + public TextMessageChunkEvent() { + super(EventType.TEXT_MESSAGE_CHUNK); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageContentEvent.java b/java-sdk/core/src/main/java/com/agui/event/TextMessageContentEvent.java new file mode 100644 index 000000000..4209dff48 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/TextMessageContentEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class TextMessageContentEvent extends BaseEvent { + + public TextMessageContentEvent() { + super(EventType.TEXT_MESSAGE_CONTENT); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageEndEvent.java b/java-sdk/core/src/main/java/com/agui/event/TextMessageEndEvent.java new file mode 100644 index 000000000..e3ed5c5eb --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/TextMessageEndEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class TextMessageEndEvent extends BaseEvent { + + public TextMessageEndEvent() { + super(EventType.TEXT_MESSAGE_END); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageStartEvent.java b/java-sdk/core/src/main/java/com/agui/event/TextMessageStartEvent.java new file mode 100644 index 000000000..f66d26e8f --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/TextMessageStartEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class TextMessageStartEvent extends BaseEvent { + + public TextMessageStartEvent() { + super(EventType.TEXT_MESSAGE_START); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/ThinkingEndEvent.java b/java-sdk/core/src/main/java/com/agui/event/ThinkingEndEvent.java new file mode 100644 index 000000000..7c0dd7405 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/ThinkingEndEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class ThinkingEndEvent extends BaseEvent { + + public ThinkingEndEvent() { + super(EventType.THINKING_END); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/ThinkingStartEvent.java b/java-sdk/core/src/main/java/com/agui/event/ThinkingStartEvent.java new file mode 100644 index 000000000..77ac0f78b --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/ThinkingStartEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class ThinkingStartEvent extends BaseEvent { + + public ThinkingStartEvent() { + super(EventType.THINKING_START); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageContentEvent.java b/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageContentEvent.java new file mode 100644 index 000000000..f5bff6325 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageContentEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class ThinkingTextMessageContentEvent extends BaseEvent { + + public ThinkingTextMessageContentEvent() { + super(EventType.THINKING_TEXT_MESSAGE_CONTENT); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageEndEvent.java b/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageEndEvent.java new file mode 100644 index 000000000..0aa9074f6 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageEndEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class ThinkingTextMessageEndEvent extends BaseEvent { + + public ThinkingTextMessageEndEvent() { + super(EventType.THINKING_TEXT_MESSAGE_END); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageStartEvent.java b/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageStartEvent.java new file mode 100644 index 000000000..671b462b7 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageStartEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class ThinkingTextMessageStartEvent extends BaseEvent { + + public ThinkingTextMessageStartEvent() { + super(EventType.THINKING_TEXT_MESSAGE_START); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallArgsEvent.java b/java-sdk/core/src/main/java/com/agui/event/ToolCallArgsEvent.java new file mode 100644 index 000000000..b0cb36e37 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/ToolCallArgsEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class ToolCallArgsEvent extends BaseEvent { + + public ToolCallArgsEvent() { + super(EventType.TOOL_CALL_ARGS); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallChunkEvent.java b/java-sdk/core/src/main/java/com/agui/event/ToolCallChunkEvent.java new file mode 100644 index 000000000..f7a3052fb --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/ToolCallChunkEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class ToolCallChunkEvent extends BaseEvent { + + public ToolCallChunkEvent() { + super(EventType.TOOL_CALL_CHUNK); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallEndEvent.java b/java-sdk/core/src/main/java/com/agui/event/ToolCallEndEvent.java new file mode 100644 index 000000000..66fef0125 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/ToolCallEndEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class ToolCallEndEvent extends BaseEvent { + + public ToolCallEndEvent() { + super(EventType.TOOL_CALL_END); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallResultEvent.java b/java-sdk/core/src/main/java/com/agui/event/ToolCallResultEvent.java new file mode 100644 index 000000000..e919ef59c --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/ToolCallResultEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class ToolCallResultEvent extends BaseEvent { + + public ToolCallResultEvent() { + super(EventType.TOOL_CALL_RESULT); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallStartEvent.java b/java-sdk/core/src/main/java/com/agui/event/ToolCallStartEvent.java new file mode 100644 index 000000000..f12e069c3 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/event/ToolCallStartEvent.java @@ -0,0 +1,10 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class ToolCallStartEvent extends BaseEvent { + + public ToolCallStartEvent() { + super(EventType.TOOL_CALL_START); + } +} diff --git a/java-sdk/core/src/main/java/com/agui/message/AssistantMessage.java b/java-sdk/core/src/main/java/com/agui/message/AssistantMessage.java new file mode 100644 index 000000000..85c5121c6 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/message/AssistantMessage.java @@ -0,0 +1,12 @@ +package com.agui.message; + +public class AssistantMessage extends BaseMessage { + + public AssistantMessage(final String id, final String content, final String name) { + super(id, content, name); + } + + public String getRole() { + return "assistant"; + } +} diff --git a/java-sdk/core/src/main/java/com/agui/message/BaseMessage.java b/java-sdk/core/src/main/java/com/agui/message/BaseMessage.java new file mode 100644 index 000000000..a4dbbb558 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/message/BaseMessage.java @@ -0,0 +1,22 @@ +package com.agui.message; + +public abstract class BaseMessage { + + private final String id; + private final String content; + private final String name; + + public BaseMessage( + final String id, + final String content, + final String name + ) { + this.id = id; + this.content = content; + this.name = name; + } + + public abstract String getRole(); +} + + diff --git a/java-sdk/core/src/main/java/com/agui/message/DeveloperMessage.java b/java-sdk/core/src/main/java/com/agui/message/DeveloperMessage.java new file mode 100644 index 000000000..df4cc6d71 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/message/DeveloperMessage.java @@ -0,0 +1,12 @@ +package com.agui.message; + +public class DeveloperMessage extends BaseMessage { + + public DeveloperMessage(final String id, final String content, final String name) { + super(id, content, name); + } + + public String getRole() { + return "developer"; + } +} diff --git a/java-sdk/core/src/main/java/com/agui/message/SystemMessage.java b/java-sdk/core/src/main/java/com/agui/message/SystemMessage.java new file mode 100644 index 000000000..55fb2d0a8 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/message/SystemMessage.java @@ -0,0 +1,12 @@ +package com.agui.message; + +public class SystemMessage extends BaseMessage { + + public SystemMessage(final String id, final String content, final String name) { + super(id, content, name); + } + + public String getRole() { + return "system"; + } +} diff --git a/java-sdk/core/src/main/java/com/agui/message/UserMessage.java b/java-sdk/core/src/main/java/com/agui/message/UserMessage.java new file mode 100644 index 000000000..1e6c0c9ed --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/message/UserMessage.java @@ -0,0 +1,12 @@ +package com.agui.message; + +public class UserMessage extends BaseMessage { + + public UserMessage(final String id, final String content, final String name) { + super(id, content, name); + } + + public String getRole() { + return "user"; + } +} diff --git a/java-sdk/core/src/main/java/com/agui/types/Context.java b/java-sdk/core/src/main/java/com/agui/types/Context.java new file mode 100644 index 000000000..a2ddd3302 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/types/Context.java @@ -0,0 +1,15 @@ +package com.agui.types; + +public class Context { + + private final String description; + private final String value; + + public Context(final String description, final String value) { + this.description = description; + this.value = value; + } + + public String getDescription() { return this.description; } + public String getValue() { return this.value; } +} diff --git a/java-sdk/core/src/main/java/com/agui/types/EventType.java b/java-sdk/core/src/main/java/com/agui/types/EventType.java new file mode 100644 index 000000000..2496d15c2 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/types/EventType.java @@ -0,0 +1,36 @@ +package com.agui.types; + +public enum EventType { + TEXT_MESSAGE_START("TEXT_MESSAGE_START"), + TEXT_MESSAGE_CONTENT("TEXT_MESSAGE_CONTENT"), + TEXT_MESSAGE_END("TEXT_MESSAGE_END"), + TEXT_MESSAGE_CHUNK("TEXT_MESSAGE_CHUNK"), + THINKING_TEXT_MESSAGE_START("THINKING_TEXT_MESSAGE_START"), + THINKING_TEXT_MESSAGE_CONTENT("THINKING_TEXT_MESSAGE_CONTENT"), + THINKING_TEXT_MESSAGE_END("THINKGIN_TEXT_MESSAGE_END"), + TOOL_CALL_START("TOOL_CALL_START"), + TOOL_CALL_ARGS("TOOL_CALL_ARGS"), + TOOL_CALL_END("TOOL_CALL_END"), + TOOL_CALL_CHUNK("TOOL_CALL_CHUNK"), + TOOL_CALL_RESULT("TOOL_CALL_RESULT"), + THINKING_START("THINKING_START"), + THINKING_END("THINKING_END"), + STATE_SNAPSHOT("STATE_SNAPSHOT"), + STATE_DELTA("STATE_DELTA"), + MESSAGES_SNAPSHOT("MESSAGES_SNAPSHOT"), + RAW("RAW"), + CUSTOM("CUSTOM"), + RUN_STARTED("RUN_STARTED"), + RUN_FINISHED("RUN_FINISHED"), + RUN_ERROR("RUN_ERROR"), + STEP_STARTED("STEP_STARTED"), + STEP_FINISHED("STEP-FINISHED") + ; + + private final String name; + + EventType(String name) { + this.name = name; + } + +} \ No newline at end of file diff --git a/java-sdk/core/src/main/java/com/agui/types/RunAgentInput.java b/java-sdk/core/src/main/java/com/agui/types/RunAgentInput.java new file mode 100644 index 000000000..38e4a59cf --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/types/RunAgentInput.java @@ -0,0 +1,63 @@ +package com.agui.types; + +import com.agui.message.BaseMessage; + +import java.util.List; + +public class RunAgentInput { + + private final String threadId; + private final String runId; + private final Object state; + private final List messages; + private final List tools; + private final List context; + private final Object forwardedProps; + + public RunAgentInput( + final String threadId, + final String runId, + final Object state, + final List messages, + final List tools, + final List context, + final Object forwardedProps + ) { + this.threadId = threadId; + this.runId = runId; + this.state = state; + this.messages = messages; + this.tools = tools; + this.context = context; + this.forwardedProps = forwardedProps; + } + + public String getThreadId() { + return this.threadId; + } + + public String getRunId() { + return this.runId; + } + + public Object getState() { + return this.state; + } + + public List getMessages() { + return this.messages; + } + + public List getTools() { + return this.tools; + } + + public List getContext() { + return this.context; + } + + public Object getForwardedProps() { + return this.forwardedProps; + } +} + diff --git a/java-sdk/core/src/main/java/com/agui/types/Tool.java b/java-sdk/core/src/main/java/com/agui/types/Tool.java new file mode 100644 index 000000000..c4bc0702d --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/types/Tool.java @@ -0,0 +1,17 @@ +package com.agui.types; + +public class Tool { + private final String name; + private final String description; + private final Object parameters; + + public Tool( + final String name, + final String description, + final Object parameters + ) { + this.name = name; + this.description = description; + this.parameters = parameters; + } +} \ No newline at end of file From 5f15abe91d37a4e753872f96ca7d41fa814a54cf Mon Sep 17 00:00:00 2001 From: Pascal Wilbrink Date: Wed, 6 Aug 2025 22:49:46 +0200 Subject: [PATCH 2/8] Initial Java SDK setup --- .idea/.gitignore | 3 + java-sdk/.gitignore | 38 ++++ java-sdk/.idea/.gitignore | 3 + .../client/subscriber/AgentSubscriber.java | 185 ++++++++++++++++++ .../java/com/agui/message/ToolMessage.java | 19 ++ java-sdk/pom.xml | 44 +++++ 6 files changed, 292 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 java-sdk/.gitignore create mode 100644 java-sdk/.idea/.gitignore create mode 100644 java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java create mode 100644 java-sdk/core/src/main/java/com/agui/message/ToolMessage.java create mode 100644 java-sdk/pom.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..26d33521a --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/java-sdk/.gitignore b/java-sdk/.gitignore new file mode 100644 index 000000000..5ff6309b7 --- /dev/null +++ b/java-sdk/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/java-sdk/.idea/.gitignore b/java-sdk/.idea/.gitignore new file mode 100644 index 000000000..26d33521a --- /dev/null +++ b/java-sdk/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java new file mode 100644 index 000000000..8da026de9 --- /dev/null +++ b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java @@ -0,0 +1,185 @@ +package com.agui.client.subscriber; + +import com.agui.client.ToolCall; +import com.agui.event.*; +import com.agui.message.BaseMessage; + +import java.util.concurrent.CompletableFuture; + +public interface AgentSubscriber { + + // Request lifecycle + default CompletableFuture onRunInitialized( + AgentSubscriberParams params) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onRunFailed( + AgentSubscriberParams params, + Exception error) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onRunFinalized( + AgentSubscriberParams params) { + return CompletableFuture.completedFuture(null); + } + + // Events + default CompletableFuture onEvent( + AgentSubscriberParams params, + BaseEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onRunStartedEvent( + AgentSubscriberParams params, + RunStartedEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onRunFinishedEvent( + AgentSubscriberParams params, + RunFinishedEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onRunErrorEvent( + AgentSubscriberParams params, + RunErrorEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onStepStartedEvent( + AgentSubscriberParams params, + StepStartedEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onStepFinishedEvent( + AgentSubscriberParams params, + StepFinishedEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onTextMessageStartEvent( + AgentSubscriberParams params, + TextMessageStartEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onTextMessageContentEvent( + AgentSubscriberParams params, + TextMessageContentEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onTextMessageEndEvent( + AgentSubscriberParams params, + TextMessageEndEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onToolCallStartEvent( + AgentSubscriberParams params, + ToolCallStartEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onToolCallArgsEvent( + AgentSubscriberParams params, + ToolCallArgsEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onToolCallEndEvent( + AgentSubscriberParams params, + ToolCallEndEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onToolCallResultEvent( + AgentSubscriberParams params, + ToolCallResultEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onStateSnapshotEvent( + AgentSubscriberParams params, + StateSnapshotEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onStateDeltaEvent( + AgentSubscriberParams params, + StateDeltaEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onMessagesSnapshotEvent( + AgentSubscriberParams params, + MessagesSnapshotEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onRawEvent( + AgentSubscriberParams params, + RawEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onCustomEvent( + AgentSubscriberParams params, + CustomEvent event + ) { + return CompletableFuture.completedFuture(null); + } + + // State changes + + default CompletableFuture onMessagesChanged( + AgentSubscriberParams params + ) { + return CompletableFuture.completedFuture(null); + } + + default CompletableFuture onStateChanged( + AgentSubscriberParams params + ) { + return CompletableFuture.completedFuture(null); + } + + + default CompletableFuture onNewMessage( + AgentSubscriberParams params, + BaseMessage message + ) { + return CompletableFuture.completedFuture(null); + } + + + default CompletableFuture onNewToolCall( + AgentSubscriberParams params, + ToolCall toolCall + ) { + return CompletableFuture.completedFuture(null); + } +} + diff --git a/java-sdk/core/src/main/java/com/agui/message/ToolMessage.java b/java-sdk/core/src/main/java/com/agui/message/ToolMessage.java new file mode 100644 index 000000000..6f29cbc93 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/message/ToolMessage.java @@ -0,0 +1,19 @@ +package com.agui.message; + +public class ToolMessage extends BaseMessage { + + private final String toolCallId; + private final String error; + + public ToolMessage(final String id, final String content, final String name, final String toolCallId, final String error) { + super(id, content, name); + + this.toolCallId = toolCallId; + this.error = error; + } + + public String getRole() { + return "tool"; + } +} + diff --git a/java-sdk/pom.xml b/java-sdk/pom.xml new file mode 100644 index 000000000..a784108fd --- /dev/null +++ b/java-sdk/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + com.ag-ui + ag-ui + 0.0.1-SNAPSHOT + pom + + + core + client + + + + 18 + 18 + UTF-8 + + + + + Pascal Wilbrink + pascal.wilbrink@gmail.com + https://github.com/pascalwilbrink + + Developer + + + + + + Pascal Wilbrink + pascal.wilbrink@gmail.com + https://github.com/pascalwilbrink + + Maintainer + Developer + + + + \ No newline at end of file From 9c070f1c4949ac19772e0cb2a303a2d45a8d8bf2 Mon Sep 17 00:00:00 2001 From: Pascal Wilbrink Date: Wed, 6 Aug 2025 22:56:06 +0200 Subject: [PATCH 3/8] Added java test --- .github/workflows/test.yml | 28 ++- .idea/ag-ui.iml | 9 + .idea/dbnavigator.xml | 403 +++++++++++++++++++++++++++++++++ .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + java-sdk/.idea/dbnavigator.xml | 403 +++++++++++++++++++++++++++++++++ java-sdk/.idea/encodings.xml | 11 + java-sdk/.idea/misc.xml | 14 ++ java-sdk/.idea/uiDesigner.xml | 124 ++++++++++ java-sdk/.idea/vcs.xml | 6 + 11 files changed, 1017 insertions(+), 1 deletion(-) create mode 100644 .idea/ag-ui.iml create mode 100644 .idea/dbnavigator.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 java-sdk/.idea/dbnavigator.xml create mode 100644 java-sdk/.idea/encodings.xml create mode 100644 java-sdk/.idea/misc.xml create mode 100644 java-sdk/.idea/uiDesigner.xml create mode 100644 java-sdk/.idea/vcs.xml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ddfd0bfd9..401af0f67 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -89,4 +89,30 @@ jobs: - name: Run tests working-directory: typescript-sdk - run: pnpm run test \ No newline at end of file + run: pnpm run test + + java: + name: Java SDK Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: '18' + distribution: 'temurin' + + - name: Cache Maven dependencies + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-m2- + + - name: Run tests + working-directory: java-sdk + run: mvn test \ No newline at end of file diff --git a/.idea/ag-ui.iml b/.idea/ag-ui.iml new file mode 100644 index 000000000..d6ebd4805 --- /dev/null +++ b/.idea/ag-ui.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml new file mode 100644 index 000000000..6dafaf6e4 --- /dev/null +++ b/.idea/dbnavigator.xml @@ -0,0 +1,403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..4869cb0e7 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..88dd6ff14 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/java-sdk/.idea/dbnavigator.xml b/java-sdk/.idea/dbnavigator.xml new file mode 100644 index 000000000..6dafaf6e4 --- /dev/null +++ b/java-sdk/.idea/dbnavigator.xml @@ -0,0 +1,403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java-sdk/.idea/encodings.xml b/java-sdk/.idea/encodings.xml new file mode 100644 index 000000000..a4f3fa07b --- /dev/null +++ b/java-sdk/.idea/encodings.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java-sdk/.idea/misc.xml b/java-sdk/.idea/misc.xml new file mode 100644 index 000000000..4258c62fd --- /dev/null +++ b/java-sdk/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/java-sdk/.idea/uiDesigner.xml b/java-sdk/.idea/uiDesigner.xml new file mode 100644 index 000000000..2b63946d5 --- /dev/null +++ b/java-sdk/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java-sdk/.idea/vcs.xml b/java-sdk/.idea/vcs.xml new file mode 100644 index 000000000..6c0b86358 --- /dev/null +++ b/java-sdk/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 5654c685fd55ddeabe8346ce09356c9b0fa65fda Mon Sep 17 00:00:00 2001 From: Pascal Wilbrink Date: Wed, 6 Aug 2025 22:57:18 +0200 Subject: [PATCH 4/8] Added java test GH action --- .idea/.gitignore | 3 - .idea/ag-ui.iml | 9 - .idea/dbnavigator.xml | 403 --------------------------------- .idea/misc.xml | 6 - .idea/modules.xml | 8 - .idea/vcs.xml | 6 - java-sdk/.idea/.gitignore | 3 - java-sdk/.idea/dbnavigator.xml | 403 --------------------------------- java-sdk/.idea/encodings.xml | 11 - java-sdk/.idea/misc.xml | 14 -- java-sdk/.idea/uiDesigner.xml | 124 ---------- java-sdk/.idea/vcs.xml | 6 - 12 files changed, 996 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/ag-ui.iml delete mode 100644 .idea/dbnavigator.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 java-sdk/.idea/.gitignore delete mode 100644 java-sdk/.idea/dbnavigator.xml delete mode 100644 java-sdk/.idea/encodings.xml delete mode 100644 java-sdk/.idea/misc.xml delete mode 100644 java-sdk/.idea/uiDesigner.xml delete mode 100644 java-sdk/.idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d33521a..000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/ag-ui.iml b/.idea/ag-ui.iml deleted file mode 100644 index d6ebd4805..000000000 --- a/.idea/ag-ui.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml deleted file mode 100644 index 6dafaf6e4..000000000 --- a/.idea/dbnavigator.xml +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 4869cb0e7..000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 88dd6ff14..000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfb..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/java-sdk/.idea/.gitignore b/java-sdk/.idea/.gitignore deleted file mode 100644 index 26d33521a..000000000 --- a/java-sdk/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/java-sdk/.idea/dbnavigator.xml b/java-sdk/.idea/dbnavigator.xml deleted file mode 100644 index 6dafaf6e4..000000000 --- a/java-sdk/.idea/dbnavigator.xml +++ /dev/null @@ -1,403 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java-sdk/.idea/encodings.xml b/java-sdk/.idea/encodings.xml deleted file mode 100644 index a4f3fa07b..000000000 --- a/java-sdk/.idea/encodings.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/java-sdk/.idea/misc.xml b/java-sdk/.idea/misc.xml deleted file mode 100644 index 4258c62fd..000000000 --- a/java-sdk/.idea/misc.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/java-sdk/.idea/uiDesigner.xml b/java-sdk/.idea/uiDesigner.xml deleted file mode 100644 index 2b63946d5..000000000 --- a/java-sdk/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java-sdk/.idea/vcs.xml b/java-sdk/.idea/vcs.xml deleted file mode 100644 index 6c0b86358..000000000 --- a/java-sdk/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 45e6bb5a7108179273dd4bd4dd6e85583dfbc68d Mon Sep 17 00:00:00 2001 From: Pascal Wilbrink Date: Thu, 7 Aug 2025 12:16:35 +0200 Subject: [PATCH 5/8] Added okhttp Http Agent --- .../java/com/agui/client/AbstractAgent.java | 111 +++++++--- .../src/main/java/com/agui/client/State.java | 4 - .../client/subscriber/AgentStateMutation.java | 2 +- .../client/subscriber/AgentSubscriber.java | 194 +++-------------- .../subscriber/AgentSubscriberParams.java | 2 +- .../test/java/com/agui/client/TestAgent.java | 4 +- .../subscriber/AgentSubscriberTest.java | 22 +- java-sdk/core/pom.xml | 26 +++ .../main/java/com/agui/event/BaseEvent.java | 32 +++ .../com/agui/event/MessagesSnapshotEvent.java | 13 ++ .../java/com/agui/event/RunFinishedEvent.java | 26 ++- .../java/com/agui/event/RunStartedEvent.java | 20 ++ .../com/agui/event/TextMessageChunkEvent.java | 22 +- .../agui/event/TextMessageContentEvent.java | 19 ++ .../com/agui/event/TextMessageEndEvent.java | 11 + .../com/agui/event/TextMessageStartEvent.java | 19 ++ .../com/agui/event/ToolCallArgsEvent.java | 19 ++ .../com/agui/event/ToolCallChunkEvent.java | 29 ++- .../java/com/agui/event/ToolCallEndEvent.java | 10 + .../com/agui/event/ToolCallResultEvent.java | 37 ++++ .../com/agui/event/ToolCallStartEvent.java | 30 ++- .../com/agui/message/AssistantMessage.java | 16 +- .../java/com/agui/message/BaseMessage.java | 53 ++++- .../com/agui/message/DeveloperMessage.java | 4 - .../java/com/agui/message/SystemMessage.java | 4 - .../java/com/agui/message/ToolMessage.java | 21 +- .../java/com/agui/message/UserMessage.java | 4 - .../src/main/java/com/agui/types/Context.java | 14 +- .../java/com/agui/types/FunctionCall.java | 4 + .../java/com/agui/types/RunAgentInput.java | 65 +----- .../src/main/java/com/agui/types/State.java | 4 + .../main/java/com/agui/types/ToolCall.java | 4 + .../java/com/agui/event/BaseEventTest.java | 195 ++++++++++++++++++ java-sdk/ok-http/pom.xml | 51 +++++ .../src/main/java/com/agui/HttpAgent.java | 125 +++++++++++ .../src/main/java/com/agui/HttpClient.java | 73 +++++++ .../src/test/java/com/agui/HttpAgentTest.java | 76 +++++++ java-sdk/pom.xml | 1 + 38 files changed, 1035 insertions(+), 331 deletions(-) delete mode 100644 java-sdk/client/src/main/java/com/agui/client/State.java create mode 100644 java-sdk/core/src/main/java/com/agui/types/FunctionCall.java create mode 100644 java-sdk/core/src/main/java/com/agui/types/State.java create mode 100644 java-sdk/core/src/main/java/com/agui/types/ToolCall.java create mode 100644 java-sdk/core/src/test/java/com/agui/event/BaseEventTest.java create mode 100644 java-sdk/ok-http/pom.xml create mode 100644 java-sdk/ok-http/src/main/java/com/agui/HttpAgent.java create mode 100644 java-sdk/ok-http/src/main/java/com/agui/HttpClient.java create mode 100644 java-sdk/ok-http/src/test/java/com/agui/HttpAgentTest.java diff --git a/java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java b/java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java index 3a43c0cf2..12fbd9e57 100644 --- a/java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java +++ b/java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java @@ -1,20 +1,14 @@ package com.agui.client; -import com.agui.client.subscriber.AgentStateMutation; import com.agui.client.subscriber.AgentSubscriber; import com.agui.client.subscriber.AgentSubscriberParams; -import com.agui.event.BaseEvent; -import com.agui.event.RunFinishedEvent; +import com.agui.event.*; import com.agui.message.BaseMessage; import com.agui.types.RunAgentInput; +import com.agui.types.State; import io.reactivex.Observable; -import io.reactivex.internal.operators.observable.ObservableAny; -import javax.swing.plaf.basic.BasicListUI; import java.util.*; -import java.util.concurrent.CompletableFuture; - -import static java.util.Arrays.asList; public abstract class AbstractAgent { @@ -25,15 +19,15 @@ public abstract class AbstractAgent { private State state; private boolean debug = false; - private List agentSubscribers = new ArrayList<>(); + private final List agentSubscribers = new ArrayList<>(); public AbstractAgent( - String agentId, - String description, - String threadId, - List messages, - State state, - boolean debug + final String agentId, + final String description, + final String threadId, + final List messages, + final State state, + final boolean debug ) { this.agentId = agentId; this.description = Objects.nonNull(description) ? description : ""; @@ -53,7 +47,11 @@ public Subscription subscribe(final AgentSubscriber subscriber) { protected abstract Observable run(final RunAgentInput input); - public CompletableFuture runAgent( + public void runAgent(RunAgentParameters parameters) { + this.runAgent(parameters, null); + } + + public void runAgent( RunAgentParameters parameters, AgentSubscriber subscriber ) { @@ -62,35 +60,90 @@ public CompletableFuture runAgent( var input = this.prepareRunAgentInput(parameters); Object result = null; - List subscribers = asList( + List subscribers = new ArrayList<>(); + subscribers.add( new AgentSubscriber() { @Override - public CompletableFuture onRunFinishedEvent(AgentSubscriberParams params, RunFinishedEvent event) { + public void onRunFinishedEvent(RunFinishedEvent event) { //result = event.getResult(); - - return CompletableFuture.completedFuture(null); } - }, - subscriber + } ); + + if (Objects.nonNull(subscriber)) { + subscribers.add(subscriber); + } subscribers.addAll(this.agentSubscribers); this.onInitialize(input, subscribers); - return null; + this.run(input) + .map((event) -> { + subscribers.forEach((s) -> { + s.onEvent(event); + }); + + switch (event.getType()) { + case RUN_STARTED -> subscriber.onRunStartedEvent((RunStartedEvent) event); + case RUN_ERROR -> subscriber.onRunErrorEvent((RunErrorEvent) event); + case RUN_FINISHED -> subscriber.onRunFinishedEvent((RunFinishedEvent) event); + case STEP_STARTED -> subscriber.onStepStartedEvent((StepStartedEvent) event); + case STEP_FINISHED -> subscriber.onStepFinishedEvent((StepFinishedEvent) event); + case TEXT_MESSAGE_START -> subscriber.onTextMessageStartEvent((TextMessageStartEvent) event); + case TEXT_MESSAGE_CONTENT -> subscriber.onTextMessageContentEvent((TextMessageContentEvent) event); + case TEXT_MESSAGE_END -> subscriber.onTextMessageEndEvent((TextMessageEndEvent) event); + case TOOL_CALL_START -> subscriber.onToolCallStartEvent((ToolCallStartEvent) event); + case TOOL_CALL_ARGS -> subscriber.onToolCallArgsEvent((ToolCallArgsEvent) event); + case TOOL_CALL_RESULT -> subscriber.onToolCallResultEvent((ToolCallResultEvent) event); + case TOOL_CALL_END -> subscriber.onToolCallEndEvent((ToolCallEndEvent) event); + case RAW -> subscriber.onRawEvent((RawEvent) event); + case CUSTOM -> subscriber.onCustomEvent((CustomEvent) event); + case MESSAGES_SNAPSHOT -> subscriber.onMessagesSnapshotEvent((MessagesSnapshotEvent) event); + case STATE_SNAPSHOT -> subscriber.onStateSnapshotEvent((StateSnapshotEvent) event); + case STATE_DELTA -> subscriber.onStateDeltaEvent((StateDeltaEvent) event); + } + + return event; + }) + .doFinally(() -> { + subscribers.forEach(s -> { + var params = new AgentSubscriberParams( + this.messages, + this.state, + this, + input + ); + s.onRunFinalized(params); + }); + System.out.println("parameters = " + parameters + ", subscriber = " + subscriber); + }).subscribe(); +/* + () => this.run(input), + transformChunks(this.debug), + verifyEvents(this.debug), + (source$) => this.apply(input, source$, subscribers), + (source$) => this.processApplyEvents(input, source$, subscribers), + catchError((error) => { + return this.onError(input, error, subscribers); + }), + finalize(() => { + void this.onFinalize(input, subscribers); + }), +*/ } + protected void onInitialize( final RunAgentInput input, final List subscribers ) { subscribers.forEach(subscriber -> subscriber.onRunInitialized( - new AgentSubscriberParams( - this.messages, - this.state, - this, - input - ) + new AgentSubscriberParams( + this.messages, + this.state, + this, + input + ) )); } diff --git a/java-sdk/client/src/main/java/com/agui/client/State.java b/java-sdk/client/src/main/java/com/agui/client/State.java deleted file mode 100644 index 10790ca33..000000000 --- a/java-sdk/client/src/main/java/com/agui/client/State.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.agui.client; - -public class State { -} diff --git a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java index 0e29b96ff..395a81e5c 100644 --- a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java +++ b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java @@ -1,6 +1,6 @@ package com.agui.client.subscriber; -import com.agui.client.State; +import com.agui.types.State; import com.agui.message.BaseMessage; import java.util.List; diff --git a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java index 8da026de9..578319013 100644 --- a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java +++ b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java @@ -4,182 +4,38 @@ import com.agui.event.*; import com.agui.message.BaseMessage; -import java.util.concurrent.CompletableFuture; - public interface AgentSubscriber { // Request lifecycle - default CompletableFuture onRunInitialized( - AgentSubscriberParams params) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onRunFailed( - AgentSubscriberParams params, - Exception error) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onRunFinalized( - AgentSubscriberParams params) { - return CompletableFuture.completedFuture(null); - } + default void onRunInitialized(AgentSubscriberParams params) { } + default void onRunFailed(AgentSubscriberParams params, Throwable error) { } + default void onRunFinalized(AgentSubscriberParams params) { } // Events - default CompletableFuture onEvent( - AgentSubscriberParams params, - BaseEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onRunStartedEvent( - AgentSubscriberParams params, - RunStartedEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onRunFinishedEvent( - AgentSubscriberParams params, - RunFinishedEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onRunErrorEvent( - AgentSubscriberParams params, - RunErrorEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onStepStartedEvent( - AgentSubscriberParams params, - StepStartedEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onStepFinishedEvent( - AgentSubscriberParams params, - StepFinishedEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onTextMessageStartEvent( - AgentSubscriberParams params, - TextMessageStartEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onTextMessageContentEvent( - AgentSubscriberParams params, - TextMessageContentEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onTextMessageEndEvent( - AgentSubscriberParams params, - TextMessageEndEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onToolCallStartEvent( - AgentSubscriberParams params, - ToolCallStartEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onToolCallArgsEvent( - AgentSubscriberParams params, - ToolCallArgsEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onToolCallEndEvent( - AgentSubscriberParams params, - ToolCallEndEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onToolCallResultEvent( - AgentSubscriberParams params, - ToolCallResultEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onStateSnapshotEvent( - AgentSubscriberParams params, - StateSnapshotEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onStateDeltaEvent( - AgentSubscriberParams params, - StateDeltaEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onMessagesSnapshotEvent( - AgentSubscriberParams params, - MessagesSnapshotEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onRawEvent( - AgentSubscriberParams params, - RawEvent event - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onCustomEvent( - AgentSubscriberParams params, - CustomEvent event - ) { - return CompletableFuture.completedFuture(null); - } + default void onEvent(BaseEvent event) { } + default void onRunStartedEvent(RunStartedEvent event) { } + default void onRunFinishedEvent(RunFinishedEvent event) { } + default void onRunErrorEvent(RunErrorEvent event) { } + default void onStepStartedEvent(StepStartedEvent event) { } + default void onStepFinishedEvent(StepFinishedEvent event) { } + default void onTextMessageStartEvent(TextMessageStartEvent event) { } + default void onTextMessageContentEvent(TextMessageContentEvent event) { } + default void onTextMessageEndEvent(TextMessageEndEvent event) { } + default void onToolCallStartEvent(ToolCallStartEvent event) { } + default void onToolCallArgsEvent(ToolCallArgsEvent event) { } + default void onToolCallEndEvent(ToolCallEndEvent event) { } + default void onToolCallResultEvent(ToolCallResultEvent event) { } + default void onStateSnapshotEvent(StateSnapshotEvent event) { } + default void onStateDeltaEvent(StateDeltaEvent event) { } + default void onMessagesSnapshotEvent(MessagesSnapshotEvent event) { } + default void onRawEvent(RawEvent event) { } + default void onCustomEvent(CustomEvent event) { } // State changes + default void onMessagesChanged(AgentSubscriberParams params) { } + default void onStateChanged(AgentSubscriberParams params) { } + default void onNewMessage(BaseMessage message) { } + default void onNewToolCall(ToolCall toolCall) { } - default CompletableFuture onMessagesChanged( - AgentSubscriberParams params - ) { - return CompletableFuture.completedFuture(null); - } - - default CompletableFuture onStateChanged( - AgentSubscriberParams params - ) { - return CompletableFuture.completedFuture(null); - } - - - default CompletableFuture onNewMessage( - AgentSubscriberParams params, - BaseMessage message - ) { - return CompletableFuture.completedFuture(null); - } - - - default CompletableFuture onNewToolCall( - AgentSubscriberParams params, - ToolCall toolCall - ) { - return CompletableFuture.completedFuture(null); - } } diff --git a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java index 5c4b40c7a..93d365afe 100644 --- a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java +++ b/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java @@ -2,7 +2,7 @@ import com.agui.client.AbstractAgent; -import com.agui.client.State; +import com.agui.types.State; import com.agui.message.BaseMessage; import com.agui.types.RunAgentInput; diff --git a/java-sdk/client/src/test/java/com/agui/client/TestAgent.java b/java-sdk/client/src/test/java/com/agui/client/TestAgent.java index 692c490e9..a4406396d 100644 --- a/java-sdk/client/src/test/java/com/agui/client/TestAgent.java +++ b/java-sdk/client/src/test/java/com/agui/client/TestAgent.java @@ -2,6 +2,8 @@ import com.agui.event.BaseEvent; import com.agui.message.BaseMessage; +import com.agui.types.RunAgentInput; +import com.agui.types.State; import io.reactivex.Observable; import io.reactivex.subjects.BehaviorSubject; @@ -18,7 +20,7 @@ public TestAgent(String agentId, String description, String threadId, List run(com.agui.types.RunAgentInput input) { + protected Observable run(RunAgentInput input) { return this.subject; } diff --git a/java-sdk/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java b/java-sdk/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java index 5175f5a4b..0e6e8b76b 100644 --- a/java-sdk/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java +++ b/java-sdk/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java @@ -2,15 +2,12 @@ import com.agui.client.RunAgentParameters; -import com.agui.client.State; +import com.agui.types.State; import com.agui.client.TestAgent; -import com.agui.types.RunAgentInput; import org.junit.jupiter.api.Test; import java.util.Collections; -import java.util.concurrent.CompletableFuture; -import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; class AgentSubscriberTest { @@ -37,23 +34,20 @@ public void testOnRunInitialized() { params, new AgentSubscriber() { @Override - public CompletableFuture onRunInitialized(AgentSubscriberParams params) { + public void onRunInitialized(AgentSubscriberParams params) { assertThat(params.getAgent()).isEqualTo(agent); - assertThat(params.getInput().getThreadId()).isEqualTo("THREAD_ID"); - assertThat(params.getInput().getRunId()).isEqualTo("RUN_ID"); - assertThat(params.getInput().getContext()).hasSize(0); - assertThat(params.getInput().getMessages()).hasSize(0); - assertThat(params.getInput().getTools()).hasSize(0); + assertThat(params.getInput().threadId()).isEqualTo("THREAD_ID"); + assertThat(params.getInput().runId()).isEqualTo("RUN_ID"); + assertThat(params.getInput().context()).hasSize(0); + assertThat(params.getInput().messages()).hasSize(0); + assertThat(params.getInput().tools()).hasSize(0); assertThat(params.getMessages()).hasSize(0); assertThat(params.getState()).isEqualTo(agent.getState()); - - return AgentSubscriber.super.onRunInitialized(params); } } ); - agent.runAgent(RunAgentParameters.builder() - .build(), new AgentSubscriber() {}); + agent.runAgent(RunAgentParameters.builder().build(), new AgentSubscriber() {}); } } \ No newline at end of file diff --git a/java-sdk/core/pom.xml b/java-sdk/core/pom.xml index 462f53844..66fe31762 100644 --- a/java-sdk/core/pom.xml +++ b/java-sdk/core/pom.xml @@ -16,6 +16,32 @@ 18 UTF-8 + + + com.fasterxml.jackson.core + jackson-annotations + 2.19.0 + compile + + + org.junit.jupiter + junit-jupiter + 5.8.1 + test + + + com.fasterxml.jackson.core + jackson-databind + 2.19.0 + test + + + org.assertj + assertj-core + 3.24.2 + test + + diff --git a/java-sdk/core/src/main/java/com/agui/event/BaseEvent.java b/java-sdk/core/src/main/java/com/agui/event/BaseEvent.java index 1f0d9786a..baca0d09c 100644 --- a/java-sdk/core/src/main/java/com/agui/event/BaseEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/BaseEvent.java @@ -1,7 +1,39 @@ package com.agui.event; import com.agui.types.EventType; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "type" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = CustomEvent.class, name = "CUSTOM"), + @JsonSubTypes.Type(value = MessagesSnapshotEvent.class, name = "MESSAGES_SNAPSHOT"), + @JsonSubTypes.Type(value = RawEvent.class, name = "RAW"), + @JsonSubTypes.Type(value = RunErrorEvent.class, name = "RUN_ERROR"), + @JsonSubTypes.Type(value = RunFinishedEvent.class, name = "RUN_FINISHED"), + @JsonSubTypes.Type(value = RunStartedEvent.class, name = "RUN_STARTED"), + @JsonSubTypes.Type(value = StateDeltaEvent.class, name = "STATE_DELTA"), + @JsonSubTypes.Type(value = StateSnapshotEvent.class, name = "STATE_SNAPSHOT"), + @JsonSubTypes.Type(value = StepFinishedEvent.class, name = "STEP_FINISHED"), + @JsonSubTypes.Type(value = StepStartedEvent.class, name = "STEP_STARTED"), + @JsonSubTypes.Type(value = TextMessageChunkEvent.class, name = "TEXT_MESSAGE_CHUNK"), + @JsonSubTypes.Type(value = TextMessageContentEvent.class, name = "TEXT_MESSAGE_CONTENT"), + @JsonSubTypes.Type(value = TextMessageEndEvent.class, name = "TEXT_MESSAGE_END"), + @JsonSubTypes.Type(value = TextMessageStartEvent.class, name = "TEXT_MESSAGE_START"), + @JsonSubTypes.Type(value = ThinkingEndEvent.class, name = "THINKING_END"), + @JsonSubTypes.Type(value = ThinkingStartEvent.class, name = "THINKING_START"), + @JsonSubTypes.Type(value = ThinkingTextMessageContentEvent.class, name = "THINKING_TEXT_MESSAGE_CONTENT"), + @JsonSubTypes.Type(value = ThinkingTextMessageEndEvent.class, name = "THINKING_TEXT_MESSAGE_END"), + @JsonSubTypes.Type(value = ThinkingTextMessageStartEvent.class, name = "THINKING_TEXT_MESSAGE_START"), + @JsonSubTypes.Type(value = ToolCallArgsEvent.class, name = "TOOL_CALL_ARGS"), + @JsonSubTypes.Type(value = ToolCallChunkEvent.class, name = "TOOL_CALL_CHUNK"), + @JsonSubTypes.Type(value = ToolCallEndEvent.class, name = "TOOL_CALL_END"), + @JsonSubTypes.Type(value = ToolCallResultEvent.class, name = "TOOL_CALL_RESULT"), + @JsonSubTypes.Type(value = ToolCallStartEvent.class, name = "TOOL_CALL_START") +}) public class BaseEvent { private final EventType type; diff --git a/java-sdk/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java b/java-sdk/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java index 82d945c37..46efdbf4a 100644 --- a/java-sdk/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java @@ -1,10 +1,23 @@ package com.agui.event; +import com.agui.message.BaseMessage; import com.agui.types.EventType; +import java.util.List; + public class MessagesSnapshotEvent extends BaseEvent { + private List messages; + public MessagesSnapshotEvent() { super(EventType.MESSAGES_SNAPSHOT); } + + public void setMessages(final List messages) { + this.messages = messages; + } + + public List getMessages() { + return this.messages; + } } diff --git a/java-sdk/core/src/main/java/com/agui/event/RunFinishedEvent.java b/java-sdk/core/src/main/java/com/agui/event/RunFinishedEvent.java index 66ad5f4b6..277a4dfa3 100644 --- a/java-sdk/core/src/main/java/com/agui/event/RunFinishedEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/RunFinishedEvent.java @@ -4,30 +4,34 @@ public class RunFinishedEvent extends BaseEvent { - private final String threadId; - private final String runId; - private final Object result; - - public RunFinishedEvent( - final String threadId, - final String runId, - final Object result - ) { + private String threadId; + private String runId; + private Object result; + + public RunFinishedEvent() { super(EventType.RUN_FINISHED); + } + public void setThreadId(final String threadId) { this.threadId = threadId; - this.runId = runId; - this.result = result; } public String getThreadId() { return this.threadId; } + public void setRunId(final String runId) { + this.runId = runId; + } + public String getRunId() { return this.runId; } + public void setResult(final Object result) { + this.result = result; + } + public Object getResult() { return this.result; } diff --git a/java-sdk/core/src/main/java/com/agui/event/RunStartedEvent.java b/java-sdk/core/src/main/java/com/agui/event/RunStartedEvent.java index 08cbf273a..031d0d0b0 100644 --- a/java-sdk/core/src/main/java/com/agui/event/RunStartedEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/RunStartedEvent.java @@ -4,7 +4,27 @@ public class RunStartedEvent extends BaseEvent { + private String threadId; + + private String runId; + public RunStartedEvent() { super(EventType.RUN_STARTED); } + + public void setThreadId(final String threadId) { + this.threadId = threadId; + } + + public String getThreadId() { + return this.threadId; + } + + public void setRunId(final String runId) { + this.runId = runId; + } + + public String getRunId() { + return this.runId; + } } diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageChunkEvent.java b/java-sdk/core/src/main/java/com/agui/event/TextMessageChunkEvent.java index b17971f07..184c42c86 100644 --- a/java-sdk/core/src/main/java/com/agui/event/TextMessageChunkEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/TextMessageChunkEvent.java @@ -4,7 +4,27 @@ public class TextMessageChunkEvent extends BaseEvent { - public TextMessageChunkEvent() { + private final String messageId; + private final String role; + private final String delta; + + public TextMessageChunkEvent(final String messageId, final String role, final String delta) { super(EventType.TEXT_MESSAGE_CHUNK); + + this.messageId = messageId; + this.role = role; + this.delta = delta; + } + + public String getMessageId() { + return this.messageId; + } + + public String getRole() { + return this.role; + } + + public String getDelta() { + return this.delta; } } diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageContentEvent.java b/java-sdk/core/src/main/java/com/agui/event/TextMessageContentEvent.java index 4209dff48..9aa3008a3 100644 --- a/java-sdk/core/src/main/java/com/agui/event/TextMessageContentEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/TextMessageContentEvent.java @@ -4,7 +4,26 @@ public class TextMessageContentEvent extends BaseEvent { + private String messageId; + private String delta; + public TextMessageContentEvent() { super(EventType.TEXT_MESSAGE_CONTENT); } + + public void setMessageId(final String messageId) { + this.messageId = messageId; + } + + public String getMessageId() { + return this.messageId; + } + + public void setDelta(final String delta) { + this.delta = delta; + } + + public String getDelta() { + return this.delta; + } } diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageEndEvent.java b/java-sdk/core/src/main/java/com/agui/event/TextMessageEndEvent.java index e3ed5c5eb..d6cc0a12e 100644 --- a/java-sdk/core/src/main/java/com/agui/event/TextMessageEndEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/TextMessageEndEvent.java @@ -4,7 +4,18 @@ public class TextMessageEndEvent extends BaseEvent { + private String messageId; + public TextMessageEndEvent() { super(EventType.TEXT_MESSAGE_END); } + + public void setMessageId(final String messageId) { + this.messageId = messageId; + } + + public String getMessageId() { + return this.messageId; + } + } diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageStartEvent.java b/java-sdk/core/src/main/java/com/agui/event/TextMessageStartEvent.java index f66d26e8f..bddd03b5f 100644 --- a/java-sdk/core/src/main/java/com/agui/event/TextMessageStartEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/TextMessageStartEvent.java @@ -4,7 +4,26 @@ public class TextMessageStartEvent extends BaseEvent { + private String messageId; + private String role; + public TextMessageStartEvent() { super(EventType.TEXT_MESSAGE_START); } + + public void setMessageId(final String messageId) { + this.messageId = messageId; + } + + public String getMessageId() { + return this.messageId; + } + + public void setRole(final String role) { + this.role = role; + } + + public String getRole() { + return this.role; + } } diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallArgsEvent.java b/java-sdk/core/src/main/java/com/agui/event/ToolCallArgsEvent.java index b0cb36e37..1cadabc68 100644 --- a/java-sdk/core/src/main/java/com/agui/event/ToolCallArgsEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/ToolCallArgsEvent.java @@ -4,7 +4,26 @@ public class ToolCallArgsEvent extends BaseEvent { + private String toolCallId; + private String delta; + public ToolCallArgsEvent() { super(EventType.TOOL_CALL_ARGS); } + + public void setToolCallId(final String toolCallId) { + this.toolCallId = toolCallId; + } + + public String getToolCallId() { + return this.toolCallId; + } + + public void setDelta(final String delta) { + this.delta = delta; + } + + public String getDelta() { + return this.delta; + } } diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallChunkEvent.java b/java-sdk/core/src/main/java/com/agui/event/ToolCallChunkEvent.java index f7a3052fb..f2da0155e 100644 --- a/java-sdk/core/src/main/java/com/agui/event/ToolCallChunkEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/ToolCallChunkEvent.java @@ -4,7 +4,34 @@ public class ToolCallChunkEvent extends BaseEvent { - public ToolCallChunkEvent() { + private final String toolCallId; + private final String toolCallName; + private final String parentMessageId; + private final String delta; + + public ToolCallChunkEvent(final String toolCallId, final String toolCallName, final String parentMessageId, final String delta) { super(EventType.TOOL_CALL_CHUNK); + + this.toolCallId = toolCallId; + this.toolCallName = toolCallName; + + this.parentMessageId = parentMessageId; + this.delta = delta; + } + + public String getToolCallId() { + return this.toolCallId; + } + + public String getToolCallName() { + return this.toolCallName; + } + + public String getParentMessageId() { + return this.parentMessageId; + } + + public String getDelta() { + return this.delta; } } diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallEndEvent.java b/java-sdk/core/src/main/java/com/agui/event/ToolCallEndEvent.java index 66fef0125..c447afe37 100644 --- a/java-sdk/core/src/main/java/com/agui/event/ToolCallEndEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/ToolCallEndEvent.java @@ -4,7 +4,17 @@ public class ToolCallEndEvent extends BaseEvent { + private String toolCallId; + public ToolCallEndEvent() { super(EventType.TOOL_CALL_END); } + + public void setToolCallId(final String toolCallId) { + this.toolCallId = toolCallId; + } + + public String getToolCallId() { + return this.toolCallId; + } } diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallResultEvent.java b/java-sdk/core/src/main/java/com/agui/event/ToolCallResultEvent.java index e919ef59c..940020ba6 100644 --- a/java-sdk/core/src/main/java/com/agui/event/ToolCallResultEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/ToolCallResultEvent.java @@ -4,7 +4,44 @@ public class ToolCallResultEvent extends BaseEvent { + private String toolCallId; + private String content; + private String messageId; + private String role; + public ToolCallResultEvent() { super(EventType.TOOL_CALL_RESULT); } + + public void setToolCallId(final String toolCallId) { + this.toolCallId = toolCallId; + } + + public String getToolCallId() { + return this.toolCallId; + } + + public void setContent(final String content) { + this.content = content; + } + + public String getContent() { + return this.content; + } + + public void setMessageId(final String messageId) { + this.messageId = messageId; + } + + public String getMessageId() { + return this.messageId; + } + + public void setRole(final String role) { + this.role = role; + } + + public String getRole() { + return this.role; + } } diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallStartEvent.java b/java-sdk/core/src/main/java/com/agui/event/ToolCallStartEvent.java index f12e069c3..d699a5722 100644 --- a/java-sdk/core/src/main/java/com/agui/event/ToolCallStartEvent.java +++ b/java-sdk/core/src/main/java/com/agui/event/ToolCallStartEvent.java @@ -4,7 +4,35 @@ public class ToolCallStartEvent extends BaseEvent { + private String toolCallId; + private String toolCallName; + private String parentMessageId; + public ToolCallStartEvent() { super(EventType.TOOL_CALL_START); } -} + + public void setToolCallId(final String toolCallId) { + this.toolCallId = toolCallId; + } + + public String getToolCallId() { + return this.toolCallId; + } + + public void setToolCallName(final String toolCallName) { + this.toolCallName = toolCallName; + } + + public String getToolCallName() { + return this.toolCallName; + } + + public void setParentMessageId(final String parentMessageId) { + this.parentMessageId = parentMessageId; + } + public String getParentMessageId() { + return this.parentMessageId; + } + +} \ No newline at end of file diff --git a/java-sdk/core/src/main/java/com/agui/message/AssistantMessage.java b/java-sdk/core/src/main/java/com/agui/message/AssistantMessage.java index 85c5121c6..65bf1f282 100644 --- a/java-sdk/core/src/main/java/com/agui/message/AssistantMessage.java +++ b/java-sdk/core/src/main/java/com/agui/message/AssistantMessage.java @@ -1,12 +1,22 @@ package com.agui.message; +import com.agui.types.ToolCall; + +import java.util.List; + public class AssistantMessage extends BaseMessage { - public AssistantMessage(final String id, final String content, final String name) { - super(id, content, name); - } + private List toolCalls; public String getRole() { return "assistant"; } + + public void setToolCalls(final List toolCalls) { + this.toolCalls = toolCalls; + } + + public List getToolCalls() { + return this.toolCalls; + } } diff --git a/java-sdk/core/src/main/java/com/agui/message/BaseMessage.java b/java-sdk/core/src/main/java/com/agui/message/BaseMessage.java index a4dbbb558..5a66c3a90 100644 --- a/java-sdk/core/src/main/java/com/agui/message/BaseMessage.java +++ b/java-sdk/core/src/main/java/com/agui/message/BaseMessage.java @@ -1,22 +1,59 @@ package com.agui.message; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "role" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = AssistantMessage.class, name = "assistant"), + @JsonSubTypes.Type(value = DeveloperMessage.class, name = "developer"), + @JsonSubTypes.Type(value = UserMessage.class, name = "user"), + @JsonSubTypes.Type(value = SystemMessage.class, name = "system"), + @JsonSubTypes.Type(value = ToolMessage.class, name = "tool") +}) public abstract class BaseMessage { - private final String id; - private final String content; - private final String name; + private String id; + private String content; + private String name; + + public BaseMessage() { } - public BaseMessage( - final String id, - final String content, - final String name - ) { + public BaseMessage(final String id, final String content, final String name) { this.id = id; this.content = content; this.name = name; } public abstract String getRole(); + + public void setId(final String id) { + this.id = id; + } + + public String getId() { + return this.id; + } + + public void setContent(final String content) { + this.content = content; + } + + public String getContent() { + return this.content; + } + + public void setName(final String name) { + this.name = name; + } + + public String getName() { + return this.name; + } } diff --git a/java-sdk/core/src/main/java/com/agui/message/DeveloperMessage.java b/java-sdk/core/src/main/java/com/agui/message/DeveloperMessage.java index df4cc6d71..45dd2c838 100644 --- a/java-sdk/core/src/main/java/com/agui/message/DeveloperMessage.java +++ b/java-sdk/core/src/main/java/com/agui/message/DeveloperMessage.java @@ -2,10 +2,6 @@ public class DeveloperMessage extends BaseMessage { - public DeveloperMessage(final String id, final String content, final String name) { - super(id, content, name); - } - public String getRole() { return "developer"; } diff --git a/java-sdk/core/src/main/java/com/agui/message/SystemMessage.java b/java-sdk/core/src/main/java/com/agui/message/SystemMessage.java index 55fb2d0a8..2bbbd75fa 100644 --- a/java-sdk/core/src/main/java/com/agui/message/SystemMessage.java +++ b/java-sdk/core/src/main/java/com/agui/message/SystemMessage.java @@ -2,10 +2,6 @@ public class SystemMessage extends BaseMessage { - public SystemMessage(final String id, final String content, final String name) { - super(id, content, name); - } - public String getRole() { return "system"; } diff --git a/java-sdk/core/src/main/java/com/agui/message/ToolMessage.java b/java-sdk/core/src/main/java/com/agui/message/ToolMessage.java index 6f29cbc93..a90fedba7 100644 --- a/java-sdk/core/src/main/java/com/agui/message/ToolMessage.java +++ b/java-sdk/core/src/main/java/com/agui/message/ToolMessage.java @@ -2,18 +2,27 @@ public class ToolMessage extends BaseMessage { - private final String toolCallId; - private final String error; + private String toolCallId; + private String error; - public ToolMessage(final String id, final String content, final String name, final String toolCallId, final String error) { - super(id, content, name); + public String getRole() { + return "tool"; + } + public void setToolCallId(final String toolCallId) { this.toolCallId = toolCallId; + } + + public String getToolCallId() { + return this.toolCallId; + } + + public void setError(final String error) { this.error = error; } - public String getRole() { - return "tool"; + public String getError() { + return this.error; } } diff --git a/java-sdk/core/src/main/java/com/agui/message/UserMessage.java b/java-sdk/core/src/main/java/com/agui/message/UserMessage.java index 1e6c0c9ed..4431350f0 100644 --- a/java-sdk/core/src/main/java/com/agui/message/UserMessage.java +++ b/java-sdk/core/src/main/java/com/agui/message/UserMessage.java @@ -2,10 +2,6 @@ public class UserMessage extends BaseMessage { - public UserMessage(final String id, final String content, final String name) { - super(id, content, name); - } - public String getRole() { return "user"; } diff --git a/java-sdk/core/src/main/java/com/agui/types/Context.java b/java-sdk/core/src/main/java/com/agui/types/Context.java index a2ddd3302..68af7b497 100644 --- a/java-sdk/core/src/main/java/com/agui/types/Context.java +++ b/java-sdk/core/src/main/java/com/agui/types/Context.java @@ -1,15 +1,3 @@ package com.agui.types; -public class Context { - - private final String description; - private final String value; - - public Context(final String description, final String value) { - this.description = description; - this.value = value; - } - - public String getDescription() { return this.description; } - public String getValue() { return this.value; } -} +public record Context(String description, String value) { } diff --git a/java-sdk/core/src/main/java/com/agui/types/FunctionCall.java b/java-sdk/core/src/main/java/com/agui/types/FunctionCall.java new file mode 100644 index 000000000..7c3307ff1 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/types/FunctionCall.java @@ -0,0 +1,4 @@ +package com.agui.types; + +public record FunctionCall(String name, String arguments) { +} diff --git a/java-sdk/core/src/main/java/com/agui/types/RunAgentInput.java b/java-sdk/core/src/main/java/com/agui/types/RunAgentInput.java index 38e4a59cf..90c1cc705 100644 --- a/java-sdk/core/src/main/java/com/agui/types/RunAgentInput.java +++ b/java-sdk/core/src/main/java/com/agui/types/RunAgentInput.java @@ -4,60 +4,13 @@ import java.util.List; -public class RunAgentInput { - - private final String threadId; - private final String runId; - private final Object state; - private final List messages; - private final List tools; - private final List context; - private final Object forwardedProps; - - public RunAgentInput( - final String threadId, - final String runId, - final Object state, - final List messages, - final List tools, - final List context, - final Object forwardedProps - ) { - this.threadId = threadId; - this.runId = runId; - this.state = state; - this.messages = messages; - this.tools = tools; - this.context = context; - this.forwardedProps = forwardedProps; - } - - public String getThreadId() { - return this.threadId; - } - - public String getRunId() { - return this.runId; - } - - public Object getState() { - return this.state; - } - - public List getMessages() { - return this.messages; - } - - public List getTools() { - return this.tools; - } - - public List getContext() { - return this.context; - } - - public Object getForwardedProps() { - return this.forwardedProps; - } -} +public record RunAgentInput( + String threadId, + String runId, + Object state, + List messages, + List tools, + List context, + Object forwardedProps +) { } diff --git a/java-sdk/core/src/main/java/com/agui/types/State.java b/java-sdk/core/src/main/java/com/agui/types/State.java new file mode 100644 index 000000000..ebc041ab0 --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/types/State.java @@ -0,0 +1,4 @@ +package com.agui.types; + +public class State { +} diff --git a/java-sdk/core/src/main/java/com/agui/types/ToolCall.java b/java-sdk/core/src/main/java/com/agui/types/ToolCall.java new file mode 100644 index 000000000..f6c80555c --- /dev/null +++ b/java-sdk/core/src/main/java/com/agui/types/ToolCall.java @@ -0,0 +1,4 @@ +package com.agui.types; + +public record ToolCall(String id, String type, FunctionCall functionCall) { } + diff --git a/java-sdk/core/src/test/java/com/agui/event/BaseEventTest.java b/java-sdk/core/src/test/java/com/agui/event/BaseEventTest.java new file mode 100644 index 000000000..200ea5d35 --- /dev/null +++ b/java-sdk/core/src/test/java/com/agui/event/BaseEventTest.java @@ -0,0 +1,195 @@ +package com.agui.event; + +import com.agui.types.EventType; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("BaseEvent") +public class BaseEventTest { + + private BaseEvent baseEvent; + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + } + + @Nested + @DisplayName("Constructor Tests") + class ConstructorTests { + + @Test + @DisplayName("Should create BaseEvent with valid EventType") + void shouldCreateBaseEventWithValidEventType() { + EventType eventType = EventType.CUSTOM; + + baseEvent = new BaseEvent(eventType); + + assertThat(baseEvent).isNotNull(); + assertThat(eventType).isEqualTo(baseEvent.getType()); + assertThat(0).isEqualTo(baseEvent.getTimestamp()); + assertThat(baseEvent.getRawEvent()).isNull(); + } + + @ParameterizedTest + @EnumSource(EventType.class) + @DisplayName("Should create BaseEvent with all EventType values") + void shouldCreateBaseEventWithAllEventTypes(EventType eventType) { + baseEvent = new BaseEvent(eventType); + + assertThat(baseEvent).isNotNull(); + assertThat(eventType).isEqualTo(baseEvent.getType()); + } + + } + + @Nested + @DisplayName("Getter and Setter Tests") + class GetterSetterTests { + + @BeforeEach + void setUp() { + baseEvent = new BaseEvent(EventType.CUSTOM); + } + + @Test + @DisplayName("Should get type correctly") + void shouldGetTypeCorrectly() { + // Given + EventType expectedType = EventType.CUSTOM; + + // When + EventType actualType = baseEvent.getType(); + + // Then + assertThat(expectedType).isEqualTo(actualType); + } + + @Test + @DisplayName("Should set and get timestamp correctly") + void shouldSetAndGetTimestampCorrectly() { + int expectedTimestamp = 1234567890; + + baseEvent.setTimestamp(expectedTimestamp); + int actualTimestamp = baseEvent.getTimestamp(); + + assertThat(expectedTimestamp).isEqualTo(actualTimestamp); + } + + @Test + @DisplayName("Should handle zero timestamp") + void shouldHandleZeroTimestamp() { + int zeroTimestamp = 0; + + baseEvent.setTimestamp(zeroTimestamp); + + assertThat(zeroTimestamp).isEqualTo(baseEvent.getTimestamp()); + } + + @Test + @DisplayName("Should set and get rawEvent correctly with String") + void shouldSetAndGetRawEventWithString() { + String expectedRawEvent = "test raw event"; + + baseEvent.setRawEvent(expectedRawEvent); + Object actualRawEvent = baseEvent.getRawEvent(); + + assertThat(expectedRawEvent).isEqualTo(actualRawEvent); + } + + @Test + @DisplayName("Should set and get rawEvent correctly with complex object") + void shouldSetAndGetRawEventWithComplexObject() { + CustomTestObject expectedRawEvent = new CustomTestObject("test", 123); + + baseEvent.setRawEvent(expectedRawEvent); + Object actualRawEvent = baseEvent.getRawEvent(); + + assertThat(expectedRawEvent).isEqualTo(actualRawEvent); + assertThat(actualRawEvent).isInstanceOf(CustomTestObject.class); + + CustomTestObject castObject = (CustomTestObject) actualRawEvent; + assertThat("test").isEqualTo(castObject.name); + assertThat(123).isEqualTo(castObject.value); + } + + @Test + @DisplayName("Should set rawEvent to null") + void shouldSetRawEventToNull() { + baseEvent.setRawEvent("initial value"); + baseEvent.setRawEvent(null); + + assertThat(baseEvent.getRawEvent()).isNull(); + } + } + + @Nested + @DisplayName("JSON Serialization Tests") + class JsonSerializationTests { + + @Test + @DisplayName("Should serialize BaseEvent to JSON correctly") + void shouldSerializeBaseEventToJson() throws Exception { + // Given + baseEvent = new BaseEvent(EventType.CUSTOM); + baseEvent.setTimestamp(1234567890); + baseEvent.setRawEvent("test raw event"); + + String json = objectMapper.writeValueAsString(baseEvent); + + assertThat(json).isNotNull(); + assertThat(json).contains("\"type\":\"CUSTOM\""); + assertThat(json).contains("\"timestamp\":1234567890"); + assertThat(json).contains("\"rawEvent\":\"test raw event\""); + } + + @Test + @DisplayName("Should serialize BaseEvent with null rawEvent") + void shouldSerializeBaseEventWithNullRawEvent() throws Exception { + baseEvent = new BaseEvent(EventType.CUSTOM); + baseEvent.setTimestamp(1234567890); + + String json = objectMapper.writeValueAsString(baseEvent); + + assertThat(json).isNotNull(); + assertThat(json).contains("\"type\":\"CUSTOM\""); + assertThat(json).contains("\"timestamp\":1234567890"); + } + } + + private static class CustomTestObject { + public final String name; + public final int value; + + public CustomTestObject(String name, int value) { + this.name = name; + this.value = value; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + CustomTestObject that = (CustomTestObject) obj; + return value == that.value && + (Objects.equals(name, that.name)); + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + value; + return result; + } + } +} \ No newline at end of file diff --git a/java-sdk/ok-http/pom.xml b/java-sdk/ok-http/pom.xml new file mode 100644 index 000000000..af2f452df --- /dev/null +++ b/java-sdk/ok-http/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + com.ag-ui + ag-ui + 0.0.1-SNAPSHOT + + + ok-http + + + 21 + 21 + UTF-8 + + + + com.ag-ui + client + 0.0.1-SNAPSHOT + compile + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + com.fasterxml.jackson.core + jackson-databind + 2.19.0 + + + + org.junit.jupiter + junit-jupiter + 5.12.2 + test + + + org.assertj + assertj-core + 3.24.2 + test + + + + \ No newline at end of file diff --git a/java-sdk/ok-http/src/main/java/com/agui/HttpAgent.java b/java-sdk/ok-http/src/main/java/com/agui/HttpAgent.java new file mode 100644 index 000000000..313c47a7d --- /dev/null +++ b/java-sdk/ok-http/src/main/java/com/agui/HttpAgent.java @@ -0,0 +1,125 @@ +package com.agui; + +import com.agui.client.AbstractAgent; +import com.agui.event.*; +import com.agui.types.State; +import com.agui.message.BaseMessage; +import com.agui.types.RunAgentInput; +import io.reactivex.Observable; + +import java.util.ArrayList; +import java.util.List; + +public class HttpAgent extends AbstractAgent { + + protected final HttpClient httpClient; + + private HttpAgent( + final String agentId, + final String description, + final String threadId, + final String url, + final List messages, + final State state, + final boolean debug + ) { + super(agentId, description, threadId, messages, state, debug); + + this.httpClient = new HttpClient(url); + } + + @Override + protected Observable run(RunAgentInput input) { + return this.httpClient.streamEvents(input); + } + + public static class Builder { + private String agentId; + private String description = ""; + private String threadId; + private String url; + private List messages = new ArrayList<>(); + private State state = new State(); + private boolean debug = false; + + public Builder() {} + + public Builder agentId(String agentId) { + this.agentId = agentId; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Builder threadId(String threadId) { + this.threadId = threadId; + return this; + } + + public Builder url(String url) { + this.url = url; + return this; + } + + public Builder messages(List messages) { + this.messages = messages != null ? messages : new ArrayList<>(); + return this; + } + + public Builder addMessage(BaseMessage message) { + if (this.messages == null) { + this.messages = new ArrayList<>(); + } + this.messages.add(message); + return this; + } + + public Builder state(State state) { + this.state = state != null ? state : new State(); + return this; + } + + public Builder debug(boolean debug) { + this.debug = debug; + return this; + } + + public Builder debug() { + this.debug = true; + return this; + } + + private void validate() { + if (agentId == null || agentId.trim().isEmpty()) { + throw new IllegalArgumentException("agentId is required"); + } + if (threadId == null || threadId.trim().isEmpty()) { + throw new IllegalArgumentException("threadId is required"); + } + if (url == null || url.trim().isEmpty()) { + throw new IllegalArgumentException("url is required"); + } + } + + public HttpAgent build() { + validate(); + return new HttpAgent(agentId, description, threadId, url, messages, state, debug); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder withUrl(String url) { + return new Builder().url(url); + } + + public static Builder withAgentId(String agentId) { + return new Builder().agentId(agentId); + } + +} diff --git a/java-sdk/ok-http/src/main/java/com/agui/HttpClient.java b/java-sdk/ok-http/src/main/java/com/agui/HttpClient.java new file mode 100644 index 000000000..9cb6bd9b1 --- /dev/null +++ b/java-sdk/ok-http/src/main/java/com/agui/HttpClient.java @@ -0,0 +1,73 @@ +package com.agui; + +import com.agui.event.BaseEvent; +import com.agui.types.RunAgentInput; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import io.reactivex.Observable; +import okhttp3.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class HttpClient { + private final OkHttpClient client; + + private final String url; + + private final ObjectMapper objectMapper; + + public HttpClient(final String url) { + this.client = new OkHttpClient.Builder() + .readTimeout(0, TimeUnit.MILLISECONDS) + .build(); + + this.url = url; + + this.objectMapper = new ObjectMapper(); + this.objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + } + + public Observable streamEvents(final RunAgentInput input) { + return Observable.create(emitter -> { + try { + var body = RequestBody.create(objectMapper.writeValueAsString(input), MediaType.get("application/json")); + + Request request = new Request.Builder() + .url(url) + .header("Accept", "application/json") + .post(body) + .build(); + + client.newCall(request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + try (BufferedReader reader = new BufferedReader(response.body().charStream())) { + String line; + while ((line = reader.readLine()) != null && !emitter.isDisposed()) { + if (line.trim().startsWith("data: ")) { + BaseEvent event = objectMapper.readValue(line.trim().substring(6).trim(), BaseEvent.class); + emitter.onNext(event); + } + } + emitter.onComplete(); + } catch (IOException e) { + emitter.onError(e); + } + } + + @Override + public void onFailure(Call call, IOException e) { + emitter.onError(e); + } + }); + + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } +} + + diff --git a/java-sdk/ok-http/src/test/java/com/agui/HttpAgentTest.java b/java-sdk/ok-http/src/test/java/com/agui/HttpAgentTest.java new file mode 100644 index 000000000..f5b88d9ed --- /dev/null +++ b/java-sdk/ok-http/src/test/java/com/agui/HttpAgentTest.java @@ -0,0 +1,76 @@ +package com.agui; + + +import com.agui.client.RunAgentParameters; +import com.agui.client.subscriber.AgentSubscriberParams; +import com.agui.event.BaseEvent; +import com.agui.message.UserMessage; +import com.agui.types.State; +import com.agui.client.subscriber.AgentSubscriber; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.Assertions.assertThat; + +public class HttpAgentTest { + + @Test + public void itShouldCallEndpoint() throws InterruptedException { + var message = new UserMessage(); + message.setContent("Hi, what's the weather in Hilversum?"); + + var agent = HttpAgent.withAgentId("simpleAgent") + .threadId("THREAD") + .description("Agent Description") + .url("http://localhost:3033/ai/mastra/run/weatherAgent") + .state(new State()) + .addMessage(message) + .debug() + .build(); + + var parameters = RunAgentParameters.builder() + .runId("1") + .build(); + + CountDownLatch latch = new CountDownLatch(1); + List receivedEvents = new ArrayList<>(); + AtomicReference error = new AtomicReference<>(); + + agent.runAgent(parameters, new AgentSubscriber() { + @Override + public void onEvent(BaseEvent event) { + receivedEvents.add(event); + System.out.println("Received event: " + event); + } + + @Override + public void onRunFinalized(AgentSubscriberParams params) { + System.out.println("Agent completed successfully"); + latch.countDown(); + } + + @Override + public void onRunFailed(AgentSubscriberParams params, Throwable throwable) { + System.err.println("Error occurred: " + throwable.getMessage()); + error.set(throwable); + latch.countDown(); + } + }); + + // Wait up to 30 seconds for completion + boolean completed = latch.await(30, TimeUnit.SECONDS); + + assertThat(completed).isTrue(); + assertThat(error.get()).isNull(); + assertThat(receivedEvents.size()).isGreaterThan(0); + + System.out.println("Test completed successfully with " + receivedEvents.size() + " events"); + + } + +} \ No newline at end of file diff --git a/java-sdk/pom.xml b/java-sdk/pom.xml index a784108fd..f8440a715 100644 --- a/java-sdk/pom.xml +++ b/java-sdk/pom.xml @@ -12,6 +12,7 @@ core client + ok-http From fdd9b749334b6b36cf382063025f4333c7b4877e Mon Sep 17 00:00:00 2001 From: Pascal Wilbrink Date: Thu, 7 Aug 2025 22:26:58 +0200 Subject: [PATCH 6/8] Removed jackson dependency from java-sdk/core --- .../java/com/agui/client/AbstractAgent.java | 204 ------------ .../main/java/com/agui/client/ToolCall.java | 4 - .../test/java/com/agui/client/TestAgent.java | 38 --- java-sdk/integrations/spring-ai/pom.xml | 91 ++++++ .../src/main/java/com/agui/CorsConfig.java | 37 +++ .../main/java/com/agui/MainApplication.java | 12 + .../java/com/agui/spring/AgUiController.java | 113 +++++++ .../java/com/agui/spring/AgUiParameters.java | 56 ++++ .../java/com/agui/spring/SpringAgent.java | 202 ++++++++++++ .../src/main/resources/application.properties | 1 + .../src/main/java/com/agui/HttpClient.java | 73 ----- java-sdk/{ => packages}/.gitignore | 2 +- java-sdk/{ => packages}/client/pom.xml | 5 - .../java/com/agui/client/AbstractAgent.java | 308 ++++++++++++++++++ .../com/agui/client/RunAgentParameters.java | 0 .../java/com/agui/client/RunAgentResult.java | 0 .../java/com/agui/client/Subscription.java | 0 .../client/subscriber/AgentStateMutation.java | 0 .../client/subscriber/AgentSubscriber.java | 2 +- .../subscriber/AgentSubscriberParams.java | 0 .../test/java/com/agui/client/TestAgent.java | 185 +++++++++++ .../subscriber/AgentSubscriberTest.java | 0 java-sdk/{ => packages}/core/pom.xml | 0 .../main/java/com/agui/event/BaseEvent.java | 2 + .../main/java/com/agui/event/CustomEvent.java | 0 .../com/agui/event/MessagesSnapshotEvent.java | 0 .../main/java/com/agui/event/RawEvent.java | 0 .../java/com/agui/event/RunErrorEvent.java | 0 .../java/com/agui/event/RunFinishedEvent.java | 0 .../java/com/agui/event/RunStartedEvent.java | 0 .../java/com/agui/event/StateDeltaEvent.java | 0 .../com/agui/event/StateSnapshotEvent.java | 0 .../com/agui/event/StepFinishedEvent.java | 0 .../java/com/agui/event/StepStartedEvent.java | 0 .../com/agui/event/TextMessageChunkEvent.java | 0 .../agui/event/TextMessageContentEvent.java | 0 .../com/agui/event/TextMessageEndEvent.java | 0 .../com/agui/event/TextMessageStartEvent.java | 0 .../java/com/agui/event/ThinkingEndEvent.java | 0 .../com/agui/event/ThinkingStartEvent.java | 0 .../ThinkingTextMessageContentEvent.java | 0 .../event/ThinkingTextMessageEndEvent.java | 0 .../event/ThinkingTextMessageStartEvent.java | 0 .../com/agui/event/ToolCallArgsEvent.java | 0 .../com/agui/event/ToolCallChunkEvent.java | 0 .../java/com/agui/event/ToolCallEndEvent.java | 0 .../com/agui/event/ToolCallResultEvent.java | 0 .../com/agui/event/ToolCallStartEvent.java | 0 .../com/agui/message/AssistantMessage.java | 3 +- .../java/com/agui/message/BaseMessage.java | 0 .../com/agui/message/DeveloperMessage.java | 0 .../java/com/agui/message/SystemMessage.java | 0 .../java/com/agui/message/ToolMessage.java | 0 .../java/com/agui/message/UserMessage.java | 0 .../src/main/java/com/agui/types/Context.java | 0 .../main/java/com/agui/types/EventType.java | 0 .../java/com/agui/types/FunctionCall.java | 0 .../java/com/agui/types/RunAgentInput.java | 0 .../src/main/java/com/agui/types/State.java | 0 .../src/main/java/com/agui/types/Tool.java | 0 .../main/java/com/agui/types/ToolCall.java | 2 +- .../java/com/agui/event/BaseEventTest.java | 0 java-sdk/packages/http/pom.xml | 40 +++ .../src/main/java/com/agui/HttpAgent.java | 39 ++- .../src/main/java/com/agui/HttpClient.java | 53 +++ java-sdk/{ => packages}/ok-http/pom.xml | 6 + .../main/java/com/agui/okhttp/HttpClient.java | 186 +++++++++++ .../src/test/java/com/agui/HttpAgentTest.java | 3 +- java-sdk/packages/spring/pom.xml | 58 ++++ .../main/java/com/agui/spring/HttpClient.java | 103 ++++++ 70 files changed, 1485 insertions(+), 343 deletions(-) delete mode 100644 java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java delete mode 100644 java-sdk/client/src/main/java/com/agui/client/ToolCall.java delete mode 100644 java-sdk/client/src/test/java/com/agui/client/TestAgent.java create mode 100644 java-sdk/integrations/spring-ai/pom.xml create mode 100644 java-sdk/integrations/spring-ai/src/main/java/com/agui/CorsConfig.java create mode 100644 java-sdk/integrations/spring-ai/src/main/java/com/agui/MainApplication.java create mode 100644 java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/AgUiController.java create mode 100644 java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/AgUiParameters.java create mode 100644 java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/SpringAgent.java create mode 100644 java-sdk/integrations/spring-ai/src/main/resources/application.properties delete mode 100644 java-sdk/ok-http/src/main/java/com/agui/HttpClient.java rename java-sdk/{ => packages}/.gitignore (94%) rename java-sdk/{ => packages}/client/pom.xml (93%) create mode 100644 java-sdk/packages/client/src/main/java/com/agui/client/AbstractAgent.java rename java-sdk/{ => packages}/client/src/main/java/com/agui/client/RunAgentParameters.java (100%) rename java-sdk/{ => packages}/client/src/main/java/com/agui/client/RunAgentResult.java (100%) rename java-sdk/{ => packages}/client/src/main/java/com/agui/client/Subscription.java (100%) rename java-sdk/{ => packages}/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java (100%) rename java-sdk/{ => packages}/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java (98%) rename java-sdk/{ => packages}/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java (100%) create mode 100644 java-sdk/packages/client/src/test/java/com/agui/client/TestAgent.java rename java-sdk/{ => packages}/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java (100%) rename java-sdk/{ => packages}/core/pom.xml (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/BaseEvent.java (97%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/CustomEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/RawEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/RunErrorEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/RunFinishedEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/RunStartedEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/StateDeltaEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/StateSnapshotEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/StepFinishedEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/StepStartedEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/TextMessageChunkEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/TextMessageContentEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/TextMessageEndEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/TextMessageStartEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/ThinkingEndEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/ThinkingStartEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/ThinkingTextMessageContentEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/ThinkingTextMessageEndEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/ThinkingTextMessageStartEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/ToolCallArgsEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/ToolCallChunkEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/ToolCallEndEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/ToolCallResultEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/event/ToolCallStartEvent.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/message/AssistantMessage.java (81%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/message/BaseMessage.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/message/DeveloperMessage.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/message/SystemMessage.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/message/ToolMessage.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/message/UserMessage.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/types/Context.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/types/EventType.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/types/FunctionCall.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/types/RunAgentInput.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/types/State.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/types/Tool.java (100%) rename java-sdk/{ => packages}/core/src/main/java/com/agui/types/ToolCall.java (86%) rename java-sdk/{ => packages}/core/src/test/java/com/agui/event/BaseEventTest.java (100%) create mode 100644 java-sdk/packages/http/pom.xml rename java-sdk/{ok-http => packages/http}/src/main/java/com/agui/HttpAgent.java (73%) create mode 100644 java-sdk/packages/http/src/main/java/com/agui/HttpClient.java rename java-sdk/{ => packages}/ok-http/pom.xml (89%) create mode 100644 java-sdk/packages/ok-http/src/main/java/com/agui/okhttp/HttpClient.java rename java-sdk/{ => packages}/ok-http/src/test/java/com/agui/HttpAgentTest.java (95%) create mode 100644 java-sdk/packages/spring/pom.xml create mode 100644 java-sdk/packages/spring/src/main/java/com/agui/spring/HttpClient.java diff --git a/java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java b/java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java deleted file mode 100644 index 12fbd9e57..000000000 --- a/java-sdk/client/src/main/java/com/agui/client/AbstractAgent.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.agui.client; - -import com.agui.client.subscriber.AgentSubscriber; -import com.agui.client.subscriber.AgentSubscriberParams; -import com.agui.event.*; -import com.agui.message.BaseMessage; -import com.agui.types.RunAgentInput; -import com.agui.types.State; -import io.reactivex.Observable; - -import java.util.*; - -public abstract class AbstractAgent { - - private String agentId; - private String description; - private String threadId; - private List messages; - private State state; - private boolean debug = false; - - private final List agentSubscribers = new ArrayList<>(); - - public AbstractAgent( - final String agentId, - final String description, - final String threadId, - final List messages, - final State state, - final boolean debug - ) { - this.agentId = agentId; - this.description = Objects.nonNull(description) ? description : ""; - this.threadId = Objects.nonNull(threadId) ? threadId : UUID.randomUUID().toString(); - this.messages = Objects.nonNull(messages) ? messages : new ArrayList<>(); - - this.state = Objects.nonNull(state) ? state : new State(); - this.debug = debug; - } - - public Subscription subscribe(final AgentSubscriber subscriber) { - this.agentSubscribers.add(subscriber); - - return () -> this.agentSubscribers.remove(subscriber); - } - - protected abstract Observable run(final RunAgentInput input); - - - public void runAgent(RunAgentParameters parameters) { - this.runAgent(parameters, null); - } - - public void runAgent( - RunAgentParameters parameters, - AgentSubscriber subscriber - ) { - this.agentId = Objects.nonNull(this.agentId) ? this.agentId : UUID.randomUUID().toString(); - - var input = this.prepareRunAgentInput(parameters); - Object result = null; - - List subscribers = new ArrayList<>(); - subscribers.add( - new AgentSubscriber() { - @Override - public void onRunFinishedEvent(RunFinishedEvent event) { - //result = event.getResult(); - } - } - ); - - if (Objects.nonNull(subscriber)) { - subscribers.add(subscriber); - } - subscribers.addAll(this.agentSubscribers); - - this.onInitialize(input, subscribers); - - this.run(input) - .map((event) -> { - subscribers.forEach((s) -> { - s.onEvent(event); - }); - - switch (event.getType()) { - case RUN_STARTED -> subscriber.onRunStartedEvent((RunStartedEvent) event); - case RUN_ERROR -> subscriber.onRunErrorEvent((RunErrorEvent) event); - case RUN_FINISHED -> subscriber.onRunFinishedEvent((RunFinishedEvent) event); - case STEP_STARTED -> subscriber.onStepStartedEvent((StepStartedEvent) event); - case STEP_FINISHED -> subscriber.onStepFinishedEvent((StepFinishedEvent) event); - case TEXT_MESSAGE_START -> subscriber.onTextMessageStartEvent((TextMessageStartEvent) event); - case TEXT_MESSAGE_CONTENT -> subscriber.onTextMessageContentEvent((TextMessageContentEvent) event); - case TEXT_MESSAGE_END -> subscriber.onTextMessageEndEvent((TextMessageEndEvent) event); - case TOOL_CALL_START -> subscriber.onToolCallStartEvent((ToolCallStartEvent) event); - case TOOL_CALL_ARGS -> subscriber.onToolCallArgsEvent((ToolCallArgsEvent) event); - case TOOL_CALL_RESULT -> subscriber.onToolCallResultEvent((ToolCallResultEvent) event); - case TOOL_CALL_END -> subscriber.onToolCallEndEvent((ToolCallEndEvent) event); - case RAW -> subscriber.onRawEvent((RawEvent) event); - case CUSTOM -> subscriber.onCustomEvent((CustomEvent) event); - case MESSAGES_SNAPSHOT -> subscriber.onMessagesSnapshotEvent((MessagesSnapshotEvent) event); - case STATE_SNAPSHOT -> subscriber.onStateSnapshotEvent((StateSnapshotEvent) event); - case STATE_DELTA -> subscriber.onStateDeltaEvent((StateDeltaEvent) event); - } - - return event; - }) - .doFinally(() -> { - subscribers.forEach(s -> { - var params = new AgentSubscriberParams( - this.messages, - this.state, - this, - input - ); - s.onRunFinalized(params); - }); - System.out.println("parameters = " + parameters + ", subscriber = " + subscriber); - }).subscribe(); -/* - () => this.run(input), - transformChunks(this.debug), - verifyEvents(this.debug), - (source$) => this.apply(input, source$, subscribers), - (source$) => this.processApplyEvents(input, source$, subscribers), - catchError((error) => { - return this.onError(input, error, subscribers); - }), - finalize(() => { - void this.onFinalize(input, subscribers); - }), -*/ - } - - - protected void onInitialize( - final RunAgentInput input, - final List subscribers - ) { - subscribers.forEach(subscriber -> subscriber.onRunInitialized( - new AgentSubscriberParams( - this.messages, - this.state, - this, - input - ) - )); - } - - public void addMessage(final BaseMessage message) { - this.messages.add(message); - - this.agentSubscribers - .forEach((subscriber -> { - // On new message - - })); - - // Fire onNewToolCall if the message is from assistant and contains tool calls - - - // Fire onMessagesChanged sequentially - } - - public void addMessages(final List messages) { - this.messages.forEach(this::addMessage); - } - - public void setMessages(final List messages) { - this.messages = messages; - - this.agentSubscribers - .forEach((subscriber -> { - // Fire onMessagesChanged - })); - } - - public void setState(final State state) { - this.state = state; - - this.agentSubscribers - .forEach(subscriber -> { - // Fire onStateChanged - }); - } - - protected RunAgentInput prepareRunAgentInput(RunAgentParameters parameters) { - return new RunAgentInput( - this.threadId, - parameters.getRunId().orElse(UUID.randomUUID().toString()), - this.state, - this.messages, - parameters.getTools().orElse(Collections.emptyList()), - parameters.getContext().orElse(Collections.emptyList()), - parameters.getForwardedProps().orElse(null) - ); - - } - - public State getState() { - return this.state; - } - -} \ No newline at end of file diff --git a/java-sdk/client/src/main/java/com/agui/client/ToolCall.java b/java-sdk/client/src/main/java/com/agui/client/ToolCall.java deleted file mode 100644 index 1661ec612..000000000 --- a/java-sdk/client/src/main/java/com/agui/client/ToolCall.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.agui.client; - -public class ToolCall { -} diff --git a/java-sdk/client/src/test/java/com/agui/client/TestAgent.java b/java-sdk/client/src/test/java/com/agui/client/TestAgent.java deleted file mode 100644 index a4406396d..000000000 --- a/java-sdk/client/src/test/java/com/agui/client/TestAgent.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.agui.client; - -import com.agui.event.BaseEvent; -import com.agui.message.BaseMessage; -import com.agui.types.RunAgentInput; -import com.agui.types.State; -import io.reactivex.Observable; -import io.reactivex.subjects.BehaviorSubject; - -import java.util.List; - -public class TestAgent extends AbstractAgent { - - private final BehaviorSubject subject; - - public TestAgent(String agentId, String description, String threadId, List messages, State state, boolean debug) { - super(agentId, description, threadId, messages, state, debug); - - this.subject = BehaviorSubject.create(); - } - - @Override - protected Observable run(RunAgentInput input) { - return this.subject; - } - - public void emit(final BaseEvent event) { - this.subject.onNext(event); - } - - public void complete() { - this.subject.onComplete(); - } - - public void fail(Throwable t) { - this.subject.onError(t); - } -} diff --git a/java-sdk/integrations/spring-ai/pom.xml b/java-sdk/integrations/spring-ai/pom.xml new file mode 100644 index 000000000..195255bb4 --- /dev/null +++ b/java-sdk/integrations/spring-ai/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + com.ag-ui + ag-ui + 0.0.1-SNAPSHOT + ../../pom.xml + + + spring-ai + + + 21 + 21 + UTF-8 + 3.2.0 + 1.0.0 + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.ai + spring-ai-model + 1.0.0 + + + org.springframework.ai + spring-ai-starter-model-ollama + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-test + test + + + com.ag-ui + client + 0.0.1-SNAPSHOT + compile + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + \ No newline at end of file diff --git a/java-sdk/integrations/spring-ai/src/main/java/com/agui/CorsConfig.java b/java-sdk/integrations/spring-ai/src/main/java/com/agui/CorsConfig.java new file mode 100644 index 000000000..09ee55009 --- /dev/null +++ b/java-sdk/integrations/spring-ai/src/main/java/com/agui/CorsConfig.java @@ -0,0 +1,37 @@ +package com.agui; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +import java.util.Arrays; + +@Configuration +public class CorsConfig { + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOriginPatterns(Arrays.asList("*")); // Or specify domains + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); + configuration.setAllowedHeaders(Arrays.asList("*")); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } + + @Bean + public FilterRegistrationBean corsFilter() { + FilterRegistrationBean bean = new FilterRegistrationBean<>( + new CorsFilter(corsConfigurationSource()) + ); + bean.setOrder(Ordered.HIGHEST_PRECEDENCE); + return bean; + } +} \ No newline at end of file diff --git a/java-sdk/integrations/spring-ai/src/main/java/com/agui/MainApplication.java b/java-sdk/integrations/spring-ai/src/main/java/com/agui/MainApplication.java new file mode 100644 index 000000000..068021053 --- /dev/null +++ b/java-sdk/integrations/spring-ai/src/main/java/com/agui/MainApplication.java @@ -0,0 +1,12 @@ +package com.agui; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MainApplication { + + public static void main(String[] args) { + SpringApplication.run(MainApplication.class, args); + } +} diff --git a/java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/AgUiController.java b/java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/AgUiController.java new file mode 100644 index 000000000..49286af03 --- /dev/null +++ b/java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/AgUiController.java @@ -0,0 +1,113 @@ +package com.agui.spring; + +import com.agui.client.RunAgentParameters; +import com.agui.client.subscriber.AgentSubscriber; +import com.agui.client.subscriber.AgentSubscriberParams; +import com.agui.event.BaseEvent; +import com.agui.types.State; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.ai.ollama.OllamaChatModel; +import org.springframework.ai.ollama.api.OllamaApi; +import org.springframework.ai.ollama.api.OllamaOptions; +import org.springframework.http.CacheControl; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +@RestController +public class AgUiController { + + @PostMapping(value = "/sse/{agentId}") + public ResponseEntity streamData(@PathVariable("agentId") final String agentId, @RequestBody() final AgUiParameters agUiParameters) { + SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); + + var chatModel = OllamaChatModel.builder() + .defaultOptions(OllamaOptions.builder().model("llama3.2").build()) + .ollamaApi(OllamaApi.builder().baseUrl("http://localhost:11434").build()) + .build(); + + SpringAgent agent = new SpringAgent( + agentId, + "description", + Objects.nonNull(agUiParameters.getThreadId()) ? agUiParameters.getThreadId() : UUID.randomUUID().toString(), + agUiParameters.getMessages().stream().map(m -> { + if (Objects.isNull(m.getName())) { + m.setName(""); + } + return m; + }).toList(), + chatModel, + new State(), + true + ); + + var parameters = RunAgentParameters.builder() + .runId(UUID.randomUUID().toString()) + .context(agUiParameters.getContext()) + .forwardedProps(agUiParameters.getForwardedProps()) + .tools(agUiParameters.getTools()) + .build(); + + var objectMapper = new ObjectMapper(); + + agent.runAgent(parameters, new AgentSubscriber() { + @Override + public void onEvent(BaseEvent event) { + try { + emitter.send(SseEmitter.event().data(" " + objectMapper.writeValueAsString(event)).build()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @Override + public void onRunFinalized(AgentSubscriberParams params) { + emitter.complete(); + } + @Override + public void onRunFailed(AgentSubscriberParams params, Throwable throwable) { + emitter.completeWithError(throwable); + } + }); + + return ResponseEntity + .ok() + .cacheControl(CacheControl.noCache()) + .body(emitter); + } + + @GetMapping(value = "/{agentId}", produces = MediaType.TEXT_PLAIN_VALUE) + public ResponseBodyEmitter streamData( + @PathVariable("agentId") final String agentId, + HttpServletResponse response + ) { + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Connection", "keep-alive"); + response.setContentType("text/plain;charset=UTF-8"); + + ResponseBodyEmitter emitter = new ResponseBodyEmitter(); + + // Process data in a separate thread + CompletableFuture.runAsync(() -> { + try { + for (int i = 0; i < 10; i++) { + emitter.send("Data chunk " + i + "\n"); + Thread.sleep(1000); // Simulate processing delay + } + emitter.complete(); + } catch (Exception e) { + emitter.completeWithError(e); + } + }); + + return emitter; + } + +} diff --git a/java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/AgUiParameters.java b/java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/AgUiParameters.java new file mode 100644 index 000000000..ce9544c51 --- /dev/null +++ b/java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/AgUiParameters.java @@ -0,0 +1,56 @@ +package com.agui.spring; + +import com.agui.message.BaseMessage; +import com.agui.types.Context; +import com.agui.types.Tool; + +import java.util.List; + +public class AgUiParameters { + + private String threadId; + private List tools; + private List context; + private Object forwardedProps; + private List messages; + + public void setThreadId(final String threadId) { + this.threadId = threadId; + } + + public String getThreadId() { + return this.threadId; + } + + public void setTools(final List tools) { + this.tools = tools; + } + + public List getTools() { + return tools; + } + + public void setContext(final List context) { + this.context = context; + } + + public List getContext() { + return this.context; + } + + public void setForwardedProps(final Object forwardedProps) { + this.forwardedProps = forwardedProps; + } + + public Object getForwardedProps() { + return this.forwardedProps; + } + + public void setMessages(final List messages) { + this.messages = messages; + } + + public List getMessages() { + return this.messages; + } +} diff --git a/java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/SpringAgent.java b/java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/SpringAgent.java new file mode 100644 index 000000000..096069596 --- /dev/null +++ b/java-sdk/integrations/spring-ai/src/main/java/com/agui/spring/SpringAgent.java @@ -0,0 +1,202 @@ +package com.agui.spring; + +import com.agui.client.AbstractAgent; +import com.agui.event.*; +import com.agui.message.BaseMessage; +import com.agui.types.RunAgentInput; +import com.agui.types.State; +import org.springframework.ai.chat.messages.*; +import org.springframework.ai.chat.model.ChatModel; + +import java.sql.Array; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; + +public class SpringAgent extends AbstractAgent { + + private final ChatModel chatModel; + + public SpringAgent( + final String agentId, + final String description, + final String threadId, + final List messages, + final ChatModel chatModel, + final State state, + final boolean debug + ) { + super(agentId, description, threadId, messages, state, debug); + + this.chatModel = chatModel; + } + + @Override + protected CompletableFuture run(RunAgentInput input, Consumer eventHandler) { + var threadId = Objects.nonNull(input.threadId()) ? input.threadId() : UUID.randomUUID().toString(); + var runId = Objects.nonNull(input.runId()) ? input.runId() : UUID.randomUUID().toString(); + + eventHandler.accept(generateRunStartedEvent(input, runId, threadId)); + + CompletableFuture future = new CompletableFuture<>(); + + var messageId = UUID.randomUUID().toString(); + + StringBuilder message = new StringBuilder(); + + this.chatModel.stream(this.convertToSpringMessages(input.messages()).toArray(new Message[0])) + .doFirst(() -> { + var event = new TextMessageStartEvent(); + event.setRole("assistant"); + event.setMessageId(messageId); + event.setTimestamp(LocalDateTime.now().getNano()); + eventHandler.accept(event); + }) + .doOnNext((res) -> { + if (Objects.nonNull(res) && !res.isEmpty()) { + var contentEvent = new TextMessageContentEvent(); + contentEvent.setTimestamp(LocalDateTime.now().getNano()); + contentEvent.setDelta(res); + contentEvent.setMessageId(messageId); + eventHandler.accept(contentEvent); + message.append(res); + } + }) + .doOnError(future::completeExceptionally) + .doOnCancel(() -> future.completeExceptionally(new RuntimeException("Cancelled"))) + .doOnComplete(() -> { + var textMessageContentEvent = new TextMessageContentEvent(); + textMessageContentEvent.setDelta(message.toString()); + textMessageContentEvent.setMessageId(messageId); + textMessageContentEvent.setTimestamp(LocalDateTime.now().getNano()); + + eventHandler.accept(textMessageContentEvent); + + var textMessageEndEvent = new TextMessageEndEvent(); + textMessageEndEvent.setTimestamp(LocalDateTime.now().getNano()); + textMessageEndEvent.setMessageId(messageId); + eventHandler.accept(textMessageEndEvent); + + var assistantMessage = new com.agui.message.AssistantMessage(); + assistantMessage.setId(messageId); + assistantMessage.setContent(message.toString()); + assistantMessage.setName(""); + this.addMessage(assistantMessage); + + var snapshotEvent = new MessagesSnapshotEvent(); + snapshotEvent.setMessages(this.messages); + snapshotEvent.setTimestamp(LocalDateTime.now().getNano()); + + eventHandler.accept(snapshotEvent); + + var event = new RunFinishedEvent(); + + event.setRunId(runId); + event.setResult(message.toString()); + event.setThreadId(threadId); + + event.setTimestamp(LocalDateTime.now().getNano()); + eventHandler.accept(event); + + future.complete(null); + + }) + .subscribe(); + + return future; + } + + private List convertToSpringMessages(final List messages) { + return messages.stream().map((message) -> { + switch (message.getRole()) { + case "assistant": + com.agui.message.AssistantMessage mappedAssistantMessage = (com.agui.message.AssistantMessage)message; + + return new AssistantMessage( + mappedAssistantMessage.getContent(), + Map.of( + "id", + Objects.nonNull(mappedAssistantMessage.getId()) ? mappedAssistantMessage.getId() : UUID.randomUUID().toString(), + "name", + Objects.nonNull(mappedAssistantMessage.getName()) ? mappedAssistantMessage.getName() : "" + ), + Objects.isNull(mappedAssistantMessage.getToolCalls()) + ? emptyList() + : mappedAssistantMessage.getToolCalls().stream().map(toolCall -> new AssistantMessage.ToolCall( + Objects.nonNull(toolCall.id()) ? toolCall.id() : UUID.randomUUID().toString(), + toolCall.type(), + toolCall.function().name(), + toolCall.function().arguments() + )).toList() + ); + case "user": + default: + com.agui.message.UserMessage mappedUserMessage = (com.agui.message.UserMessage)message; + + return UserMessage.builder() + .text(mappedUserMessage.getContent()) + .metadata( + Map.of( + "id", + Objects.nonNull(mappedUserMessage.getId()) ? mappedUserMessage.getId() : UUID.randomUUID().toString(), + "name", + Objects.nonNull(mappedUserMessage.getName()) ? mappedUserMessage.getName() : "" + ) + ).build(); + case "system": + com.agui.message.SystemMessage mappedSystemMessage = (com.agui.message.SystemMessage)message; + + return SystemMessage.builder() + .text(mappedSystemMessage.getContent()) + .metadata( + Map.of( + "id", + Objects.nonNull(mappedSystemMessage.getId()) ? mappedSystemMessage.getId() : UUID.randomUUID().toString(), + "name", + Objects.nonNull(mappedSystemMessage.getName()) ? mappedSystemMessage.getName() : "" + ) + ).build(); + case "developer": + com.agui.message.DeveloperMessage mappedDeveloperMessage = (com.agui.message.DeveloperMessage)message; + + return UserMessage.builder() + .text(mappedDeveloperMessage.getContent()) + .metadata( + Map.of( + "id", + Objects.nonNull(mappedDeveloperMessage.getId()) ? mappedDeveloperMessage.getId() : UUID.randomUUID().toString(), + "name", + Objects.nonNull(mappedDeveloperMessage.getName()) ? mappedDeveloperMessage.getName() : "" + ) + ).build(); + case "tool": + com.agui.message.ToolMessage mappedToolMessage = (com.agui.message.ToolMessage)message; + + return new ToolResponseMessage( + asList( + new ToolResponseMessage.ToolResponse(mappedToolMessage.getToolCallId(), mappedToolMessage.getName(), Objects.nonNull(mappedToolMessage.getError()) ? mappedToolMessage.getError() : mappedToolMessage.getContent()) + ), + Map.of( + "id", + Objects.nonNull(mappedToolMessage.getId()) ? mappedToolMessage.getId() : UUID.randomUUID().toString(), + "name", + Objects.nonNull(mappedToolMessage.getName()) ? mappedToolMessage.getName() : "" + ) + ); + } + }).toList(); + } + + private RunStartedEvent generateRunStartedEvent(final RunAgentInput input, String runId, String threadId) { + var event = new RunStartedEvent(); + event.setThreadId(threadId); + event.setRunId(runId); + event.setTimestamp(LocalDateTime.now().getNano()); + + return event; + } +} diff --git a/java-sdk/integrations/spring-ai/src/main/resources/application.properties b/java-sdk/integrations/spring-ai/src/main/resources/application.properties new file mode 100644 index 000000000..2109a440d --- /dev/null +++ b/java-sdk/integrations/spring-ai/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=demo diff --git a/java-sdk/ok-http/src/main/java/com/agui/HttpClient.java b/java-sdk/ok-http/src/main/java/com/agui/HttpClient.java deleted file mode 100644 index 9cb6bd9b1..000000000 --- a/java-sdk/ok-http/src/main/java/com/agui/HttpClient.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.agui; - -import com.agui.event.BaseEvent; -import com.agui.types.RunAgentInput; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import io.reactivex.Observable; -import okhttp3.*; - -import java.io.BufferedReader; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -public class HttpClient { - private final OkHttpClient client; - - private final String url; - - private final ObjectMapper objectMapper; - - public HttpClient(final String url) { - this.client = new OkHttpClient.Builder() - .readTimeout(0, TimeUnit.MILLISECONDS) - .build(); - - this.url = url; - - this.objectMapper = new ObjectMapper(); - this.objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); - } - - public Observable streamEvents(final RunAgentInput input) { - return Observable.create(emitter -> { - try { - var body = RequestBody.create(objectMapper.writeValueAsString(input), MediaType.get("application/json")); - - Request request = new Request.Builder() - .url(url) - .header("Accept", "application/json") - .post(body) - .build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - try (BufferedReader reader = new BufferedReader(response.body().charStream())) { - String line; - while ((line = reader.readLine()) != null && !emitter.isDisposed()) { - if (line.trim().startsWith("data: ")) { - BaseEvent event = objectMapper.readValue(line.trim().substring(6).trim(), BaseEvent.class); - emitter.onNext(event); - } - } - emitter.onComplete(); - } catch (IOException e) { - emitter.onError(e); - } - } - - @Override - public void onFailure(Call call, IOException e) { - emitter.onError(e); - } - }); - - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } -} - - diff --git a/java-sdk/.gitignore b/java-sdk/packages/.gitignore similarity index 94% rename from java-sdk/.gitignore rename to java-sdk/packages/.gitignore index 5ff6309b7..fa180dea5 100644 --- a/java-sdk/.gitignore +++ b/java-sdk/packages/.gitignore @@ -5,7 +5,7 @@ target/ ### IntelliJ IDEA ### .idea/modules.xml -.idea/jarRepositories.xml +../.idea/jarRepositories.xml .idea/compiler.xml .idea/libraries/ *.iws diff --git a/java-sdk/client/pom.xml b/java-sdk/packages/client/pom.xml similarity index 93% rename from java-sdk/client/pom.xml rename to java-sdk/packages/client/pom.xml index 661eb48d5..e20d4ba6d 100644 --- a/java-sdk/client/pom.xml +++ b/java-sdk/packages/client/pom.xml @@ -46,11 +46,6 @@ 0.0.1-SNAPSHOT compile - - io.reactivex.rxjava2 - rxjava - 2.2.21 - org.junit.jupiter junit-jupiter diff --git a/java-sdk/packages/client/src/main/java/com/agui/client/AbstractAgent.java b/java-sdk/packages/client/src/main/java/com/agui/client/AbstractAgent.java new file mode 100644 index 000000000..d41c967b7 --- /dev/null +++ b/java-sdk/packages/client/src/main/java/com/agui/client/AbstractAgent.java @@ -0,0 +1,308 @@ +package com.agui.client; + +import com.agui.client.subscriber.AgentSubscriber; +import com.agui.client.subscriber.AgentSubscriberParams; +import com.agui.event.*; +import com.agui.message.BaseMessage; +import com.agui.types.RunAgentInput; +import com.agui.types.State; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +public abstract class AbstractAgent { + + protected String agentId; + protected String description; + protected String threadId; + protected List messages; + protected State state; + protected boolean debug = false; + + private final List agentSubscribers = new ArrayList<>(); + + public AbstractAgent( + final String agentId, + final String description, + final String threadId, + final List messages, + final State state, + final boolean debug + ) { + this.agentId = agentId; + this.description = Objects.nonNull(description) ? description : ""; + this.threadId = Objects.nonNull(threadId) ? threadId : UUID.randomUUID().toString(); + this.messages = Objects.nonNull(messages) ? messages : new ArrayList<>(); + this.state = Objects.nonNull(state) ? state : new State(); + this.debug = debug; + } + + public Subscription subscribe(final AgentSubscriber subscriber) { + this.agentSubscribers.add(subscriber); + return () -> this.agentSubscribers.remove(subscriber); + } + + // New signature: CompletableFuture with event handler callback + protected abstract CompletableFuture run(final RunAgentInput input, Consumer eventHandler); + + public CompletableFuture runAgent(RunAgentParameters parameters) { + return this.runAgent(parameters, null); + } + + public CompletableFuture runAgent( + RunAgentParameters parameters, + AgentSubscriber subscriber + ) { + this.agentId = Objects.nonNull(this.agentId) ? this.agentId : UUID.randomUUID().toString(); + + var input = this.prepareRunAgentInput(parameters); + List subscribers = prepareSubscribers(subscriber); + + this.onInitialize(input, subscribers); + + // Create the event handler that processes each event + Consumer eventHandler = event -> { + try { + // Notify all subscribers of the general event + subscribers.forEach(s -> { + try { + s.onEvent(event); + } catch (Exception e) { + System.err.println("Error in subscriber.onEvent: " + e.getMessage()); + if (debug) { + e.printStackTrace(); + } + } + }); + + // Handle specific event types if subscriber is provided + if (Objects.nonNull(subscriber)) { + handleEventByType(event, subscriber); + } + } catch (Exception e) { + System.err.println("Error handling event: " + e.getMessage()); + if (debug) { + e.printStackTrace(); + } + } + }; + + // Run the agent and handle completion/errors + return this.run(input, eventHandler) + .whenComplete((result, throwable) -> { + try { + // Equivalent to RxJava's doFinally - always executed + subscribers.forEach(s -> { + try { + var params = new AgentSubscriberParams( + this.messages, + this.state, + this, + input + ); + s.onRunFinalized(params); + } catch (Exception e) { + System.err.println("Error in subscriber.onRunFinalized: " + e.getMessage()); + if (debug) { + e.printStackTrace(); + } + } + }); + + if (debug) { + System.out.println("Agent run completed - parameters = " + parameters + + ", subscriber = " + subscriber); + } + + if (throwable != null) { + System.err.println("Agent run completed with error: " + throwable.getMessage()); + if (debug) { + throwable.printStackTrace(); + } + } + } catch (Exception e) { + System.err.println("Error in completion handler: " + e.getMessage()); + if (debug) { + e.printStackTrace(); + } + } + }); + } + + private List prepareSubscribers(AgentSubscriber subscriber) { + List subscribers = new ArrayList<>(); + + // Add default subscriber for handling RunFinishedEvent + subscribers.add(new AgentSubscriber() { + @Override + public void onRunFinishedEvent(RunFinishedEvent event) { + // Handle result if needed + // Object result = event.getResult(); + } + }); + + if (Objects.nonNull(subscriber)) { + subscribers.add(subscriber); + } + + subscribers.addAll(this.agentSubscribers); + return subscribers; + } + + private void handleEventByType(BaseEvent event, AgentSubscriber subscriber) { + try { + switch (event.getType()) { + case RUN_STARTED -> subscriber.onRunStartedEvent((RunStartedEvent) event); + case RUN_ERROR -> subscriber.onRunErrorEvent((RunErrorEvent) event); + case RUN_FINISHED -> subscriber.onRunFinishedEvent((RunFinishedEvent) event); + case STEP_STARTED -> subscriber.onStepStartedEvent((StepStartedEvent) event); + case STEP_FINISHED -> subscriber.onStepFinishedEvent((StepFinishedEvent) event); + case TEXT_MESSAGE_START -> subscriber.onTextMessageStartEvent((TextMessageStartEvent) event); + case TEXT_MESSAGE_CONTENT -> subscriber.onTextMessageContentEvent((TextMessageContentEvent) event); + case TEXT_MESSAGE_CHUNK -> { + var contentEvent = new TextMessageContentEvent(); + contentEvent.setMessageId(((TextMessageChunkEvent)event).getMessageId()); + contentEvent.setDelta(((TextMessageChunkEvent)event).getDelta()); + contentEvent.setTimestamp(event.getTimestamp()); + subscriber.onTextMessageContentEvent(contentEvent); + } + case TEXT_MESSAGE_END -> subscriber.onTextMessageEndEvent((TextMessageEndEvent) event); + case TOOL_CALL_START -> subscriber.onToolCallStartEvent((ToolCallStartEvent) event); + case TOOL_CALL_ARGS -> subscriber.onToolCallArgsEvent((ToolCallArgsEvent) event); + case TOOL_CALL_RESULT -> subscriber.onToolCallResultEvent((ToolCallResultEvent) event); + case TOOL_CALL_END -> subscriber.onToolCallEndEvent((ToolCallEndEvent) event); + case RAW -> subscriber.onRawEvent((RawEvent) event); + case CUSTOM -> subscriber.onCustomEvent((CustomEvent) event); + case MESSAGES_SNAPSHOT -> subscriber.onMessagesSnapshotEvent((MessagesSnapshotEvent) event); + case STATE_SNAPSHOT -> subscriber.onStateSnapshotEvent((StateSnapshotEvent) event); + case STATE_DELTA -> subscriber.onStateDeltaEvent((StateDeltaEvent) event); + default -> { + if (debug) { + System.out.println("Unhandled event type: " + event.getType()); + } + } + } + } catch (Exception e) { + System.err.println("Error handling event type " + event.getType() + ": " + e.getMessage()); + if (debug) { + e.printStackTrace(); + } + } + } + + protected void onInitialize( + final RunAgentInput input, + final List subscribers + ) { + subscribers.forEach(subscriber -> { + try { + subscriber.onRunInitialized( + new AgentSubscriberParams( + this.messages, + this.state, + this, + input + ) + ); + } catch (Exception e) { + System.err.println("Error in subscriber.onRunInitialized: " + e.getMessage()); + if (debug) { + e.printStackTrace(); + } + } + }); + } + + public void addMessage(final BaseMessage message) { + if (Objects.isNull(message.getId())) { + message.setId(UUID.randomUUID().toString()); + } + if (Objects.isNull(message.getName())) { + message.setName(""); + } + this.messages.add(message); + + this.agentSubscribers.forEach(subscriber -> { + try { + subscriber.onNewMessage(message); + } catch (Exception e) { + System.err.println("Error in message subscriber: " + e.getMessage()); + if (debug) { + e.printStackTrace(); + } + } + }); + + // TODO: Fire onNewToolCall if the message is from assistant and contains tool calls + // TODO: Fire onMessagesChanged sequentially + } + + public void addMessages(final List messages) { + messages.forEach(this::addMessage); // Fixed: was using this.messages instead of parameter + } + + public void setMessages(final List messages) { + this.messages = messages; + + this.agentSubscribers.forEach(subscriber -> { + try { + // TODO: Fire onMessagesChanged + // subscriber.onMessagesChanged(messages); + } catch (Exception e) { + System.err.println("Error in messages changed subscriber: " + e.getMessage()); + if (debug) { + e.printStackTrace(); + } + } + }); + } + + public void setState(final State state) { + this.state = state; + + this.agentSubscribers.forEach(subscriber -> { + try { + // TODO: Fire onStateChanged + // subscriber.onStateChanged(state); + } catch (Exception e) { + System.err.println("Error in state changed subscriber: " + e.getMessage()); + if (debug) { + e.printStackTrace(); + } + } + }); + } + + protected RunAgentInput prepareRunAgentInput(RunAgentParameters parameters) { + return new RunAgentInput( + this.threadId, + parameters.getRunId().orElse(UUID.randomUUID().toString()), + this.state, + this.messages, + parameters.getTools().orElse(Collections.emptyList()), + parameters.getContext().orElse(Collections.emptyList()), + parameters.getForwardedProps().orElse(null) + ); + } + + public State getState() { + return this.state; + } + + // Utility method for subclasses to easily emit events + protected void emitEvent(BaseEvent event, Consumer eventHandler) { + if (eventHandler != null) { + eventHandler.accept(event); + } + } + + // Utility method for subclasses to handle errors in event emission + protected CompletableFuture handleEventEmissionError(Throwable throwable) { + System.err.println("Error during event emission: " + throwable.getMessage()); + if (debug) { + throwable.printStackTrace(); + } + return CompletableFuture.failedFuture(throwable); + } +} \ No newline at end of file diff --git a/java-sdk/client/src/main/java/com/agui/client/RunAgentParameters.java b/java-sdk/packages/client/src/main/java/com/agui/client/RunAgentParameters.java similarity index 100% rename from java-sdk/client/src/main/java/com/agui/client/RunAgentParameters.java rename to java-sdk/packages/client/src/main/java/com/agui/client/RunAgentParameters.java diff --git a/java-sdk/client/src/main/java/com/agui/client/RunAgentResult.java b/java-sdk/packages/client/src/main/java/com/agui/client/RunAgentResult.java similarity index 100% rename from java-sdk/client/src/main/java/com/agui/client/RunAgentResult.java rename to java-sdk/packages/client/src/main/java/com/agui/client/RunAgentResult.java diff --git a/java-sdk/client/src/main/java/com/agui/client/Subscription.java b/java-sdk/packages/client/src/main/java/com/agui/client/Subscription.java similarity index 100% rename from java-sdk/client/src/main/java/com/agui/client/Subscription.java rename to java-sdk/packages/client/src/main/java/com/agui/client/Subscription.java diff --git a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java b/java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java similarity index 100% rename from java-sdk/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java rename to java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java diff --git a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java b/java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java similarity index 98% rename from java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java rename to java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java index 578319013..9ba15543c 100644 --- a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java +++ b/java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java @@ -1,8 +1,8 @@ package com.agui.client.subscriber; -import com.agui.client.ToolCall; import com.agui.event.*; import com.agui.message.BaseMessage; +import com.agui.types.ToolCall; public interface AgentSubscriber { diff --git a/java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java b/java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java similarity index 100% rename from java-sdk/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java rename to java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java diff --git a/java-sdk/packages/client/src/test/java/com/agui/client/TestAgent.java b/java-sdk/packages/client/src/test/java/com/agui/client/TestAgent.java new file mode 100644 index 000000000..330419030 --- /dev/null +++ b/java-sdk/packages/client/src/test/java/com/agui/client/TestAgent.java @@ -0,0 +1,185 @@ +package com.agui.client; + +import com.agui.event.BaseEvent; +import com.agui.message.BaseMessage; +import com.agui.types.RunAgentInput; +import com.agui.types.State; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +public class TestAgent extends AbstractAgent { + + private final AtomicReference> eventHandlerRef = new AtomicReference<>(); + private final AtomicReference> runFutureRef = new AtomicReference<>(); + private final AtomicBoolean isRunning = new AtomicBoolean(false); + + public TestAgent(String agentId, String description, String threadId, List messages, State state, boolean debug) { + super(agentId, description, threadId, messages, state, debug); + } + + @Override + protected CompletableFuture run(RunAgentInput input, Consumer eventHandler) { + // Store the event handler for later use + eventHandlerRef.set(eventHandler); + isRunning.set(true); + + // Create a CompletableFuture that we'll complete manually + CompletableFuture future = new CompletableFuture<>(); + runFutureRef.set(future); + + // Handle cancellation + future.whenComplete((result, throwable) -> { + if (future.isCancelled()) { + isRunning.set(false); + eventHandlerRef.set(null); + } + }); + + return future; + } + + /** + * Emit an event to the current event handler (if running) + * + * @param event The event to emit + */ + public void emitEvent(BaseEvent event) { + Consumer handler = eventHandlerRef.get(); + if (handler != null && isRunning.get()) { + try { + handler.accept(event); + } catch (Exception e) { + System.err.println("Error emitting event: " + e.getMessage()); + if (debug) { + e.printStackTrace(); + } + } + } + } + + /** + * Complete the agent run successfully + */ + public void complete() { + CompletableFuture future = runFutureRef.get(); + if (future != null && !future.isDone()) { + isRunning.set(false); + eventHandlerRef.set(null); + future.complete(null); + } + } + + /** + * Fail the agent run with an error + * + * @param t The throwable that caused the failure + */ + public void fail(Throwable t) { + CompletableFuture future = runFutureRef.get(); + if (future != null && !future.isDone()) { + isRunning.set(false); + eventHandlerRef.set(null); + future.completeExceptionally(t); + } + } + + /** + * Check if the agent is currently running + * + * @return true if the agent is running, false otherwise + */ + public boolean isRunning() { + return isRunning.get(); + } + + /** + * Cancel the current run if it's active + */ + public void cancel() { + CompletableFuture future = runFutureRef.get(); + if (future != null && !future.isDone()) { + future.cancel(true); + } + } + + /** + * Utility method to emit multiple events in sequence + * + * @param events The events to emit + */ + public void emitEvents(BaseEvent... events) { + for (BaseEvent event : events) { + emitEvent(event); + } + } + + /** + * Utility method to emit multiple events from a list + * + * @param events The list of events to emit + */ + public void emitEvents(List events) { + for (BaseEvent event : events) { + emitEvent(event); + } + } + + /** + * Builder pattern for easier test agent creation + */ + public static class Builder { + private String agentId; + private String description = ""; + private String threadId; + private List messages; + private State state; + private boolean debug = false; + + public Builder agentId(String agentId) { + this.agentId = agentId; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Builder threadId(String threadId) { + this.threadId = threadId; + return this; + } + + public Builder messages(List messages) { + this.messages = messages; + return this; + } + + public Builder state(State state) { + this.state = state; + return this; + } + + public Builder debug(boolean debug) { + this.debug = debug; + return this; + } + + public Builder debug() { + this.debug = true; + return this; + } + + public TestAgent build() { + return new TestAgent(agentId, description, threadId, messages, state, debug); + } + } + + public static Builder builder() { + return new Builder(); + } +} \ No newline at end of file diff --git a/java-sdk/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java b/java-sdk/packages/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java similarity index 100% rename from java-sdk/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java rename to java-sdk/packages/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java diff --git a/java-sdk/core/pom.xml b/java-sdk/packages/core/pom.xml similarity index 100% rename from java-sdk/core/pom.xml rename to java-sdk/packages/core/pom.xml diff --git a/java-sdk/core/src/main/java/com/agui/event/BaseEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/BaseEvent.java similarity index 97% rename from java-sdk/core/src/main/java/com/agui/event/BaseEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/BaseEvent.java index baca0d09c..854e8bc2b 100644 --- a/java-sdk/core/src/main/java/com/agui/event/BaseEvent.java +++ b/java-sdk/packages/core/src/main/java/com/agui/event/BaseEvent.java @@ -1,6 +1,7 @@ package com.agui.event; import com.agui.types.EventType; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; @@ -46,6 +47,7 @@ public BaseEvent(final EventType type) { this.type = type; } + @JsonIgnore public EventType getType() { return this.type; } diff --git a/java-sdk/core/src/main/java/com/agui/event/CustomEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/CustomEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/CustomEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/CustomEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/RawEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/RawEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/RawEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/RawEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/RunErrorEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/RunErrorEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/RunErrorEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/RunErrorEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/RunFinishedEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/RunFinishedEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/RunFinishedEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/RunFinishedEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/RunStartedEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/RunStartedEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/RunStartedEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/RunStartedEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/StateDeltaEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/StateDeltaEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/StateDeltaEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/StateDeltaEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/StateSnapshotEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/StateSnapshotEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/StateSnapshotEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/StateSnapshotEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/StepFinishedEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/StepFinishedEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/StepFinishedEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/StepFinishedEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/StepStartedEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/StepStartedEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/StepStartedEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/StepStartedEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageChunkEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageChunkEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/TextMessageChunkEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/TextMessageChunkEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageContentEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageContentEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/TextMessageContentEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/TextMessageContentEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageEndEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageEndEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/TextMessageEndEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/TextMessageEndEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/TextMessageStartEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageStartEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/TextMessageStartEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/TextMessageStartEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/ThinkingEndEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ThinkingEndEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/ThinkingEndEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/ThinkingEndEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/ThinkingStartEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ThinkingStartEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/ThinkingStartEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/ThinkingStartEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageContentEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ThinkingTextMessageContentEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageContentEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/ThinkingTextMessageContentEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageEndEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ThinkingTextMessageEndEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageEndEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/ThinkingTextMessageEndEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageStartEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ThinkingTextMessageStartEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/ThinkingTextMessageStartEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/ThinkingTextMessageStartEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallArgsEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallArgsEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/ToolCallArgsEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/ToolCallArgsEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallChunkEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallChunkEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/ToolCallChunkEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/ToolCallChunkEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallEndEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallEndEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/ToolCallEndEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/ToolCallEndEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallResultEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallResultEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/ToolCallResultEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/ToolCallResultEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/event/ToolCallStartEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallStartEvent.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/event/ToolCallStartEvent.java rename to java-sdk/packages/core/src/main/java/com/agui/event/ToolCallStartEvent.java diff --git a/java-sdk/core/src/main/java/com/agui/message/AssistantMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/AssistantMessage.java similarity index 81% rename from java-sdk/core/src/main/java/com/agui/message/AssistantMessage.java rename to java-sdk/packages/core/src/main/java/com/agui/message/AssistantMessage.java index 65bf1f282..75692ab2a 100644 --- a/java-sdk/core/src/main/java/com/agui/message/AssistantMessage.java +++ b/java-sdk/packages/core/src/main/java/com/agui/message/AssistantMessage.java @@ -2,11 +2,12 @@ import com.agui.types.ToolCall; +import java.util.ArrayList; import java.util.List; public class AssistantMessage extends BaseMessage { - private List toolCalls; + private List toolCalls = new ArrayList<>(); public String getRole() { return "assistant"; diff --git a/java-sdk/core/src/main/java/com/agui/message/BaseMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/BaseMessage.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/message/BaseMessage.java rename to java-sdk/packages/core/src/main/java/com/agui/message/BaseMessage.java diff --git a/java-sdk/core/src/main/java/com/agui/message/DeveloperMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/DeveloperMessage.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/message/DeveloperMessage.java rename to java-sdk/packages/core/src/main/java/com/agui/message/DeveloperMessage.java diff --git a/java-sdk/core/src/main/java/com/agui/message/SystemMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/SystemMessage.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/message/SystemMessage.java rename to java-sdk/packages/core/src/main/java/com/agui/message/SystemMessage.java diff --git a/java-sdk/core/src/main/java/com/agui/message/ToolMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/ToolMessage.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/message/ToolMessage.java rename to java-sdk/packages/core/src/main/java/com/agui/message/ToolMessage.java diff --git a/java-sdk/core/src/main/java/com/agui/message/UserMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/UserMessage.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/message/UserMessage.java rename to java-sdk/packages/core/src/main/java/com/agui/message/UserMessage.java diff --git a/java-sdk/core/src/main/java/com/agui/types/Context.java b/java-sdk/packages/core/src/main/java/com/agui/types/Context.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/types/Context.java rename to java-sdk/packages/core/src/main/java/com/agui/types/Context.java diff --git a/java-sdk/core/src/main/java/com/agui/types/EventType.java b/java-sdk/packages/core/src/main/java/com/agui/types/EventType.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/types/EventType.java rename to java-sdk/packages/core/src/main/java/com/agui/types/EventType.java diff --git a/java-sdk/core/src/main/java/com/agui/types/FunctionCall.java b/java-sdk/packages/core/src/main/java/com/agui/types/FunctionCall.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/types/FunctionCall.java rename to java-sdk/packages/core/src/main/java/com/agui/types/FunctionCall.java diff --git a/java-sdk/core/src/main/java/com/agui/types/RunAgentInput.java b/java-sdk/packages/core/src/main/java/com/agui/types/RunAgentInput.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/types/RunAgentInput.java rename to java-sdk/packages/core/src/main/java/com/agui/types/RunAgentInput.java diff --git a/java-sdk/core/src/main/java/com/agui/types/State.java b/java-sdk/packages/core/src/main/java/com/agui/types/State.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/types/State.java rename to java-sdk/packages/core/src/main/java/com/agui/types/State.java diff --git a/java-sdk/core/src/main/java/com/agui/types/Tool.java b/java-sdk/packages/core/src/main/java/com/agui/types/Tool.java similarity index 100% rename from java-sdk/core/src/main/java/com/agui/types/Tool.java rename to java-sdk/packages/core/src/main/java/com/agui/types/Tool.java diff --git a/java-sdk/core/src/main/java/com/agui/types/ToolCall.java b/java-sdk/packages/core/src/main/java/com/agui/types/ToolCall.java similarity index 86% rename from java-sdk/core/src/main/java/com/agui/types/ToolCall.java rename to java-sdk/packages/core/src/main/java/com/agui/types/ToolCall.java index f6c80555c..0040dd044 100644 --- a/java-sdk/core/src/main/java/com/agui/types/ToolCall.java +++ b/java-sdk/packages/core/src/main/java/com/agui/types/ToolCall.java @@ -1,4 +1,4 @@ package com.agui.types; -public record ToolCall(String id, String type, FunctionCall functionCall) { } +public record ToolCall(String id, String type, FunctionCall function) { } diff --git a/java-sdk/core/src/test/java/com/agui/event/BaseEventTest.java b/java-sdk/packages/core/src/test/java/com/agui/event/BaseEventTest.java similarity index 100% rename from java-sdk/core/src/test/java/com/agui/event/BaseEventTest.java rename to java-sdk/packages/core/src/test/java/com/agui/event/BaseEventTest.java diff --git a/java-sdk/packages/http/pom.xml b/java-sdk/packages/http/pom.xml new file mode 100644 index 000000000..4cdb9f091 --- /dev/null +++ b/java-sdk/packages/http/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + com.ag-ui + ag-ui + 0.0.1-SNAPSHOT + + + http + + + 21 + 21 + UTF-8 + + + + com.fasterxml.jackson.core + jackson-databind + 2.19.0 + compile + + + com.ag-ui + core + 0.0.1-SNAPSHOT + compile + + + com.ag-ui + client + 0.0.1-SNAPSHOT + compile + + + + \ No newline at end of file diff --git a/java-sdk/ok-http/src/main/java/com/agui/HttpAgent.java b/java-sdk/packages/http/src/main/java/com/agui/HttpAgent.java similarity index 73% rename from java-sdk/ok-http/src/main/java/com/agui/HttpAgent.java rename to java-sdk/packages/http/src/main/java/com/agui/HttpAgent.java index 313c47a7d..f6e2bae0f 100644 --- a/java-sdk/ok-http/src/main/java/com/agui/HttpAgent.java +++ b/java-sdk/packages/http/src/main/java/com/agui/HttpAgent.java @@ -5,10 +5,11 @@ import com.agui.types.State; import com.agui.message.BaseMessage; import com.agui.types.RunAgentInput; -import io.reactivex.Observable; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; public class HttpAgent extends AbstractAgent { @@ -18,26 +19,36 @@ private HttpAgent( final String agentId, final String description, final String threadId, - final String url, + final HttpClient httpClient, final List messages, final State state, final boolean debug ) { super(agentId, description, threadId, messages, state, debug); - this.httpClient = new HttpClient(url); + this.httpClient = httpClient; } @Override - protected Observable run(RunAgentInput input) { - return this.httpClient.streamEvents(input); + protected CompletableFuture run(RunAgentInput input, Consumer eventHandler) { + // Fixed: Now properly passing the eventHandler to the HTTP client + return this.httpClient.streamEvents(input, eventHandler); + } + + /** + * Close the underlying HTTP client when the agent is no longer needed + */ + public void close() { + if (httpClient != null) { + httpClient.close(); + } } public static class Builder { private String agentId; private String description = ""; private String threadId; - private String url; + private HttpClient httpClient; private List messages = new ArrayList<>(); private State state = new State(); private boolean debug = false; @@ -59,8 +70,8 @@ public Builder threadId(String threadId) { return this; } - public Builder url(String url) { - this.url = url; + public Builder httpClient(HttpClient httpClient) { + this.httpClient = httpClient; return this; } @@ -99,14 +110,14 @@ private void validate() { if (threadId == null || threadId.trim().isEmpty()) { throw new IllegalArgumentException("threadId is required"); } - if (url == null || url.trim().isEmpty()) { - throw new IllegalArgumentException("url is required"); + if (httpClient == null) { + throw new IllegalArgumentException("http client is required"); } } public HttpAgent build() { validate(); - return new HttpAgent(agentId, description, threadId, url, messages, state, debug); + return new HttpAgent(agentId, description, threadId, httpClient, messages, state, debug); } } @@ -114,12 +125,12 @@ public static Builder builder() { return new Builder(); } - public static Builder withUrl(String url) { - return new Builder().url(url); + public static Builder withHttpClient(HttpClient httpClient) { + return new Builder().httpClient(httpClient); } public static Builder withAgentId(String agentId) { return new Builder().agentId(agentId); } -} +} \ No newline at end of file diff --git a/java-sdk/packages/http/src/main/java/com/agui/HttpClient.java b/java-sdk/packages/http/src/main/java/com/agui/HttpClient.java new file mode 100644 index 000000000..4054d1694 --- /dev/null +++ b/java-sdk/packages/http/src/main/java/com/agui/HttpClient.java @@ -0,0 +1,53 @@ +package com.agui; + +import com.agui.event.BaseEvent; +import com.agui.types.RunAgentInput; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +public abstract class HttpClient { + + protected final ObjectMapper objectMapper; + + public HttpClient() { + + this.objectMapper = new ObjectMapper(); + this.objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + } + + /** + * Stream events from the server, calling the eventHandler for each received event. + * + * @param input The input to send to the server + * @param eventHandler Callback function that handles each received event + * @return CompletableFuture that completes when the stream ends or fails + */ + public abstract CompletableFuture streamEvents(final RunAgentInput input, Consumer eventHandler); + + /** + * Alternative method that returns a CompletableFuture without event handler + * (for cases where you just want to know when the stream completes) + */ + public CompletableFuture streamEvents(final RunAgentInput input) { + return streamEvents(input, null); + } + + /** + * Utility method to create a cancellable stream that can be interrupted + */ + public abstract CompletableFuture streamEventsWithCancellation( + final RunAgentInput input, + Consumer eventHandler, + AtomicBoolean cancellationToken + ); + + /** + * Close the underlying HTTP client + */ + public abstract void close(); + +} diff --git a/java-sdk/ok-http/pom.xml b/java-sdk/packages/ok-http/pom.xml similarity index 89% rename from java-sdk/ok-http/pom.xml rename to java-sdk/packages/ok-http/pom.xml index af2f452df..509fd4aa3 100644 --- a/java-sdk/ok-http/pom.xml +++ b/java-sdk/packages/ok-http/pom.xml @@ -46,6 +46,12 @@ 3.24.2 test + + com.ag-ui + http + 0.0.1-SNAPSHOT + compile + \ No newline at end of file diff --git a/java-sdk/packages/ok-http/src/main/java/com/agui/okhttp/HttpClient.java b/java-sdk/packages/ok-http/src/main/java/com/agui/okhttp/HttpClient.java new file mode 100644 index 000000000..2673efc49 --- /dev/null +++ b/java-sdk/packages/ok-http/src/main/java/com/agui/okhttp/HttpClient.java @@ -0,0 +1,186 @@ +package com.agui.okhttp; + +import com.agui.event.BaseEvent; +import com.agui.types.RunAgentInput; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import okhttp3.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +public class HttpClient extends com.agui.HttpClient { + + private final OkHttpClient client; + + private final String url; + + public HttpClient(final String url) { + super(); + + this.url = url; + + this.client = new OkHttpClient.Builder() + .readTimeout(0, TimeUnit.MILLISECONDS) + .build(); + } + + @Override + public CompletableFuture streamEvents(final RunAgentInput input, Consumer eventHandler) { + CompletableFuture future = new CompletableFuture<>(); + AtomicBoolean isCancelled = new AtomicBoolean(false); + + try { + var body = RequestBody.create( + objectMapper.writeValueAsString(input), + MediaType.get("application/json") + ); + + Request request = new Request.Builder() + .url(this.url) + .header("Accept", "application/json") + .post(body) + .build(); + + Call call = client.newCall(request); + + // Allow cancellation of the CompletableFuture to cancel the HTTP call + future.whenComplete((result, throwable) -> { + if (future.isCancelled()) { + isCancelled.set(true); + call.cancel(); + } + }); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + try (BufferedReader reader = new BufferedReader(response.body().charStream())) { + String line; + while ((line = reader.readLine()) != null && !isCancelled.get()) { + if (line.trim().startsWith("data: ")) { + try { + String jsonData = line.trim().substring(6).trim(); + BaseEvent event = objectMapper.readValue(jsonData, BaseEvent.class); + + // Call the event handler for each event + if (eventHandler != null) { + eventHandler.accept(event); + } + } catch (Exception e) { + // Log parsing errors but continue processing + System.err.println("Error parsing event: " + e.getMessage()); + // Optionally, you could fail the entire future here: + // future.completeExceptionally(e); + // return; + } + } + } + + if (!isCancelled.get()) { + future.complete(null); + } + } catch (IOException e) { + future.completeExceptionally(e); + } + } + + @Override + public void onFailure(Call call, IOException e) { + future.completeExceptionally(e); + } + }); + + } catch (Exception e) { + future.completeExceptionally(e); + } + + return future; + } + + + @Override + public CompletableFuture streamEventsWithCancellation( + final RunAgentInput input, + final Consumer eventHandler, + final AtomicBoolean cancellationToken + ) { + CompletableFuture future = new CompletableFuture<>(); + + try { + var body = RequestBody.create( + objectMapper.writeValueAsString(input), + MediaType.get("application/json") + ); + + Request request = new Request.Builder() + .url(url) + .header("Accept", "application/json") + .post(body) + .build(); + + Call call = client.newCall(request); + + // Cancel HTTP call if either the future is cancelled or the token is set + future.whenComplete((result, throwable) -> { + if (future.isCancelled() || cancellationToken.get()) { + call.cancel(); + } + }); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + try (BufferedReader reader = new BufferedReader(response.body().charStream())) { + String line; + while ((line = reader.readLine()) != null && + !future.isCancelled() && + !cancellationToken.get()) { + + if (line.trim().startsWith("data: ")) { + try { + String jsonData = line.trim().substring(6).trim(); + BaseEvent event = objectMapper.readValue(jsonData, BaseEvent.class); + + if (eventHandler != null) { + eventHandler.accept(event); + } + } catch (Exception e) { + System.err.println("Error parsing event: " + e.getMessage()); + } + } + } + + if (!future.isCancelled() && !cancellationToken.get()) { + future.complete(null); + } + } catch (IOException e) { + future.completeExceptionally(e); + } + } + + @Override + public void onFailure(Call call, IOException e) { + future.completeExceptionally(e); + } + }); + + } catch (Exception e) { + future.completeExceptionally(e); + } + + return future; + } + + @Override + public void close() { + if (client != null) { + client.dispatcher().executorService().shutdown(); + client.connectionPool().evictAll(); + } + } +} \ No newline at end of file diff --git a/java-sdk/ok-http/src/test/java/com/agui/HttpAgentTest.java b/java-sdk/packages/ok-http/src/test/java/com/agui/HttpAgentTest.java similarity index 95% rename from java-sdk/ok-http/src/test/java/com/agui/HttpAgentTest.java rename to java-sdk/packages/ok-http/src/test/java/com/agui/HttpAgentTest.java index f5b88d9ed..1a7f6b038 100644 --- a/java-sdk/ok-http/src/test/java/com/agui/HttpAgentTest.java +++ b/java-sdk/packages/ok-http/src/test/java/com/agui/HttpAgentTest.java @@ -5,6 +5,7 @@ import com.agui.client.subscriber.AgentSubscriberParams; import com.agui.event.BaseEvent; import com.agui.message.UserMessage; +import com.agui.okhttp.HttpClient; import com.agui.types.State; import com.agui.client.subscriber.AgentSubscriber; import org.junit.jupiter.api.Test; @@ -27,7 +28,7 @@ public void itShouldCallEndpoint() throws InterruptedException { var agent = HttpAgent.withAgentId("simpleAgent") .threadId("THREAD") .description("Agent Description") - .url("http://localhost:3033/ai/mastra/run/weatherAgent") + .httpClient(new HttpClient("http://localhost:3033/ai/mastra/run/weatherAgent")) .state(new State()) .addMessage(message) .debug() diff --git a/java-sdk/packages/spring/pom.xml b/java-sdk/packages/spring/pom.xml new file mode 100644 index 000000000..60127bf00 --- /dev/null +++ b/java-sdk/packages/spring/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + com.ag-ui + ag-ui + 0.0.1-SNAPSHOT + + + com.agui + spring + + + 21 + 21 + UTF-8 + + + + + org.springframework + spring-webflux + 6.0.0 + + + io.projectreactor.netty + reactor-netty-http + 1.0.0 + + + com.ag-ui + core + 0.0.1-SNAPSHOT + compile + + + com.ag-ui + http + 0.0.1-SNAPSHOT + compile + + + com.fasterxml.jackson.core + jackson-databind + 2.19.0 + compile + + + com.ag-ui + http + 0.0.1-SNAPSHOT + compile + + + + \ No newline at end of file diff --git a/java-sdk/packages/spring/src/main/java/com/agui/spring/HttpClient.java b/java-sdk/packages/spring/src/main/java/com/agui/spring/HttpClient.java new file mode 100644 index 000000000..9a55d6fa9 --- /dev/null +++ b/java-sdk/packages/spring/src/main/java/com/agui/spring/HttpClient.java @@ -0,0 +1,103 @@ +package com.agui.spring; + +import com.agui.event.BaseEvent; +import com.agui.types.RunAgentInput; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.http.MediaType; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +public class HttpClient extends com.agui.HttpClient { + + private final WebClient webClient; + private final String url; + + public HttpClient(final String url) { + super(); + this.url = url; + + this.webClient = WebClient.builder() + .codecs(configurer -> configurer + .defaultCodecs() + .maxInMemorySize(16 * 1024 * 1024)) // 16MB buffer for streaming + .build(); + } + + @Override + public CompletableFuture streamEvents(final RunAgentInput input, Consumer eventHandler) { + AtomicBoolean isCancelled = new AtomicBoolean(false); + + return webClient.post() + .uri(url) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(input)) + .retrieve() + .bodyToFlux(String.class) + .takeWhile(line -> !isCancelled.get()) + .filter(line -> line.trim().startsWith("data: ")) + .map(line -> { + try { + String jsonData = line.trim().substring(6).trim(); + return objectMapper.readValue(jsonData, BaseEvent.class); + } catch (Exception e) { + System.err.println("Error parsing event: " + e.getMessage()); + return null; + } + }) + .filter(event -> event != null) + .doOnNext(event -> { + if (eventHandler != null) { + eventHandler.accept(event); + } + }) + .then() + .doOnCancel(() -> isCancelled.set(true)) + .toFuture(); + } + + @Override + public CompletableFuture streamEventsWithCancellation( + final RunAgentInput input, + final Consumer eventHandler, + final AtomicBoolean cancellationToken + ) { + return webClient.post() + .uri(url) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(input)) + .retrieve() + .bodyToFlux(String.class) + .takeWhile(line -> !cancellationToken.get()) + .filter(line -> line.trim().startsWith("data: ")) + .map(line -> { + try { + String jsonData = line.trim().substring(6).trim(); + return objectMapper.readValue(jsonData, BaseEvent.class); + } catch (Exception e) { + System.err.println("Error parsing event: " + e.getMessage()); + return null; + } + }) + .filter(event -> event != null) + .doOnNext(event -> { + if (eventHandler != null) { + eventHandler.accept(event); + } + }) + .then() + .doOnCancel(() -> cancellationToken.set(true)) + .toFuture(); + } + + @Override + public void close() { + // WebClient doesn't require explicit cleanup as it uses shared resources + // If you need to customize connection pooling, you can create a custom + // ConnectionProvider and dispose it here + } +} \ No newline at end of file From d55b6ed293952d52e79e31a7a34d7c6d74fae6ef Mon Sep 17 00:00:00 2001 From: Pascal Wilbrink Date: Thu, 7 Aug 2025 22:27:07 +0200 Subject: [PATCH 7/8] Added Spring integration --- .../spring-ai/target/classes/application.properties | 1 + java-sdk/pom.xml | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 java-sdk/integrations/spring-ai/target/classes/application.properties diff --git a/java-sdk/integrations/spring-ai/target/classes/application.properties b/java-sdk/integrations/spring-ai/target/classes/application.properties new file mode 100644 index 000000000..2109a440d --- /dev/null +++ b/java-sdk/integrations/spring-ai/target/classes/application.properties @@ -0,0 +1 @@ +spring.application.name=demo diff --git a/java-sdk/pom.xml b/java-sdk/pom.xml index f8440a715..b658e666c 100644 --- a/java-sdk/pom.xml +++ b/java-sdk/pom.xml @@ -10,9 +10,12 @@ pom - core - client - ok-http + packages/core + packages/client + packages/ok-http + packages/http + packages/spring + integrations/spring-ai From 9a8e9010d47d4436e495731aa032bca733ebd53a Mon Sep 17 00:00:00 2001 From: Pascal Wilbrink Date: Thu, 7 Aug 2025 22:28:27 +0200 Subject: [PATCH 8/8] Delete java-sdk/integrations/spring-ai/target/classes/application.properties --- .../integrations/spring-ai/target/classes/application.properties | 1 - 1 file changed, 1 deletion(-) delete mode 100644 java-sdk/integrations/spring-ai/target/classes/application.properties diff --git a/java-sdk/integrations/spring-ai/target/classes/application.properties b/java-sdk/integrations/spring-ai/target/classes/application.properties deleted file mode 100644 index 2109a440d..000000000 --- a/java-sdk/integrations/spring-ai/target/classes/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=demo