From 684ee0b22df7929793d3e25ae1f7449c2e6fc741 Mon Sep 17 00:00:00 2001 From: Gregory Zak Date: Sat, 4 Apr 2026 15:05:06 -0700 Subject: [PATCH 1/9] feat(client): add AXONFLOW_DEMO env var for demo mode --- axonflow/client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/axonflow/client.py b/axonflow/client.py index 3033513..b75d8c8 100644 --- a/axonflow/client.py +++ b/axonflow/client.py @@ -364,6 +364,12 @@ def __init__( msg = "endpoint is required (or set AXONFLOW_AGENT_URL environment variable)" raise TypeError(msg) + if os.environ.get("AXONFLOW_DEMO") == "1": + resolved_endpoint = "https://demo.getaxonflow.com" + if not client_id: + msg = "client_id is required in demo mode (AXONFLOW_DEMO=1)" + raise TypeError(msg) + if isinstance(mode, str): mode = Mode(mode) From 7b1a33627e9b697e2ad24b2909f929fad2bb04b8 Mon Sep 17 00:00:00 2001 From: Gregory Zak Date: Sat, 4 Apr 2026 15:40:32 -0700 Subject: [PATCH 2/9] feat(client): append random suffix to client_id in demo mode --- axonflow/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/axonflow/client.py b/axonflow/client.py index b75d8c8..75844fb 100644 --- a/axonflow/client.py +++ b/axonflow/client.py @@ -32,6 +32,7 @@ import logging import os import re +import secrets from collections.abc import AsyncIterator, Coroutine, Iterator from dataclasses import dataclass from datetime import datetime @@ -369,6 +370,7 @@ def __init__( if not client_id: msg = "client_id is required in demo mode (AXONFLOW_DEMO=1)" raise TypeError(msg) + client_id = f"{client_id}-{secrets.token_hex(3)}" if isinstance(mode, str): mode = Mode(mode) From ab3c1a6310ac634dcb4aaa5bbf1e68bf90b83f64 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Thu, 9 Apr 2026 15:10:22 +0200 Subject: [PATCH 3/9] feat(community-saas): try.getaxonflow.com integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename AXONFLOW_DEMO → AXONFLOW_TRY - URL: demo.getaxonflow.com → try.getaxonflow.com - Remove client-side random suffix (server generates UUID tenant_id) - Add register_try() helper in axonflow.community - Checkpoint telemetry reports endpoint_type: "community-saas" in try mode --- CHANGELOG.md | 12 +++++++++--- axonflow/client.py | 8 +++----- axonflow/community.py | 25 +++++++++++++++++++++++++ axonflow/telemetry.py | 3 +++ 4 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 axonflow/community.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d2e105..2ef6c7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,12 @@ All notable changes to the AxonFlow Python 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). -## [6.3.0] - Release Pending (2026-04-09) +## [6.3.0] - Unreleased ### Added - +- `AXONFLOW_TRY=1` environment variable to connect to `try.getaxonflow.com` shared evaluation server +- `register_try()` helper in `axonflow.community` for self-registering a tenant +- Checkpoint telemetry reports `endpoint_type: "community-saas"` when try mode is active - **Explicit IPv6 + RFC1918 boundary test coverage.** The v6.2.0 test suite relied on Python stdlib `ipaddress.is_private` for correctness and didn't assert the behavior explicitly. New cases cover: - `172.15.0.1` and `172.32.0.1` must be `remote` (RFC1918 boundary) - `172.16.0.0` and `172.31.255.255` must be `private_network` @@ -17,7 +19,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Public IPv6 (`2001:4860:4860::8888`, `2606:4700:4700::1111`) → `remote` - IPv6 loopback `::1` → `localhost` -No runtime behavior change — Python's stdlib classifier was already correct for all these cases. The added tests are cross-SDK parity with TS/Java/Go. +No runtime behavior change for the IPv6 cases — Python's stdlib classifier was already correct for all these cases. The added tests are cross-SDK parity with TS/Java/Go. + +### Changed +- Renamed `AXONFLOW_DEMO` to `AXONFLOW_TRY` (demo mode → try mode) +- Removed client-side random suffix from client_id (server generates UUID tenant_id) --- diff --git a/axonflow/client.py b/axonflow/client.py index 75844fb..36c37be 100644 --- a/axonflow/client.py +++ b/axonflow/client.py @@ -32,7 +32,6 @@ import logging import os import re -import secrets from collections.abc import AsyncIterator, Coroutine, Iterator from dataclasses import dataclass from datetime import datetime @@ -365,12 +364,11 @@ def __init__( msg = "endpoint is required (or set AXONFLOW_AGENT_URL environment variable)" raise TypeError(msg) - if os.environ.get("AXONFLOW_DEMO") == "1": - resolved_endpoint = "https://demo.getaxonflow.com" + if os.environ.get("AXONFLOW_TRY") == "1": + resolved_endpoint = "https://try.getaxonflow.com" if not client_id: - msg = "client_id is required in demo mode (AXONFLOW_DEMO=1)" + msg = "client_id is required in try mode (AXONFLOW_TRY=1)" raise TypeError(msg) - client_id = f"{client_id}-{secrets.token_hex(3)}" if isinstance(mode, str): mode = Mode(mode) diff --git a/axonflow/community.py b/axonflow/community.py new file mode 100644 index 0000000..6c51df6 --- /dev/null +++ b/axonflow/community.py @@ -0,0 +1,25 @@ +"""Community SaaS registration helper for try.getaxonflow.com.""" + +import httpx + + +TRY_ENDPOINT = "https://try.getaxonflow.com" + + +def register_try(label: str = "", endpoint: str = TRY_ENDPOINT) -> dict: + """Register for a free evaluation tenant on try.getaxonflow.com. + + Returns dict with keys: tenant_id, secret, secret_prefix, expires_at, endpoint, note. + Store the secret securely — it is shown only once. + + Args: + label: Optional human-readable name for the registration. + endpoint: Override the default endpoint (for local testing). + """ + response = httpx.post( + f"{endpoint}/api/v1/register", + json={"label": label} if label else {}, + timeout=10.0, + ) + response.raise_for_status() + return response.json() diff --git a/axonflow/telemetry.py b/axonflow/telemetry.py index 0be8137..552c19b 100644 --- a/axonflow/telemetry.py +++ b/axonflow/telemetry.py @@ -87,6 +87,7 @@ def _classify_endpoint(url: str | None) -> str: # noqa: PLR0911 """Classify the configured AxonFlow endpoint for analytics (#1525). Returns one of: + ``"community-saas"`` — try.getaxonflow.com shared evaluation server ``"localhost"`` — localhost, 127.0.0.1, ::1, 0.0.0.0, ``*.localhost`` ``"private_network"`` — RFC1918 ranges, link-local, ``*.local``, ``*.internal``, ``*.lan``, ``*.intranet`` @@ -95,6 +96,8 @@ def _classify_endpoint(url: str | None) -> str: # noqa: PLR0911 The raw URL is never sent — only the classification. See issue #1525. """ + if os.environ.get("AXONFLOW_TRY") == "1": + return "community-saas" if not url: return "unknown" try: From 638d3a3ebd4d0c5de617f89729fae175dbb331c5 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Thu, 9 Apr 2026 17:17:02 +0200 Subject: [PATCH 4/9] chore: set release date 2026-04-09 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ef6c7c..c607052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to the AxonFlow Python 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). -## [6.3.0] - Unreleased +## [6.3.0] - 2026-04-09 ### Added - `AXONFLOW_TRY=1` environment variable to connect to `try.getaxonflow.com` shared evaluation server From ab2d74e24918af3e350a712fab0aa3c5b438ef4d Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Thu, 9 Apr 2026 17:33:08 +0200 Subject: [PATCH 5/9] fix(lint): sort imports in community module --- axonflow/community.py | 1 - 1 file changed, 1 deletion(-) diff --git a/axonflow/community.py b/axonflow/community.py index 6c51df6..d757d04 100644 --- a/axonflow/community.py +++ b/axonflow/community.py @@ -2,7 +2,6 @@ import httpx - TRY_ENDPOINT = "https://try.getaxonflow.com" From 7ec25f35f722e3ec38358d9672b61b425bd80e90 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Thu, 9 Apr 2026 17:36:31 +0200 Subject: [PATCH 6/9] fix(lint): add type annotations to community module --- axonflow/community.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/axonflow/community.py b/axonflow/community.py index d757d04..71ed53a 100644 --- a/axonflow/community.py +++ b/axonflow/community.py @@ -1,11 +1,15 @@ """Community SaaS registration helper for try.getaxonflow.com.""" +from __future__ import annotations + +from typing import Any + import httpx TRY_ENDPOINT = "https://try.getaxonflow.com" -def register_try(label: str = "", endpoint: str = TRY_ENDPOINT) -> dict: +def register_try(label: str = "", endpoint: str = TRY_ENDPOINT) -> dict[str, Any]: """Register for a free evaluation tenant on try.getaxonflow.com. Returns dict with keys: tenant_id, secret, secret_prefix, expires_at, endpoint, note. @@ -21,4 +25,5 @@ def register_try(label: str = "", endpoint: str = TRY_ENDPOINT) -> dict: timeout=10.0, ) response.raise_for_status() - return response.json() + data: dict[str, Any] = response.json() + return data From 53cd41c04dcc06f6e77765a4e0918b99ee9aa57c Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Thu, 9 Apr 2026 17:43:51 +0200 Subject: [PATCH 7/9] fix(changelog): remove entries not in this release --- CHANGELOG.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c607052..64cb14f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,19 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `AXONFLOW_TRY=1` environment variable to connect to `try.getaxonflow.com` shared evaluation server - `register_try()` helper in `axonflow.community` for self-registering a tenant - Checkpoint telemetry reports `endpoint_type: "community-saas"` when try mode is active -- **Explicit IPv6 + RFC1918 boundary test coverage.** The v6.2.0 test suite relied on Python stdlib `ipaddress.is_private` for correctness and didn't assert the behavior explicitly. New cases cover: - - `172.15.0.1` and `172.32.0.1` must be `remote` (RFC1918 boundary) - - `172.16.0.0` and `172.31.255.255` must be `private_network` - - IPv6 ULA (`fd00::1`, `fd12:3456:789a::1`, `fc00::1`) → `private_network` - - IPv6 link-local (`fe80::1`) → `private_network` - - Public IPv6 (`2001:4860:4860::8888`, `2606:4700:4700::1111`) → `remote` - - IPv6 loopback `::1` → `localhost` - -No runtime behavior change for the IPv6 cases — Python's stdlib classifier was already correct for all these cases. The added tests are cross-SDK parity with TS/Java/Go. - -### Changed -- Renamed `AXONFLOW_DEMO` to `AXONFLOW_TRY` (demo mode → try mode) -- Removed client-side random suffix from client_id (server generates UUID tenant_id) --- From 29d0527c05a799ce442f75b02230004d60644010 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Fri, 10 Apr 2026 00:20:31 +0200 Subject: [PATCH 8/9] fix: check AXONFLOW_TRY before endpoint validation (was raising TypeError) --- axonflow/client.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/axonflow/client.py b/axonflow/client.py index 36c37be..f753ee9 100644 --- a/axonflow/client.py +++ b/axonflow/client.py @@ -358,17 +358,18 @@ def __init__( As of v1.0.0, all routes go through a single endpoint (Single Entry Point Architecture). """ - # Support AXONFLOW_AGENT_URL env var for backwards compatibility - resolved_endpoint = endpoint or os.environ.get("AXONFLOW_AGENT_URL") - if not resolved_endpoint: - msg = "endpoint is required (or set AXONFLOW_AGENT_URL environment variable)" - raise TypeError(msg) - + # Try mode: auto-connect to try.getaxonflow.com (must be checked before endpoint validation) if os.environ.get("AXONFLOW_TRY") == "1": resolved_endpoint = "https://try.getaxonflow.com" if not client_id: msg = "client_id is required in try mode (AXONFLOW_TRY=1)" raise TypeError(msg) + else: + # Support AXONFLOW_AGENT_URL env var for backwards compatibility + resolved_endpoint = endpoint or os.environ.get("AXONFLOW_AGENT_URL") + if not resolved_endpoint: + msg = "endpoint is required (or set AXONFLOW_AGENT_URL environment variable)" + raise TypeError(msg) if isinstance(mode, str): mode = Mode(mode) From 833a0323eca35929312b5843fab095863f3a9694 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Fri, 10 Apr 2026 01:05:17 +0200 Subject: [PATCH 9/9] =?UTF-8?q?fix(lint):=20resolve=20mypy=20type=20error?= =?UTF-8?q?=20on=20resolved=5Fendpoint=20(str=20|=20None=20=E2=86=92=20str?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- axonflow/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axonflow/client.py b/axonflow/client.py index f753ee9..d2ec080 100644 --- a/axonflow/client.py +++ b/axonflow/client.py @@ -366,7 +366,7 @@ def __init__( raise TypeError(msg) else: # Support AXONFLOW_AGENT_URL env var for backwards compatibility - resolved_endpoint = endpoint or os.environ.get("AXONFLOW_AGENT_URL") + resolved_endpoint = endpoint or os.environ.get("AXONFLOW_AGENT_URL") or "" if not resolved_endpoint: msg = "endpoint is required (or set AXONFLOW_AGENT_URL environment variable)" raise TypeError(msg)