-
Notifications
You must be signed in to change notification settings - Fork 797
Fix(harness): session state update and tool call result notifications. #1703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
53ea9f8
9a6980d
e63f254
e01cf12
84e480b
8b398db
770919c
c01610a
61db53a
a56d4e3
b25bb4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,8 @@ | |
| import com.fasterxml.jackson.annotation.JsonSubTypes; | ||
| import com.fasterxml.jackson.annotation.JsonTypeInfo; | ||
| import java.time.Instant; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
| import java.util.UUID; | ||
|
|
||
| /** | ||
|
|
@@ -34,6 +36,7 @@ | |
| @JsonSubTypes({ | ||
| @JsonSubTypes.Type(value = AgentStartEvent.class, name = "AGENT_START"), | ||
| @JsonSubTypes.Type(value = AgentEndEvent.class, name = "AGENT_END"), | ||
| @JsonSubTypes.Type(value = AgentResultEvent.class, name = "AGENT_RESULT"), | ||
| @JsonSubTypes.Type(value = ModelCallStartEvent.class, name = "MODEL_CALL_START"), | ||
| @JsonSubTypes.Type(value = ModelCallEndEvent.class, name = "MODEL_CALL_END"), | ||
| @JsonSubTypes.Type(value = TextBlockStartEvent.class, name = "TEXT_BLOCK_START"), | ||
|
|
@@ -62,14 +65,19 @@ | |
| value = ExternalExecutionResultEvent.class, | ||
| name = "EXTERNAL_EXECUTION_RESULT"), | ||
| @JsonSubTypes.Type(value = RequestStopEvent.class, name = "REQUEST_STOP"), | ||
| @JsonSubTypes.Type(value = SubagentExposedEvent.class, name = "SUBAGENT_EXPOSED") | ||
| @JsonSubTypes.Type(value = SubagentExposedEvent.class, name = "SUBAGENT_EXPOSED"), | ||
| @JsonSubTypes.Type(value = HintBlockEvent.class, name = "HINT_BLOCK"), | ||
| @JsonSubTypes.Type(value = CustomEvent.class, name = "CUSTOM") | ||
| }) | ||
| public abstract class AgentEvent { | ||
|
|
||
| private final String id; | ||
| private final String createdAt; | ||
| private String source; | ||
|
|
||
| @JsonInclude(JsonInclude.Include.NON_EMPTY) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] Metadata field lacks validation: The |
||
| private Map<String, Object> metadata; | ||
|
|
||
| protected AgentEvent() { | ||
| this.id = UUID.randomUUID().toString().replace("-", ""); | ||
| this.createdAt = Instant.now().toString(); | ||
|
|
@@ -108,6 +116,21 @@ public AgentEvent withSource(String source) { | |
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Returns optional metadata attached to this event. May be {@code null} or empty. | ||
| */ | ||
| public Map<String, Object> getMetadata() { | ||
| return metadata; | ||
| } | ||
|
|
||
| /** | ||
| * Attaches arbitrary key-value metadata to this event and returns it for chaining. | ||
| */ | ||
| public AgentEvent withMetadata(Map<String, Object> metadata) { | ||
| this.metadata = metadata != null ? new LinkedHashMap<>(metadata) : null; | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return getClass().getSimpleName() + "{id='" + id + "', type=" + getType() + '}'; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /* | ||
| * Copyright 2024-2026 the original author or authors. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package io.agentscope.core.event; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import io.agentscope.core.message.Msg; | ||
|
|
||
| /** | ||
| * Emitted when an agent successfully finishes processing an invocation, carrying the final result | ||
| * message. | ||
| * | ||
| * <p>This event is emitted as part of the agent event stream (both {@code call()} and | ||
| * {@code streamEvents()} paths) immediately before {@link AgentEndEvent}. Callers of | ||
| * {@code streamEvents()} can filter for this event to obtain the final {@link Msg} directly | ||
| * from the event stream without subscribing to the {@code Mono<Msg>} return value separately. | ||
| * | ||
| * <p>{@code call()} internally uses this event to extract the result from the shared | ||
| * {@code buildAgentStream()} core, ensuring both paths run through the same {@code onAgent} | ||
| * middleware chain. | ||
| */ | ||
| public class AgentResultEvent extends AgentEvent { | ||
|
|
||
| private final Msg result; | ||
|
|
||
| public AgentResultEvent(Msg result) { | ||
| this.result = result; | ||
| } | ||
|
|
||
| @JsonCreator | ||
| public AgentResultEvent( | ||
| @JsonProperty("id") String id, | ||
| @JsonProperty("createdAt") String createdAt, | ||
| @JsonProperty("result") Msg result) { | ||
| super(id, createdAt); | ||
| this.result = result; | ||
| } | ||
|
|
||
| @Override | ||
| public AgentEventType getType() { | ||
| return AgentEventType.AGENT_RESULT; | ||
| } | ||
|
|
||
| public Msg getResult() { | ||
| return result; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| /* | ||
| * Copyright 2024-2026 the original author or authors. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package io.agentscope.core.event; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Generic extensible event for signals that don't fit a specific {@link AgentEvent} subtype. | ||
| * | ||
| * <p>Used by service-layer middleware to notify front-end subscribers about state changes (task | ||
| * progress, team membership, permission updates, etc.) without polluting the core agent event enum | ||
| * with application-specific types. | ||
| * | ||
| * <p>Front-end implementations should handle unknown {@link #getName()} values gracefully — skip | ||
| * with no error. | ||
| * | ||
| * <p>Well-known {@code name} values: | ||
| * <ul> | ||
| * <li>{@code "state_updated"} — agent state (tasks / permission) changed during a tool call</li> | ||
| * <li>{@code "team_updated"} — team membership changed (member added / team created or | ||
| * dissolved)</li> | ||
| * </ul> | ||
| */ | ||
| public class CustomEvent extends AgentEvent { | ||
|
|
||
| private final String name; | ||
| private final Map<String, Object> value; | ||
|
|
||
| @JsonCreator | ||
| public CustomEvent( | ||
| @JsonProperty("id") String id, | ||
| @JsonProperty("createdAt") String createdAt, | ||
| @JsonProperty("name") String name, | ||
| @JsonProperty("value") Map<String, Object> value) { | ||
| super(id, createdAt); | ||
| this.name = name; | ||
| this.value = value != null ? value : Map.of(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] Immutable empty map may surprise callers: When
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] Immutable empty map may surprise callers: When |
||
| } | ||
|
|
||
| public CustomEvent(String name, Map<String, Object> value) { | ||
| this.name = name; | ||
| this.value = value != null ? value : Map.of(); | ||
| } | ||
|
|
||
| public CustomEvent(String name) { | ||
| this(name, Map.of()); | ||
| } | ||
|
|
||
| @Override | ||
| public AgentEventType getType() { | ||
| return AgentEventType.CUSTOM; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the kind of notification. See class javadoc for well-known values. | ||
| */ | ||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the arbitrary JSON-serializable payload whose schema depends on {@link #getName()}. | ||
| */ | ||
| public Map<String, Object> getValue() { | ||
| return value; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| /* | ||
| * Copyright 2024-2026 the original author or authors. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package io.agentscope.core.event; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonCreator; | ||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
|
|
||
| /** | ||
| * One-shot hint block event. | ||
| * | ||
| * <p>Unlike text/thinking blocks, hint blocks are not streamed — the full content is available at | ||
| * creation time (team messages, background tool results, user interruptions, etc.). A single event | ||
| * carries the complete hint. | ||
| */ | ||
| public class HintBlockEvent extends AgentEvent { | ||
|
|
||
| private final String replyId; | ||
| private final String blockId; | ||
| private final String hintSource; | ||
| private final String hint; | ||
|
|
||
| @JsonCreator | ||
| public HintBlockEvent( | ||
| @JsonProperty("id") String id, | ||
| @JsonProperty("createdAt") String createdAt, | ||
| @JsonProperty("replyId") String replyId, | ||
| @JsonProperty("blockId") String blockId, | ||
| @JsonProperty("hintSource") String hintSource, | ||
| @JsonProperty("hint") String hint) { | ||
| super(id, createdAt); | ||
| this.replyId = replyId; | ||
| this.blockId = blockId; | ||
| this.hintSource = hintSource; | ||
| this.hint = hint; | ||
| } | ||
|
|
||
| public HintBlockEvent(String replyId, String blockId, String hintSource, String hint) { | ||
| this.replyId = replyId; | ||
| this.blockId = blockId; | ||
| this.hintSource = hintSource; | ||
| this.hint = hint; | ||
| } | ||
|
|
||
| @Override | ||
| public AgentEventType getType() { | ||
| return AgentEventType.HINT_BLOCK; | ||
| } | ||
|
|
||
| public String getReplyId() { | ||
| return replyId; | ||
| } | ||
|
|
||
| public String getBlockId() { | ||
| return blockId; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the sender or origin of this hint. For team messages this is the sender's display | ||
| * name (e.g. {@code "alice"}); for system notifications it may be {@code "system"} or | ||
| * {@code null}. | ||
| * | ||
| * <p>Named {@code hintSource} to avoid collision with {@link AgentEvent#getSource()} which | ||
| * carries the subagent forwarding path. | ||
| */ | ||
| public String getHintSource() { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nitpick] hintSource naming rationale could be clearer: The Javadoc explains that
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nitpick] hintSource naming rationale could be clearer: The Javadoc explains that |
||
| return hintSource; | ||
| } | ||
|
|
||
| public String getHint() { | ||
| return hint; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[minor] Metadata field lacks validation: The
withMetadatamethod accepts anyMap<String, Object>but themetadatafield is typed asMap<String, Object>. If callers store non-serializable objects or complex types in this map, Jackson serialization may fail at runtime. Consider adding a note in the Javadoc that values should be JSON-serializable, or provide a builder/validator that enforces this constraint.