From 7fb2fb552a5c0e60d59596ba488b6c2cb981a29e Mon Sep 17 00:00:00 2001 From: Gregory Zak Date: Fri, 17 Jul 2026 11:21:03 -0700 Subject: [PATCH 1/2] feat(audit): add callerName field to AuditToolCallRequest audit_tool_call's tool_type field was misleadingly named -- every real caller (claude_code/codex/cursor/openclaw) used it to identify WHICH CLIENT made the call, not any property of the tool. Add callerName (wire: caller_name) alongside the existing toolType field, which is now a deprecated input fallback (not removed, not renamed). The server resolves caller_name if supplied, else the legacy tool_type, else a default. Adds unit coverage proving caller_name serializes standalone, tool_type still works standalone, and both together, plus a runtime-e2e test proving callerName reaches policy_details.caller_name against a live agent + orchestrator. Refs: getaxonflow/axonflow-enterprise#2912 (epic #2905) Signed-off-by: Gregory Zak --- CHANGELOG.md | 13 +- .../CallerNameAuditTest.java | 205 ++++++++++++++++++ runtime-e2e/caller_name_audit/README.md | 74 +++++++ .../sdk/types/AuditToolCallRequest.java | 43 ++++ .../getaxonflow/sdk/AuditToolCallTest.java | 87 ++++++++ tests/fixtures/wire-shape-baseline.json | 6 + 6 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 runtime-e2e/caller_name_audit/CallerNameAuditTest.java create mode 100644 runtime-e2e/caller_name_audit/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c59ab1..e2be471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] Hostile-testing sweep ahead of the BukuWarung integration -(getaxonflow/axonflow-enterprise#2861). Examples + runtime-e2e only — no -library changes. +(getaxonflow/axonflow-enterprise#2861), plus the `caller_name` audit field +below. ### Fixed @@ -27,6 +27,15 @@ library changes. SQLi → `deny` on `/api/v1/decide`, `allowed=false` on check-input, sync == async on both planes. +- **`AuditToolCallRequest.callerName` (wire: `caller_name`)** — identifies + WHICH CLIENT made a tool call (e.g. `claude_code`, `codex`, `cursor`, + `openclaw`), replacing the misleadingly-named `toolType` field for that + purpose (getaxonflow/axonflow-enterprise#2912, epic #2905). `toolType` is + kept as a **deprecated** input fallback — not removed, not renamed; the + server resolves `caller_name` if supplied, else the legacy `tool_type`, + else a default. `runtime-e2e/caller_name_audit/` proves `callerName` + reaches `policy_details.caller_name` on a live agent + orchestrator. + ## [8.5.1] - 2026-06-16: TLS security hardening (production guard) Patch release. No public API changes. diff --git a/runtime-e2e/caller_name_audit/CallerNameAuditTest.java b/runtime-e2e/caller_name_audit/CallerNameAuditTest.java new file mode 100644 index 0000000..4e2461e --- /dev/null +++ b/runtime-e2e/caller_name_audit/CallerNameAuditTest.java @@ -0,0 +1,205 @@ +/* + * runtime-e2e/caller_name_audit/CallerNameAuditTest.java + * + * Real-stack assertion for getaxonflow/axonflow-enterprise#2912 (sub-issue of + * epic #2905): `AuditToolCallRequest.callerName` (wire: `caller_name`) reaches + * `policy_details.caller_name` on the persisted audit row, through the SDK's + * real public `auditToolCall()` surface against a live AxonFlow agent + + * orchestrator — no mocks. + * + * Background: audit_tool_call's `tool_type` field was misleadingly named — + * every real caller (claude_code/codex/cursor/openclaw) used it to identify + * WHICH CLIENT made the call, not any property of the tool. `caller_name` is + * the field that actually matches that contract; `tool_type` is kept as a + * deprecated input fallback (NOT removed). The server resolves: caller_name + * if supplied -> legacy tool_type if supplied -> a default. + * + * This test drives three scenarios entirely through + * `com.getaxonflow.sdk.AxonFlow#auditToolCall`: + * 1. callerName alone -> policy_details.caller_name = callerName + * 2. legacy toolType alone -> policy_details.caller_name = toolType (fallback) + * 3. both callerName AND toolType -> callerName wins + * + * The SDK's typed `AuditLogEntry` does not surface `policy_details` (it's not + * part of the SDK's public read contract yet), so this test reads the row + * back with a raw HTTP GET against `/api/v1/audit/{id}` through the same + * agent, authenticated with the identical Basic-auth credentials the SDK's + * own transport sends — the same "read back what the SDK can't parse yet" + * pattern used by runtime-e2e/decision_context_transfer_basis for + * `/api/v1/decide`. + * + * Run (against a real agent+orchestrator, Community mode needs no license): + * + * mvn -q -DskipTests package + * mvn -q -DskipTests dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt + * SDK_JAR=$(ls target/axonflow-sdk-*.jar | grep -v sources | grep -v javadoc | head -1) + * AXONFLOW_ENDPOINT=http://localhost:8080 \ + * java -cp "$SDK_JAR:$(cat /tmp/cp.txt)" \ + * runtime-e2e/caller_name_audit/CallerNameAuditTest.java + */ +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.getaxonflow.sdk.AxonFlow; +import com.getaxonflow.sdk.AxonFlowConfig; +import com.getaxonflow.sdk.types.AuditToolCallRequest; +import com.getaxonflow.sdk.types.AuditToolCallResponse; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Base64; +import java.nio.charset.StandardCharsets; + +@SuppressWarnings("deprecation") // exercises the deprecated toolType(...) fallback intentionally +public class CallerNameAuditTest { + + static int assertionsRun = 0; + static boolean failed = false; + + static void fail(String msg) { + System.err.println("FAIL: " + msg); + failed = true; + } + + static void pass(String msg) { + assertionsRun++; + System.out.println("PASS: " + msg); + } + + public static void main(String[] args) throws Exception { + String endpoint = System.getenv().getOrDefault("AXONFLOW_ENDPOINT", "http://localhost:8080"); + String clientId = + System.getenv() + .getOrDefault("AXONFLOW_CLIENT_ID", "javasdk-2912-e2e-" + System.currentTimeMillis()); + String clientSecret = System.getenv().getOrDefault("AXONFLOW_CLIENT_SECRET", ""); + + AxonFlow client = + AxonFlow.create( + AxonFlowConfig.builder() + .endpoint(endpoint) + .clientId(clientId) + .clientSecret(clientSecret) + .build()); + + ObjectMapper mapper = new ObjectMapper(); + HttpClient http = HttpClient.newHttpClient(); + String basicAuth = + "Basic " + + Base64.getEncoder() + .encodeToString((clientId + ":" + clientSecret).getBytes(StandardCharsets.UTF_8)); + + // ------------------------------------------------------------------ + // 1. callerName alone (the new field) -> policy_details.caller_name + // carries it, and the legacy tool_type key is no longer written for + // new rows. + // ------------------------------------------------------------------ + System.out.println("[1/3] auditToolCall with callerName only ..."); + AuditToolCallResponse resp1 = + client.auditToolCall( + AuditToolCallRequest.builder() + .toolName("rte2912-newfield") + .callerName("cursor") + .build()); + JsonNode policyDetails1 = fetchPolicyDetails(http, mapper, endpoint, basicAuth, resp1); + assertCallerName(policyDetails1, "cursor", "callerName alone"); + assertNoLegacyToolTypeKey(policyDetails1, "callerName alone"); + + // ------------------------------------------------------------------ + // 2. legacy toolType alone (no callerName) -> falls back correctly into + // policy_details.caller_name (backward compatible). + // ------------------------------------------------------------------ + System.out.println("[2/3] auditToolCall with legacy toolType only ..."); + AuditToolCallResponse resp2 = + client.auditToolCall( + AuditToolCallRequest.builder().toolName("rte2912-legacy").toolType("codex").build()); + JsonNode policyDetails2 = fetchPolicyDetails(http, mapper, endpoint, basicAuth, resp2); + assertCallerName(policyDetails2, "codex", "legacy toolType alone"); + + // ------------------------------------------------------------------ + // 3. BOTH callerName and legacy toolType supplied -> callerName wins; + // the stale toolType value never leaks into policy_details. + // ------------------------------------------------------------------ + System.out.println("[3/3] auditToolCall with BOTH callerName and legacy toolType ..."); + AuditToolCallResponse resp3 = + client.auditToolCall( + AuditToolCallRequest.builder() + .toolName("rte2912-both") + .toolType("stale_legacy_value") + .callerName("openclaw") + .build()); + JsonNode policyDetails3 = fetchPolicyDetails(http, mapper, endpoint, basicAuth, resp3); + assertCallerName(policyDetails3, "openclaw", "both supplied, callerName wins"); + + int expectedAssertions = 4; + if (assertionsRun != expectedAssertions) { + fail("anti-skip guard tripped: ran " + assertionsRun + " of " + expectedAssertions + " assertions"); + } + if (failed) { + System.out.println("RESULT: FAIL"); + System.exit(1); + } + System.out.println("RESULT: PASS (" + assertionsRun + "/" + expectedAssertions + ")"); + } + + /** + * Reads the persisted row back via a raw HTTP GET to /api/v1/audit/{id} + * through the agent (the SDK does not wrap this endpoint, and its typed + * AuditLogEntry does not surface policy_details), authenticated with the + * same Basic-auth credentials the SDK's own transport used for the write. + * Polls briefly since the audit write is async. + */ + static JsonNode fetchPolicyDetails( + HttpClient http, + ObjectMapper mapper, + String endpoint, + String basicAuth, + AuditToolCallResponse response) + throws Exception { + if (response == null || response.getAuditId() == null || response.getAuditId().isEmpty()) { + fail("auditToolCall did not return an audit_id"); + return mapper.createObjectNode(); + } + String auditId = response.getAuditId(); + System.out.println(" auditToolCall -> audit_id=" + auditId); + + for (int i = 0; i < 10; i++) { + HttpRequest req = + HttpRequest.newBuilder(URI.create(endpoint + "/api/v1/audit/" + auditId)) + .header("Authorization", basicAuth) + .GET() + .build(); + HttpResponse httpResp = http.send(req, HttpResponse.BodyHandlers.ofString()); + if (httpResp.statusCode() == 200) { + JsonNode node = mapper.readTree(httpResp.body()); + JsonNode policyDetails = node.get("policy_details"); + if (policyDetails != null && policyDetails.has("caller_name")) { + System.out.println(" GET /api/v1/audit/" + auditId + " -> policy_details=" + policyDetails); + return policyDetails; + } + } else { + System.out.println( + " GET /api/v1/audit/" + auditId + " -> HTTP " + httpResp.statusCode() + ": " + httpResp.body()); + } + Thread.sleep(1500); + } + fail("GET /api/v1/audit/" + auditId + " never returned policy_details.caller_name"); + return mapper.createObjectNode(); + } + + static void assertCallerName(JsonNode policyDetails, String expected, String label) { + String actual = policyDetails.has("caller_name") ? policyDetails.get("caller_name").asText() : null; + if (expected.equals(actual)) { + pass(label + ": policy_details.caller_name=\"" + actual + "\""); + } else { + fail(label + ": policy_details.caller_name=\"" + actual + "\", want \"" + expected + "\""); + } + } + + static void assertNoLegacyToolTypeKey(JsonNode policyDetails, String label) { + if (!policyDetails.has("tool_type")) { + pass(label + ": policy_details.tool_type key absent (no longer written for new rows, #2912)"); + } else { + fail(label + ": policy_details.tool_type unexpectedly present: " + policyDetails.get("tool_type")); + } + } +} diff --git a/runtime-e2e/caller_name_audit/README.md b/runtime-e2e/caller_name_audit/README.md new file mode 100644 index 0000000..8cbef6c --- /dev/null +++ b/runtime-e2e/caller_name_audit/README.md @@ -0,0 +1,74 @@ +# caller_name_audit (getaxonflow/axonflow-enterprise#2912, epic #2905) + +Real-stack proof that `AuditToolCallRequest.callerName` (wire: `caller_name`) +reaches `policy_details.caller_name` on the persisted audit row, driven +entirely through the SDK's real public `AxonFlow#auditToolCall()` against a +live agent + orchestrator. No mocks. + +## Background + +`audit_tool_call`'s `tool_type` field was misleadingly named — every real +caller (claude_code/codex/cursor/openclaw) used it to identify WHICH CLIENT +made the call, not any property of the tool. `callerName` is the field that +actually matches that contract. `toolType` is kept as a **deprecated input +fallback** — not removed, not renamed. The server resolves: `caller_name` if +supplied, else the legacy `tool_type`, else a default. + +## What this proves + +1. **`callerName` alone** — `policy_details.caller_name` carries it, and the + legacy `tool_type` key is no longer written for new rows. +2. **Legacy `toolType` alone** (no `callerName`) — falls back correctly into + `policy_details.caller_name` (backward compatible). +3. **Both supplied** — `callerName` wins; the stale `toolType` value never + leaks into `policy_details`. + +The SDK's typed `AuditLogEntry` doesn't surface `policy_details` yet, so the +read-back uses a raw HTTP GET to `/api/v1/audit/{id}` through the same agent, +authenticated with the identical Basic-auth credentials the SDK's own +transport sent for the write — the same pattern +`runtime-e2e/decision_context_transfer_basis` uses for `/api/v1/decide`. + +## Prerequisite: platform support is not yet on `main` + +`caller_name` support (axonflow-enterprise#2953) is implemented but, as of +this writing, still an open PR on the `feat/2912-caller-name-tool-type-deprecation` +branch — not yet merged to `axonflow-enterprise` main. Against a stack built +from `axonflow-enterprise` main, this test will FAIL (the polling loop in +`fetchPolicyDetails` times out waiting for `policy_details.caller_name`, +which the server doesn't write yet) — that's not a bug in this test, it +means the platform side isn't deployed on whatever stack you're pointed at. +Point your local `axonflow-enterprise` checkout at that branch (or a later +commit that includes it) before running this test. + +## Run + +Community mode needs no license — any client ID is its own tenant: + +```bash +./mvnw -q -DskipTests package +./mvnw -q -DskipTests dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt +SDK_JAR=$(ls target/axonflow-sdk-*.jar | grep -v sources | grep -v javadoc | head -1) +AXONFLOW_ENDPOINT=http://localhost:8080 \ + java -cp "$SDK_JAR:$(cat /tmp/cp.txt)" \ + runtime-e2e/caller_name_audit/CallerNameAuditTest.java +``` + +Override `AXONFLOW_CLIENT_ID` / `AXONFLOW_CLIENT_SECRET` for enterprise / +community-saas stacks that need real credentials. Exits non-zero (and prints +`FAIL: ...`) if any of the 4 assertions fails, or if the anti-skip guard trips +(fewer assertions ran than expected). + +## Companion coverage + +`src/test/java/com/getaxonflow/sdk/AuditToolCallTest.java` exercises the same +wire contract through WireMock: `callerName` serializes to `caller_name`, +legacy `toolType` still serializes standalone, and both together — the +runtime proof here is the redundant real-stack confirmation that the field +actually lands in `policy_details.caller_name` server-side. + +## Cross-repo parity + +Platform-side runtime proof (agent MCP `audit_tool_call` tool -> orchestrator +`POST /api/v1/audit/tool-call` -> `audit_logs.policy_details`) lives at +`axonflow-enterprise/runtime-e2e/2912_caller_name_deprecation/test.sh`. diff --git a/src/main/java/com/getaxonflow/sdk/types/AuditToolCallRequest.java b/src/main/java/com/getaxonflow/sdk/types/AuditToolCallRequest.java index d802d55..119de46 100644 --- a/src/main/java/com/getaxonflow/sdk/types/AuditToolCallRequest.java +++ b/src/main/java/com/getaxonflow/sdk/types/AuditToolCallRequest.java @@ -52,9 +52,20 @@ public final class AuditToolCallRequest { @JsonProperty("tool_name") private final String toolName; + /** + * @deprecated (getaxonflow/axonflow-enterprise#2912) misnamed for what every real caller + * (claude_code/codex/cursor/openclaw) actually used it for — identifying WHICH CLIENT made + * the call, not any property of the tool. Use {@link #callerName} instead. Kept as a + * deprecated input fallback: the server resolves {@code caller_name} if supplied, else this + * legacy {@code tool_type} field, else a default. + */ + @Deprecated @JsonProperty("tool_type") private final String toolType; + @JsonProperty("caller_name") + private final String callerName; + @JsonProperty("input") private final Map input; @@ -88,6 +99,7 @@ private AuditToolCallRequest(Builder builder) { throw new IllegalArgumentException("toolName cannot be empty"); } this.toolType = builder.toolType; + this.callerName = builder.callerName; this.input = builder.input != null ? Collections.unmodifiableMap(new HashMap<>(builder.input)) : null; this.output = @@ -108,10 +120,20 @@ public String getToolName() { return toolName; } + /** + * @deprecated (getaxonflow/axonflow-enterprise#2912) use {@link #getCallerName()} instead. + */ + @Deprecated public String getToolType() { return toolType; } + /** Returns the name of the client/integration that made this tool call (e.g. "claude_code", + * "codex", "cursor", "openclaw"). Preferred over the deprecated {@link #getToolType()}. */ + public String getCallerName() { + return callerName; + } + public Map getInput() { return input; } @@ -159,6 +181,7 @@ public boolean equals(Object o) { AuditToolCallRequest that = (AuditToolCallRequest) o; return Objects.equals(toolName, that.toolName) && Objects.equals(toolType, that.toolType) + && Objects.equals(callerName, that.callerName) && Objects.equals(input, that.input) && Objects.equals(output, that.output) && Objects.equals(workflowId, that.workflowId) @@ -175,6 +198,7 @@ public int hashCode() { return Objects.hash( toolName, toolType, + callerName, input, output, workflowId, @@ -195,6 +219,9 @@ public String toString() { + ", toolType='" + toolType + '\'' + + ", callerName='" + + callerName + + '\'' + ", workflowId='" + workflowId + '\'' @@ -215,6 +242,7 @@ public String toString() { public static final class Builder { private String toolName; private String toolType; + private String callerName; private Map input; private Map output; private String workflowId; @@ -243,12 +271,27 @@ public Builder toolName(String toolName) { * * @param toolType the tool type (e.g., "function", "mcp", "api") * @return this builder + * @deprecated (getaxonflow/axonflow-enterprise#2912) use {@link #callerName(String)} instead. + * Kept as a deprecated input fallback: the server resolves {@code caller_name} if + * supplied, else this legacy {@code tool_type} field, else a default. */ + @Deprecated public Builder toolType(String toolType) { this.toolType = toolType; return this; } + /** + * Sets the name of the client/integration that made this tool call. + * + * @param callerName the caller name (e.g., "claude_code", "codex", "cursor", "openclaw") + * @return this builder + */ + public Builder callerName(String callerName) { + this.callerName = callerName; + return this; + } + /** * Sets the input parameters passed to the tool. * diff --git a/src/test/java/com/getaxonflow/sdk/AuditToolCallTest.java b/src/test/java/com/getaxonflow/sdk/AuditToolCallTest.java index 6c3f542..e640d48 100644 --- a/src/test/java/com/getaxonflow/sdk/AuditToolCallTest.java +++ b/src/test/java/com/getaxonflow/sdk/AuditToolCallTest.java @@ -103,6 +103,93 @@ void shouldAuditToolCallWithAllFields() { .withRequestBody(matchingJsonPath("$.policies_applied[1]", equalTo("policy_b")))); } + @Test + @DisplayName("should serialize caller_name (getaxonflow/axonflow-enterprise#2912)") + void shouldSerializeCallerName() { + stubFor( + post(urlEqualTo("/api/v1/audit/tool-call")) + .willReturn( + aResponse() + .withStatus(201) + .withHeader("Content-Type", "application/json") + .withBody( + "{\"audit_id\":\"aud_tc_caller\",\"status\":\"recorded\",\"timestamp\":\"2026-07-16T12:00:00Z\"}"))); + + AuditToolCallRequest request = + AuditToolCallRequest.builder().toolName("web_search").callerName("cursor").build(); + + AuditToolCallResponse response = axonflow.auditToolCall(request); + + assertThat(response).isNotNull(); + assertThat(request.getCallerName()).isEqualTo("cursor"); + + verify( + postRequestedFor(urlEqualTo("/api/v1/audit/tool-call")) + .withRequestBody(matchingJsonPath("$.tool_name", equalTo("web_search"))) + .withRequestBody(matchingJsonPath("$.caller_name", equalTo("cursor"))) + .withRequestBody(notMatching(".*\"tool_type\".*"))); + } + + @Test + @DisplayName("should serialize legacy tool_type standalone (deprecated fallback)") + void shouldSerializeLegacyToolTypeStandalone() { + stubFor( + post(urlEqualTo("/api/v1/audit/tool-call")) + .willReturn( + aResponse() + .withStatus(201) + .withHeader("Content-Type", "application/json") + .withBody( + "{\"audit_id\":\"aud_tc_legacy\",\"status\":\"recorded\",\"timestamp\":\"2026-07-16T12:01:00Z\"}"))); + + AuditToolCallRequest request = + AuditToolCallRequest.builder().toolName("web_search").toolType("claude_code").build(); + + AuditToolCallResponse response = axonflow.auditToolCall(request); + + assertThat(response).isNotNull(); + assertThat(request.getToolType()).isEqualTo("claude_code"); + assertThat(request.getCallerName()).isNull(); + + verify( + postRequestedFor(urlEqualTo("/api/v1/audit/tool-call")) + .withRequestBody(matchingJsonPath("$.tool_name", equalTo("web_search"))) + .withRequestBody(matchingJsonPath("$.tool_type", equalTo("claude_code"))) + .withRequestBody(notMatching(".*\"caller_name\".*"))); + } + + @Test + @DisplayName("should serialize both caller_name and legacy tool_type together") + void shouldSerializeBothCallerNameAndToolType() { + stubFor( + post(urlEqualTo("/api/v1/audit/tool-call")) + .willReturn( + aResponse() + .withStatus(201) + .withHeader("Content-Type", "application/json") + .withBody( + "{\"audit_id\":\"aud_tc_both\",\"status\":\"recorded\",\"timestamp\":\"2026-07-16T12:02:00Z\"}"))); + + AuditToolCallRequest request = + AuditToolCallRequest.builder() + .toolName("web_search") + .toolType("function") + .callerName("openclaw") + .build(); + + AuditToolCallResponse response = axonflow.auditToolCall(request); + + assertThat(response).isNotNull(); + assertThat(request.getToolType()).isEqualTo("function"); + assertThat(request.getCallerName()).isEqualTo("openclaw"); + + verify( + postRequestedFor(urlEqualTo("/api/v1/audit/tool-call")) + .withRequestBody(matchingJsonPath("$.tool_name", equalTo("web_search"))) + .withRequestBody(matchingJsonPath("$.tool_type", equalTo("function"))) + .withRequestBody(matchingJsonPath("$.caller_name", equalTo("openclaw")))); + } + @Test @DisplayName("should audit tool call with required fields only") void shouldAuditToolCallWithRequiredFieldsOnly() { diff --git a/tests/fixtures/wire-shape-baseline.json b/tests/fixtures/wire-shape-baseline.json index c55bf83..70e0417 100644 --- a/tests/fixtures/wire-shape-baseline.json +++ b/tests/fixtures/wire-shape-baseline.json @@ -203,6 +203,12 @@ ], "spec_only": [] }, + "AuditToolCallRequest": { + "sdk_only": [ + "caller_name" + ], + "spec_only": [] + }, "Budget": { "sdk_only": [ "enabled" From aa349c0305a13b0b66a1cfa0dd4cbf22e3837402 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Fri, 17 Jul 2026 22:35:13 +0200 Subject: [PATCH 2/2] docs(audit): note platform floor for caller_name attribution A pre-#2953 platform silently drops caller_name, so a caller who sets only caller_name on an older platform gets the default with no in-IDE attribution hint. Add the platform-floor caveat (requires v9.11.0+; set tool_type as a fallback on older platforms) to the caller_name field doc comment. getaxonflow/axonflow-enterprise#2912 Signed-off-by: Saurabh Jain --- .../com/getaxonflow/sdk/types/AuditToolCallRequest.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/getaxonflow/sdk/types/AuditToolCallRequest.java b/src/main/java/com/getaxonflow/sdk/types/AuditToolCallRequest.java index 119de46..36839c2 100644 --- a/src/main/java/com/getaxonflow/sdk/types/AuditToolCallRequest.java +++ b/src/main/java/com/getaxonflow/sdk/types/AuditToolCallRequest.java @@ -128,8 +128,13 @@ public String getToolType() { return toolType; } - /** Returns the name of the client/integration that made this tool call (e.g. "claude_code", - * "codex", "cursor", "openclaw"). Preferred over the deprecated {@link #getToolType()}. */ + /** + * Returns the name of the client/integration that made this tool call (e.g. "claude_code", + * "codex", "cursor", "openclaw"). Preferred over the deprecated {@link #getToolType()}. + * + *

Requires a platform with caller_name support (v9.11.0+); older platforms silently drop + * this field, so also set {@code toolType} if you need attribution there. + */ public String getCallerName() { return callerName; }