ADR-045: idempiere-cli Integration Architecture
Status
Proposed
Date
2025-12-10
Deciders
- Cloudempiere AI Team
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:
- New package structure
- New REST API endpoints
- Integration with existing CLI infrastructure
- Configuration management
- Deployment modes
Decision Drivers
- Backward compatibility - Existing CLI commands must continue to work
- Clean separation - Satellite functionality clearly separated
- Reusability - Shared logic between CLI, REST, and MCP
- Testability - All components independently testable
- Operability - Clear deployment and configuration model
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"
- Uses local LangChain4j
- No HTTP server
- Direct database access
Mode 2: MCP Server (Existing)
idempiere-cli server mcp
- MCP protocol on port 8765
- For Claude Desktop / Cursor
Mode 3: REST API (Existing)
idempiere-cli server api
- CLI tools as REST endpoints
- Port 8765
Mode 4: Satellite Service (NEW)
idempiere-cli server satellite
- OpenAI-compatible API
- Port 8080 (default)
- For iDempiere plugin integration
- Full enterprise features
Mode 5: Combined (NEW)
idempiere-cli server satellite --with-mcp
- Both Satellite API (8080) and MCP (8765)
- For maximum flexibility
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.
Related ADRs
- ADR-042: Satellite AI Provider Integration
- ADR-043: Satellite Service Architecture Evolution
- ADR-044: Component Extraction Strategy
ADR-045 | Version 1.0 | 2025-12-10 Status: Proposed