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
98 changes: 86 additions & 12 deletions src/main/java/com/getaxonflow/sdk/AxonFlow.java
Original file line number Diff line number Diff line change
Expand Up @@ -1744,11 +1744,12 @@ public CompletableFuture<RollbackPlanResponse> rollbackPlanAsync(
// ========================================================================

/**
* Lists configured LLM providers and their per-provider health snapshot.
* Lists configured LLM providers from a SINGLE page of results.
*
* <p>Calls {@code GET /api/v1/llm-providers}. Mirrors the Python SDK's {@code
* list_providers()}, the Go SDK's {@code ListProviders()}, and the TypeScript
* SDK's {@code listProviders()}.
* SDK's {@code listProviders()}. For multi-page traversal use {@link
* #listAllLLMProviders}; for pagination metadata use {@link #listLLMProvidersPaged}.
*
* @return list of configured providers
*/
Expand All @@ -1765,26 +1766,99 @@ public List<LLMProvider> listLLMProviders() {
* @return list of matching providers
*/
public List<LLMProvider> listLLMProviders(String type, Boolean enabled) {
return listLLMProviders(type, enabled, null, null);
}

/**
* Lists configured LLM providers with full filter + pagination control.
*
* @param type filter by provider type (null for no filter)
* @param enabled filter by the enabled boolean (null for no filter)
* @param page 1-indexed page number (null or non-positive for server default)
* @param pageSize items per page, server cap 100 (null or non-positive for default)
* @return providers from the requested page
*/
public List<LLMProvider> listLLMProviders(
String type, Boolean enabled, Integer page, Integer pageSize) {
return listLLMProvidersPaged(type, enabled, page, pageSize).getProviders();
}

/**
* Lists one page of providers along with pagination metadata so callers can
* paginate manually.
*/
public LLMProviderListResponse listLLMProvidersPaged(
String type, Boolean enabled, Integer page, Integer pageSize) {
return retryExecutor.execute(
() -> {
StringBuilder path = new StringBuilder("/api/v1/llm-providers");
boolean hasQuery = false;
HttpUrl base = HttpUrl.parse(config.getEndpoint() + "/api/v1/llm-providers");
if (base == null) {
throw new AxonFlowException("Invalid endpoint URL: " + config.getEndpoint());
}
HttpUrl.Builder b = base.newBuilder();
if (type != null && !type.isEmpty()) {
path.append('?').append("type=").append(type);
hasQuery = true;
b.addQueryParameter("type", type);
}
if (enabled != null) {
path.append(hasQuery ? '&' : '?').append("enabled=").append(enabled);
b.addQueryParameter("enabled", enabled.toString());
}
Request httpRequest = buildOrchestratorRequest("GET", path.toString(), null);
if (page != null && page > 0) {
b.addQueryParameter("page", page.toString());
}
if (pageSize != null && pageSize > 0) {
b.addQueryParameter("page_size", pageSize.toString());
}
HttpUrl url = b.build();

Request.Builder reqBuilder = new Request.Builder().url(url).get();
addAuthHeaders(reqBuilder);
Request httpRequest = reqBuilder.build();
try (Response response = httpClient.newCall(httpRequest).execute()) {
JsonNode node = parseResponseNode(response);
JsonNode providers = node.has("providers") ? node.get("providers") : node;
return objectMapper.convertValue(
providers, new TypeReference<List<LLMProvider>>() {});
// The platform always wraps in {providers, pagination}, but tolerate
// bare arrays from older builds via a synthetic single-page meta.
if (node.isArray()) {
List<LLMProvider> providers =
objectMapper.convertValue(node, new TypeReference<List<LLMProvider>>() {});
PaginationMeta synthetic =
new PaginationMeta(1, providers.size(), providers.size(), 1);
return new LLMProviderListResponse(providers, synthetic);
}
return objectMapper.convertValue(node, LLMProviderListResponse.class);
}
},
"listLLMProviders");
"listLLMProvidersPaged");
}

/**
* Walks every page of providers and returns the combined list.
*
* <p>Defaults to {@code pageSize=100} (the server-side max) to minimise round trips.
*
* @param type filter by provider type (null for no filter)
* @param enabled filter by the enabled boolean (null for no filter)
* @return all providers across every page
*/
public List<LLMProvider> listAllLLMProviders(String type, Boolean enabled) {
return listAllLLMProviders(type, enabled, 100);
}

/** As {@link #listAllLLMProviders(String, Boolean)} but with explicit page size. */
public List<LLMProvider> listAllLLMProviders(String type, Boolean enabled, int pageSize) {
java.util.ArrayList<LLMProvider> all = new java.util.ArrayList<>();
int page = 1;
while (true) {
LLMProviderListResponse resp = listLLMProvidersPaged(type, enabled, page, pageSize);
all.addAll(resp.getProviders());
PaginationMeta meta = resp.getPagination();
if (meta == null
|| meta.getTotalPages() <= page
|| resp.getProviders().isEmpty()) {
break;
}
page += 1;
}
return all;
}

/**
Expand Down
100 changes: 84 additions & 16 deletions src/main/java/com/getaxonflow/sdk/types/LLMProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,64 @@

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;

/**
* A registered LLM provider returned by {@code GET /api/v1/llm-providers}.
*
* <p>Mirrors {@code LLMProvider} in the Python and Go SDKs and the {@code LLMProvider}
* TypeScript interface.
* <p>Mirrors the platform's {@code LLMProviderResource} schema. Optional fields are
* populated when the provider config has them set; {@code settings} is a free-form
* provider-specific map.
*
* <p>{@code enabled} and {@code hasApiKey} are typed as {@link Boolean} (boxed) so a
* missing or {@code null} value in the JSON response is distinguishable from the
* explicit boolean values — primitive {@code boolean} would silently default to
* {@code false} and mask whether the field was actually emitted.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public final class LLMProvider {

private final String name;
private final String type;
private final boolean enabled;
private final int priority;
private final int weight;
private final boolean hasApiKey;
private final Boolean enabled;
private final Integer priority;
private final Integer weight;
private final Boolean hasApiKey;
private final LLMProviderHealth health;
private final String endpoint;
private final String model;
private final String region;
private final Integer rateLimit;
private final Integer timeoutSeconds;
private final Map<String, Object> settings;

public LLMProvider(
@JsonProperty("name") String name,
@JsonProperty("type") String type,
@JsonProperty("enabled") boolean enabled,
@JsonProperty("priority") int priority,
@JsonProperty("weight") int weight,
@JsonProperty("has_api_key") boolean hasApiKey,
@JsonProperty("health") LLMProviderHealth health) {
@JsonProperty("enabled") Boolean enabled,
@JsonProperty("priority") Integer priority,
@JsonProperty("weight") Integer weight,
@JsonProperty("has_api_key") Boolean hasApiKey,
@JsonProperty("health") LLMProviderHealth health,
@JsonProperty("endpoint") String endpoint,
@JsonProperty("model") String model,
@JsonProperty("region") String region,
@JsonProperty("rate_limit") Integer rateLimit,
@JsonProperty("timeout_seconds") Integer timeoutSeconds,
@JsonProperty("settings") Map<String, Object> settings) {
this.name = name;
this.type = type;
this.enabled = enabled;
this.priority = priority;
this.weight = weight;
this.hasApiKey = hasApiKey;
this.health = health;
this.endpoint = endpoint;
this.model = model;
this.region = region;
this.rateLimit = rateLimit;
this.timeoutSeconds = timeoutSeconds;
this.settings = settings;
}

public String getName() {
Expand All @@ -60,25 +85,68 @@ public String getType() {
return type;
}

public boolean isEnabled() {
/** May be null if the platform omitted the field. */
public Boolean getEnabled() {
return enabled;
}

public int getPriority() {
/** Convenience: true if explicitly enabled, false otherwise (including null). */
public boolean isEnabled() {
return Boolean.TRUE.equals(enabled);
}

/** May be null if the platform omitted the field. */
public Integer getPriority() {
return priority;
}

public int getWeight() {
/** May be null if the platform omitted the field. */
public Integer getWeight() {
return weight;
}

@JsonProperty("has_api_key")
public boolean hasApiKey() {
/** May be null if the platform omitted the field. */
public Boolean getHasApiKey() {
return hasApiKey;
}

/** Convenience: true if has_api_key is explicitly true, false otherwise (including null). */
public boolean hasApiKey() {
return Boolean.TRUE.equals(hasApiKey);
}

/** Health snapshot; may be null if the platform did not return a health probe. */
public LLMProviderHealth getHealth() {
return health;
}

/** API endpoint URL; null if unset on this provider. */
public String getEndpoint() {
return endpoint;
}

/** Default model name; null if unset on this provider. */
public String getModel() {
return model;
}

/** AWS region (Bedrock); null if unset on this provider. */
public String getRegion() {
return region;
}

/** Max requests per second; null if unset on this provider. */
public Integer getRateLimit() {
return rateLimit;
}

/** Request timeout in seconds; null if unset on this provider. */
public Integer getTimeoutSeconds() {
return timeoutSeconds;
}

/** Free-form provider-specific settings; null if unset. */
public Map<String, Object> getSettings() {
return settings;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ public String getMessage() {
}

/** ISO 8601 timestamp of the last health probe; may be null. */
@JsonProperty("last_checked")
public String getLastChecked() {
return lastChecked;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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
*/
package com.getaxonflow.sdk.types;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collections;
import java.util.List;

/** Paginated wrapper returned by {@code GET /api/v1/llm-providers}. */
@JsonIgnoreProperties(ignoreUnknown = true)
public final class LLMProviderListResponse {

private final List<LLMProvider> providers;
private final PaginationMeta pagination;

public LLMProviderListResponse(
@JsonProperty("providers") List<LLMProvider> providers,
@JsonProperty("pagination") PaginationMeta pagination) {
this.providers = providers != null ? providers : Collections.emptyList();
this.pagination = pagination;
}

public List<LLMProvider> getProviders() {
return providers;
}

/** May be null if the server didn't return a pagination block. */
public PaginationMeta getPagination() {
return pagination;
}
}
50 changes: 50 additions & 0 deletions src/main/java/com/getaxonflow/sdk/types/PaginationMeta.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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
*/
package com.getaxonflow.sdk.types;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

/** Pagination metadata returned alongside paginated list responses. */
@JsonIgnoreProperties(ignoreUnknown = true)
public final class PaginationMeta {

private final int page;
private final int pageSize;
private final int totalItems;
private final int totalPages;

public PaginationMeta(
@JsonProperty("page") int page,
@JsonProperty("page_size") int pageSize,
@JsonProperty("total_items") int totalItems,
@JsonProperty("total_pages") int totalPages) {
this.page = page;
this.pageSize = pageSize;
this.totalItems = totalItems;
this.totalPages = totalPages;
}

public int getPage() {
return page;
}

public int getPageSize() {
return pageSize;
}

public int getTotalItems() {
return totalItems;
}

public int getTotalPages() {
return totalPages;
}
}
Loading
Loading