@@ -649,6 +649,178 @@ public CompletableFuture<AuditToolCallResponse> auditToolCallAsync(AuditToolCall
649649 return CompletableFuture .supplyAsync (() -> auditToolCall (request ), asyncExecutor );
650650 }
651651
652+ // ========================================================================
653+ // Circuit Breaker Observability
654+ // ========================================================================
655+
656+ /**
657+ * Gets the current circuit breaker status, including all active (tripped) circuits.
658+ *
659+ * <p>Example usage:
660+ * <pre>{@code
661+ * CircuitBreakerStatusResponse status = axonflow.getCircuitBreakerStatus();
662+ * System.out.println("Active circuits: " + status.getCount());
663+ * System.out.println("Emergency stop: " + status.isEmergencyStopActive());
664+ * }</pre>
665+ *
666+ * @return the circuit breaker status
667+ * @throws AxonFlowException if the request fails
668+ */
669+ public CircuitBreakerStatusResponse getCircuitBreakerStatus () {
670+ return retryExecutor .execute (() -> {
671+ Request httpRequest = buildOrchestratorRequest ("GET" , "/api/v1/circuit-breaker/status" , null );
672+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
673+ JsonNode node = parseResponseNode (response );
674+ if (node .has ("data" ) && node .get ("data" ).isObject ()) {
675+ return objectMapper .treeToValue (node .get ("data" ), CircuitBreakerStatusResponse .class );
676+ }
677+ return objectMapper .treeToValue (node , CircuitBreakerStatusResponse .class );
678+ }
679+ }, "getCircuitBreakerStatus" );
680+ }
681+
682+ /**
683+ * Asynchronously gets the current circuit breaker status.
684+ *
685+ * @return a future containing the circuit breaker status
686+ */
687+ public CompletableFuture <CircuitBreakerStatusResponse > getCircuitBreakerStatusAsync () {
688+ return CompletableFuture .supplyAsync (this ::getCircuitBreakerStatus , asyncExecutor );
689+ }
690+
691+ /**
692+ * Gets the circuit breaker history, including past trips and resets.
693+ *
694+ * <p>Example usage:
695+ * <pre>{@code
696+ * CircuitBreakerHistoryResponse history = axonflow.getCircuitBreakerHistory(50);
697+ * for (CircuitBreakerHistoryEntry entry : history.getHistory()) {
698+ * System.out.println(entry.getScope() + "/" + entry.getScopeId() + " - " + entry.getState());
699+ * }
700+ * }</pre>
701+ *
702+ * @param limit the maximum number of history entries to return
703+ * @return the circuit breaker history
704+ * @throws IllegalArgumentException if limit is less than 1
705+ * @throws AxonFlowException if the request fails
706+ */
707+ public CircuitBreakerHistoryResponse getCircuitBreakerHistory (int limit ) {
708+ if (limit < 1 ) {
709+ throw new IllegalArgumentException ("limit must be at least 1" );
710+ }
711+
712+ return retryExecutor .execute (() -> {
713+ String path = "/api/v1/circuit-breaker/history?limit=" + limit ;
714+ Request httpRequest = buildOrchestratorRequest ("GET" , path , null );
715+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
716+ JsonNode node = parseResponseNode (response );
717+ if (node .has ("data" ) && node .get ("data" ).isObject ()) {
718+ return objectMapper .treeToValue (node .get ("data" ), CircuitBreakerHistoryResponse .class );
719+ }
720+ return objectMapper .treeToValue (node , CircuitBreakerHistoryResponse .class );
721+ }
722+ }, "getCircuitBreakerHistory" );
723+ }
724+
725+ /**
726+ * Asynchronously gets the circuit breaker history.
727+ *
728+ * @param limit the maximum number of history entries to return
729+ * @return a future containing the circuit breaker history
730+ */
731+ public CompletableFuture <CircuitBreakerHistoryResponse > getCircuitBreakerHistoryAsync (int limit ) {
732+ return CompletableFuture .supplyAsync (() -> getCircuitBreakerHistory (limit ), asyncExecutor );
733+ }
734+
735+ /**
736+ * Gets the circuit breaker configuration for a specific tenant.
737+ *
738+ * <p>Example usage:
739+ * <pre>{@code
740+ * CircuitBreakerConfig config = axonflow.getCircuitBreakerConfig("tenant_123");
741+ * System.out.println("Error threshold: " + config.getErrorThreshold());
742+ * System.out.println("Auto recovery: " + config.isEnableAutoRecovery());
743+ * }</pre>
744+ *
745+ * @param tenantId the tenant ID to get configuration for
746+ * @return the circuit breaker configuration
747+ * @throws NullPointerException if tenantId is null
748+ * @throws IllegalArgumentException if tenantId is empty
749+ * @throws AxonFlowException if the request fails
750+ */
751+ public CircuitBreakerConfig getCircuitBreakerConfig (String tenantId ) {
752+ Objects .requireNonNull (tenantId , "tenantId cannot be null" );
753+ if (tenantId .isEmpty ()) {
754+ throw new IllegalArgumentException ("tenantId cannot be empty" );
755+ }
756+
757+ return retryExecutor .execute (() -> {
758+ String path = "/api/v1/circuit-breaker/config?tenant_id=" + java .net .URLEncoder .encode (tenantId , java .nio .charset .StandardCharsets .UTF_8 );
759+ Request httpRequest = buildOrchestratorRequest ("GET" , path , null );
760+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
761+ JsonNode node = parseResponseNode (response );
762+ if (node .has ("data" ) && node .get ("data" ).isObject ()) {
763+ return objectMapper .treeToValue (node .get ("data" ), CircuitBreakerConfig .class );
764+ }
765+ return objectMapper .treeToValue (node , CircuitBreakerConfig .class );
766+ }
767+ }, "getCircuitBreakerConfig" );
768+ }
769+
770+ /**
771+ * Asynchronously gets the circuit breaker configuration for a specific tenant.
772+ *
773+ * @param tenantId the tenant ID to get configuration for
774+ * @return a future containing the circuit breaker configuration
775+ */
776+ public CompletableFuture <CircuitBreakerConfig > getCircuitBreakerConfigAsync (String tenantId ) {
777+ return CompletableFuture .supplyAsync (() -> getCircuitBreakerConfig (tenantId ), asyncExecutor );
778+ }
779+
780+ /**
781+ * Updates the circuit breaker configuration for a tenant.
782+ *
783+ * <p>Example usage:
784+ * <pre>{@code
785+ * CircuitBreakerConfig updated = axonflow.updateCircuitBreakerConfig(
786+ * CircuitBreakerConfigUpdate.builder()
787+ * .tenantId("tenant_123")
788+ * .errorThreshold(10)
789+ * .violationThreshold(5)
790+ * .enableAutoRecovery(true)
791+ * .build());
792+ * }</pre>
793+ *
794+ * @param config the configuration update
795+ * @return confirmation with tenant_id and message
796+ * @throws NullPointerException if config is null
797+ * @throws AxonFlowException if the request fails
798+ */
799+ public CircuitBreakerConfigUpdateResponse updateCircuitBreakerConfig (CircuitBreakerConfigUpdate config ) {
800+ Objects .requireNonNull (config , "config cannot be null" );
801+
802+ return retryExecutor .execute (() -> {
803+ Request httpRequest = buildOrchestratorRequest ("PUT" , "/api/v1/circuit-breaker/config" , config );
804+ try (Response response = httpClient .newCall (httpRequest ).execute ()) {
805+ JsonNode node = parseResponseNode (response );
806+ if (node .has ("data" ) && node .get ("data" ).isObject ()) {
807+ return objectMapper .treeToValue (node .get ("data" ), CircuitBreakerConfigUpdateResponse .class );
808+ }
809+ return objectMapper .treeToValue (node , CircuitBreakerConfigUpdateResponse .class );
810+ }
811+ }, "updateCircuitBreakerConfig" );
812+ }
813+
814+ /**
815+ * Asynchronously updates the circuit breaker configuration for a tenant.
816+ *
817+ * @param config the configuration update
818+ * @return a future containing the update confirmation
819+ */
820+ public CompletableFuture <CircuitBreakerConfigUpdateResponse > updateCircuitBreakerConfigAsync (CircuitBreakerConfigUpdate config ) {
821+ return CompletableFuture .supplyAsync (() -> updateCircuitBreakerConfig (config ), asyncExecutor );
822+ }
823+
652824 // ========================================================================
653825 // Proxy Mode - Query Execution
654826 // ========================================================================
0 commit comments