Skip to content

Commit 8d0bcbc

Browse files
Mark Pollackhtynkn
andcommitted
Add UsageUpdate support to SessionUpdate types
Add UsageUpdate (unstable spec feature) for reporting context window and cost usage during sessions. Includes Cost record with amount/currency fields per the ACP unstable schema. Fixes PR #2: aligns indentation, adds Cost type, uses bare golden file format, and adds UNSTABLE annotations. Co-authored-by: Huang YunKun <htynkn@gmail.com>
1 parent 821005b commit 8d0bcbc

File tree

4 files changed

+44
-36
lines changed

4 files changed

+44
-36
lines changed

acp-core/src/main/java/com/agentclientprotocol/sdk/spec/AcpSchema.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -934,19 +934,28 @@ public CurrentModeUpdate(String sessionUpdate, String currentModeId) {
934934
}
935935

936936
/**
937-
* Usage update - reports token/resource usage for the session
937+
* Usage update - context window and cost update for the session (UNSTABLE)
938938
*/
939939
@JsonIgnoreProperties(ignoreUnknown = true)
940940
@JsonInclude(JsonInclude.Include.NON_NULL)
941941
public record UsageUpdate(@JsonProperty("sessionUpdate") String sessionUpdate,
942-
@JsonProperty("used") Long used,
943-
@JsonProperty("size") Long size,
944-
@JsonProperty("_meta") Map<String, Object> meta) implements SessionUpdate {
942+
@JsonProperty("used") Long used, @JsonProperty("size") Long size,
943+
@JsonProperty("cost") Cost cost,
944+
@JsonProperty("_meta") Map<String, Object> meta) implements SessionUpdate {
945945
public UsageUpdate(String sessionUpdate, Long used, Long size) {
946-
this(sessionUpdate, used, size, null);
946+
this(sessionUpdate, used, size, null, null);
947947
}
948948
}
949949

950+
/**
951+
* Cost information for a session (UNSTABLE)
952+
*/
953+
@JsonIgnoreProperties(ignoreUnknown = true)
954+
@JsonInclude(JsonInclude.Include.NON_NULL)
955+
public record Cost(@JsonProperty("amount") Double amount,
956+
@JsonProperty("currency") String currency) {
957+
}
958+
950959
// ---------------------------
951960
// Tool Call Types
952961
// ---------------------------

acp-core/src/test/java/com/agentclientprotocol/sdk/spec/SessionUpdateDeserializationTest.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import static org.assertj.core.api.Assertions.assertThat;
1616

1717
/**
18-
* Tests for deserializing all 9 SessionUpdate types from JSON.
18+
* Tests for deserializing all SessionUpdate types from JSON.
1919
*
2020
* <p>
2121
* These tests verify that each SessionUpdate type can be correctly deserialized from JSON,
@@ -266,17 +266,37 @@ void currentModeUpdateDeserialization() throws IOException {
266266
void usageUpdateDeserialization() throws IOException {
267267
String json = loadGolden("session-update-usage-update.json");
268268

269-
// The golden file is a full JSON-RPC notification; extract the inner update
270-
com.fasterxml.jackson.databind.JsonNode root = new com.fasterxml.jackson.databind.ObjectMapper().readTree(json);
271-
String updateJson = root.get("params").get("update").toString();
272-
273-
AcpSchema.SessionUpdate update = deserializeSessionUpdate(updateJson);
269+
AcpSchema.SessionUpdate update = deserializeSessionUpdate(json);
274270

275271
assertThat(update).isInstanceOf(AcpSchema.UsageUpdate.class);
276272
AcpSchema.UsageUpdate usageUpdate = (AcpSchema.UsageUpdate) update;
277273
assertThat(usageUpdate.sessionUpdate()).isEqualTo("usage_update");
278274
assertThat(usageUpdate.used()).isEqualTo(53000L);
279275
assertThat(usageUpdate.size()).isEqualTo(200000L);
276+
assertThat(usageUpdate.cost()).isNull();
277+
}
278+
279+
@Test
280+
void usageUpdateWithCostDeserialization() throws IOException {
281+
String json = """
282+
{
283+
"sessionUpdate": "usage_update",
284+
"used": 53000,
285+
"size": 200000,
286+
"cost": {
287+
"amount": 0.42,
288+
"currency": "USD"
289+
}
290+
}
291+
""";
292+
293+
AcpSchema.SessionUpdate update = deserializeSessionUpdate(json);
294+
295+
assertThat(update).isInstanceOf(AcpSchema.UsageUpdate.class);
296+
AcpSchema.UsageUpdate usageUpdate = (AcpSchema.UsageUpdate) update;
297+
assertThat(usageUpdate.cost()).isNotNull();
298+
assertThat(usageUpdate.cost().amount()).isEqualTo(0.42);
299+
assertThat(usageUpdate.cost().currency()).isEqualTo("USD");
280300
}
281301

282302
// ---------------------------

acp-core/src/test/java/com/agentclientprotocol/sdk/test/GoldenFileTest.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,6 @@ void parseSessionUpdateNotification() throws IOException {
8686
assertThat(node.get("params").get("update").get("sessionUpdate").asText()).isEqualTo("agentMessage");
8787
}
8888

89-
@Test
90-
void parseSessionUpdateUsageUpdate() throws IOException {
91-
String json = loadGoldenFile("session-update-usage-update.json");
92-
JsonNode node = objectMapper.readTree(json);
93-
94-
assertThat(node.get("method").asText()).isEqualTo("session/update");
95-
assertThat(node.has("id")).isFalse();
96-
assertThat(node.get("params").get("sessionId").asText()).isEqualTo("sess_abc123");
97-
JsonNode update = node.get("params").get("update");
98-
assertThat(update.get("sessionUpdate").asText()).isEqualTo("usage_update");
99-
assertThat(update.get("used").asLong()).isEqualTo(53000L);
100-
assertThat(update.get("size").asLong()).isEqualTo(200000L);
101-
}
102-
10389
@Test
10490
void serializeInitializeRequestMatchesExpectedStructure() throws IOException {
10591
AcpSchema.InitializeRequest request = new AcpSchema.InitializeRequest(1, new AcpSchema.ClientCapabilities());
Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
{
2-
"jsonrpc": "2.0",
3-
"method": "session/update",
4-
"params": {
5-
"sessionId": "sess_abc123",
6-
"update": {
7-
"sessionUpdate": "usage_update",
8-
"used": 53000,
9-
"size": 200000
10-
}
11-
}
12-
}
2+
"sessionUpdate": "usage_update",
3+
"used": 53000,
4+
"size": 200000
5+
}

0 commit comments

Comments
 (0)