diff --git a/acp-core/src/main/java/com/agentclientprotocol/sdk/spec/AcpSchema.java b/acp-core/src/main/java/com/agentclientprotocol/sdk/spec/AcpSchema.java index aa26fc6..fd65d27 100644 --- a/acp-core/src/main/java/com/agentclientprotocol/sdk/spec/AcpSchema.java +++ b/acp-core/src/main/java/com/agentclientprotocol/sdk/spec/AcpSchema.java @@ -809,7 +809,8 @@ public record Annotations(@JsonProperty("audience") List audience, @JsonPr @JsonSubTypes.Type(value = ToolCallUpdateNotification.class, name = "tool_call_update"), @JsonSubTypes.Type(value = Plan.class, name = "plan"), @JsonSubTypes.Type(value = AvailableCommandsUpdate.class, name = "available_commands_update"), - @JsonSubTypes.Type(value = CurrentModeUpdate.class, name = "current_mode_update") }) + @JsonSubTypes.Type(value = CurrentModeUpdate.class, name = "current_mode_update"), + @JsonSubTypes.Type(value = UsageUpdate.class, name = "usage_update") }) public interface SessionUpdate { } @@ -932,6 +933,20 @@ public CurrentModeUpdate(String sessionUpdate, String currentModeId) { } } + /** + * Usage update - reports token/resource usage for the session + */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record UsageUpdate(@JsonProperty("sessionUpdate") String sessionUpdate, + @JsonProperty("used") Long used, + @JsonProperty("size") Long size, + @JsonProperty("_meta") Map meta) implements SessionUpdate { + public UsageUpdate(String sessionUpdate, Long used, Long size) { + this(sessionUpdate, used, size, null); + } + } + // --------------------------- // Tool Call Types // --------------------------- diff --git a/acp-core/src/test/java/com/agentclientprotocol/sdk/spec/SessionUpdateDeserializationTest.java b/acp-core/src/test/java/com/agentclientprotocol/sdk/spec/SessionUpdateDeserializationTest.java index 759a712..7fbb918 100644 --- a/acp-core/src/test/java/com/agentclientprotocol/sdk/spec/SessionUpdateDeserializationTest.java +++ b/acp-core/src/test/java/com/agentclientprotocol/sdk/spec/SessionUpdateDeserializationTest.java @@ -15,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for deserializing all 8 SessionUpdate types from JSON. + * Tests for deserializing all 9 SessionUpdate types from JSON. * *

* These tests verify that each SessionUpdate type can be correctly deserialized from JSON, @@ -258,6 +258,27 @@ void currentModeUpdateDeserialization() throws IOException { assertThat(modeUpdate.currentModeId()).isEqualTo("architect"); } + // --------------------------- + // UsageUpdate Tests + // --------------------------- + + @Test + void usageUpdateDeserialization() throws IOException { + String json = loadGolden("session-update-usage-update.json"); + + // The golden file is a full JSON-RPC notification; extract the inner update + com.fasterxml.jackson.databind.JsonNode root = new com.fasterxml.jackson.databind.ObjectMapper().readTree(json); + String updateJson = root.get("params").get("update").toString(); + + AcpSchema.SessionUpdate update = deserializeSessionUpdate(updateJson); + + assertThat(update).isInstanceOf(AcpSchema.UsageUpdate.class); + AcpSchema.UsageUpdate usageUpdate = (AcpSchema.UsageUpdate) update; + assertThat(usageUpdate.sessionUpdate()).isEqualTo("usage_update"); + assertThat(usageUpdate.used()).isEqualTo(53000L); + assertThat(usageUpdate.size()).isEqualTo(200000L); + } + // --------------------------- // All Status Values Tests // --------------------------- diff --git a/acp-core/src/test/java/com/agentclientprotocol/sdk/test/GoldenFileTest.java b/acp-core/src/test/java/com/agentclientprotocol/sdk/test/GoldenFileTest.java index 82901b8..6ab7851 100644 --- a/acp-core/src/test/java/com/agentclientprotocol/sdk/test/GoldenFileTest.java +++ b/acp-core/src/test/java/com/agentclientprotocol/sdk/test/GoldenFileTest.java @@ -86,6 +86,20 @@ void parseSessionUpdateNotification() throws IOException { assertThat(node.get("params").get("update").get("sessionUpdate").asText()).isEqualTo("agentMessage"); } + @Test + void parseSessionUpdateUsageUpdate() throws IOException { + String json = loadGoldenFile("session-update-usage-update.json"); + JsonNode node = objectMapper.readTree(json); + + assertThat(node.get("method").asText()).isEqualTo("session/update"); + assertThat(node.has("id")).isFalse(); + assertThat(node.get("params").get("sessionId").asText()).isEqualTo("sess_abc123"); + JsonNode update = node.get("params").get("update"); + assertThat(update.get("sessionUpdate").asText()).isEqualTo("usage_update"); + assertThat(update.get("used").asLong()).isEqualTo(53000L); + assertThat(update.get("size").asLong()).isEqualTo(200000L); + } + @Test void serializeInitializeRequestMatchesExpectedStructure() throws IOException { AcpSchema.InitializeRequest request = new AcpSchema.InitializeRequest(1, new AcpSchema.ClientCapabilities()); diff --git a/acp-core/src/test/resources/golden/session-update-usage-update.json b/acp-core/src/test/resources/golden/session-update-usage-update.json new file mode 100644 index 0000000..21131e2 --- /dev/null +++ b/acp-core/src/test/resources/golden/session-update-usage-update.json @@ -0,0 +1,12 @@ +{ + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "sess_abc123", + "update": { + "sessionUpdate": "usage_update", + "used": 53000, + "size": 200000 + } + } +} \ No newline at end of file