Skip to content

Commit 725dc9a

Browse files
feat: version discovery, User-Agent, trace_id, ToolContext, telemetry (#102)
* feat: version discovery, User-Agent fix, capability detection - Add SDK_VERSION constant (3.8.0) in AxonFlowConfig - Fix default User-Agent from axonflow-java-sdk/1.0.0 to axonflow-sdk-java/3.8.0 - Extend HealthStatus with capabilities and sdkCompatibility fields - Add PlatformCapability and SDKCompatibility types - Add hasCapability() method on HealthStatus - Log warning when SDK version is below platform min_sdk_version - Update tests for new HealthStatus constructor * docs: add v3.8.0 changelog entry * fix: use semantic version comparison for mismatch warning * feat: add trace_id and ToolContext to workflow types * fix: address code review findings P1: Add backward-compatible constructor overloads for HealthStatus (4-arg) and workflow types (CreateWorkflowRequest, CreateWorkflowResponse, StepGateRequest, WorkflowStatusResponse, ListWorkflowsOptions) to avoid breaking callers that don't use new traceId/toolContext parameters. P1: Make capabilities list immutable and never-null in HealthStatus constructor. Add null guard to hasCapability() to prevent NPE. P2: Make PlatformCapability and SDKCompatibility final. Add equals/hashCode/toString to both. Update HealthStatus equals/hashCode/toString to include capabilities and sdkCompatibility. P2: Strip pre-release suffixes in compareSemver before parsing to handle versions like "3.8.0-beta.1" correctly. Changelog: Add traceId, ToolContext, and toolContext entries to [3.8.0]. * feat: add anonymous runtime telemetry Fire-and-forget checkpoint ping on client init with SDK version, OS, arch, runtime version, deployment mode, and enabled features. Off by default for community/sandbox, on for production. Opt out via AXONFLOW_TELEMETRY=off or DO_NOT_TRACK=1. * fix: telemetry defaults OFF for self-hosted (no credentials) Production mode without credentials (community/self-hosted) now defaults telemetry to OFF, matching Go/TypeScript SDKs and documented policy. * test: align telemetry tests with canonical 24-test matrix Add unique instance_id, config disable integration, timeout handling, non-200 response, AXONFLOW_TELEMETRY integration, and enterprise mode payload verification tests. * fix(telemetry): fix docs URL to docs.getaxonflow.com * feat(telemetry): default ON for all modes except sandbox Remove credential-based default logic. Telemetry is now ON by default for all modes except sandbox. Opt out via DO_NOT_TRACK=1, AXONFLOW_TELEMETRY=off, or telemetry config flag. * fix(telemetry): trim whitespace on env vars per TELEMETRY_CONTRACT Align with cross-SDK contract: trim DO_NOT_TRACK and AXONFLOW_TELEMETRY before comparison so "1 " and " off" are handled correctly. * fix: set v3.8.0 release date, add telemetry first-run notice
1 parent 641638d commit 725dc9a

13 files changed

Lines changed: 1144 additions & 37 deletions

File tree

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,30 @@ All notable changes to the AxonFlow Java SDK will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [3.8.0] - 2026-03-03
9+
10+
### Added
11+
12+
- `healthCheck()` now returns `capabilities` list and `sdkCompatibility` in `HealthStatus`
13+
- `hasCapability(name)` method on `HealthStatus` to check if platform supports a specific feature
14+
- `SDK_VERSION` constant on `AxonFlowConfig` for programmatic SDK version access
15+
- User-Agent header corrected from `axonflow-java-sdk/1.0.0` to `axonflow-sdk-java/{version}`
16+
- Version mismatch warning logged when SDK version is below platform's `min_sdk_version`
17+
- `PlatformCapability` and `SDKCompatibility` types
18+
- `traceId` field on `CreateWorkflowRequest`, `CreateWorkflowResponse`, `WorkflowStatusResponse`, and `ListWorkflowsOptions` for distributed tracing correlation
19+
- `ToolContext` type for per-tool governance within workflow steps
20+
- `toolContext` field on `StepGateRequest` for tool-level policy enforcement
21+
- `listWorkflows()` now supports `traceId` filter parameter
22+
- Anonymous runtime telemetry for version adoption tracking and feature usage signals
23+
- `TelemetryEnabled` / `telemetry` configuration option to explicitly control telemetry
24+
- `AXONFLOW_TELEMETRY=off` and `DO_NOT_TRACK=1` environment variable opt-out support
25+
26+
### Fixed
27+
28+
- Default User-Agent was hardcoded to `1.0.0` regardless of actual SDK version
29+
30+
---
31+
832
## [3.7.0] - 2026-02-28
933

1034
### Added

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,12 @@ MCPQueryResponse resp = client.queryConnector(query);
576576

577577
For enterprise features, contact [sales@getaxonflow.com](mailto:sales@getaxonflow.com).
578578

579+
## Telemetry
580+
581+
This SDK sends anonymous usage telemetry (SDK version, OS, enabled features) to help improve AxonFlow.
582+
No prompts, payloads, or PII are ever collected. Opt out: `AXONFLOW_TELEMETRY=off` or `DO_NOT_TRACK=1`.
583+
See [Telemetry Documentation](https://docs.getaxonflow.com/docs/telemetry) for full details.
584+
579585
## Contributing
580586

581587
We welcome contributions. Please see our [Contributing Guide](CONTRIBUTING.md) for details.

src/main/java/com/getaxonflow/sdk/AxonFlow.java

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.getaxonflow.sdk;
1717

1818
import com.getaxonflow.sdk.exceptions.*;
19+
import com.getaxonflow.sdk.telemetry.TelemetryReporter;
1920
import com.getaxonflow.sdk.types.*;
2021
import com.getaxonflow.sdk.types.codegovernance.*;
2122
import com.getaxonflow.sdk.types.costcontrols.CostControlTypes.*;
@@ -138,6 +139,17 @@ private AxonFlow(AxonFlowConfig config) {
138139
this.masfeatNamespace = new MASFEATNamespace();
139140

140141
logger.info("AxonFlow client initialized for {}", config.getEndpoint());
142+
143+
// Send telemetry ping (fire-and-forget).
144+
boolean hasCredentials = config.getClientId() != null && !config.getClientId().isEmpty()
145+
&& config.getClientSecret() != null && !config.getClientSecret().isEmpty();
146+
TelemetryReporter.sendPing(
147+
config.getMode() != null ? config.getMode().getValue() : "production",
148+
config.getEndpoint(),
149+
config.getTelemetry(),
150+
config.isDebug(),
151+
hasCredentials
152+
);
141153
}
142154

143155
private static ObjectMapper createObjectMapper() {
@@ -149,6 +161,40 @@ private static ObjectMapper createObjectMapper() {
149161
return mapper;
150162
}
151163

164+
/**
165+
* Compares two semantic version strings numerically (major.minor.patch).
166+
* Returns negative if a < b, zero if equal, positive if a > b.
167+
*/
168+
private static int compareSemver(String a, String b) {
169+
String[] partsA = a.split("\\.");
170+
String[] partsB = b.split("\\.");
171+
int length = Math.max(partsA.length, partsB.length);
172+
for (int i = 0; i < length; i++) {
173+
int numA = 0;
174+
int numB = 0;
175+
if (i < partsA.length) {
176+
try {
177+
String cleanA = partsA[i].contains("-") ? partsA[i].substring(0, partsA[i].indexOf("-")) : partsA[i];
178+
numA = Integer.parseInt(cleanA);
179+
} catch (NumberFormatException ignored) {
180+
// default to 0
181+
}
182+
}
183+
if (i < partsB.length) {
184+
try {
185+
String cleanB = partsB[i].contains("-") ? partsB[i].substring(0, partsB[i].indexOf("-")) : partsB[i];
186+
numB = Integer.parseInt(cleanB);
187+
} catch (NumberFormatException ignored) {
188+
// default to 0
189+
}
190+
}
191+
if (numA != numB) {
192+
return Integer.compare(numA, numB);
193+
}
194+
}
195+
return 0;
196+
}
197+
152198
// ========================================================================
153199
// Factory Methods
154200
// ========================================================================
@@ -206,12 +252,21 @@ public static AxonFlow sandbox(String agentUrl) {
206252
* @throws ConnectionException if the Agent cannot be reached
207253
*/
208254
public HealthStatus healthCheck() {
209-
return retryExecutor.execute(() -> {
255+
HealthStatus status = retryExecutor.execute(() -> {
210256
Request request = buildRequest("GET", "/health", null);
211257
try (Response response = httpClient.newCall(request).execute()) {
212258
return parseResponse(response, HealthStatus.class);
213259
}
214260
}, "healthCheck");
261+
262+
if (status.getSdkCompatibility() != null
263+
&& status.getSdkCompatibility().getMinSdkVersion() != null
264+
&& compareSemver(AxonFlowConfig.SDK_VERSION, status.getSdkCompatibility().getMinSdkVersion()) < 0) {
265+
logger.warn("SDK version {} is below minimum supported version {}. Please upgrade.",
266+
AxonFlowConfig.SDK_VERSION, status.getSdkCompatibility().getMinSdkVersion());
267+
}
268+
269+
return status;
215270
}
216271

217272
/**
@@ -265,7 +320,7 @@ public HealthStatus orchestratorHealthCheck() {
265320
Request httpRequest = buildOrchestratorRequest("GET", "/health", null);
266321
try (Response response = httpClient.newCall(httpRequest).execute()) {
267322
if (!response.isSuccessful()) {
268-
return new HealthStatus("unhealthy", null, null, null);
323+
return new HealthStatus("unhealthy", null, null, null, null, null);
269324
}
270325
return parseResponse(response, HealthStatus.class);
271326
}
@@ -4303,6 +4358,9 @@ public com.getaxonflow.sdk.types.workflow.WorkflowTypes.ListWorkflowsResponse li
43034358
if (options.getOffset() > 0) {
43044359
appendQueryParam(query, "offset", String.valueOf(options.getOffset()));
43054360
}
4361+
if (options.getTraceId() != null) {
4362+
appendQueryParam(query, "trace_id", options.getTraceId());
4363+
}
43064364
}
43074365

43084366
if (query.length() > 0) {

src/main/java/com/getaxonflow/sdk/AxonFlowConfig.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
*/
4343
public final class AxonFlowConfig {
4444

45+
/** SDK version string. */
46+
public static final String SDK_VERSION = "3.8.0";
47+
4548
/** Default timeout for HTTP requests. */
4649
public static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(60);
4750

@@ -58,6 +61,7 @@ public final class AxonFlowConfig {
5861
private final RetryConfig retryConfig;
5962
private final CacheConfig cacheConfig;
6063
private final String userAgent;
64+
private final Boolean telemetry;
6165

6266
private AxonFlowConfig(Builder builder) {
6367
this.endpoint = normalizeUrl(builder.endpoint != null ? builder.endpoint : DEFAULT_ENDPOINT);
@@ -69,7 +73,8 @@ private AxonFlowConfig(Builder builder) {
6973
this.insecureSkipVerify = builder.insecureSkipVerify;
7074
this.retryConfig = builder.retryConfig != null ? builder.retryConfig : RetryConfig.defaults();
7175
this.cacheConfig = builder.cacheConfig != null ? builder.cacheConfig : CacheConfig.defaults();
72-
this.userAgent = builder.userAgent != null ? builder.userAgent : "axonflow-java-sdk/1.0.0";
76+
this.userAgent = builder.userAgent != null ? builder.userAgent : "axonflow-sdk-java/" + SDK_VERSION;
77+
this.telemetry = builder.telemetry;
7378

7479
validate();
7580
}
@@ -208,6 +213,18 @@ public String getUserAgent() {
208213
return userAgent;
209214
}
210215

216+
/**
217+
* Returns the telemetry config override.
218+
*
219+
* <p>{@code null} means use the default behavior (ON for production, OFF for sandbox).
220+
* {@code Boolean.TRUE} forces telemetry on, {@code Boolean.FALSE} forces it off.
221+
*
222+
* @return the telemetry override, or null for default behavior
223+
*/
224+
public Boolean getTelemetry() {
225+
return telemetry;
226+
}
227+
211228
public static Builder builder() {
212229
return new Builder();
213230
}
@@ -254,6 +271,7 @@ public static final class Builder {
254271
private RetryConfig retryConfig;
255272
private CacheConfig cacheConfig;
256273
private String userAgent;
274+
private Boolean telemetry;
257275

258276
private Builder() {}
259277

@@ -386,6 +404,23 @@ public Builder userAgent(String userAgent) {
386404
return this;
387405
}
388406

407+
/**
408+
* Sets the telemetry override.
409+
*
410+
* <p>{@code null} (default) uses the mode-based default: ON for production, OFF for sandbox.
411+
* {@code Boolean.TRUE} forces telemetry on, {@code Boolean.FALSE} forces it off.
412+
*
413+
* <p>Telemetry can also be disabled globally via environment variables:
414+
* {@code DO_NOT_TRACK=1} or {@code AXONFLOW_TELEMETRY=off}.
415+
*
416+
* @param telemetry true to enable, false to disable, null for default behavior
417+
* @return this builder
418+
*/
419+
public Builder telemetry(Boolean telemetry) {
420+
this.telemetry = telemetry;
421+
return this;
422+
}
423+
389424
/**
390425
* Builds the configuration.
391426
*

0 commit comments

Comments
 (0)