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/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/packages/.gitignore b/java-sdk/packages/.gitignore new file mode 100644 index 000000000..fa180dea5 --- /dev/null +++ b/java-sdk/packages/.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/packages/client/pom.xml b/java-sdk/packages/client/pom.xml new file mode 100644 index 000000000..e20d4ba6d --- /dev/null +++ b/java-sdk/packages/client/pom.xml @@ -0,0 +1,81 @@ + + + 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 + + + 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/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/packages/client/src/main/java/com/agui/client/RunAgentParameters.java b/java-sdk/packages/client/src/main/java/com/agui/client/RunAgentParameters.java new file mode 100644 index 000000000..aca26d630 --- /dev/null +++ b/java-sdk/packages/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/packages/client/src/main/java/com/agui/client/RunAgentResult.java b/java-sdk/packages/client/src/main/java/com/agui/client/RunAgentResult.java new file mode 100644 index 000000000..16f59fb4b --- /dev/null +++ b/java-sdk/packages/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/packages/client/src/main/java/com/agui/client/Subscription.java b/java-sdk/packages/client/src/main/java/com/agui/client/Subscription.java new file mode 100644 index 000000000..72de02824 --- /dev/null +++ b/java-sdk/packages/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/packages/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java b/java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java new file mode 100644 index 000000000..395a81e5c --- /dev/null +++ b/java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentStateMutation.java @@ -0,0 +1,13 @@ +package com.agui.client.subscriber; + +import com.agui.types.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/packages/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java b/java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java new file mode 100644 index 000000000..9ba15543c --- /dev/null +++ b/java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentSubscriber.java @@ -0,0 +1,41 @@ +package com.agui.client.subscriber; + +import com.agui.event.*; +import com.agui.message.BaseMessage; +import com.agui.types.ToolCall; + +public interface AgentSubscriber { + + // Request lifecycle + default void onRunInitialized(AgentSubscriberParams params) { } + default void onRunFailed(AgentSubscriberParams params, Throwable error) { } + default void onRunFinalized(AgentSubscriberParams params) { } + + // Events + 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) { } + +} + diff --git a/java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java b/java-sdk/packages/client/src/main/java/com/agui/client/subscriber/AgentSubscriberParams.java new file mode 100644 index 000000000..93d365afe --- /dev/null +++ b/java-sdk/packages/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.types.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/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/packages/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java b/java-sdk/packages/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java new file mode 100644 index 000000000..0e6e8b76b --- /dev/null +++ b/java-sdk/packages/client/src/test/java/com/agui/client/subscriber/AgentSubscriberTest.java @@ -0,0 +1,53 @@ +package com.agui.client.subscriber; + + +import com.agui.client.RunAgentParameters; +import com.agui.types.State; +import com.agui.client.TestAgent; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +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 void onRunInitialized(AgentSubscriberParams params) { + assertThat(params.getAgent()).isEqualTo(agent); + 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()); + } + } + ); + + agent.runAgent(RunAgentParameters.builder().build(), new AgentSubscriber() {}); + } +} \ No newline at end of file diff --git a/java-sdk/packages/core/pom.xml b/java-sdk/packages/core/pom.xml new file mode 100644 index 000000000..66fe31762 --- /dev/null +++ b/java-sdk/packages/core/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + com.ag-ui + ag-ui + 0.0.1-SNAPSHOT + + + core + + + 18 + 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 + + + + + + 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/packages/core/src/main/java/com/agui/event/BaseEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/BaseEvent.java new file mode 100644 index 000000000..854e8bc2b --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/BaseEvent.java @@ -0,0 +1,71 @@ +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; + +@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; + + private int timestamp; + + private Object rawEvent; + + public BaseEvent(final EventType type) { + this.type = type; + } + + @JsonIgnore + 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/packages/core/src/main/java/com/agui/event/CustomEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/CustomEvent.java new file mode 100644 index 000000000..887a15dd6 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java new file mode 100644 index 000000000..46efdbf4a --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/MessagesSnapshotEvent.java @@ -0,0 +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/packages/core/src/main/java/com/agui/event/RawEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/RawEvent.java new file mode 100644 index 000000000..96a51e1f5 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/RunErrorEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/RunErrorEvent.java new file mode 100644 index 000000000..5a4255c1e --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/RunFinishedEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/RunFinishedEvent.java new file mode 100644 index 000000000..277a4dfa3 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/RunFinishedEvent.java @@ -0,0 +1,38 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class RunFinishedEvent extends BaseEvent { + + private String threadId; + private String runId; + private Object result; + + public RunFinishedEvent() { + super(EventType.RUN_FINISHED); + } + + 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; + } + + public void setResult(final Object result) { + this.result = result; + } + + public Object getResult() { + return this.result; + } +} \ No newline at end of file diff --git a/java-sdk/packages/core/src/main/java/com/agui/event/RunStartedEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/RunStartedEvent.java new file mode 100644 index 000000000..031d0d0b0 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/RunStartedEvent.java @@ -0,0 +1,30 @@ +package com.agui.event; + +import com.agui.types.EventType; + +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/packages/core/src/main/java/com/agui/event/StateDeltaEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/StateDeltaEvent.java new file mode 100644 index 000000000..ef50e18b1 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/StateSnapshotEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/StateSnapshotEvent.java new file mode 100644 index 000000000..d0002cebb --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/StepFinishedEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/StepFinishedEvent.java new file mode 100644 index 000000000..5c2e093b2 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/StepStartedEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/StepStartedEvent.java new file mode 100644 index 000000000..1f0a25674 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/TextMessageChunkEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageChunkEvent.java new file mode 100644 index 000000000..184c42c86 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageChunkEvent.java @@ -0,0 +1,30 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class TextMessageChunkEvent extends BaseEvent { + + 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/packages/core/src/main/java/com/agui/event/TextMessageContentEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageContentEvent.java new file mode 100644 index 000000000..9aa3008a3 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageContentEvent.java @@ -0,0 +1,29 @@ +package com.agui.event; + +import com.agui.types.EventType; + +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/packages/core/src/main/java/com/agui/event/TextMessageEndEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageEndEvent.java new file mode 100644 index 000000000..d6cc0a12e --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageEndEvent.java @@ -0,0 +1,21 @@ +package com.agui.event; + +import com.agui.types.EventType; + +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/packages/core/src/main/java/com/agui/event/TextMessageStartEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageStartEvent.java new file mode 100644 index 000000000..bddd03b5f --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/TextMessageStartEvent.java @@ -0,0 +1,29 @@ +package com.agui.event; + +import com.agui.types.EventType; + +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/packages/core/src/main/java/com/agui/event/ThinkingEndEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ThinkingEndEvent.java new file mode 100644 index 000000000..7c0dd7405 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/ThinkingStartEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ThinkingStartEvent.java new file mode 100644 index 000000000..77ac0f78b --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/ThinkingTextMessageContentEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ThinkingTextMessageContentEvent.java new file mode 100644 index 000000000..f5bff6325 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/ThinkingTextMessageEndEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ThinkingTextMessageEndEvent.java new file mode 100644 index 000000000..0aa9074f6 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/ThinkingTextMessageStartEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ThinkingTextMessageStartEvent.java new file mode 100644 index 000000000..671b462b7 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/event/ToolCallArgsEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallArgsEvent.java new file mode 100644 index 000000000..1cadabc68 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallArgsEvent.java @@ -0,0 +1,29 @@ +package com.agui.event; + +import com.agui.types.EventType; + +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/packages/core/src/main/java/com/agui/event/ToolCallChunkEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallChunkEvent.java new file mode 100644 index 000000000..f2da0155e --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallChunkEvent.java @@ -0,0 +1,37 @@ +package com.agui.event; + +import com.agui.types.EventType; + +public class ToolCallChunkEvent extends BaseEvent { + + 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/packages/core/src/main/java/com/agui/event/ToolCallEndEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallEndEvent.java new file mode 100644 index 000000000..c447afe37 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallEndEvent.java @@ -0,0 +1,20 @@ +package com.agui.event; + +import com.agui.types.EventType; + +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/packages/core/src/main/java/com/agui/event/ToolCallResultEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallResultEvent.java new file mode 100644 index 000000000..940020ba6 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallResultEvent.java @@ -0,0 +1,47 @@ +package com.agui.event; + +import com.agui.types.EventType; + +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/packages/core/src/main/java/com/agui/event/ToolCallStartEvent.java b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallStartEvent.java new file mode 100644 index 000000000..d699a5722 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/event/ToolCallStartEvent.java @@ -0,0 +1,38 @@ +package com.agui.event; + +import com.agui.types.EventType; + +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/packages/core/src/main/java/com/agui/message/AssistantMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/AssistantMessage.java new file mode 100644 index 000000000..75692ab2a --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/message/AssistantMessage.java @@ -0,0 +1,23 @@ +package com.agui.message; + +import com.agui.types.ToolCall; + +import java.util.ArrayList; +import java.util.List; + +public class AssistantMessage extends BaseMessage { + + private List toolCalls = new ArrayList<>(); + + 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/packages/core/src/main/java/com/agui/message/BaseMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/BaseMessage.java new file mode 100644 index 000000000..5a66c3a90 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/message/BaseMessage.java @@ -0,0 +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 String id; + private String content; + private String name; + + public BaseMessage() { } + + 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/packages/core/src/main/java/com/agui/message/DeveloperMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/DeveloperMessage.java new file mode 100644 index 000000000..45dd2c838 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/message/DeveloperMessage.java @@ -0,0 +1,8 @@ +package com.agui.message; + +public class DeveloperMessage extends BaseMessage { + + public String getRole() { + return "developer"; + } +} diff --git a/java-sdk/packages/core/src/main/java/com/agui/message/SystemMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/SystemMessage.java new file mode 100644 index 000000000..2bbbd75fa --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/message/SystemMessage.java @@ -0,0 +1,8 @@ +package com.agui.message; + +public class SystemMessage extends BaseMessage { + + public String getRole() { + return "system"; + } +} diff --git a/java-sdk/packages/core/src/main/java/com/agui/message/ToolMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/ToolMessage.java new file mode 100644 index 000000000..a90fedba7 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/message/ToolMessage.java @@ -0,0 +1,28 @@ +package com.agui.message; + +public class ToolMessage extends BaseMessage { + + private String toolCallId; + private String error; + + 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 getError() { + return this.error; + } +} + diff --git a/java-sdk/packages/core/src/main/java/com/agui/message/UserMessage.java b/java-sdk/packages/core/src/main/java/com/agui/message/UserMessage.java new file mode 100644 index 000000000..4431350f0 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/message/UserMessage.java @@ -0,0 +1,8 @@ +package com.agui.message; + +public class UserMessage extends BaseMessage { + + public String getRole() { + return "user"; + } +} diff --git a/java-sdk/packages/core/src/main/java/com/agui/types/Context.java b/java-sdk/packages/core/src/main/java/com/agui/types/Context.java new file mode 100644 index 000000000..68af7b497 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/types/Context.java @@ -0,0 +1,3 @@ +package com.agui.types; + +public record Context(String description, String value) { } diff --git a/java-sdk/packages/core/src/main/java/com/agui/types/EventType.java b/java-sdk/packages/core/src/main/java/com/agui/types/EventType.java new file mode 100644 index 000000000..2496d15c2 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/types/FunctionCall.java b/java-sdk/packages/core/src/main/java/com/agui/types/FunctionCall.java new file mode 100644 index 000000000..7c3307ff1 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/types/RunAgentInput.java b/java-sdk/packages/core/src/main/java/com/agui/types/RunAgentInput.java new file mode 100644 index 000000000..90c1cc705 --- /dev/null +++ b/java-sdk/packages/core/src/main/java/com/agui/types/RunAgentInput.java @@ -0,0 +1,16 @@ +package com.agui.types; + +import com.agui.message.BaseMessage; + +import java.util.List; + +public record RunAgentInput( + String threadId, + String runId, + Object state, + List messages, + List tools, + List context, + Object forwardedProps +) { } + diff --git a/java-sdk/packages/core/src/main/java/com/agui/types/State.java b/java-sdk/packages/core/src/main/java/com/agui/types/State.java new file mode 100644 index 000000000..ebc041ab0 --- /dev/null +++ b/java-sdk/packages/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/packages/core/src/main/java/com/agui/types/Tool.java b/java-sdk/packages/core/src/main/java/com/agui/types/Tool.java new file mode 100644 index 000000000..c4bc0702d --- /dev/null +++ b/java-sdk/packages/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 diff --git a/java-sdk/packages/core/src/main/java/com/agui/types/ToolCall.java b/java-sdk/packages/core/src/main/java/com/agui/types/ToolCall.java new file mode 100644 index 000000000..0040dd044 --- /dev/null +++ b/java-sdk/packages/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 function) { } + diff --git a/java-sdk/packages/core/src/test/java/com/agui/event/BaseEventTest.java b/java-sdk/packages/core/src/test/java/com/agui/event/BaseEventTest.java new file mode 100644 index 000000000..200ea5d35 --- /dev/null +++ b/java-sdk/packages/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/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/packages/http/src/main/java/com/agui/HttpAgent.java b/java-sdk/packages/http/src/main/java/com/agui/HttpAgent.java new file mode 100644 index 000000000..f6e2bae0f --- /dev/null +++ b/java-sdk/packages/http/src/main/java/com/agui/HttpAgent.java @@ -0,0 +1,136 @@ +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 java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +public class HttpAgent extends AbstractAgent { + + protected final HttpClient httpClient; + + private HttpAgent( + final String agentId, + final String description, + final String threadId, + final HttpClient httpClient, + final List messages, + final State state, + final boolean debug + ) { + super(agentId, description, threadId, messages, state, debug); + + this.httpClient = httpClient; + } + + @Override + 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 HttpClient httpClient; + 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 httpClient(HttpClient httpClient) { + this.httpClient = httpClient; + 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 (httpClient == null) { + throw new IllegalArgumentException("http client is required"); + } + } + + public HttpAgent build() { + validate(); + return new HttpAgent(agentId, description, threadId, httpClient, messages, state, debug); + } + } + + public static Builder builder() { + return new Builder(); + } + + 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/packages/ok-http/pom.xml b/java-sdk/packages/ok-http/pom.xml new file mode 100644 index 000000000..509fd4aa3 --- /dev/null +++ b/java-sdk/packages/ok-http/pom.xml @@ -0,0 +1,57 @@ + + + 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 + + + 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/packages/ok-http/src/test/java/com/agui/HttpAgentTest.java b/java-sdk/packages/ok-http/src/test/java/com/agui/HttpAgentTest.java new file mode 100644 index 000000000..1a7f6b038 --- /dev/null +++ b/java-sdk/packages/ok-http/src/test/java/com/agui/HttpAgentTest.java @@ -0,0 +1,77 @@ +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.okhttp.HttpClient; +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") + .httpClient(new HttpClient("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/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 diff --git a/java-sdk/pom.xml b/java-sdk/pom.xml new file mode 100644 index 000000000..b658e666c --- /dev/null +++ b/java-sdk/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + com.ag-ui + ag-ui + 0.0.1-SNAPSHOT + pom + + + packages/core + packages/client + packages/ok-http + packages/http + packages/spring + integrations/spring-ai + + + + 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