ADR-045: idempiere-cli Integration Architecture

Status

Proposed

Date

2025-12-10

Deciders

Context and Problem Statement

Following ADR-043 and ADR-044, we need to define the target architecture for idempiere-cli after integrating the extracted AI components. This includes:

  1. New package structure
  2. New REST API endpoints
  3. Integration with existing CLI infrastructure
  4. Configuration management
  5. Deployment modes

Decision Drivers

Decision Outcome

Extend idempiere-cli with a new satellite package that integrates with existing infrastructure while adding OpenAI-compatible REST endpoints.

Target Package Structure

idempiere-cli/
├── src/main/java/org/idempiere/cli/
│   │
│   ├── ═══════════════════════════════════════════════════════════════
│   │   EXISTING PACKAGES (Unchanged or Minor Updates)
│   ├── ═══════════════════════════════════════════════════════════════
│   │
│   ├── IdempiereCli.java                    # Main entry point
│   ├── commands/                            # Picocli commands (existing)
│   │   ├── AskCommand.java                  # UPDATE: Use satellite if available
│   │   ├── AiParentCommand.java
│   │   ├── ServerParentCommand.java         # UPDATE: Add 'satellite' subcommand
│   │   └── ...
│   │
│   ├── ai/                                  # Existing AI infrastructure
│   │   ├── langchain/
│   │   │   ├── CliRouterAgent.java          # Existing (keep for CLI mode)
│   │   │   └── tools/                       # Existing tools
│   │   ├── shared/
│   │   │   ├── ToolResult.java              # Existing (reuse in satellite)
│   │   │   └── *ToolLogic.java              # Existing shared logic
│   │   └── observability/
│   │       └── CliChatModelListener.java    # Existing
│   │
│   ├── api/rest/
│   │   └── CliApiResource.java              # Existing CLI REST API
│   │
│   ├── mcp/                                 # Existing MCP server
│   │   └── tools/
│   │
│   ├── rag/                                 # Existing RAG infrastructure
│   │   ├── RagService.java
│   │   └── ...
│   │
│   ├── ═══════════════════════════════════════════════════════════════
│   │   NEW SATELLITE PACKAGE (From com.cloudempiere.ai extraction)
│   ├── ═══════════════════════════════════════════════════════════════
│   │
│   └── satellite/                           # NEW: Chat API Service
│       │
│       ├── ─────────────────────────────────────────────────────────
│       │   API Layer (OpenAI-Compatible REST)
│       ├── ─────────────────────────────────────────────────────────
│       ├── api/
│       │   ├── SatelliteApiResource.java    # /v1/chat/completions, /v1/embeddings
│       │   ├── SatelliteHealthResource.java # /health, /health/live, /health/ready
│       │   ├── SatelliteMetricsResource.java# /metrics (Prometheus)
│       │   ├── ModelListResource.java       # /v1/models
│       │   └── dto/
│       │       ├── ChatCompletionRequest.java
│       │       ├── ChatCompletionResponse.java
│       │       ├── ChatCompletionChoice.java
│       │       ├── ChatMessage.java
│       │       ├── ChatCompletionUsage.java
│       │       ├── EmbeddingRequest.java
│       │       ├── EmbeddingResponse.java
│       │       ├── ModelInfo.java
│       │       └── ErrorResponse.java
│       │
│       ├── ─────────────────────────────────────────────────────────
│       │   Context Layer (iDempiere Context Handling)
│       ├── ─────────────────────────────────────────────────────────
│       ├── context/
│       │   ├── ChatContext.java        # Context record from headers
│       │   ├── ContextExtractor.java        # JAX-RS filter to extract context
│       │   ├── ConversationContext.java     # Chat memory context
│       │   └── ConversationContextManager.java # TTL-based context cache
│       │
│       ├── ─────────────────────────────────────────────────────────
│       │   Security Layer (Guardrails, Validation)
│       ├── ─────────────────────────────────────────────────────────
│       ├── security/
│       │   ├── BearerTokenFilter.java       # Token validation (future)
│       │   ├── InputGuard.java              # PII detection, injection prevention
│       │   ├── OutputGuard.java             # Response filtering
│       │   ├── ExecutionGuard.java          # Tool execution validation
│       │   └── GuardResult.java             # Guard decision DTO
│       │
│       ├── ─────────────────────────────────────────────────────────
│       │   Observability Layer (Metrics, Audit, Cost)
│       ├── ─────────────────────────────────────────────────────────
│       ├── observability/
│       │   ├── SatelliteMetricsListener.java # ChatModelListener impl
│       │   ├── CostGuard.java               # Budget enforcement
│       │   ├── CostCalculator.java          # Token → USD calculation
│       │   ├── AuditService.java            # Request/response audit
│       │   └── UsageMetrics.java            # Metrics DTO
│       │
│       ├── ─────────────────────────────────────────────────────────
│       │   Routing Layer (Model → Provider Routing)
│       ├── ─────────────────────────────────────────────────────────
│       ├── routing/
│       │   ├── ModelRouter.java             # Model name → provider
│       │   ├── ProviderRegistry.java        # Available providers
│       │   ├── EntityExtractor.java         # NL entity extraction
│       │   ├── PromptAnalyzer.java          # Intent analysis
│       │   └── SourceDecision.java          # Query routing decision
│       │
│       ├── ─────────────────────────────────────────────────────────
│       │   Database Layer (Optional - for metrics persistence)
│       ├── ─────────────────────────────────────────────────────────
│       ├── model/
│       │   ├── AigProvider.java             # Provider config entity
│       │   ├── AigUsageMetrics.java         # Usage tracking entity
│       │   └── AigProviderRepository.java   # Panache repository
│       │
│       ├── ─────────────────────────────────────────────────────────
│       │   Tool Layer (Business Tools for AI)
│       ├── ─────────────────────────────────────────────────────────
│       ├── tool/
│       │   ├── ISatelliteTool.java          # Custom tool interface
│       │   ├── SatelliteToolRegistry.java   # Tool registry (CDI)
│       │   ├── ToolPermission.java          # Permission requirements
│       │   ├── BoundaryValidator.java       # Boundary enforcement
│       │   └── impl/
│       │       ├── DatabaseQueryTool.java   # Secure SQL execution
│       │       ├── TableMetadataTool.java   # AD metadata lookup
│       │       └── KnowledgeSearchTool.java # RAG search tool
│       │
│       ├── ─────────────────────────────────────────────────────────
│       │   Agent Layer (Agent Orchestration)
│       ├── ─────────────────────────────────────────────────────────
│       ├── agent/
│       │   ├── ChatAgent.java          # @RegisterAiService agent
│       │   ├── AgentBoundary.java           # Security boundary
│       │   ├── AgentBoundaryRegistry.java   # Boundary registry
│       │   └── AgentContext.java            # Execution context
│       │
│       ├── ─────────────────────────────────────────────────────────
│       │   Streaming Layer (SSE Support)
│       ├── ─────────────────────────────────────────────────────────
│       ├── streaming/
│       │   ├── StreamingResponseWriter.java # SSE writer
│       │   ├── StreamCallback.java          # Streaming callback interface
│       │   └── ChunkAccumulator.java        # Chunk buffering
│       │
│       └── ─────────────────────────────────────────────────────────
│           Configuration
│       ─────────────────────────────────────────────────────────────
│       └── config/
│           ├── SatelliteConfig.java         # @ConfigMapping
│           └── ModelPricing.java            # Cost per token by model
│
├── src/main/resources/
│   ├── application.properties               # UPDATE: Add satellite config
│   └── META-INF/
│       └── resources/
│           └── openapi.yaml                 # OpenAPI spec for satellite
│
└── src/test/java/org/idempiere/cli/satellite/
    ├── api/
    │   └── SatelliteApiResourceTest.java
    ├── security/
    │   └── InputGuardTest.java
    ├── observability/
    │   └── CostCalculatorTest.java
    └── routing/
        └── ModelRouterTest.java

Configuration

application.properties Additions

# ==================== Satellite Service Configuration ====================

# Satellite Profile: Enable REST API for satellite mode
# Usage: java -Dquarkus.profile=chat-api -jar idempiere-hub-runner.jar server satellite
%satellite.quarkus.http.port=8080
%satellite.quarkus.http.host=0.0.0.0
%satellite.quarkus.log.level=INFO
%satellite.quarkus.http.cors.enabled=true
%satellite.quarkus.http.cors.origins=*

# Health and Metrics
%satellite.quarkus.smallrye-health.root-path=/health
%satellite.quarkus.micrometer.export.prometheus.path=/metrics
%satellite.quarkus.micrometer.export.prometheus.enabled=true

# OpenAPI/Swagger for Satellite
%satellite.quarkus.smallrye-openapi.info-title=Chat API Service
%satellite.quarkus.smallrye-openapi.info-version=1.0.0
%satellite.quarkus.smallrye-openapi.info-description=OpenAI-compatible AI API for iDempiere

# ==================== Satellite Security (Future) ====================
satellite.auth.enabled=false
satellite.auth.tokens=${SATELLITE_AUTH_TOKENS:}

# ==================== Satellite Observability ====================
satellite.metrics.enabled=true
satellite.metrics.persist-to-db=false
satellite.audit.enabled=true
satellite.audit.log-requests=true
satellite.audit.log-responses=false

# ==================== Satellite Cost Management ====================
satellite.cost.tracking.enabled=true
satellite.cost.budget.daily-limit-usd=100.0
satellite.cost.budget.monthly-limit-usd=1000.0
satellite.cost.alert.threshold=0.8

# ==================== Model Pricing (per 1M tokens, USD) ====================
satellite.pricing.claude-opus-4=15.00,75.00
satellite.pricing.claude-sonnet-4=3.00,15.00
satellite.pricing.claude-3-5-sonnet=3.00,15.00
satellite.pricing.claude-3-haiku=0.25,1.25
satellite.pricing.gpt-4o=2.50,10.00
satellite.pricing.gpt-4o-mini=0.15,0.60
satellite.pricing.llama3.2=0.0,0.0
satellite.pricing.mistral=0.0,0.0

# ==================== Model Routing ====================
# Default provider when model prefix doesn't match
satellite.routing.default-provider=anthropic

# Model prefix → provider mapping
satellite.routing.prefixes.claude=anthropic
satellite.routing.prefixes.anthropic=bedrock
satellite.routing.prefixes.gpt=openai
satellite.routing.prefixes.o1=openai
satellite.routing.prefixes.llama=ollama
satellite.routing.prefixes.mistral=ollama
satellite.routing.prefixes.qwen=ollama

# ==================== Guardrails ====================
satellite.guardrails.input.enabled=true
satellite.guardrails.input.block-pii=true
satellite.guardrails.input.block-injection=true
satellite.guardrails.output.enabled=true
satellite.guardrails.output.redact-pii=true

REST API Specification

Endpoints

Method Path Description
POST /v1/chat/completions Chat completion (OpenAI-compatible)
POST /v1/embeddings Generate embeddings
GET /v1/models List available models
GET /health Combined health check
GET /health/live Liveness probe
GET /health/ready Readiness probe
GET /metrics Prometheus metrics

Request Headers

Header Required Description
Authorization Future Bearer <token>
X-iDempiere-Client-ID No AD_Client_ID for multi-tenant
X-iDempiere-Org-ID No AD_Org_ID
X-iDempiere-User-ID No AD_User_ID (for audit)
X-iDempiere-Role-ID No AD_Role_ID (for permissions)
X-iDempiere-Session-ID No Session correlation
X-iDempiere-Window-ID No Current window context
X-iDempiere-Language No Preferred language
X-Conversation-ID No Conversation tracking

New CLI Command

Add satellite subcommand to ServerParentCommand:

@Command(name = "server", description = "Server management commands")
public class ServerParentCommand {

    // Existing subcommands...

    @Command(name = "satellite",
             description = "Start Chat API Service (OpenAI-compatible API)")
    public static class SatelliteCommand implements Callable<Integer> {

        @Option(names = {"-p", "--port"}, defaultValue = "8080",
                description = "HTTP port")
        int port;

        @Option(names = {"--host"}, defaultValue = "0.0.0.0",
                description = "Bind address")
        String host;

        @Override
        public Integer call() {
            ConsoleOutput.info("Starting Chat API Service...");
            ConsoleOutput.info("  Port: " + port);
            ConsoleOutput.info("  Host: " + host);
            ConsoleOutput.info("");
            ConsoleOutput.info("Endpoints:");
            ConsoleOutput.info("  POST /v1/chat/completions");
            ConsoleOutput.info("  POST /v1/embeddings");
            ConsoleOutput.info("  GET  /v1/models");
            ConsoleOutput.info("  GET  /health");
            ConsoleOutput.info("  GET  /metrics");
            ConsoleOutput.info("");
            ConsoleOutput.success("Chat API Service started on http://" +
                                  host + ":" + port);

            // Keep running (Quarkus handles HTTP server)
            Quarkus.waitForExit();
            return 0;
        }
    }
}

Usage:

# Start satellite service
idempiere-cli server satellite

# With custom port
idempiere-cli server satellite --port 9000

# Or via Quarkus profile
java -Dquarkus.profile=chat-api -jar idempiere-hub-runner.jar server satellite

Integration Points

1. With Existing CliRouterAgent

The existing CliRouterAgent (for CLI ask command) can optionally route through satellite:

@ApplicationScoped
public class AskCommand implements Callable<Integer> {

    @Inject
    CliRouterAgent localAgent;  // Existing

    @Inject
    @RestClient
    SatelliteClient satelliteClient;  // New (optional)

    @ConfigProperty(name = "satellite.endpoint", defaultValue = "")
    String satelliteEndpoint;

    @Override
    public Integer call() {
        if (!satelliteEndpoint.isEmpty()) {
            // Route through satellite (for consistency)
            return askViaSatellite(request);
        } else {
            // Local processing (existing behavior)
            return localAgent.route(request);
        }
    }
}

2. With Existing RAG Infrastructure

Satellite reuses existing RAG:

@ApplicationScoped
public class KnowledgeSearchTool implements ISatelliteTool {

    @Inject
    RagService ragService;  // Existing!

    @Override
    public ToolResult execute(Map<String, Object> params, ChatContext ctx) {
        String query = (String) params.get("query");
        return ragService.search(query, null);  // Reuse existing
    }
}

3. With Existing ToolResult

Satellite uses existing ToolResult for consistency:

// satellite/api/SatelliteApiResource.java
@POST
@Path("/v1/chat/completions")
public Response chatCompletion(ChatCompletionRequest request) {
    // ... processing ...

    // Convert internal result to OpenAI format
    ChatCompletionResponse response = ChatCompletionResponse.from(aiResponse);
    return Response.ok(response).build();
}

Deployment Modes

Mode 1: CLI Only (Existing)

idempiere-cli ai ask "list tables"

Mode 2: MCP Server (Existing)

idempiere-cli server mcp

Mode 3: REST API (Existing)

idempiere-cli server api

Mode 4: Satellite Service (NEW)

idempiere-cli server satellite

Mode 5: Combined (NEW)

idempiere-cli server satellite --with-mcp

Metrics & Monitoring

Prometheus Metrics

# Token usage
satellite_tokens_input_total{model="claude-sonnet-4",client="1000000"} 12345
satellite_tokens_output_total{model="claude-sonnet-4",client="1000000"} 6789

# Cost tracking
satellite_cost_usd_total{model="claude-sonnet-4",client="1000000"} 0.057

# Request metrics
satellite_requests_total{model="claude-sonnet-4",status="success"} 100
satellite_requests_total{model="claude-sonnet-4",status="error"} 2

# Latency
satellite_request_duration_seconds{model="claude-sonnet-4",quantile="0.5"} 1.2
satellite_request_duration_seconds{model="claude-sonnet-4",quantile="0.95"} 3.5

# Guardrail triggers
satellite_guardrail_triggers_total{type="pii_detected",action="mask"} 5
satellite_guardrail_triggers_total{type="injection_blocked",action="block"} 1

Health Check Response

{
  "status": "UP",
  "checks": [
    {"name": "database", "status": "UP"},
    {"name": "ollama", "status": "UP"},
    {"name": "anthropic", "status": "UP", "data": {"quota_remaining": "95%"}},
    {"name": "vector-store", "status": "UP", "data": {"embeddings": 15234}}
  ]
}

Migration Impact on idempiere-cli

Files Added

Count Location Description
~15 satellite/api/ REST resources and DTOs
~5 satellite/context/ Context handling
~5 satellite/security/ Guardrails
~6 satellite/observability/ Metrics, audit
~6 satellite/routing/ Model routing
~3 satellite/model/ Entities (optional)
~8 satellite/tool/ Tool framework
~5 satellite/agent/ Agent orchestration
~3 satellite/streaming/ SSE support
~2 satellite/config/ Configuration

Total: ~58 new files

Files Modified

File Change
application.properties Add satellite configuration
ServerParentCommand.java Add satellite subcommand
AskCommand.java Optional satellite routing
pom.xml Add dependencies (Micrometer, etc.)

Breaking Changes

None - All changes are additive. Existing CLI commands continue to work.


ADR-045 | Version 1.0 | 2025-12-10 Status: Proposed

Path: /docs/developers/architecture/idempiere-hub/045-idempiere-cli-integration-architecture