{"repo": "axiom750/axiomshell", "files": {"src/main/java/org/orbytex/axiomshell/ai/config/ProviderConfiguration.java": {"14e918bf704cce9fdd9c3642c72f1fcd8a49c0a2": {"t": 1785745666.8433185, "n": 13489, "h": "c8342d4a4ea6f237", "c": "package org.orbytex.axiomshell.ai.config;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.orbytex.axiomshell.config.KeyStore;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Configuration for AI providers.\n * \n * Loads provider settings from:\n * 1. config/ai-providers.json (if exists)\n * 2. Environment variables (override file config)\n * 3. System properties (override environment variables)\n * \n * Configuration file format:\n *
\n * {\n *   \"defaultProvider\": \"openai\",\n *   \"providers\": {\n *     \"openai\": {\n *       \"apiKey\": \"${OPENAI_API_KEY}\",\n *       \"baseUrl\": \"https://api.openai.com/v1\",\n *       \"model\": \"gpt-4\",\n *       \"timeout\": 60000\n *     },\n *     \"anthropic\": {\n *       \"apiKey\": \"${ANTHROPIC_API_KEY}\",\n *       \"baseUrl\": \"https://api.anthropic.com/v1\",\n *       \"model\": \"claude-3-5-sonnet-20241022\",\n *       \"timeout\": 60000\n *     }\n *   }\n * }\n * 
\n * \n * Environment variables:\n * - OPENAI_API_KEY\n * - ANTHROPIC_API_KEY\n * - GEMINI_API_KEY\n * - GROQ_API_KEY\n * - AXIOMSHELL_AI_PROVIDER (default provider)\n * \n * System properties:\n * - axiomshell.ai.provider (default provider)\n * - axiomshell.openai.apiKey\n * - axiomshell.anthropic.apiKey\n * - etc.\n */\npublic class ProviderConfiguration {\n\n private static final String CONFIG_DIR = \"config\";\n private static final String CONFIG_FILE = \"ai-providers.json\";\n \n private final String defaultProvider;\n private final Map providers;\n\n private ProviderConfiguration(String defaultProvider, Map providers) {\n this.defaultProvider = defaultProvider;\n this.providers = new HashMap<>(providers);\n }\n\n /**\n * Loads configuration from file, environment, and system properties.\n * Never throws - returns default configuration if loading fails.\n */\n public static ProviderConfiguration load() {\n try {\n Path configFile = Paths.get(CONFIG_DIR, CONFIG_FILE);\n \n if (Files.exists(configFile)) {\n return loadFromFile(configFile);\n }\n } catch (Exception e) {\n // Silently fall back to environment/system properties only\n }\n \n return loadFromEnvironment();\n }\n\n /**\n * Loads configuration from JSON file.\n */\n private static ProviderConfiguration loadFromFile(Path configFile) throws IOException {\n ObjectMapper mapper = new ObjectMapper();\n ConfigData data = mapper.readValue(configFile.toFile(), ConfigData.class);\n \n String defaultProvider = resolveDefaultProvider(data.defaultProvider);\n Map providers = new HashMap<>();\n \n if (data.providers != null) {\n for (Map.Entry entry : data.providers.entrySet()) {\n String name = entry.getKey();\n ProviderConfigData providerData = entry.getValue();\n \n ProviderConfig config = ProviderConfig.builder()\n .name(name)\n .apiKey(resolveValue(providerData.apiKey, name))\n .baseUrl(providerData.baseUrl)\n .model(providerData.model)\n .timeout(providerData.timeout != null ? providerData.timeout : 60000)\n .build();\n \n providers.put(name, config);\n }\n }\n \n // Add providers from environment even if not in file\n addEnvironmentProviders(providers);\n \n return new ProviderConfiguration(defaultProvider, providers);\n }\n\n /**\n * Loads configuration from environment variables and system properties only.\n */\n private static ProviderConfiguration loadFromEnvironment() {\n String defaultProvider = resolveDefaultProvider(null);\n Map providers = new HashMap<>();\n \n addEnvironmentProviders(providers);\n \n return new ProviderConfiguration(defaultProvider, providers);\n }\n\n /**\n * Adds provider configurations from environment variables and KeyStore.\n */\n private static void addEnvironmentProviders(Map providers) {\n KeyStore keyStore = KeyStore.getInstance();\n \n // OpenAI\n String openaiKey = keyStore.getKey(\"openai\");\n if (openaiKey == null) {\n openaiKey = getEnvOrProperty(\"OPENAI_API_KEY\", \"axiomshell.openai.apiKey\");\n }\n if (openaiKey != null && !providers.containsKey(\"openai\")) {\n providers.put(\"openai\", ProviderConfig.builder()\n .name(\"openai\")\n .apiKey(openaiKey)\n .baseUrl(\"https://api.openai.com/v1\")\n .model(\"gpt-4\")\n .timeout(60000)\n .build());\n }\n \n // Anthropic\n String anthropicKey = keyStore.getKey(\"anthropic\");\n if (anthropicKey == null) {\n anthropicKey = getEnvOrProperty(\"ANTHROPIC_API_KEY\", \"axiomshell.anthropic.apiKey\");\n }\n if (anthropicKey != null && !providers.containsKey(\"anthropic\")) {\n providers.put(\"anthropic\", ProviderConfig.builder()\n .name(\"anthropic\")\n .apiKey(anthropicKey)\n .baseUrl(\"https://api.anthropic.com\")\n .model(\"claude-3-5-sonnet-20241022\")\n .timeout(60000)\n .build());\n }\n \n // Gemini\n String geminiKey = keyStore.getKey(\"gemini\");\n if (geminiKey == null) {\n geminiKey = getEnvOrProperty(\"GEMINI_API_KEY\", \"axiomshell.gemini.apiKey\");\n }\n if (geminiKey != null && !providers.containsKey(\"gemini\")) {\n providers.put(\"gemini\", ProviderConfig.builder()\n .name(\"gemini\")\n .apiKey(geminiKey)\n .baseUrl(\"https://generativelanguage.googleapis.com\")\n .model(\"gemini-3.5-flash\")\n .timeout(60000)\n .build());\n }\n \n // Groq\n String groqKey = keyStore.getKey(\"groq\");\n if (groqKey == null) {\n groqKey = getEnvOrProperty(\"GROQ_API_KEY\", \"axiomshell.groq.apiKey\");\n }\n if (groqKey != null && !providers.containsKey(\"groq\")) {\n providers.put(\"groq\", ProviderConfig.builder()\n .name(\"groq\")\n .apiKey(groqKey)\n .baseUrl(\"https://api.groq.com\")\n .model(\"llama-3.1-8b-instant\")\n .timeout(60000)\n .build());\n }\n \n // Mistral\n String mistralKey = keyStore.getKey(\"mistral\");\n if (mistralKey == null) {\n mistralKey = getEnvOrProperty(\"MISTRAL_API_KEY\", \"axiomshell.mistral.apiKey\");\n }\n if (mistralKey != null && !providers.containsKey(\"mistral\")) {\n providers.put(\"mistral\", ProviderConfig.builder()\n .name(\"mistral\")\n .apiKey(mistralKey)\n .baseUrl(\"https://api.mistral.ai\")\n .model(\"mistral-large-latest\")\n .timeout(60000)\n .build());\n }\n \n // DeepSeek\n String deepseekKey = keyStore.getKey(\"deepseek\");\n if (deepseekKey == null) {\n deepseekKey = getEnvOrProperty(\"DEEPSEEK_API_KEY\", \"axiomshell.deepseek.apiKey\");\n }\n if (deepseekKey != null && !providers.containsKey(\"deepseek\")) {\n providers.put(\"deepseek\", ProviderConfig.builder()\n .name(\"deepseek\")\n .apiKey(deepseekKey)\n .baseUrl(\"https://api.deepseek.com\")\n .model(\"deepseek-chat\")\n .timeout(60000)\n .build());\n }\n \n // Qwen\n String qwenKey = keyStore.getKey(\"qwen\");\n if (qwenKey == null) {\n qwenKey = getEnvOrProperty(\"QWEN_API_KEY\", \"axiomshell.qwen.apiKey\");\n }\n if (qwenKey != null && !providers.containsKey(\"qwen\")) {\n providers.put(\"qwen\", ProviderConfig.builder()\n .name(\"qwen\")\n .apiKey(qwenKey)\n .baseUrl(\"https://dashscope.aliyuncs.com/api\")\n .model(\"qwen-turbo\")\n .timeout(60000)\n .build());\n }\n \n // xAI (Grok)\n String xaiKey = keyStore.getKey(\"xai\");\n if (xaiKey == null) {\n xaiKey = getEnvOrProperty(\"XAI_API_KEY\", \"axiomshell.xai.apiKey\");\n }\n if (xaiKey != null && !providers.containsKey(\"xai\")) {\n providers.put(\"xai\", ProviderConfig.builder()\n .name(\"xai\")\n .apiKey(xaiKey)\n .baseUrl(\"https://api.x.ai\")\n .model(\"grok-beta\")\n .timeout(60000)\n .build());\n }\n \n // Meta\n String metaKey = keyStore.getKey(\"meta\");\n if (metaKey == null) {\n metaKey = getEnvOrProperty(\"META_API_KEY\", \"axiomshell.meta.apiKey\");\n }\n if (metaKey != null && !providers.containsKey(\"meta\")) {\n providers.put(\"meta\", ProviderConfig.builder()\n .name(\"meta\")\n .apiKey(metaKey)\n .baseUrl(\"https://api.meta.ai\")\n .model(\"llama-3.1-405b\")\n .timeout(60000)\n .build());\n }\n }\n\n /**\n * Resolves a value that might contain environment variable placeholders.\n * Example: \"${OPENAI_API_KEY}\" -> actual key value\n */\n private static String resolveValue(String value, String providerName) {\n if (value == null) {\n return null;\n }\n \n // Check for ${VAR_NAME} pattern\n if (value.startsWith(\"${\") && value.endsWith(\"}\")) {\n String envVar = value.substring(2, value.length() - 1);\n return System.getenv(envVar);\n }\n \n return value;\n }\n\n /**\n * Resolves the default provider from config, environment, or fallback.\n */\n private static String resolveDefaultProvider(String fileDefault) {\n // System property has highest priority\n String sysProp = System.getProperty(\"axiomshell.ai.provider\");\n if (sysProp != null) {\n return sysProp;\n }\n \n // Environment variable next\n String envVar = System.getenv(\"AXIOMSHELL_AI_PROVIDER\");\n if (envVar != null) {\n return envVar;\n }\n \n // File default\n if (fileDefault != null) {\n return fileDefault;\n }\n \n // Check KeyStore for any configured provider\n KeyStore keyStore = KeyStore.getInstance();\n Set configuredProviders = keyStore.getConfiguredProviders();\n if (!configuredProviders.isEmpty()) {\n // Use first configured provider from KeyStore\n String firstProvider = configuredProviders.iterator().next();\n return firstProvider;\n }\n \n // Final fallback\n return \"openai\";\n }\n\n /**\n * Gets value from KeyStore, environment variable, or system property.\n * Priority: KeyStore > System Property > Environment Variable\n */\n private static String getEnvOrProperty(String envName, String propertyName) {\n // Check KeyStore first\n String provider = envName.replace(\"_API_KEY\", \"\").toLowerCase();\n KeyStore keyStore = KeyStore.getInstance();\n String keystoreValue = keyStore.getKey(provider);\n if (keystoreValue != null) {\n return keystoreValue;\n }\n \n // Then system property\n String property = System.getProperty(propertyName);\n if (property != null) {\n return property;\n }\n \n // Finally environment variable\n return System.getenv(envName);\n }\n\n public String getDefaultProvider() {\n return defaultProvider;\n }\n\n public ProviderConfig getProvider(String name) {\n return providers.get(name);\n }\n\n public ProviderConfig getDefaultProviderConfig() {\n return providers.get(defaultProvider);\n }\n\n public boolean hasProvider(String name) {\n return providers.containsKey(name);\n }\n\n public Map getAllProviders() {\n return new HashMap<>(providers);\n }\n\n @Override\n public String toString() {\n return \"ProviderConfiguration{\" +\n \"defaultProvider='\" + defaultProvider + '\\'' +\n \", providers=\" + providers.keySet() +\n '}';\n }\n\n /**\n * Internal DTO for JSON deserialization.\n */\n private static class ConfigData {\n public String defaultProvider;\n public Map providers;\n }\n\n /**\n * Internal DTO for provider config JSON deserialization.\n */\n private static class ProviderConfigData {\n public String apiKey;\n public String baseUrl;\n public String model;\n public Integer timeout;\n }\n}\n"}}}}