Skip to content

Commit 127a781

Browse files
feat(client): add demo mode via AXONFLOW_DEMO env var (#120)
* feat(client): add AXONFLOW_DEMO env var for demo mode * feat(client): append random suffix to clientId in demo mode * feat(community-saas): try.getaxonflow.com integration - Rename AXONFLOW_DEMO → AXONFLOW_TRY - URL: demo.getaxonflow.com → try.getaxonflow.com - Remove client-side random suffix (server generates UUID tenant_id) - Add AxonFlowTry.register() helper - Checkpoint telemetry reports endpoint_type: "community-saas" in try mode * chore: set release date 2026-04-09 * fix(changelog): remove entries not in this release --------- Co-authored-by: Saurabh Jain <saurabhjain1592@gmail.com>
1 parent 5288bd1 commit 127a781

4 files changed

Lines changed: 124 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,13 @@ 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-
## [5.3.0] - Release Pending (2026-04-09)
8+
## [5.3.0] - 2026-04-09
99

10-
### Fixed
10+
### Added
1111

12-
- **IPv6 endpoint classification.** `classifyEndpoint` now handles IPv6 private ranges and expanded loopback forms that previously fell through to `REMOTE`, matching the Python and Go SDK implementations:
13-
- IPv6 ULA (`fc00::/7`, RFC 4193) → `private_network`
14-
- IPv6 link-local (`fe80::/10`) → `private_network`
15-
- Expanded IPv6 loopback (`0:0:0:0:0:0:0:1`, zero-padded forms) → `localhost`
16-
- IPv6 unspecified (`::`) → `localhost` (symmetric with `0.0.0.0`)
17-
- Public IPv6 addresses (`2001::/3` space) → `remote`
18-
- A new `expandIPv6(addr)` helper expands `::` compression into a full 8-hextet form for prefix comparison. Not a general-purpose parser — assumes input came from `URI.getHost()` after brackets are stripped.
12+
- `AXONFLOW_TRY=1` environment variable to connect to `try.getaxonflow.com` shared evaluation server
13+
- `AxonFlowTry.register()` helper for self-registering a tenant
14+
- Checkpoint telemetry reports `endpoint_type: "community-saas"` when try mode is active
1915

2016
---
2117

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ private static String detectSdkVersion() {
7979
/** Default endpoint URL. */
8080
public static final String DEFAULT_ENDPOINT = "http://localhost:8080";
8181

82+
/** Try mode endpoint URL. */
83+
public static final String TRY_ENDPOINT = "https://try.getaxonflow.com";
84+
8285
private final String endpoint;
8386
private final String clientId;
8487
private final String clientSecret;
@@ -90,9 +93,14 @@ private static String detectSdkVersion() {
9093
private final CacheConfig cacheConfig;
9194
private final String userAgent;
9295
private final Boolean telemetry;
96+
private final boolean tryMode;
9397

9498
private AxonFlowConfig(Builder builder) {
95-
this.endpoint = normalizeUrl(builder.endpoint != null ? builder.endpoint : DEFAULT_ENDPOINT);
99+
this.tryMode = "1".equals(System.getenv("AXONFLOW_TRY"));
100+
this.endpoint =
101+
this.tryMode
102+
? normalizeUrl(TRY_ENDPOINT)
103+
: normalizeUrl(builder.endpoint != null ? builder.endpoint : DEFAULT_ENDPOINT);
96104
this.clientId = builder.clientId;
97105
this.clientSecret = builder.clientSecret;
98106
this.mode = builder.mode != null ? builder.mode : Mode.PRODUCTION;
@@ -112,6 +120,10 @@ private void validate() {
112120
if (endpoint == null || endpoint.isEmpty()) {
113121
throw new ConfigurationException("endpoint is required", "endpoint");
114122
}
123+
if (tryMode && (clientId == null || clientId.isEmpty())) {
124+
throw new ConfigurationException(
125+
"clientId is required in try mode (AXONFLOW_TRY=1)", "clientId");
126+
}
115127
// Credentials are optional for community/self-hosted deployments
116128
// Enterprise features require credentials (validated at method call time)
117129
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2025 AxonFlow
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.getaxonflow.sdk;
17+
18+
import java.io.IOException;
19+
import java.net.URI;
20+
import java.net.http.HttpClient;
21+
import java.net.http.HttpRequest;
22+
import java.net.http.HttpResponse;
23+
import java.time.Duration;
24+
25+
import com.fasterxml.jackson.databind.ObjectMapper;
26+
27+
/**
28+
* Registration helper for try.getaxonflow.com shared evaluation server.
29+
*/
30+
public class AxonFlowTry {
31+
32+
public static final String TRY_ENDPOINT = "https://try.getaxonflow.com";
33+
34+
private static final ObjectMapper MAPPER = new ObjectMapper();
35+
private static final HttpClient CLIENT = HttpClient.newBuilder()
36+
.connectTimeout(Duration.ofSeconds(10))
37+
.build();
38+
39+
/**
40+
* Register for a free evaluation tenant. Store the secret securely — it is shown only once.
41+
*/
42+
public static TryRegistration register() throws IOException, InterruptedException {
43+
return register("", TRY_ENDPOINT);
44+
}
45+
46+
/**
47+
* Register with an optional label.
48+
*/
49+
public static TryRegistration register(String label) throws IOException, InterruptedException {
50+
return register(label, TRY_ENDPOINT);
51+
}
52+
53+
/**
54+
* Register with a custom endpoint (for local testing).
55+
*/
56+
public static TryRegistration register(String label, String endpoint) throws IOException, InterruptedException {
57+
String body = label != null && !label.isEmpty()
58+
? String.format("{\"label\":\"%s\"}", label)
59+
: "{}";
60+
61+
HttpRequest request = HttpRequest.newBuilder()
62+
.uri(URI.create(endpoint + "/api/v1/register"))
63+
.header("Content-Type", "application/json")
64+
.POST(HttpRequest.BodyPublishers.ofString(body))
65+
.timeout(Duration.ofSeconds(10))
66+
.build();
67+
68+
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
69+
70+
if (response.statusCode() != 201) {
71+
throw new IOException("Registration failed (" + response.statusCode() + "): " + response.body());
72+
}
73+
74+
return MAPPER.readValue(response.body(), TryRegistration.class);
75+
}
76+
77+
/**
78+
* Registration response from try.getaxonflow.com.
79+
*/
80+
public static class TryRegistration {
81+
private String tenant_id;
82+
private String secret;
83+
private String secret_prefix;
84+
private String expires_at;
85+
private String endpoint;
86+
private String note;
87+
88+
// Getters
89+
public String getTenantId() { return tenant_id; }
90+
public String getSecret() { return secret; }
91+
public String getSecretPrefix() { return secret_prefix; }
92+
public String getExpiresAt() { return expires_at; }
93+
public String getEndpoint() { return endpoint; }
94+
public String getNote() { return note; }
95+
96+
// Setters for Jackson
97+
public void setTenant_id(String v) { this.tenant_id = v; }
98+
public void setSecret(String v) { this.secret = v; }
99+
public void setSecret_prefix(String v) { this.secret_prefix = v; }
100+
public void setExpires_at(String v) { this.expires_at = v; }
101+
public void setEndpoint(String v) { this.endpoint = v; }
102+
public void setNote(String v) { this.note = v; }
103+
}
104+
}

src/main/java/com/getaxonflow/sdk/telemetry/TelemetryReporter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ public static final class EndpointType {
231231
public static final String LOCALHOST = "localhost";
232232
public static final String PRIVATE_NETWORK = "private_network";
233233
public static final String REMOTE = "remote";
234+
public static final String COMMUNITY_SAAS = "community-saas";
234235
public static final String UNKNOWN = "unknown";
235236

236237
private EndpointType() {}
@@ -248,6 +249,7 @@ private EndpointType() {}
248249
* <p>The raw URL is never sent — only the classification.
249250
*/
250251
public static String classifyEndpoint(String url) {
252+
if ("1".equals(System.getenv("AXONFLOW_TRY"))) return EndpointType.COMMUNITY_SAAS;
251253
if (url == null || url.isEmpty()) {
252254
return EndpointType.UNKNOWN;
253255
}

0 commit comments

Comments
 (0)