1 line
18 KiB
JSON
1 line
18 KiB
JSON
{"repo": "abhiindian/automated-job-search", "files": {"Ai/ChatClientFactory.cs": {"30bec5a0ec7b7a49dc70f6839386febeef2a467f": {"t": 1785745822.8236003, "n": 5075, "h": "76c66004c4f6a587", "c": "using System;\nusing System.ClientModel;\nusing Anthropic.SDK;\nusing Microsoft.Extensions.AI;\nusing OpenAI;\nusing AutomatedJobSearch.Logging;\nusing AutomatedJobSearch.Models;\n\nnamespace AutomatedJobSearch.Ai;\n\n/// <summary>\n/// Builds a Microsoft.Extensions.AI <see cref=\"IChatClient\"/> for the configured\n/// provider. All AI agents in this project consume this single abstraction.\n/// </summary>\npublic static class ChatClientFactory\n{\n public static IChatClient Create(AiConfig config)\n {\n return config.Provider switch\n {\n AiProviderType.Claude => CreateClaude(config.Claude),\n AiProviderType.LmStudio => CreateOpenAICompat(\n baseUrl: config.LmStudio.BaseUrl,\n apiKey: \"lm-studio\",\n model: config.LmStudio.ModelName,\n label: \"LM Studio\",\n temperature: config.LmStudio.Temperature,\n maxTokens: config.LmStudio.MaxTokens),\n AiProviderType.GithubCopilot => CreateOpenAICompat(\n baseUrl: ResolveCopilotBaseUrl(config.GithubCopilot.BaseUrl),\n apiKey: ResolveGithubToken(config.GithubCopilot.GithubToken),\n model: config.GithubCopilot.ModelName,\n label: \"GitHub Copilot/Models\",\n temperature: config.GithubCopilot.Temperature,\n maxTokens: config.GithubCopilot.MaxTokens),\n AiProviderType.DeepSeek => CreateOpenAICompat(\n baseUrl: ResolveEnvOrValue(config.DeepSeek.BaseUrl, \"DEEPSEEK_BASE_URL\", \"https://api.deepseek.com/v1\"),\n apiKey: ResolveEnvOrValue(config.DeepSeek.ApiKey, \"DEEPSEEK_API_KEY\", \"\"),\n model: ResolveEnvOrValue(config.DeepSeek.ModelName, \"DEEPSEEK_MODEL_NAME\", \"deepseek-chat\"),\n label: \"DeepSeek\",\n temperature: config.DeepSeek.Temperature,\n maxTokens: config.DeepSeek.MaxTokens),\n _ => throw new Exception($\"Unknown AI provider: {config.Provider}\"),\n };\n }\n\n private static IChatClient CreateClaude(ClaudeConfig c)\n {\n var apiKey = !string.IsNullOrWhiteSpace(c.ApiKey) && c.ApiKey != \"${ANTHROPIC_API_KEY}\"\n ? c.ApiKey\n : Environment.GetEnvironmentVariable(\"ANTHROPIC_API_KEY\");\n\n if (string.IsNullOrWhiteSpace(apiKey))\n throw new Exception(\"ANTHROPIC_API_KEY is required for Claude provider. Set it in environment or .env.\");\n\n Log.Logger.Information(\"Building Claude IChatClient (model: {Model})\", c.ModelName);\n var anthropic = new AnthropicClient(new APIAuthentication(apiKey));\n return WithDefaultOptions(anthropic.Messages, c.ModelName, c.Temperature, c.MaxTokens);\n }\n\n private static IChatClient CreateOpenAICompat(string baseUrl, string apiKey, string model, string label, double temperature, int maxTokens)\n {\n Log.Logger.Information(\"Building {Label} IChatClient (endpoint: {Url}, model: {Model})\", label, baseUrl, model);\n\n var endpoint = NormalizeEndpoint(baseUrl);\n var options = new OpenAIClientOptions { Endpoint = endpoint };\n var credential = new ApiKeyCredential(string.IsNullOrWhiteSpace(apiKey) ? \"x\" : apiKey);\n var openAI = new OpenAIClient(credential, options);\n return WithDefaultOptions(openAI.GetChatClient(model).AsIChatClient(), model, temperature, maxTokens);\n }\n\n /// <summary>\n /// Applies the configured model/temperature/maxTokens as per-request defaults.\n /// Callers can still override any of them via their own <see cref=\"ChatOptions\"/>.\n /// </summary>\n private static IChatClient WithDefaultOptions(IChatClient inner, string model, double temperature, int maxTokens)\n => new ChatClientBuilder(inner)\n .ConfigureOptions(o =>\n {\n o.ModelId ??= model;\n o.Temperature ??= (float)temperature;\n if (maxTokens > 0) o.MaxOutputTokens ??= maxTokens;\n })\n .Build();\n\n private static Uri NormalizeEndpoint(string baseUrl)\n {\n var trimmed = baseUrl.TrimEnd('/');\n if (trimmed.EndsWith(\"/chat/completions\", StringComparison.OrdinalIgnoreCase))\n trimmed = trimmed.Substring(0, trimmed.Length - \"/chat/completions\".Length);\n return new Uri(trimmed);\n }\n\n private static string ResolveCopilotBaseUrl(string configured)\n {\n if (string.IsNullOrWhiteSpace(configured)) return \"https://models.inference.ai.azure.com\";\n return configured;\n }\n\n private static string ResolveGithubToken(string? configured)\n {\n if (!string.IsNullOrWhiteSpace(configured) && configured != \"${GITHUB_TOKEN}\") return configured;\n return Environment.GetEnvironmentVariable(\"GITHUB_TOKEN\") ?? \"\";\n }\n\n private static string ResolveEnvOrValue(string? configured, string envVar, string fallback)\n {\n if (!string.IsNullOrWhiteSpace(configured) && !configured.StartsWith(\"${\")) return configured;\n return Environment.GetEnvironmentVariable(envVar) ?? fallback;\n }\n}\n"}, "c8c0c765fdca299368a5a8e02d3e8dcb74e246e6": {"t": 1785756812.6299136, "n": 5075, "h": "76c66004c4f6a587", "c": "using System;\nusing System.ClientModel;\nusing Anthropic.SDK;\nusing Microsoft.Extensions.AI;\nusing OpenAI;\nusing AutomatedJobSearch.Logging;\nusing AutomatedJobSearch.Models;\n\nnamespace AutomatedJobSearch.Ai;\n\n/// <summary>\n/// Builds a Microsoft.Extensions.AI <see cref=\"IChatClient\"/> for the configured\n/// provider. All AI agents in this project consume this single abstraction.\n/// </summary>\npublic static class ChatClientFactory\n{\n public static IChatClient Create(AiConfig config)\n {\n return config.Provider switch\n {\n AiProviderType.Claude => CreateClaude(config.Claude),\n AiProviderType.LmStudio => CreateOpenAICompat(\n baseUrl: config.LmStudio.BaseUrl,\n apiKey: \"lm-studio\",\n model: config.LmStudio.ModelName,\n label: \"LM Studio\",\n temperature: config.LmStudio.Temperature,\n maxTokens: config.LmStudio.MaxTokens),\n AiProviderType.GithubCopilot => CreateOpenAICompat(\n baseUrl: ResolveCopilotBaseUrl(config.GithubCopilot.BaseUrl),\n apiKey: ResolveGithubToken(config.GithubCopilot.GithubToken),\n model: config.GithubCopilot.ModelName,\n label: \"GitHub Copilot/Models\",\n temperature: config.GithubCopilot.Temperature,\n maxTokens: config.GithubCopilot.MaxTokens),\n AiProviderType.DeepSeek => CreateOpenAICompat(\n baseUrl: ResolveEnvOrValue(config.DeepSeek.BaseUrl, \"DEEPSEEK_BASE_URL\", \"https://api.deepseek.com/v1\"),\n apiKey: ResolveEnvOrValue(config.DeepSeek.ApiKey, \"DEEPSEEK_API_KEY\", \"\"),\n model: ResolveEnvOrValue(config.DeepSeek.ModelName, \"DEEPSEEK_MODEL_NAME\", \"deepseek-chat\"),\n label: \"DeepSeek\",\n temperature: config.DeepSeek.Temperature,\n maxTokens: config.DeepSeek.MaxTokens),\n _ => throw new Exception($\"Unknown AI provider: {config.Provider}\"),\n };\n }\n\n private static IChatClient CreateClaude(ClaudeConfig c)\n {\n var apiKey = !string.IsNullOrWhiteSpace(c.ApiKey) && c.ApiKey != \"${ANTHROPIC_API_KEY}\"\n ? c.ApiKey\n : Environment.GetEnvironmentVariable(\"ANTHROPIC_API_KEY\");\n\n if (string.IsNullOrWhiteSpace(apiKey))\n throw new Exception(\"ANTHROPIC_API_KEY is required for Claude provider. Set it in environment or .env.\");\n\n Log.Logger.Information(\"Building Claude IChatClient (model: {Model})\", c.ModelName);\n var anthropic = new AnthropicClient(new APIAuthentication(apiKey));\n return WithDefaultOptions(anthropic.Messages, c.ModelName, c.Temperature, c.MaxTokens);\n }\n\n private static IChatClient CreateOpenAICompat(string baseUrl, string apiKey, string model, string label, double temperature, int maxTokens)\n {\n Log.Logger.Information(\"Building {Label} IChatClient (endpoint: {Url}, model: {Model})\", label, baseUrl, model);\n\n var endpoint = NormalizeEndpoint(baseUrl);\n var options = new OpenAIClientOptions { Endpoint = endpoint };\n var credential = new ApiKeyCredential(string.IsNullOrWhiteSpace(apiKey) ? \"x\" : apiKey);\n var openAI = new OpenAIClient(credential, options);\n return WithDefaultOptions(openAI.GetChatClient(model).AsIChatClient(), model, temperature, maxTokens);\n }\n\n /// <summary>\n /// Applies the configured model/temperature/maxTokens as per-request defaults.\n /// Callers can still override any of them via their own <see cref=\"ChatOptions\"/>.\n /// </summary>\n private static IChatClient WithDefaultOptions(IChatClient inner, string model, double temperature, int maxTokens)\n => new ChatClientBuilder(inner)\n .ConfigureOptions(o =>\n {\n o.ModelId ??= model;\n o.Temperature ??= (float)temperature;\n if (maxTokens > 0) o.MaxOutputTokens ??= maxTokens;\n })\n .Build();\n\n private static Uri NormalizeEndpoint(string baseUrl)\n {\n var trimmed = baseUrl.TrimEnd('/');\n if (trimmed.EndsWith(\"/chat/completions\", StringComparison.OrdinalIgnoreCase))\n trimmed = trimmed.Substring(0, trimmed.Length - \"/chat/completions\".Length);\n return new Uri(trimmed);\n }\n\n private static string ResolveCopilotBaseUrl(string configured)\n {\n if (string.IsNullOrWhiteSpace(configured)) return \"https://models.inference.ai.azure.com\";\n return configured;\n }\n\n private static string ResolveGithubToken(string? configured)\n {\n if (!string.IsNullOrWhiteSpace(configured) && configured != \"${GITHUB_TOKEN}\") return configured;\n return Environment.GetEnvironmentVariable(\"GITHUB_TOKEN\") ?? \"\";\n }\n\n private static string ResolveEnvOrValue(string? configured, string envVar, string fallback)\n {\n if (!string.IsNullOrWhiteSpace(configured) && !configured.StartsWith(\"${\")) return configured;\n return Environment.GetEnvironmentVariable(envVar) ?? fallback;\n }\n}\n"}, "896a137ca360bc989378ae652da35a0b1964ded4": {"t": 1785756814.2454796, "n": 3969, "h": "6f8094f7b29dfd60", "c": "using System;\nusing System.ClientModel;\nusing Anthropic.SDK;\nusing Microsoft.Extensions.AI;\nusing OpenAI;\nusing AutomatedJobSearch.Logging;\nusing AutomatedJobSearch.Models;\n\nnamespace AutomatedJobSearch.Ai;\n\n/// <summary>\n/// Builds a Microsoft.Extensions.AI <see cref=\"IChatClient\"/> for the configured\n/// provider. All AI agents in this project consume this single abstraction.\n/// </summary>\npublic static class ChatClientFactory\n{\n public static IChatClient Create(AiConfig config)\n {\n return config.Provider switch\n {\n AiProviderType.Claude => CreateClaude(config.Claude),\n AiProviderType.LmStudio => CreateOpenAICompat(\n baseUrl: config.LmStudio.BaseUrl,\n apiKey: \"lm-studio\",\n model: config.LmStudio.ModelName,\n label: \"LM Studio\"),\n AiProviderType.GithubCopilot => CreateOpenAICompat(\n baseUrl: ResolveCopilotBaseUrl(config.GithubCopilot.BaseUrl),\n apiKey: ResolveGithubToken(config.GithubCopilot.GithubToken),\n model: config.GithubCopilot.ModelName,\n label: \"GitHub Copilot/Models\"),\n AiProviderType.DeepSeek => CreateOpenAICompat(\n baseUrl: ResolveEnvOrValue(config.DeepSeek.BaseUrl, \"DEEPSEEK_BASE_URL\", \"https://api.deepseek.com/v1\"),\n apiKey: ResolveEnvOrValue(config.DeepSeek.ApiKey, \"DEEPSEEK_API_KEY\", \"\"),\n model: ResolveEnvOrValue(config.DeepSeek.ModelName, \"DEEPSEEK_MODEL_NAME\", \"deepseek-chat\"),\n label: \"DeepSeek\"),\n _ => throw new Exception($\"Unknown AI provider: {config.Provider}\"),\n };\n }\n\n private static IChatClient CreateClaude(ClaudeConfig c)\n {\n var apiKey = !string.IsNullOrWhiteSpace(c.ApiKey) && c.ApiKey != \"${ANTHROPIC_API_KEY}\"\n ? c.ApiKey\n : Environment.GetEnvironmentVariable(\"ANTHROPIC_API_KEY\");\n\n if (string.IsNullOrWhiteSpace(apiKey))\n throw new Exception(\"ANTHROPIC_API_KEY is required for Claude provider. Set it in environment or .env.\");\n\n Log.Logger.Information(\"Building Claude IChatClient (model: {Model})\", c.ModelName);\n var anthropic = new AnthropicClient(new APIAuthentication(apiKey));\n return anthropic.Messages;\n }\n\n private static IChatClient CreateOpenAICompat(string baseUrl, string apiKey, string model, string label)\n {\n Log.Logger.Information(\"Building {Label} IChatClient (endpoint: {Url}, model: {Model})\", label, baseUrl, model);\n\n var endpoint = NormalizeEndpoint(baseUrl);\n var options = new OpenAIClientOptions { Endpoint = endpoint };\n var credential = new ApiKeyCredential(string.IsNullOrWhiteSpace(apiKey) ? \"x\" : apiKey);\n var openAI = new OpenAIClient(credential, options);\n return openAI.GetChatClient(model).AsIChatClient();\n }\n\n private static Uri NormalizeEndpoint(string baseUrl)\n {\n var trimmed = baseUrl.TrimEnd('/');\n if (trimmed.EndsWith(\"/chat/completions\", StringComparison.OrdinalIgnoreCase))\n trimmed = trimmed.Substring(0, trimmed.Length - \"/chat/completions\".Length);\n return new Uri(trimmed);\n }\n\n private static string ResolveCopilotBaseUrl(string configured)\n {\n if (string.IsNullOrWhiteSpace(configured)) return \"https://models.inference.ai.azure.com\";\n return configured;\n }\n\n private static string ResolveGithubToken(string? configured)\n {\n if (!string.IsNullOrWhiteSpace(configured) && configured != \"${GITHUB_TOKEN}\") return configured;\n return Environment.GetEnvironmentVariable(\"GITHUB_TOKEN\") ?? \"\";\n }\n\n private static string ResolveEnvOrValue(string? configured, string envVar, string fallback)\n {\n if (!string.IsNullOrWhiteSpace(configured) && !configured.StartsWith(\"${\")) return configured;\n return Environment.GetEnvironmentVariable(envVar) ?? fallback;\n }\n}\n"}, "61d8edd7dea2a2e9facd5a15ea740f5d4f4678d1": {"t": 1785756815.7059314, "n": 3270, "h": "4138505773a99bbe", "c": "using System;\nusing System.ClientModel;\nusing Anthropic.SDK;\nusing Microsoft.Extensions.AI;\nusing OpenAI;\nusing AutomatedJobSearch.Logging;\nusing AutomatedJobSearch.Models;\n\nnamespace AutomatedJobSearch.Ai;\n\n/// <summary>\n/// Builds a Microsoft.Extensions.AI <see cref=\"IChatClient\"/> for the configured\n/// provider. All AI agents in this project consume this single abstraction.\n/// </summary>\npublic static class ChatClientFactory\n{\n public static IChatClient Create(AiConfig config)\n {\n return config.Provider switch\n {\n AiProviderType.Claude => CreateClaude(config.Claude),\n AiProviderType.LmStudio => CreateOpenAICompat(\n baseUrl: config.LmStudio.BaseUrl,\n apiKey: \"lm-studio\",\n model: config.LmStudio.ModelName,\n label: \"LM Studio\"),\n AiProviderType.GithubCopilot => CreateOpenAICompat(\n baseUrl: ResolveCopilotBaseUrl(config.GithubCopilot.BaseUrl),\n apiKey: ResolveGithubToken(config.GithubCopilot.GithubToken),\n model: config.GithubCopilot.ModelName,\n label: \"GitHub Copilot/Models\"),\n _ => throw new Exception($\"Unknown AI provider: {config.Provider}\"),\n };\n }\n\n private static IChatClient CreateClaude(ClaudeConfig c)\n {\n var apiKey = !string.IsNullOrWhiteSpace(c.ApiKey) && c.ApiKey != \"${ANTHROPIC_API_KEY}\"\n ? c.ApiKey\n : Environment.GetEnvironmentVariable(\"ANTHROPIC_API_KEY\");\n\n if (string.IsNullOrWhiteSpace(apiKey))\n throw new Exception(\"ANTHROPIC_API_KEY is required for Claude provider. Set it in environment or .env.\");\n\n Log.Logger.Information(\"Building Claude IChatClient (model: {Model})\", c.ModelName);\n var anthropic = new AnthropicClient(new APIAuthentication(apiKey));\n return anthropic.Messages;\n }\n\n private static IChatClient CreateOpenAICompat(string baseUrl, string apiKey, string model, string label)\n {\n Log.Logger.Information(\"Building {Label} IChatClient (endpoint: {Url}, model: {Model})\", label, baseUrl, model);\n\n var endpoint = NormalizeEndpoint(baseUrl);\n var options = new OpenAIClientOptions { Endpoint = endpoint };\n var credential = new ApiKeyCredential(string.IsNullOrWhiteSpace(apiKey) ? \"x\" : apiKey);\n var openAI = new OpenAIClient(credential, options);\n return openAI.GetChatClient(model).AsIChatClient();\n }\n\n private static Uri NormalizeEndpoint(string baseUrl)\n {\n var trimmed = baseUrl.TrimEnd('/');\n if (trimmed.EndsWith(\"/chat/completions\", StringComparison.OrdinalIgnoreCase))\n trimmed = trimmed.Substring(0, trimmed.Length - \"/chat/completions\".Length);\n return new Uri(trimmed);\n }\n\n private static string ResolveCopilotBaseUrl(string configured)\n {\n if (string.IsNullOrWhiteSpace(configured)) return \"https://models.inference.ai.azure.com\";\n return configured;\n }\n\n private static string ResolveGithubToken(string? configured)\n {\n if (!string.IsNullOrWhiteSpace(configured) && configured != \"${GITHUB_TOKEN}\") return configured;\n return Environment.GetEnvironmentVariable(\"GITHUB_TOKEN\") ?? \"\";\n }\n}\n"}}}} |