Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
63 KiB
JSON
1 line
63 KiB
JSON
{"repo": "carstenartur/sandbox", "files": {"sandbox_common_core/src/main/java/org/sandbox/jdt/triggerpattern/llm/DeepSeekClient.java": {"f7b7e527311e5ee87c60ef2387d901108475e79e": {"t": 1785745669.855472, "n": 2456, "h": "24506dffd328a182", "c": "/*******************************************************************************\n * Copyright (c) 2025 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.llm;\n\nimport java.net.http.HttpClient;\n\n/**\n * REST client for the DeepSeek API (OpenAI-compatible format).\n *\n * <p>Uses the DeepSeek chat completions endpoint with the {@code deepseek-reasoner}\n * model by default.</p>\n */\npublic class DeepSeekClient extends OpenAiCompatibleClient {\n\nprivate static final String DEFAULT_MODEL = \"deepseek-reasoner\"; //$NON-NLS-1$\nprivate static final String API_URL = \"https://api.deepseek.com/v1/chat/completions\"; //$NON-NLS-1$\nprivate static final String API_KEY_ENV = \"DEEPSEEK_API_KEY\"; //$NON-NLS-1$\nprivate static final String MODEL_ENV = \"DEEPSEEK_MODEL\"; //$NON-NLS-1$\n\n/**\n * Creates a client reading the API key from the DEEPSEEK_API_KEY environment variable.\n */\npublic DeepSeekClient() {\nthis(System.getenv(API_KEY_ENV));\n}\n\n/**\n * Creates a client with the given API key.\n *\n * @param apiKey the DeepSeek API key\n */\npublic DeepSeekClient(String apiKey) {\nsuper(API_URL, apiKey, resolveModel(), \"DeepSeek\"); //$NON-NLS-1$\n}\n\n/**\n * Creates a client with the given API key and HTTP client (for testing).\n *\n * @param apiKey the DeepSeek API key\n * @param httpClient the HTTP client to use\n */\npublic DeepSeekClient(String apiKey, HttpClient httpClient) {\nthis(apiKey, httpClient, resolveModel());\n}\n\n/**\n * Creates a client with the given API key, HTTP client, and model (for testing).\n *\n * @param apiKey the DeepSeek API key\n * @param httpClient the HTTP client to use\n * @param model the model name to use\n */\npublic DeepSeekClient(String apiKey, HttpClient httpClient, String model) {\nsuper(API_URL, apiKey, model, \"DeepSeek\", httpClient); //$NON-NLS-1$\n}\n\nprivate static String resolveModel() {\nString envModel = System.getenv(MODEL_ENV);\nif (envModel != null) {\nenvModel = envModel.trim();\n}\nreturn (envModel != null && !envModel.isBlank()) ? envModel : DEFAULT_MODEL;\n}\n\n@Override\nprotected String getApiKeyEnvVar() {\nreturn API_KEY_ENV;\n}\n}\n"}, "4ee92210601b76d9b315c3a003e520daf9af2ba7": {"t": 1785755613.5724864, "n": 2456, "h": "24506dffd328a182", "c": "/*******************************************************************************\n * Copyright (c) 2025 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.llm;\n\nimport java.net.http.HttpClient;\n\n/**\n * REST client for the DeepSeek API (OpenAI-compatible format).\n *\n * <p>Uses the DeepSeek chat completions endpoint with the {@code deepseek-reasoner}\n * model by default.</p>\n */\npublic class DeepSeekClient extends OpenAiCompatibleClient {\n\nprivate static final String DEFAULT_MODEL = \"deepseek-reasoner\"; //$NON-NLS-1$\nprivate static final String API_URL = \"https://api.deepseek.com/v1/chat/completions\"; //$NON-NLS-1$\nprivate static final String API_KEY_ENV = \"DEEPSEEK_API_KEY\"; //$NON-NLS-1$\nprivate static final String MODEL_ENV = \"DEEPSEEK_MODEL\"; //$NON-NLS-1$\n\n/**\n * Creates a client reading the API key from the DEEPSEEK_API_KEY environment variable.\n */\npublic DeepSeekClient() {\nthis(System.getenv(API_KEY_ENV));\n}\n\n/**\n * Creates a client with the given API key.\n *\n * @param apiKey the DeepSeek API key\n */\npublic DeepSeekClient(String apiKey) {\nsuper(API_URL, apiKey, resolveModel(), \"DeepSeek\"); //$NON-NLS-1$\n}\n\n/**\n * Creates a client with the given API key and HTTP client (for testing).\n *\n * @param apiKey the DeepSeek API key\n * @param httpClient the HTTP client to use\n */\npublic DeepSeekClient(String apiKey, HttpClient httpClient) {\nthis(apiKey, httpClient, resolveModel());\n}\n\n/**\n * Creates a client with the given API key, HTTP client, and model (for testing).\n *\n * @param apiKey the DeepSeek API key\n * @param httpClient the HTTP client to use\n * @param model the model name to use\n */\npublic DeepSeekClient(String apiKey, HttpClient httpClient, String model) {\nsuper(API_URL, apiKey, model, \"DeepSeek\", httpClient); //$NON-NLS-1$\n}\n\nprivate static String resolveModel() {\nString envModel = System.getenv(MODEL_ENV);\nif (envModel != null) {\nenvModel = envModel.trim();\n}\nreturn (envModel != null && !envModel.isBlank()) ? envModel : DEFAULT_MODEL;\n}\n\n@Override\nprotected String getApiKeyEnvVar() {\nreturn API_KEY_ENV;\n}\n}\n"}, "2d6c2882b914522c51b50014638be12ade13cfc5": {"t": 1786067432.462205, "n": 2456, "h": "24506dffd328a182", "c": "/*******************************************************************************\n * Copyright (c) 2025 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.llm;\n\nimport java.net.http.HttpClient;\n\n/**\n * REST client for the DeepSeek API (OpenAI-compatible format).\n *\n * <p>Uses the DeepSeek chat completions endpoint with the {@code deepseek-reasoner}\n * model by default.</p>\n */\npublic class DeepSeekClient extends OpenAiCompatibleClient {\n\nprivate static final String DEFAULT_MODEL = \"deepseek-reasoner\"; //$NON-NLS-1$\nprivate static final String API_URL = \"https://api.deepseek.com/v1/chat/completions\"; //$NON-NLS-1$\nprivate static final String API_KEY_ENV = \"DEEPSEEK_API_KEY\"; //$NON-NLS-1$\nprivate static final String MODEL_ENV = \"DEEPSEEK_MODEL\"; //$NON-NLS-1$\n\n/**\n * Creates a client reading the API key from the DEEPSEEK_API_KEY environment variable.\n */\npublic DeepSeekClient() {\nthis(System.getenv(API_KEY_ENV));\n}\n\n/**\n * Creates a client with the given API key.\n *\n * @param apiKey the DeepSeek API key\n */\npublic DeepSeekClient(String apiKey) {\nsuper(API_URL, apiKey, resolveModel(), \"DeepSeek\"); //$NON-NLS-1$\n}\n\n/**\n * Creates a client with the given API key and HTTP client (for testing).\n *\n * @param apiKey the DeepSeek API key\n * @param httpClient the HTTP client to use\n */\npublic DeepSeekClient(String apiKey, HttpClient httpClient) {\nthis(apiKey, httpClient, resolveModel());\n}\n\n/**\n * Creates a client with the given API key, HTTP client, and model (for testing).\n *\n * @param apiKey the DeepSeek API key\n * @param httpClient the HTTP client to use\n * @param model the model name to use\n */\npublic DeepSeekClient(String apiKey, HttpClient httpClient, String model) {\nsuper(API_URL, apiKey, model, \"DeepSeek\", httpClient); //$NON-NLS-1$\n}\n\nprivate static String resolveModel() {\nString envModel = System.getenv(MODEL_ENV);\nif (envModel != null) {\nenvModel = envModel.trim();\n}\nreturn (envModel != null && !envModel.isBlank()) ? envModel : DEFAULT_MODEL;\n}\n\n@Override\nprotected String getApiKeyEnvVar() {\nreturn API_KEY_ENV;\n}\n}\n"}, "bb73965038dd4f4380c05a4e9a181cc0123cacbf": {"t": 1786068300.8363426, "n": 2456, "h": "24506dffd328a182", "c": "/*******************************************************************************\n * Copyright (c) 2025 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.llm;\n\nimport java.net.http.HttpClient;\n\n/**\n * REST client for the DeepSeek API (OpenAI-compatible format).\n *\n * <p>Uses the DeepSeek chat completions endpoint with the {@code deepseek-reasoner}\n * model by default.</p>\n */\npublic class DeepSeekClient extends OpenAiCompatibleClient {\n\nprivate static final String DEFAULT_MODEL = \"deepseek-reasoner\"; //$NON-NLS-1$\nprivate static final String API_URL = \"https://api.deepseek.com/v1/chat/completions\"; //$NON-NLS-1$\nprivate static final String API_KEY_ENV = \"DEEPSEEK_API_KEY\"; //$NON-NLS-1$\nprivate static final String MODEL_ENV = \"DEEPSEEK_MODEL\"; //$NON-NLS-1$\n\n/**\n * Creates a client reading the API key from the DEEPSEEK_API_KEY environment variable.\n */\npublic DeepSeekClient() {\nthis(System.getenv(API_KEY_ENV));\n}\n\n/**\n * Creates a client with the given API key.\n *\n * @param apiKey the DeepSeek API key\n */\npublic DeepSeekClient(String apiKey) {\nsuper(API_URL, apiKey, resolveModel(), \"DeepSeek\"); //$NON-NLS-1$\n}\n\n/**\n * Creates a client with the given API key and HTTP client (for testing).\n *\n * @param apiKey the DeepSeek API key\n * @param httpClient the HTTP client to use\n */\npublic DeepSeekClient(String apiKey, HttpClient httpClient) {\nthis(apiKey, httpClient, resolveModel());\n}\n\n/**\n * Creates a client with the given API key, HTTP client, and model (for testing).\n *\n * @param apiKey the DeepSeek API key\n * @param httpClient the HTTP client to use\n * @param model the model name to use\n */\npublic DeepSeekClient(String apiKey, HttpClient httpClient, String model) {\nsuper(API_URL, apiKey, model, \"DeepSeek\", httpClient); //$NON-NLS-1$\n}\n\nprivate static String resolveModel() {\nString envModel = System.getenv(MODEL_ENV);\nif (envModel != null) {\nenvModel = envModel.trim();\n}\nreturn (envModel != null && !envModel.isBlank()) ? envModel : DEFAULT_MODEL;\n}\n\n@Override\nprotected String getApiKeyEnvVar() {\nreturn API_KEY_ENV;\n}\n}\n"}}, "sandbox_triggerpattern/src/org/sandbox/jdt/triggerpattern/mining/llm/EclipseLlmService.java": {"f7b7e527311e5ee87c60ef2387d901108475e79e": {"t": 1785745691.672868, "n": 6018, "h": "320cfb9e2f180858", "c": "/*******************************************************************************\n * Copyright (c) 2026 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.mining.llm;\n\nimport org.eclipse.core.runtime.preferences.IEclipsePreferences;\nimport org.eclipse.core.runtime.preferences.InstanceScope;\nimport org.sandbox.jdt.internal.ui.preferences.LlmPreferencePage;\nimport org.sandbox.jdt.triggerpattern.llm.AiRuleInferenceEngine;\nimport org.sandbox.jdt.triggerpattern.llm.LlmClient;\nimport org.sandbox.jdt.triggerpattern.llm.LlmClientFactory;\nimport org.sandbox.jdt.triggerpattern.llm.LlmProvider;\n\n/**\n * Eclipse-level service that provides access to the AI rule inference engine.\n *\n * <p>This service wraps {@link LlmClientFactory} and manages the lifecycle of\n * the underlying {@link LlmClient}. It reads the LLM provider configuration\n * from Eclipse preferences first, falling back to environment variables when\n * preferences are empty.</p>\n *\n * <p>The service is a lazy-initialized singleton. The LLM client is created\n * on first access and reused for subsequent calls. Call {@link #shutdown()}\n * to release resources when the Eclipse workbench shuts down.</p>\n *\n * <p>Usage:</p>\n * <pre>\n * EclipseLlmService service = EclipseLlmService.getInstance();\n * AiRuleInferenceEngine engine = service.getEngine();\n * engine.inferRule(before, after).ifPresent(eval -> ...);\n * </pre>\n *\n * @since 1.2.6\n */\npublic class EclipseLlmService {\n\n\tprivate static final Object INSTANCE_LOCK= new Object();\n\n\tprivate static volatile EclipseLlmService instance;\n\n\tprivate final Object lifecycleLock= new Object();\n\n\tprivate LlmClient llmClient;\n\tprivate AiRuleInferenceEngine engine;\n\n\tprivate EclipseLlmService() {\n\t\t// lazy initialization\n\t}\n\n\t/**\n\t * Returns the singleton instance.\n\t *\n\t * @return the service instance\n\t */\n\tpublic static EclipseLlmService getInstance() {\n\t\tEclipseLlmService current= instance;\n\t\tif (current == null) {\n\t\t\tsynchronized (INSTANCE_LOCK) {\n\t\t\t\tcurrent= instance;\n\t\t\t\tif (current == null) {\n\t\t\t\t\tcurrent= new EclipseLlmService();\n\t\t\t\t\tinstance= current;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\t/**\n\t * Returns the AI rule inference engine, creating the LLM client if needed.\n\t *\n\t * <p>The LLM provider is resolved from Eclipse preferences first,\n\t * falling back to environment variables if preferences are empty.</p>\n\t *\n\t * <p><strong>Thread safety:</strong> The engine instance is shared across\n\t * concurrent analysis jobs. The underlying {@link LlmClient} implementations\n\t * should tolerate concurrent calls; if they do not, callers should serialize\n\t * access externally (e.g. via a scheduling rule on the analysis jobs).</p>\n\t *\n\t * @return the inference engine\n\t */\n\tpublic AiRuleInferenceEngine getEngine() {\n\t\tsynchronized (lifecycleLock) {\n\t\t\tif (engine == null) {\n\t\t\t\tllmClient= createClientFromPreferences();\n\t\t\t\tengine= new AiRuleInferenceEngine(llmClient);\n\t\t\t}\n\t\t\treturn engine;\n\t\t}\n\t}\n\n\t/**\n\t * Returns whether the LLM service is configured and available.\n\t *\n\t * <p>Checks if an API key is configured in preferences or environment.</p>\n\t *\n\t * @return {@code true} if an LLM provider can be configured\n\t */\n\tpublic boolean isAvailable() {\n\t\treturn hasPreferenceApiKey() || hasAnyEnvApiKey();\n\t}\n\n\t/**\n\t * Shuts down the service and releases the underlying LLM client.\n\t */\n\tpublic void shutdown() {\n\t\tsynchronized (lifecycleLock) {\n\t\t\tif (llmClient != null) {\n\t\t\t\tllmClient.close();\n\t\t\t\tllmClient= null;\n\t\t\t\tengine= null;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Resets the singleton (useful for testing or after preference changes).\n\t */\n\tpublic static void reset() {\n\t\tEclipseLlmService current;\n\t\tsynchronized (INSTANCE_LOCK) {\n\t\t\tcurrent= instance;\n\t\t\tinstance= null;\n\t\t}\n\t\tif (current != null) {\n\t\t\tcurrent.shutdown();\n\t\t}\n\t}\n\n\t/**\n\t * Creates an LLM client using Eclipse preferences, falling back to env vars.\n\t *\n\t * <p>When both provider and API key are set in preferences, the client is\n\t * constructed with the explicit API key from preferences. If only the\n\t * provider is set, the provider-specific environment variable is used.\n\t * If neither is set, auto-detection from environment variables is used.</p>\n\t */\n\tprivate static LlmClient createClientFromPreferences() {\n\t\tIEclipsePreferences prefs= InstanceScope.INSTANCE.getNode(LlmPreferencePage.PLUGIN_ID);\n\t\tString provider= prefs.get(LlmPreferencePage.PREF_PROVIDER, \"\"); //$NON-NLS-1$\n\t\tString apiKey= prefs.get(LlmPreferencePage.PREF_API_KEY, \"\"); //$NON-NLS-1$\n\n\t\tif (!provider.isBlank()) {\n\t\t\t// Use preference provider and API key (key may be blank → client reads env)\n\t\t\treturn LlmClientFactory.create(\n\t\t\t\t\tLlmProvider.fromString(provider),\n\t\t\t\t\tapiKey.isBlank() ? null : apiKey);\n\t\t}\n\n\t\t// Fallback to environment variables for both provider and API key\n\t\treturn LlmClientFactory.createFromEnvironment(null);\n\t}\n\n\tprivate static boolean hasPreferenceApiKey() {\n\t\tIEclipsePreferences prefs= InstanceScope.INSTANCE.getNode(LlmPreferencePage.PLUGIN_ID);\n\t\tString apiKey= prefs.get(LlmPreferencePage.PREF_API_KEY, \"\"); //$NON-NLS-1$\n\t\tString provider= prefs.get(LlmPreferencePage.PREF_PROVIDER, \"\"); //$NON-NLS-1$\n\t\treturn !apiKey.isBlank() && !provider.isBlank();\n\t}\n\n\tprivate static boolean hasAnyEnvApiKey() {\n\t\treturn envSet(\"GEMINI_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"OPENAI_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"DEEPSEEK_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"DASHSCOPE_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"LLAMA_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"MISTRAL_API_KEY\"); //$NON-NLS-1$\n\t}\n\n\tprivate static boolean envSet(String key) {\n\t\tString value= System.getenv(key);\n\t\treturn value != null && !value.isBlank();\n\t}\n}\n"}, "382f16d8acdadcf2a84621759c2b5321672551c9": {"t": 1785755612.9194179, "n": 6018, "h": "320cfb9e2f180858", "c": "/*******************************************************************************\n * Copyright (c) 2026 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.mining.llm;\n\nimport org.eclipse.core.runtime.preferences.IEclipsePreferences;\nimport org.eclipse.core.runtime.preferences.InstanceScope;\nimport org.sandbox.jdt.internal.ui.preferences.LlmPreferencePage;\nimport org.sandbox.jdt.triggerpattern.llm.AiRuleInferenceEngine;\nimport org.sandbox.jdt.triggerpattern.llm.LlmClient;\nimport org.sandbox.jdt.triggerpattern.llm.LlmClientFactory;\nimport org.sandbox.jdt.triggerpattern.llm.LlmProvider;\n\n/**\n * Eclipse-level service that provides access to the AI rule inference engine.\n *\n * <p>This service wraps {@link LlmClientFactory} and manages the lifecycle of\n * the underlying {@link LlmClient}. It reads the LLM provider configuration\n * from Eclipse preferences first, falling back to environment variables when\n * preferences are empty.</p>\n *\n * <p>The service is a lazy-initialized singleton. The LLM client is created\n * on first access and reused for subsequent calls. Call {@link #shutdown()}\n * to release resources when the Eclipse workbench shuts down.</p>\n *\n * <p>Usage:</p>\n * <pre>\n * EclipseLlmService service = EclipseLlmService.getInstance();\n * AiRuleInferenceEngine engine = service.getEngine();\n * engine.inferRule(before, after).ifPresent(eval -> ...);\n * </pre>\n *\n * @since 1.2.6\n */\npublic class EclipseLlmService {\n\n\tprivate static final Object INSTANCE_LOCK= new Object();\n\n\tprivate static volatile EclipseLlmService instance;\n\n\tprivate final Object lifecycleLock= new Object();\n\n\tprivate LlmClient llmClient;\n\tprivate AiRuleInferenceEngine engine;\n\n\tprivate EclipseLlmService() {\n\t\t// lazy initialization\n\t}\n\n\t/**\n\t * Returns the singleton instance.\n\t *\n\t * @return the service instance\n\t */\n\tpublic static EclipseLlmService getInstance() {\n\t\tEclipseLlmService current= instance;\n\t\tif (current == null) {\n\t\t\tsynchronized (INSTANCE_LOCK) {\n\t\t\t\tcurrent= instance;\n\t\t\t\tif (current == null) {\n\t\t\t\t\tcurrent= new EclipseLlmService();\n\t\t\t\t\tinstance= current;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\t/**\n\t * Returns the AI rule inference engine, creating the LLM client if needed.\n\t *\n\t * <p>The LLM provider is resolved from Eclipse preferences first,\n\t * falling back to environment variables if preferences are empty.</p>\n\t *\n\t * <p><strong>Thread safety:</strong> The engine instance is shared across\n\t * concurrent analysis jobs. The underlying {@link LlmClient} implementations\n\t * should tolerate concurrent calls; if they do not, callers should serialize\n\t * access externally (e.g. via a scheduling rule on the analysis jobs).</p>\n\t *\n\t * @return the inference engine\n\t */\n\tpublic AiRuleInferenceEngine getEngine() {\n\t\tsynchronized (lifecycleLock) {\n\t\t\tif (engine == null) {\n\t\t\t\tllmClient= createClientFromPreferences();\n\t\t\t\tengine= new AiRuleInferenceEngine(llmClient);\n\t\t\t}\n\t\t\treturn engine;\n\t\t}\n\t}\n\n\t/**\n\t * Returns whether the LLM service is configured and available.\n\t *\n\t * <p>Checks if an API key is configured in preferences or environment.</p>\n\t *\n\t * @return {@code true} if an LLM provider can be configured\n\t */\n\tpublic boolean isAvailable() {\n\t\treturn hasPreferenceApiKey() || hasAnyEnvApiKey();\n\t}\n\n\t/**\n\t * Shuts down the service and releases the underlying LLM client.\n\t */\n\tpublic void shutdown() {\n\t\tsynchronized (lifecycleLock) {\n\t\t\tif (llmClient != null) {\n\t\t\t\tllmClient.close();\n\t\t\t\tllmClient= null;\n\t\t\t\tengine= null;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Resets the singleton (useful for testing or after preference changes).\n\t */\n\tpublic static void reset() {\n\t\tEclipseLlmService current;\n\t\tsynchronized (INSTANCE_LOCK) {\n\t\t\tcurrent= instance;\n\t\t\tinstance= null;\n\t\t}\n\t\tif (current != null) {\n\t\t\tcurrent.shutdown();\n\t\t}\n\t}\n\n\t/**\n\t * Creates an LLM client using Eclipse preferences, falling back to env vars.\n\t *\n\t * <p>When both provider and API key are set in preferences, the client is\n\t * constructed with the explicit API key from preferences. If only the\n\t * provider is set, the provider-specific environment variable is used.\n\t * If neither is set, auto-detection from environment variables is used.</p>\n\t */\n\tprivate static LlmClient createClientFromPreferences() {\n\t\tIEclipsePreferences prefs= InstanceScope.INSTANCE.getNode(LlmPreferencePage.PLUGIN_ID);\n\t\tString provider= prefs.get(LlmPreferencePage.PREF_PROVIDER, \"\"); //$NON-NLS-1$\n\t\tString apiKey= prefs.get(LlmPreferencePage.PREF_API_KEY, \"\"); //$NON-NLS-1$\n\n\t\tif (!provider.isBlank()) {\n\t\t\t// Use preference provider and API key (key may be blank → client reads env)\n\t\t\treturn LlmClientFactory.create(\n\t\t\t\t\tLlmProvider.fromString(provider),\n\t\t\t\t\tapiKey.isBlank() ? null : apiKey);\n\t\t}\n\n\t\t// Fallback to environment variables for both provider and API key\n\t\treturn LlmClientFactory.createFromEnvironment(null);\n\t}\n\n\tprivate static boolean hasPreferenceApiKey() {\n\t\tIEclipsePreferences prefs= InstanceScope.INSTANCE.getNode(LlmPreferencePage.PLUGIN_ID);\n\t\tString apiKey= prefs.get(LlmPreferencePage.PREF_API_KEY, \"\"); //$NON-NLS-1$\n\t\tString provider= prefs.get(LlmPreferencePage.PREF_PROVIDER, \"\"); //$NON-NLS-1$\n\t\treturn !apiKey.isBlank() && !provider.isBlank();\n\t}\n\n\tprivate static boolean hasAnyEnvApiKey() {\n\t\treturn envSet(\"GEMINI_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"OPENAI_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"DEEPSEEK_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"DASHSCOPE_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"LLAMA_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"MISTRAL_API_KEY\"); //$NON-NLS-1$\n\t}\n\n\tprivate static boolean envSet(String key) {\n\t\tString value= System.getenv(key);\n\t\treturn value != null && !value.isBlank();\n\t}\n}\n"}, "1ca51a360072538cc269fcf403ba5e8735a7e9cf": {"t": 1785755614.4108362, "n": 5717, "h": "9ca80f15b24daa82", "c": "/*******************************************************************************\n * Copyright (c) 2026 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.mining.llm;\n\nimport org.eclipse.core.runtime.preferences.IEclipsePreferences;\nimport org.eclipse.core.runtime.preferences.InstanceScope;\nimport org.sandbox.jdt.internal.ui.preferences.LlmPreferencePage;\nimport org.sandbox.jdt.triggerpattern.llm.AiRuleInferenceEngine;\nimport org.sandbox.jdt.triggerpattern.llm.LlmClient;\nimport org.sandbox.jdt.triggerpattern.llm.LlmClientFactory;\nimport org.sandbox.jdt.triggerpattern.llm.LlmProvider;\n\n/**\n * Eclipse-level service that provides access to the AI rule inference engine.\n *\n * <p>This service wraps {@link LlmClientFactory} and manages the lifecycle of\n * the underlying {@link LlmClient}. It reads the LLM provider configuration\n * from Eclipse preferences first, falling back to environment variables when\n * preferences are empty.</p>\n *\n * <p>The service is a lazy-initialized singleton. The LLM client is created\n * on first access and reused for subsequent calls. Call {@link #shutdown()}\n * to release resources when the Eclipse workbench shuts down.</p>\n *\n * <p>Usage:</p>\n * <pre>\n * EclipseLlmService service = EclipseLlmService.getInstance();\n * AiRuleInferenceEngine engine = service.getEngine();\n * engine.inferRule(before, after).ifPresent(eval -> ...);\n * </pre>\n *\n * @since 1.2.6\n */\npublic class EclipseLlmService {\n\n\tprivate static volatile EclipseLlmService instance;\n\n\tprivate LlmClient llmClient;\n\tprivate AiRuleInferenceEngine engine;\n\n\tprivate EclipseLlmService() {\n\t\t// lazy initialization\n\t}\n\n\t/**\n\t * Returns the singleton instance.\n\t *\n\t * @return the service instance\n\t */\n\tpublic static EclipseLlmService getInstance() {\n\t\tif (instance == null) {\n\t\t\tsynchronized (EclipseLlmService.class) {\n\t\t\t\tif (instance == null) {\n\t\t\t\t\tinstance = new EclipseLlmService();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn instance;\n\t}\n\n\t/**\n\t * Returns the AI rule inference engine, creating the LLM client if needed.\n\t *\n\t * <p>The LLM provider is resolved from Eclipse preferences first,\n\t * falling back to environment variables if preferences are empty.</p>\n\t *\n\t * <p><strong>Thread safety:</strong> The engine instance is shared across\n\t * concurrent analysis jobs. The underlying {@link LlmClient} implementations\n\t * should tolerate concurrent calls; if they do not, callers should serialize\n\t * access externally (e.g. via a scheduling rule on the analysis jobs).</p>\n\t *\n\t * @return the inference engine\n\t */\n\tpublic synchronized AiRuleInferenceEngine getEngine() {\n\t\tif (engine == null) {\n\t\t\tllmClient = createClientFromPreferences();\n\t\t\tengine = new AiRuleInferenceEngine(llmClient);\n\t\t}\n\t\treturn engine;\n\t}\n\n\t/**\n\t * Returns whether the LLM service is configured and available.\n\t *\n\t * <p>Checks if an API key is configured in preferences or environment.</p>\n\t *\n\t * @return {@code true} if an LLM provider can be configured\n\t */\n\tpublic boolean isAvailable() {\n\t\treturn hasPreferenceApiKey() || hasAnyEnvApiKey();\n\t}\n\n\t/**\n\t * Shuts down the service and releases the underlying LLM client.\n\t */\n\tpublic synchronized void shutdown() {\n\t\tif (llmClient != null) {\n\t\t\tllmClient.close();\n\t\t\tllmClient = null;\n\t\t\tengine = null;\n\t\t}\n\t}\n\n\t/**\n\t * Resets the singleton (useful for testing or after preference changes).\n\t */\n\tpublic static synchronized void reset() {\n\t\tif (instance != null) {\n\t\t\tinstance.shutdown();\n\t\t\tinstance = null;\n\t\t}\n\t}\n\n\t/**\n\t * Creates an LLM client using Eclipse preferences, falling back to env vars.\n\t *\n\t * <p>When both provider and API key are set in preferences, the client is\n\t * constructed with the explicit API key from preferences. If only the\n\t * provider is set, the provider-specific environment variable is used.\n\t * If neither is set, auto-detection from environment variables is used.</p>\n\t */\n\tprivate static LlmClient createClientFromPreferences() {\n\t\tIEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(LlmPreferencePage.PLUGIN_ID);\n\t\tString provider = prefs.get(LlmPreferencePage.PREF_PROVIDER, \"\"); //$NON-NLS-1$\n\t\tString apiKey = prefs.get(LlmPreferencePage.PREF_API_KEY, \"\"); //$NON-NLS-1$\n\n\t\tif (!provider.isBlank()) {\n\t\t\t// Use preference provider and API key (key may be blank → client reads env)\n\t\t\treturn LlmClientFactory.create(\n\t\t\t\t\tLlmProvider.fromString(provider),\n\t\t\t\t\tapiKey.isBlank() ? null : apiKey);\n\t\t}\n\n\t\t// Fallback to environment variables for both provider and API key\n\t\treturn LlmClientFactory.createFromEnvironment(null);\n\t}\n\n\tprivate static boolean hasPreferenceApiKey() {\n\t\tIEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(LlmPreferencePage.PLUGIN_ID);\n\t\tString apiKey = prefs.get(LlmPreferencePage.PREF_API_KEY, \"\"); //$NON-NLS-1$\n\t\tString provider = prefs.get(LlmPreferencePage.PREF_PROVIDER, \"\"); //$NON-NLS-1$\n\t\treturn !apiKey.isBlank() && !provider.isBlank();\n\t}\n\n\tprivate static boolean hasAnyEnvApiKey() {\n\t\treturn envSet(\"GEMINI_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"OPENAI_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"DEEPSEEK_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"DASHSCOPE_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"LLAMA_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"MISTRAL_API_KEY\"); //$NON-NLS-1$\n\t}\n\n\tprivate static boolean envSet(String key) {\n\t\tString value = System.getenv(key);\n\t\treturn value != null && !value.isBlank();\n\t}\n}\n"}, "2d6c2882b914522c51b50014638be12ade13cfc5": {"t": 1786067438.8098624, "n": 6018, "h": "320cfb9e2f180858", "c": "/*******************************************************************************\n * Copyright (c) 2026 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.mining.llm;\n\nimport org.eclipse.core.runtime.preferences.IEclipsePreferences;\nimport org.eclipse.core.runtime.preferences.InstanceScope;\nimport org.sandbox.jdt.internal.ui.preferences.LlmPreferencePage;\nimport org.sandbox.jdt.triggerpattern.llm.AiRuleInferenceEngine;\nimport org.sandbox.jdt.triggerpattern.llm.LlmClient;\nimport org.sandbox.jdt.triggerpattern.llm.LlmClientFactory;\nimport org.sandbox.jdt.triggerpattern.llm.LlmProvider;\n\n/**\n * Eclipse-level service that provides access to the AI rule inference engine.\n *\n * <p>This service wraps {@link LlmClientFactory} and manages the lifecycle of\n * the underlying {@link LlmClient}. It reads the LLM provider configuration\n * from Eclipse preferences first, falling back to environment variables when\n * preferences are empty.</p>\n *\n * <p>The service is a lazy-initialized singleton. The LLM client is created\n * on first access and reused for subsequent calls. Call {@link #shutdown()}\n * to release resources when the Eclipse workbench shuts down.</p>\n *\n * <p>Usage:</p>\n * <pre>\n * EclipseLlmService service = EclipseLlmService.getInstance();\n * AiRuleInferenceEngine engine = service.getEngine();\n * engine.inferRule(before, after).ifPresent(eval -> ...);\n * </pre>\n *\n * @since 1.2.6\n */\npublic class EclipseLlmService {\n\n\tprivate static final Object INSTANCE_LOCK= new Object();\n\n\tprivate static volatile EclipseLlmService instance;\n\n\tprivate final Object lifecycleLock= new Object();\n\n\tprivate LlmClient llmClient;\n\tprivate AiRuleInferenceEngine engine;\n\n\tprivate EclipseLlmService() {\n\t\t// lazy initialization\n\t}\n\n\t/**\n\t * Returns the singleton instance.\n\t *\n\t * @return the service instance\n\t */\n\tpublic static EclipseLlmService getInstance() {\n\t\tEclipseLlmService current= instance;\n\t\tif (current == null) {\n\t\t\tsynchronized (INSTANCE_LOCK) {\n\t\t\t\tcurrent= instance;\n\t\t\t\tif (current == null) {\n\t\t\t\t\tcurrent= new EclipseLlmService();\n\t\t\t\t\tinstance= current;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\t/**\n\t * Returns the AI rule inference engine, creating the LLM client if needed.\n\t *\n\t * <p>The LLM provider is resolved from Eclipse preferences first,\n\t * falling back to environment variables if preferences are empty.</p>\n\t *\n\t * <p><strong>Thread safety:</strong> The engine instance is shared across\n\t * concurrent analysis jobs. The underlying {@link LlmClient} implementations\n\t * should tolerate concurrent calls; if they do not, callers should serialize\n\t * access externally (e.g. via a scheduling rule on the analysis jobs).</p>\n\t *\n\t * @return the inference engine\n\t */\n\tpublic AiRuleInferenceEngine getEngine() {\n\t\tsynchronized (lifecycleLock) {\n\t\t\tif (engine == null) {\n\t\t\t\tllmClient= createClientFromPreferences();\n\t\t\t\tengine= new AiRuleInferenceEngine(llmClient);\n\t\t\t}\n\t\t\treturn engine;\n\t\t}\n\t}\n\n\t/**\n\t * Returns whether the LLM service is configured and available.\n\t *\n\t * <p>Checks if an API key is configured in preferences or environment.</p>\n\t *\n\t * @return {@code true} if an LLM provider can be configured\n\t */\n\tpublic boolean isAvailable() {\n\t\treturn hasPreferenceApiKey() || hasAnyEnvApiKey();\n\t}\n\n\t/**\n\t * Shuts down the service and releases the underlying LLM client.\n\t */\n\tpublic void shutdown() {\n\t\tsynchronized (lifecycleLock) {\n\t\t\tif (llmClient != null) {\n\t\t\t\tllmClient.close();\n\t\t\t\tllmClient= null;\n\t\t\t\tengine= null;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Resets the singleton (useful for testing or after preference changes).\n\t */\n\tpublic static void reset() {\n\t\tEclipseLlmService current;\n\t\tsynchronized (INSTANCE_LOCK) {\n\t\t\tcurrent= instance;\n\t\t\tinstance= null;\n\t\t}\n\t\tif (current != null) {\n\t\t\tcurrent.shutdown();\n\t\t}\n\t}\n\n\t/**\n\t * Creates an LLM client using Eclipse preferences, falling back to env vars.\n\t *\n\t * <p>When both provider and API key are set in preferences, the client is\n\t * constructed with the explicit API key from preferences. If only the\n\t * provider is set, the provider-specific environment variable is used.\n\t * If neither is set, auto-detection from environment variables is used.</p>\n\t */\n\tprivate static LlmClient createClientFromPreferences() {\n\t\tIEclipsePreferences prefs= InstanceScope.INSTANCE.getNode(LlmPreferencePage.PLUGIN_ID);\n\t\tString provider= prefs.get(LlmPreferencePage.PREF_PROVIDER, \"\"); //$NON-NLS-1$\n\t\tString apiKey= prefs.get(LlmPreferencePage.PREF_API_KEY, \"\"); //$NON-NLS-1$\n\n\t\tif (!provider.isBlank()) {\n\t\t\t// Use preference provider and API key (key may be blank → client reads env)\n\t\t\treturn LlmClientFactory.create(\n\t\t\t\t\tLlmProvider.fromString(provider),\n\t\t\t\t\tapiKey.isBlank() ? null : apiKey);\n\t\t}\n\n\t\t// Fallback to environment variables for both provider and API key\n\t\treturn LlmClientFactory.createFromEnvironment(null);\n\t}\n\n\tprivate static boolean hasPreferenceApiKey() {\n\t\tIEclipsePreferences prefs= InstanceScope.INSTANCE.getNode(LlmPreferencePage.PLUGIN_ID);\n\t\tString apiKey= prefs.get(LlmPreferencePage.PREF_API_KEY, \"\"); //$NON-NLS-1$\n\t\tString provider= prefs.get(LlmPreferencePage.PREF_PROVIDER, \"\"); //$NON-NLS-1$\n\t\treturn !apiKey.isBlank() && !provider.isBlank();\n\t}\n\n\tprivate static boolean hasAnyEnvApiKey() {\n\t\treturn envSet(\"GEMINI_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"OPENAI_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"DEEPSEEK_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"DASHSCOPE_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"LLAMA_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"MISTRAL_API_KEY\"); //$NON-NLS-1$\n\t}\n\n\tprivate static boolean envSet(String key) {\n\t\tString value= System.getenv(key);\n\t\treturn value != null && !value.isBlank();\n\t}\n}\n"}, "bb73965038dd4f4380c05a4e9a181cc0123cacbf": {"t": 1786068302.0290773, "n": 6018, "h": "320cfb9e2f180858", "c": "/*******************************************************************************\n * Copyright (c) 2026 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.mining.llm;\n\nimport org.eclipse.core.runtime.preferences.IEclipsePreferences;\nimport org.eclipse.core.runtime.preferences.InstanceScope;\nimport org.sandbox.jdt.internal.ui.preferences.LlmPreferencePage;\nimport org.sandbox.jdt.triggerpattern.llm.AiRuleInferenceEngine;\nimport org.sandbox.jdt.triggerpattern.llm.LlmClient;\nimport org.sandbox.jdt.triggerpattern.llm.LlmClientFactory;\nimport org.sandbox.jdt.triggerpattern.llm.LlmProvider;\n\n/**\n * Eclipse-level service that provides access to the AI rule inference engine.\n *\n * <p>This service wraps {@link LlmClientFactory} and manages the lifecycle of\n * the underlying {@link LlmClient}. It reads the LLM provider configuration\n * from Eclipse preferences first, falling back to environment variables when\n * preferences are empty.</p>\n *\n * <p>The service is a lazy-initialized singleton. The LLM client is created\n * on first access and reused for subsequent calls. Call {@link #shutdown()}\n * to release resources when the Eclipse workbench shuts down.</p>\n *\n * <p>Usage:</p>\n * <pre>\n * EclipseLlmService service = EclipseLlmService.getInstance();\n * AiRuleInferenceEngine engine = service.getEngine();\n * engine.inferRule(before, after).ifPresent(eval -> ...);\n * </pre>\n *\n * @since 1.2.6\n */\npublic class EclipseLlmService {\n\n\tprivate static final Object INSTANCE_LOCK= new Object();\n\n\tprivate static volatile EclipseLlmService instance;\n\n\tprivate final Object lifecycleLock= new Object();\n\n\tprivate LlmClient llmClient;\n\tprivate AiRuleInferenceEngine engine;\n\n\tprivate EclipseLlmService() {\n\t\t// lazy initialization\n\t}\n\n\t/**\n\t * Returns the singleton instance.\n\t *\n\t * @return the service instance\n\t */\n\tpublic static EclipseLlmService getInstance() {\n\t\tEclipseLlmService current= instance;\n\t\tif (current == null) {\n\t\t\tsynchronized (INSTANCE_LOCK) {\n\t\t\t\tcurrent= instance;\n\t\t\t\tif (current == null) {\n\t\t\t\t\tcurrent= new EclipseLlmService();\n\t\t\t\t\tinstance= current;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn current;\n\t}\n\n\t/**\n\t * Returns the AI rule inference engine, creating the LLM client if needed.\n\t *\n\t * <p>The LLM provider is resolved from Eclipse preferences first,\n\t * falling back to environment variables if preferences are empty.</p>\n\t *\n\t * <p><strong>Thread safety:</strong> The engine instance is shared across\n\t * concurrent analysis jobs. The underlying {@link LlmClient} implementations\n\t * should tolerate concurrent calls; if they do not, callers should serialize\n\t * access externally (e.g. via a scheduling rule on the analysis jobs).</p>\n\t *\n\t * @return the inference engine\n\t */\n\tpublic AiRuleInferenceEngine getEngine() {\n\t\tsynchronized (lifecycleLock) {\n\t\t\tif (engine == null) {\n\t\t\t\tllmClient= createClientFromPreferences();\n\t\t\t\tengine= new AiRuleInferenceEngine(llmClient);\n\t\t\t}\n\t\t\treturn engine;\n\t\t}\n\t}\n\n\t/**\n\t * Returns whether the LLM service is configured and available.\n\t *\n\t * <p>Checks if an API key is configured in preferences or environment.</p>\n\t *\n\t * @return {@code true} if an LLM provider can be configured\n\t */\n\tpublic boolean isAvailable() {\n\t\treturn hasPreferenceApiKey() || hasAnyEnvApiKey();\n\t}\n\n\t/**\n\t * Shuts down the service and releases the underlying LLM client.\n\t */\n\tpublic void shutdown() {\n\t\tsynchronized (lifecycleLock) {\n\t\t\tif (llmClient != null) {\n\t\t\t\tllmClient.close();\n\t\t\t\tllmClient= null;\n\t\t\t\tengine= null;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Resets the singleton (useful for testing or after preference changes).\n\t */\n\tpublic static void reset() {\n\t\tEclipseLlmService current;\n\t\tsynchronized (INSTANCE_LOCK) {\n\t\t\tcurrent= instance;\n\t\t\tinstance= null;\n\t\t}\n\t\tif (current != null) {\n\t\t\tcurrent.shutdown();\n\t\t}\n\t}\n\n\t/**\n\t * Creates an LLM client using Eclipse preferences, falling back to env vars.\n\t *\n\t * <p>When both provider and API key are set in preferences, the client is\n\t * constructed with the explicit API key from preferences. If only the\n\t * provider is set, the provider-specific environment variable is used.\n\t * If neither is set, auto-detection from environment variables is used.</p>\n\t */\n\tprivate static LlmClient createClientFromPreferences() {\n\t\tIEclipsePreferences prefs= InstanceScope.INSTANCE.getNode(LlmPreferencePage.PLUGIN_ID);\n\t\tString provider= prefs.get(LlmPreferencePage.PREF_PROVIDER, \"\"); //$NON-NLS-1$\n\t\tString apiKey= prefs.get(LlmPreferencePage.PREF_API_KEY, \"\"); //$NON-NLS-1$\n\n\t\tif (!provider.isBlank()) {\n\t\t\t// Use preference provider and API key (key may be blank → client reads env)\n\t\t\treturn LlmClientFactory.create(\n\t\t\t\t\tLlmProvider.fromString(provider),\n\t\t\t\t\tapiKey.isBlank() ? null : apiKey);\n\t\t}\n\n\t\t// Fallback to environment variables for both provider and API key\n\t\treturn LlmClientFactory.createFromEnvironment(null);\n\t}\n\n\tprivate static boolean hasPreferenceApiKey() {\n\t\tIEclipsePreferences prefs= InstanceScope.INSTANCE.getNode(LlmPreferencePage.PLUGIN_ID);\n\t\tString apiKey= prefs.get(LlmPreferencePage.PREF_API_KEY, \"\"); //$NON-NLS-1$\n\t\tString provider= prefs.get(LlmPreferencePage.PREF_PROVIDER, \"\"); //$NON-NLS-1$\n\t\treturn !apiKey.isBlank() && !provider.isBlank();\n\t}\n\n\tprivate static boolean hasAnyEnvApiKey() {\n\t\treturn envSet(\"GEMINI_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"OPENAI_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"DEEPSEEK_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"DASHSCOPE_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"LLAMA_API_KEY\") //$NON-NLS-1$\n\t\t\t\t|| envSet(\"MISTRAL_API_KEY\"); //$NON-NLS-1$\n\t}\n\n\tprivate static boolean envSet(String key) {\n\t\tString value= System.getenv(key);\n\t\treturn value != null && !value.isBlank();\n\t}\n}\n"}}, "sandbox_common_core/src/main/java/org/sandbox/jdt/triggerpattern/llm/LlmClientFactory.java": {"f7b7e527311e5ee87c60ef2387d901108475e79e": {"t": 1785745706.1873393, "n": 4186, "h": "b56d04911d6e5696", "c": "/*******************************************************************************\n * Copyright (c) 2025 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.llm;\n\n/**\n * Factory for creating {@link LlmClient} instances.\n */\npublic class LlmClientFactory {\n\nprivate LlmClientFactory() {\n// utility class\n}\n\n/**\n * Creates an {@link LlmClient} for the given provider.\n *\n * @param provider the LLM provider to use\n * @return the configured client\n */\npublic static LlmClient create(LlmProvider provider) {\nreturn switch (provider) {\ncase GEMINI -> new GeminiClient();\ncase OPENAI -> new OpenAiClient();\ncase DEEPSEEK -> new DeepSeekClient();\ncase QWEN -> new QwenClient();\ncase LLAMA -> new LlamaClient();\ncase MISTRAL -> new MistralClient();\n};\n}\n\n/**\n * Creates an {@link LlmClient} for the given provider with an explicit API key.\n *\n * <p>Use this overload when the API key is available from Eclipse preferences\n * or another non-environment source. If {@code apiKey} is {@code null} or blank,\n * the client falls back to reading from its provider-specific environment variable.</p>\n *\n * @param provider the LLM provider to use\n * @param apiKey explicit API key (may be {@code null} or blank to fall back to env)\n * @return the configured client\n */\npublic static LlmClient create(LlmProvider provider, String apiKey) {\nif (apiKey == null || apiKey.isBlank()) {\nreturn create(provider);\n}\nreturn switch (provider) {\ncase GEMINI -> new GeminiClient(apiKey);\ncase OPENAI -> new OpenAiClient(apiKey);\ncase DEEPSEEK -> new DeepSeekClient(apiKey);\ncase QWEN -> new QwenClient(apiKey);\ncase LLAMA -> new LlamaClient(apiKey);\ncase MISTRAL -> new MistralClient(apiKey);\n};\n}\n\n/**\n * Creates an {@link LlmClient} by auto-detecting the provider.\n *\n * <p>Priority order:\n * <ol>\n * <li>Explicit CLI argument ({@code explicitProvider} parameter, non-blank)</li>\n * <li>Environment variable {@code LLM_PROVIDER}</li>\n * <li>Auto-detect from available API keys:\n * <ul>\n * <li>If {@code OPENAI_API_KEY} is set → {@link LlmProvider#OPENAI}</li>\n * <li>If {@code GEMINI_API_KEY} is set → {@link LlmProvider#GEMINI}</li>\n * </ul>\n * </li>\n * <li>Default: {@link LlmProvider#GEMINI}</li>\n * </ol>\n *\n * @param explicitProvider explicit provider name from CLI (may be null or blank)\n * @return the configured client\n */\npublic static LlmClient createFromEnvironment(String explicitProvider) {\n// 1. Explicit CLI argument\nif (explicitProvider != null && !explicitProvider.isBlank()) {\nreturn create(LlmProvider.fromString(explicitProvider));\n}\n\n// 2. Environment variable\nString envProvider = System.getenv(\"LLM_PROVIDER\"); //$NON-NLS-1$\nif (envProvider != null && !envProvider.isBlank()) {\nreturn create(LlmProvider.fromString(envProvider));\n}\n\n// 3. Auto-detect from available API keys\nString openAiKey = System.getenv(\"OPENAI_API_KEY\"); //$NON-NLS-1$\nif (openAiKey != null && !openAiKey.isBlank()) {\nreturn create(LlmProvider.OPENAI);\n}\nString geminiKey = System.getenv(\"GEMINI_API_KEY\"); //$NON-NLS-1$\nif (geminiKey != null && !geminiKey.isBlank()) {\nreturn create(LlmProvider.GEMINI);\n}\nString deepSeekKey = System.getenv(\"DEEPSEEK_API_KEY\"); //$NON-NLS-1$\nif (deepSeekKey != null && !deepSeekKey.isBlank()) {\nreturn create(LlmProvider.DEEPSEEK);\n}\nString dashScopeKey = System.getenv(\"DASHSCOPE_API_KEY\"); //$NON-NLS-1$\nif (dashScopeKey != null && !dashScopeKey.isBlank()) {\nreturn create(LlmProvider.QWEN);\n}\nString llamaKey = System.getenv(\"LLAMA_API_KEY\"); //$NON-NLS-1$\nif (llamaKey != null && !llamaKey.isBlank()) {\nreturn create(LlmProvider.LLAMA);\n}\nString mistralKey = System.getenv(\"MISTRAL_API_KEY\"); //$NON-NLS-1$\nif (mistralKey != null && !mistralKey.isBlank()) {\nreturn create(LlmProvider.MISTRAL);\n}\n\n// 4. Default: GEMINI\nreturn create(LlmProvider.GEMINI);\n}\n}\n"}, "1ca51a360072538cc269fcf403ba5e8735a7e9cf": {"t": 1785755613.277695, "n": 4186, "h": "b56d04911d6e5696", "c": "/*******************************************************************************\n * Copyright (c) 2025 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.llm;\n\n/**\n * Factory for creating {@link LlmClient} instances.\n */\npublic class LlmClientFactory {\n\nprivate LlmClientFactory() {\n// utility class\n}\n\n/**\n * Creates an {@link LlmClient} for the given provider.\n *\n * @param provider the LLM provider to use\n * @return the configured client\n */\npublic static LlmClient create(LlmProvider provider) {\nreturn switch (provider) {\ncase GEMINI -> new GeminiClient();\ncase OPENAI -> new OpenAiClient();\ncase DEEPSEEK -> new DeepSeekClient();\ncase QWEN -> new QwenClient();\ncase LLAMA -> new LlamaClient();\ncase MISTRAL -> new MistralClient();\n};\n}\n\n/**\n * Creates an {@link LlmClient} for the given provider with an explicit API key.\n *\n * <p>Use this overload when the API key is available from Eclipse preferences\n * or another non-environment source. If {@code apiKey} is {@code null} or blank,\n * the client falls back to reading from its provider-specific environment variable.</p>\n *\n * @param provider the LLM provider to use\n * @param apiKey explicit API key (may be {@code null} or blank to fall back to env)\n * @return the configured client\n */\npublic static LlmClient create(LlmProvider provider, String apiKey) {\nif (apiKey == null || apiKey.isBlank()) {\nreturn create(provider);\n}\nreturn switch (provider) {\ncase GEMINI -> new GeminiClient(apiKey);\ncase OPENAI -> new OpenAiClient(apiKey);\ncase DEEPSEEK -> new DeepSeekClient(apiKey);\ncase QWEN -> new QwenClient(apiKey);\ncase LLAMA -> new LlamaClient(apiKey);\ncase MISTRAL -> new MistralClient(apiKey);\n};\n}\n\n/**\n * Creates an {@link LlmClient} by auto-detecting the provider.\n *\n * <p>Priority order:\n * <ol>\n * <li>Explicit CLI argument ({@code explicitProvider} parameter, non-blank)</li>\n * <li>Environment variable {@code LLM_PROVIDER}</li>\n * <li>Auto-detect from available API keys:\n * <ul>\n * <li>If {@code OPENAI_API_KEY} is set → {@link LlmProvider#OPENAI}</li>\n * <li>If {@code GEMINI_API_KEY} is set → {@link LlmProvider#GEMINI}</li>\n * </ul>\n * </li>\n * <li>Default: {@link LlmProvider#GEMINI}</li>\n * </ol>\n *\n * @param explicitProvider explicit provider name from CLI (may be null or blank)\n * @return the configured client\n */\npublic static LlmClient createFromEnvironment(String explicitProvider) {\n// 1. Explicit CLI argument\nif (explicitProvider != null && !explicitProvider.isBlank()) {\nreturn create(LlmProvider.fromString(explicitProvider));\n}\n\n// 2. Environment variable\nString envProvider = System.getenv(\"LLM_PROVIDER\"); //$NON-NLS-1$\nif (envProvider != null && !envProvider.isBlank()) {\nreturn create(LlmProvider.fromString(envProvider));\n}\n\n// 3. Auto-detect from available API keys\nString openAiKey = System.getenv(\"OPENAI_API_KEY\"); //$NON-NLS-1$\nif (openAiKey != null && !openAiKey.isBlank()) {\nreturn create(LlmProvider.OPENAI);\n}\nString geminiKey = System.getenv(\"GEMINI_API_KEY\"); //$NON-NLS-1$\nif (geminiKey != null && !geminiKey.isBlank()) {\nreturn create(LlmProvider.GEMINI);\n}\nString deepSeekKey = System.getenv(\"DEEPSEEK_API_KEY\"); //$NON-NLS-1$\nif (deepSeekKey != null && !deepSeekKey.isBlank()) {\nreturn create(LlmProvider.DEEPSEEK);\n}\nString dashScopeKey = System.getenv(\"DASHSCOPE_API_KEY\"); //$NON-NLS-1$\nif (dashScopeKey != null && !dashScopeKey.isBlank()) {\nreturn create(LlmProvider.QWEN);\n}\nString llamaKey = System.getenv(\"LLAMA_API_KEY\"); //$NON-NLS-1$\nif (llamaKey != null && !llamaKey.isBlank()) {\nreturn create(LlmProvider.LLAMA);\n}\nString mistralKey = System.getenv(\"MISTRAL_API_KEY\"); //$NON-NLS-1$\nif (mistralKey != null && !mistralKey.isBlank()) {\nreturn create(LlmProvider.MISTRAL);\n}\n\n// 4. Default: GEMINI\nreturn create(LlmProvider.GEMINI);\n}\n}\n"}, "4ee92210601b76d9b315c3a003e520daf9af2ba7": {"t": 1785755614.6808112, "n": 3260, "h": "790d22e9d2982423", "c": "/*******************************************************************************\n * Copyright (c) 2025 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.llm;\n\n/**\n * Factory for creating {@link LlmClient} instances.\n */\npublic class LlmClientFactory {\n\nprivate LlmClientFactory() {\n// utility class\n}\n\n/**\n * Creates an {@link LlmClient} for the given provider.\n *\n * @param provider the LLM provider to use\n * @return the configured client\n */\npublic static LlmClient create(LlmProvider provider) {\nreturn switch (provider) {\ncase GEMINI -> new GeminiClient();\ncase OPENAI -> new OpenAiClient();\ncase DEEPSEEK -> new DeepSeekClient();\ncase QWEN -> new QwenClient();\ncase LLAMA -> new LlamaClient();\ncase MISTRAL -> new MistralClient();\n};\n}\n\n/**\n * Creates an {@link LlmClient} by auto-detecting the provider.\n *\n * <p>Priority order:\n * <ol>\n * <li>Explicit CLI argument ({@code explicitProvider} parameter, non-blank)</li>\n * <li>Environment variable {@code LLM_PROVIDER}</li>\n * <li>Auto-detect from available API keys:\n * <ul>\n * <li>If {@code OPENAI_API_KEY} is set → {@link LlmProvider#OPENAI}</li>\n * <li>If {@code GEMINI_API_KEY} is set → {@link LlmProvider#GEMINI}</li>\n * </ul>\n * </li>\n * <li>Default: {@link LlmProvider#GEMINI}</li>\n * </ol>\n *\n * @param explicitProvider explicit provider name from CLI (may be null or blank)\n * @return the configured client\n */\npublic static LlmClient createFromEnvironment(String explicitProvider) {\n// 1. Explicit CLI argument\nif (explicitProvider != null && !explicitProvider.isBlank()) {\nreturn create(LlmProvider.fromString(explicitProvider));\n}\n\n// 2. Environment variable\nString envProvider = System.getenv(\"LLM_PROVIDER\"); //$NON-NLS-1$\nif (envProvider != null && !envProvider.isBlank()) {\nreturn create(LlmProvider.fromString(envProvider));\n}\n\n// 3. Auto-detect from available API keys\nString openAiKey = System.getenv(\"OPENAI_API_KEY\"); //$NON-NLS-1$\nif (openAiKey != null && !openAiKey.isBlank()) {\nreturn create(LlmProvider.OPENAI);\n}\nString geminiKey = System.getenv(\"GEMINI_API_KEY\"); //$NON-NLS-1$\nif (geminiKey != null && !geminiKey.isBlank()) {\nreturn create(LlmProvider.GEMINI);\n}\nString deepSeekKey = System.getenv(\"DEEPSEEK_API_KEY\"); //$NON-NLS-1$\nif (deepSeekKey != null && !deepSeekKey.isBlank()) {\nreturn create(LlmProvider.DEEPSEEK);\n}\nString dashScopeKey = System.getenv(\"DASHSCOPE_API_KEY\"); //$NON-NLS-1$\nif (dashScopeKey != null && !dashScopeKey.isBlank()) {\nreturn create(LlmProvider.QWEN);\n}\nString llamaKey = System.getenv(\"LLAMA_API_KEY\"); //$NON-NLS-1$\nif (llamaKey != null && !llamaKey.isBlank()) {\nreturn create(LlmProvider.LLAMA);\n}\nString mistralKey = System.getenv(\"MISTRAL_API_KEY\"); //$NON-NLS-1$\nif (mistralKey != null && !mistralKey.isBlank()) {\nreturn create(LlmProvider.MISTRAL);\n}\n\n// 4. Default: GEMINI\nreturn create(LlmProvider.GEMINI);\n}\n}\n"}, "2d6c2882b914522c51b50014638be12ade13cfc5": {"t": 1786067441.407477, "n": 4186, "h": "b56d04911d6e5696", "c": "/*******************************************************************************\n * Copyright (c) 2025 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.llm;\n\n/**\n * Factory for creating {@link LlmClient} instances.\n */\npublic class LlmClientFactory {\n\nprivate LlmClientFactory() {\n// utility class\n}\n\n/**\n * Creates an {@link LlmClient} for the given provider.\n *\n * @param provider the LLM provider to use\n * @return the configured client\n */\npublic static LlmClient create(LlmProvider provider) {\nreturn switch (provider) {\ncase GEMINI -> new GeminiClient();\ncase OPENAI -> new OpenAiClient();\ncase DEEPSEEK -> new DeepSeekClient();\ncase QWEN -> new QwenClient();\ncase LLAMA -> new LlamaClient();\ncase MISTRAL -> new MistralClient();\n};\n}\n\n/**\n * Creates an {@link LlmClient} for the given provider with an explicit API key.\n *\n * <p>Use this overload when the API key is available from Eclipse preferences\n * or another non-environment source. If {@code apiKey} is {@code null} or blank,\n * the client falls back to reading from its provider-specific environment variable.</p>\n *\n * @param provider the LLM provider to use\n * @param apiKey explicit API key (may be {@code null} or blank to fall back to env)\n * @return the configured client\n */\npublic static LlmClient create(LlmProvider provider, String apiKey) {\nif (apiKey == null || apiKey.isBlank()) {\nreturn create(provider);\n}\nreturn switch (provider) {\ncase GEMINI -> new GeminiClient(apiKey);\ncase OPENAI -> new OpenAiClient(apiKey);\ncase DEEPSEEK -> new DeepSeekClient(apiKey);\ncase QWEN -> new QwenClient(apiKey);\ncase LLAMA -> new LlamaClient(apiKey);\ncase MISTRAL -> new MistralClient(apiKey);\n};\n}\n\n/**\n * Creates an {@link LlmClient} by auto-detecting the provider.\n *\n * <p>Priority order:\n * <ol>\n * <li>Explicit CLI argument ({@code explicitProvider} parameter, non-blank)</li>\n * <li>Environment variable {@code LLM_PROVIDER}</li>\n * <li>Auto-detect from available API keys:\n * <ul>\n * <li>If {@code OPENAI_API_KEY} is set → {@link LlmProvider#OPENAI}</li>\n * <li>If {@code GEMINI_API_KEY} is set → {@link LlmProvider#GEMINI}</li>\n * </ul>\n * </li>\n * <li>Default: {@link LlmProvider#GEMINI}</li>\n * </ol>\n *\n * @param explicitProvider explicit provider name from CLI (may be null or blank)\n * @return the configured client\n */\npublic static LlmClient createFromEnvironment(String explicitProvider) {\n// 1. Explicit CLI argument\nif (explicitProvider != null && !explicitProvider.isBlank()) {\nreturn create(LlmProvider.fromString(explicitProvider));\n}\n\n// 2. Environment variable\nString envProvider = System.getenv(\"LLM_PROVIDER\"); //$NON-NLS-1$\nif (envProvider != null && !envProvider.isBlank()) {\nreturn create(LlmProvider.fromString(envProvider));\n}\n\n// 3. Auto-detect from available API keys\nString openAiKey = System.getenv(\"OPENAI_API_KEY\"); //$NON-NLS-1$\nif (openAiKey != null && !openAiKey.isBlank()) {\nreturn create(LlmProvider.OPENAI);\n}\nString geminiKey = System.getenv(\"GEMINI_API_KEY\"); //$NON-NLS-1$\nif (geminiKey != null && !geminiKey.isBlank()) {\nreturn create(LlmProvider.GEMINI);\n}\nString deepSeekKey = System.getenv(\"DEEPSEEK_API_KEY\"); //$NON-NLS-1$\nif (deepSeekKey != null && !deepSeekKey.isBlank()) {\nreturn create(LlmProvider.DEEPSEEK);\n}\nString dashScopeKey = System.getenv(\"DASHSCOPE_API_KEY\"); //$NON-NLS-1$\nif (dashScopeKey != null && !dashScopeKey.isBlank()) {\nreturn create(LlmProvider.QWEN);\n}\nString llamaKey = System.getenv(\"LLAMA_API_KEY\"); //$NON-NLS-1$\nif (llamaKey != null && !llamaKey.isBlank()) {\nreturn create(LlmProvider.LLAMA);\n}\nString mistralKey = System.getenv(\"MISTRAL_API_KEY\"); //$NON-NLS-1$\nif (mistralKey != null && !mistralKey.isBlank()) {\nreturn create(LlmProvider.MISTRAL);\n}\n\n// 4. Default: GEMINI\nreturn create(LlmProvider.GEMINI);\n}\n}\n"}, "bb73965038dd4f4380c05a4e9a181cc0123cacbf": {"t": 1786068303.1668997, "n": 4186, "h": "b56d04911d6e5696", "c": "/*******************************************************************************\n * Copyright (c) 2025 Carsten Hammer.\n *\n * This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License 2.0\n * which accompanies this distribution, and is available at\n * https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n *\n * Contributors:\n * Carsten Hammer\n *******************************************************************************/\npackage org.sandbox.jdt.triggerpattern.llm;\n\n/**\n * Factory for creating {@link LlmClient} instances.\n */\npublic class LlmClientFactory {\n\nprivate LlmClientFactory() {\n// utility class\n}\n\n/**\n * Creates an {@link LlmClient} for the given provider.\n *\n * @param provider the LLM provider to use\n * @return the configured client\n */\npublic static LlmClient create(LlmProvider provider) {\nreturn switch (provider) {\ncase GEMINI -> new GeminiClient();\ncase OPENAI -> new OpenAiClient();\ncase DEEPSEEK -> new DeepSeekClient();\ncase QWEN -> new QwenClient();\ncase LLAMA -> new LlamaClient();\ncase MISTRAL -> new MistralClient();\n};\n}\n\n/**\n * Creates an {@link LlmClient} for the given provider with an explicit API key.\n *\n * <p>Use this overload when the API key is available from Eclipse preferences\n * or another non-environment source. If {@code apiKey} is {@code null} or blank,\n * the client falls back to reading from its provider-specific environment variable.</p>\n *\n * @param provider the LLM provider to use\n * @param apiKey explicit API key (may be {@code null} or blank to fall back to env)\n * @return the configured client\n */\npublic static LlmClient create(LlmProvider provider, String apiKey) {\nif (apiKey == null || apiKey.isBlank()) {\nreturn create(provider);\n}\nreturn switch (provider) {\ncase GEMINI -> new GeminiClient(apiKey);\ncase OPENAI -> new OpenAiClient(apiKey);\ncase DEEPSEEK -> new DeepSeekClient(apiKey);\ncase QWEN -> new QwenClient(apiKey);\ncase LLAMA -> new LlamaClient(apiKey);\ncase MISTRAL -> new MistralClient(apiKey);\n};\n}\n\n/**\n * Creates an {@link LlmClient} by auto-detecting the provider.\n *\n * <p>Priority order:\n * <ol>\n * <li>Explicit CLI argument ({@code explicitProvider} parameter, non-blank)</li>\n * <li>Environment variable {@code LLM_PROVIDER}</li>\n * <li>Auto-detect from available API keys:\n * <ul>\n * <li>If {@code OPENAI_API_KEY} is set → {@link LlmProvider#OPENAI}</li>\n * <li>If {@code GEMINI_API_KEY} is set → {@link LlmProvider#GEMINI}</li>\n * </ul>\n * </li>\n * <li>Default: {@link LlmProvider#GEMINI}</li>\n * </ol>\n *\n * @param explicitProvider explicit provider name from CLI (may be null or blank)\n * @return the configured client\n */\npublic static LlmClient createFromEnvironment(String explicitProvider) {\n// 1. Explicit CLI argument\nif (explicitProvider != null && !explicitProvider.isBlank()) {\nreturn create(LlmProvider.fromString(explicitProvider));\n}\n\n// 2. Environment variable\nString envProvider = System.getenv(\"LLM_PROVIDER\"); //$NON-NLS-1$\nif (envProvider != null && !envProvider.isBlank()) {\nreturn create(LlmProvider.fromString(envProvider));\n}\n\n// 3. Auto-detect from available API keys\nString openAiKey = System.getenv(\"OPENAI_API_KEY\"); //$NON-NLS-1$\nif (openAiKey != null && !openAiKey.isBlank()) {\nreturn create(LlmProvider.OPENAI);\n}\nString geminiKey = System.getenv(\"GEMINI_API_KEY\"); //$NON-NLS-1$\nif (geminiKey != null && !geminiKey.isBlank()) {\nreturn create(LlmProvider.GEMINI);\n}\nString deepSeekKey = System.getenv(\"DEEPSEEK_API_KEY\"); //$NON-NLS-1$\nif (deepSeekKey != null && !deepSeekKey.isBlank()) {\nreturn create(LlmProvider.DEEPSEEK);\n}\nString dashScopeKey = System.getenv(\"DASHSCOPE_API_KEY\"); //$NON-NLS-1$\nif (dashScopeKey != null && !dashScopeKey.isBlank()) {\nreturn create(LlmProvider.QWEN);\n}\nString llamaKey = System.getenv(\"LLAMA_API_KEY\"); //$NON-NLS-1$\nif (llamaKey != null && !llamaKey.isBlank()) {\nreturn create(LlmProvider.LLAMA);\n}\nString mistralKey = System.getenv(\"MISTRAL_API_KEY\"); //$NON-NLS-1$\nif (mistralKey != null && !mistralKey.isBlank()) {\nreturn create(LlmProvider.MISTRAL);\n}\n\n// 4. Default: GEMINI\nreturn create(LlmProvider.GEMINI);\n}\n}\n"}}}} |