Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@ All notable changes to the AxonFlow Java SDK will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [5.3.0] - Release Pending (2026-04-09)
## [5.3.0] - 2026-04-09

### Fixed
### Added

- **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:
- IPv6 ULA (`fc00::/7`, RFC 4193) → `private_network`
- IPv6 link-local (`fe80::/10`) → `private_network`
- Expanded IPv6 loopback (`0:0:0:0:0:0:0:1`, zero-padded forms) → `localhost`
- IPv6 unspecified (`::`) → `localhost` (symmetric with `0.0.0.0`)
- Public IPv6 addresses (`2001::/3` space) → `remote`
- 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.
- `AXONFLOW_TRY=1` environment variable to connect to `try.getaxonflow.com` shared evaluation server
- `AxonFlowTry.register()` helper for self-registering a tenant
- Checkpoint telemetry reports `endpoint_type: "community-saas"` when try mode is active

---

Expand Down
14 changes: 13 additions & 1 deletion src/main/java/com/getaxonflow/sdk/AxonFlowConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ private static String detectSdkVersion() {
/** Default endpoint URL. */
public static final String DEFAULT_ENDPOINT = "http://localhost:8080";

/** Try mode endpoint URL. */
public static final String TRY_ENDPOINT = "https://try.getaxonflow.com";

private final String endpoint;
private final String clientId;
private final String clientSecret;
Expand All @@ -90,9 +93,14 @@ private static String detectSdkVersion() {
private final CacheConfig cacheConfig;
private final String userAgent;
private final Boolean telemetry;
private final boolean tryMode;

private AxonFlowConfig(Builder builder) {
this.endpoint = normalizeUrl(builder.endpoint != null ? builder.endpoint : DEFAULT_ENDPOINT);
this.tryMode = "1".equals(System.getenv("AXONFLOW_TRY"));
this.endpoint =
this.tryMode
? normalizeUrl(TRY_ENDPOINT)
: normalizeUrl(builder.endpoint != null ? builder.endpoint : DEFAULT_ENDPOINT);
this.clientId = builder.clientId;
this.clientSecret = builder.clientSecret;
this.mode = builder.mode != null ? builder.mode : Mode.PRODUCTION;
Expand All @@ -112,6 +120,10 @@ private void validate() {
if (endpoint == null || endpoint.isEmpty()) {
throw new ConfigurationException("endpoint is required", "endpoint");
}
if (tryMode && (clientId == null || clientId.isEmpty())) {
throw new ConfigurationException(
"clientId is required in try mode (AXONFLOW_TRY=1)", "clientId");
}
// Credentials are optional for community/self-hosted deployments
// Enterprise features require credentials (validated at method call time)
}
Expand Down
104 changes: 104 additions & 0 deletions src/main/java/com/getaxonflow/sdk/AxonFlowTry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2025 AxonFlow
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.getaxonflow.sdk;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
* Registration helper for try.getaxonflow.com shared evaluation server.
*/
public class AxonFlowTry {

public static final String TRY_ENDPOINT = "https://try.getaxonflow.com";

private static final ObjectMapper MAPPER = new ObjectMapper();
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();

/**
* Register for a free evaluation tenant. Store the secret securely — it is shown only once.
*/
public static TryRegistration register() throws IOException, InterruptedException {
return register("", TRY_ENDPOINT);
}

/**
* Register with an optional label.
*/
public static TryRegistration register(String label) throws IOException, InterruptedException {
return register(label, TRY_ENDPOINT);
}

/**
* Register with a custom endpoint (for local testing).
*/
public static TryRegistration register(String label, String endpoint) throws IOException, InterruptedException {
String body = label != null && !label.isEmpty()
? String.format("{\"label\":\"%s\"}", label)
: "{}";

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint + "/api/v1/register"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.timeout(Duration.ofSeconds(10))
.build();

HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() != 201) {
throw new IOException("Registration failed (" + response.statusCode() + "): " + response.body());
}

return MAPPER.readValue(response.body(), TryRegistration.class);
}

/**
* Registration response from try.getaxonflow.com.
*/
public static class TryRegistration {
private String tenant_id;
private String secret;
private String secret_prefix;
private String expires_at;
private String endpoint;
private String note;

// Getters
public String getTenantId() { return tenant_id; }
public String getSecret() { return secret; }
public String getSecretPrefix() { return secret_prefix; }
public String getExpiresAt() { return expires_at; }
public String getEndpoint() { return endpoint; }
public String getNote() { return note; }

// Setters for Jackson
public void setTenant_id(String v) { this.tenant_id = v; }
public void setSecret(String v) { this.secret = v; }
public void setSecret_prefix(String v) { this.secret_prefix = v; }
public void setExpires_at(String v) { this.expires_at = v; }
public void setEndpoint(String v) { this.endpoint = v; }
public void setNote(String v) { this.note = v; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ public static final class EndpointType {
public static final String LOCALHOST = "localhost";
public static final String PRIVATE_NETWORK = "private_network";
public static final String REMOTE = "remote";
public static final String COMMUNITY_SAAS = "community-saas";
public static final String UNKNOWN = "unknown";

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