diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index e2af54df..d3e3173c 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -25,6 +25,36 @@ jobs:
uses: microsoft/setup-msbuild@v1.1
- name: Clear NuGet cache
run: dotnet nuget locals all --clear
+ - name: Cache telegram-bot-api build
+ id: cache-tg-bot-api
+ uses: actions/cache@v4
+ with:
+ path: telegram-bot-api-bin
+ key: tg-bot-api-win-x64-${{ hashFiles('.github/workflows/push.yml') }}
+ restore-keys: |
+ tg-bot-api-win-x64-
+ - name: Build telegram-bot-api from source
+ if: steps.cache-tg-bot-api.outputs.cache-hit != 'true'
+ shell: pwsh
+ run: |
+ git clone --recursive https://github.com/tdlib/telegram-bot-api.git
+ C:\vcpkg\vcpkg.exe install openssl:x64-windows-static zlib:x64-windows-static --no-print-usage
+ Push-Location telegram-bot-api
+ New-Item -ItemType Directory -Force build | Out-Null
+ Push-Location build
+ cmake -A x64 -DCMAKE_BUILD_TYPE=Release `
+ -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" `
+ -DVCPKG_TARGET_TRIPLET="x64-windows-static" `
+ ..
+ cmake --build . --config Release --target telegram-bot-api
+ Pop-Location
+ Pop-Location
+ New-Item -ItemType Directory -Force telegram-bot-api-bin | Out-Null
+ Copy-Item "telegram-bot-api\build\Release\telegram-bot-api.exe" "telegram-bot-api-bin\telegram-bot-api.exe"
+ - name: Copy telegram-bot-api binary to project
+ shell: pwsh
+ run: |
+ Copy-Item "telegram-bot-api-bin\telegram-bot-api.exe" "TelegramSearchBot\telegram-bot-api.exe"
- name: Restore dependencies
run: dotnet restore --force --no-cache /p:BuildWithNetFrameworkHostedCompiler=true
- name: Build
diff --git a/TelegramSearchBot.Common/Attributes/McpAttributes.cs b/TelegramSearchBot.Common/Attributes/McpAttributes.cs
index 519b0001..9a383c34 100644
--- a/TelegramSearchBot.Common/Attributes/McpAttributes.cs
+++ b/TelegramSearchBot.Common/Attributes/McpAttributes.cs
@@ -1,7 +1,6 @@
using System;
-namespace TelegramSearchBot.Attributes
-{
+namespace TelegramSearchBot.Attributes {
///
/// Marks a method as a tool that can be called by the LLM.
/// Deprecated: Use instead for built-in tools.
diff --git a/TelegramSearchBot.Common/Env.cs b/TelegramSearchBot.Common/Env.cs
index eaf50513..854a57a3 100644
--- a/TelegramSearchBot.Common/Env.cs
+++ b/TelegramSearchBot.Common/Env.cs
@@ -13,8 +13,17 @@ static Env() {
}
try {
var config = JsonConvert.DeserializeObject(File.ReadAllText(Path.Combine(WorkDir, "Config.json")));
- BaseUrl = config.BaseUrl;
- IsLocalAPI = config.IsLocalAPI;
+ EnableLocalBotAPI = config.EnableLocalBotAPI;
+ TelegramBotApiId = config.TelegramBotApiId;
+ TelegramBotApiHash = config.TelegramBotApiHash;
+ LocalBotApiPort = config.LocalBotApiPort;
+ if (config.EnableLocalBotAPI) {
+ BaseUrl = $"http://127.0.0.1:{config.LocalBotApiPort}";
+ IsLocalAPI = true;
+ } else {
+ BaseUrl = config.BaseUrl;
+ IsLocalAPI = config.IsLocalAPI;
+ }
BotToken = config.BotToken;
AdminId = config.AdminId;
EnableAutoOCR = config.EnableAutoOCR;
@@ -44,6 +53,10 @@ static Env() {
public static readonly long AdminId;
public static readonly bool EnableAutoOCR;
public static readonly bool EnableAutoASR;
+ public static readonly bool EnableLocalBotAPI;
+ public static readonly string TelegramBotApiId;
+ public static readonly string TelegramBotApiHash;
+ public static readonly int LocalBotApiPort;
public static readonly string WorkDir;
public static readonly int TaskDelayTimeout;
public static readonly bool SameServer;
@@ -70,6 +83,10 @@ public class Config {
public bool EnableAutoASR { get; set; } = false;
//public string WorkDir { get; set; } = "/data/TelegramSearchBot";
public bool IsLocalAPI { get; set; } = false;
+ public bool EnableLocalBotAPI { get; set; } = false;
+ public string TelegramBotApiId { get; set; }
+ public string TelegramBotApiHash { get; set; }
+ public int LocalBotApiPort { get; set; } = 8081;
public bool SameServer { get; set; } = false;
public int TaskDelayTimeout { get; set; } = 1000;
public string OllamaModelName { get; set; } = "qwen2.5:72b-instruct-q2_K";
diff --git a/TelegramSearchBot.Common/Model/Tools/IterationLimitReachedPayload.cs b/TelegramSearchBot.Common/Model/Tools/IterationLimitReachedPayload.cs
index b605ddef..4dec7584 100644
--- a/TelegramSearchBot.Common/Model/Tools/IterationLimitReachedPayload.cs
+++ b/TelegramSearchBot.Common/Model/Tools/IterationLimitReachedPayload.cs
@@ -22,7 +22,7 @@ public static bool IsIterationLimitMessage(string content) {
/// 在累积内容末尾追加标记
///
public static string AppendMarker(string accumulatedContent) {
- return (accumulatedContent ?? string.Empty) + Marker;
+ return ( accumulatedContent ?? string.Empty ) + Marker;
}
///
diff --git a/TelegramSearchBot.Database/Migrations/20260303031828_AddUserWithGroupUniqueIndex.cs b/TelegramSearchBot.Database/Migrations/20260303031828_AddUserWithGroupUniqueIndex.cs
index 3378f31b..79ea689b 100644
--- a/TelegramSearchBot.Database/Migrations/20260303031828_AddUserWithGroupUniqueIndex.cs
+++ b/TelegramSearchBot.Database/Migrations/20260303031828_AddUserWithGroupUniqueIndex.cs
@@ -1,15 +1,12 @@
-using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
-namespace TelegramSearchBot.Migrations
-{
+namespace TelegramSearchBot.Migrations {
///
- public partial class AddUserWithGroupUniqueIndex : Migration
- {
+ public partial class AddUserWithGroupUniqueIndex : Migration {
///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
+ protected override void Up(MigrationBuilder migrationBuilder) {
migrationBuilder.CreateIndex(
name: "IX_UsersWithGroup_UserId_GroupId",
table: "UsersWithGroup",
@@ -18,8 +15,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
}
///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
+ protected override void Down(MigrationBuilder migrationBuilder) {
migrationBuilder.DropIndex(
name: "IX_UsersWithGroup_UserId_GroupId",
table: "UsersWithGroup");
diff --git a/TelegramSearchBot.Database/Migrations/20260313124507_AddChannelWithModelIsDeleted.cs b/TelegramSearchBot.Database/Migrations/20260313124507_AddChannelWithModelIsDeleted.cs
index 045e395e..ccd5f76c 100644
--- a/TelegramSearchBot.Database/Migrations/20260313124507_AddChannelWithModelIsDeleted.cs
+++ b/TelegramSearchBot.Database/Migrations/20260313124507_AddChannelWithModelIsDeleted.cs
@@ -1,15 +1,12 @@
-using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
-namespace TelegramSearchBot.Migrations
-{
+namespace TelegramSearchBot.Migrations {
///
- public partial class AddChannelWithModelIsDeleted : Migration
- {
+ public partial class AddChannelWithModelIsDeleted : Migration {
///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
+ protected override void Up(MigrationBuilder migrationBuilder) {
migrationBuilder.AddColumn(
name: "IsDeleted",
table: "ChannelsWithModel",
@@ -19,8 +16,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
}
///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
+ protected override void Down(MigrationBuilder migrationBuilder) {
migrationBuilder.DropColumn(
name: "IsDeleted",
table: "ChannelsWithModel");
diff --git a/TelegramSearchBot.LLM.Test/Service/AI/LLM/GeneralLLMServiceTests.cs b/TelegramSearchBot.LLM.Test/Service/AI/LLM/GeneralLLMServiceTests.cs
index 52ec785b..87e3e109 100644
--- a/TelegramSearchBot.LLM.Test/Service/AI/LLM/GeneralLLMServiceTests.cs
+++ b/TelegramSearchBot.LLM.Test/Service/AI/LLM/GeneralLLMServiceTests.cs
@@ -102,12 +102,20 @@ public async Task GetChannelsAsync_NoModels_ReturnsEmpty() {
public async Task GetChannelsAsync_WithModel_ReturnsOrderedChannels() {
// Arrange
var channel1 = new LLMChannel {
- Name = "ch1", Gateway = "gw1", ApiKey = "key1",
- Provider = LLMProvider.OpenAI, Parallel = 2, Priority = 1
+ Name = "ch1",
+ Gateway = "gw1",
+ ApiKey = "key1",
+ Provider = LLMProvider.OpenAI,
+ Parallel = 2,
+ Priority = 1
};
var channel2 = new LLMChannel {
- Name = "ch2", Gateway = "gw2", ApiKey = "key2",
- Provider = LLMProvider.OpenAI, Parallel = 3, Priority = 10
+ Name = "ch2",
+ Gateway = "gw2",
+ ApiKey = "key2",
+ Provider = LLMProvider.OpenAI,
+ Parallel = 3,
+ Priority = 10
};
_dbContext.LLMChannels.AddRange(channel1, channel2);
await _dbContext.SaveChangesAsync();
@@ -130,7 +138,10 @@ public async Task GetChannelsAsync_WithModel_ReturnsOrderedChannels() {
public async Task ExecAsync_NoModelConfigured_YieldsNoResults() {
// Arrange - no group settings configured
var message = new TelegramSearchBot.Model.Data.Message {
- Content = "test", GroupId = 123, MessageId = 1, FromUserId = 1
+ Content = "test",
+ GroupId = 123,
+ MessageId = 1,
+ FromUserId = 1
};
// Act
@@ -153,8 +164,12 @@ public async Task GetAvailableCapacityAsync_NoChannels_ReturnsZero() {
public async Task GetAvailableCapacityAsync_WithChannels_ReturnsCapacity() {
// Arrange
var channel = new LLMChannel {
- Name = "ch1", Gateway = "gw1", ApiKey = "key1",
- Provider = LLMProvider.OpenAI, Parallel = 5, Priority = 1
+ Name = "ch1",
+ Gateway = "gw1",
+ ApiKey = "key1",
+ Provider = LLMProvider.OpenAI,
+ Parallel = 5,
+ Priority = 1
};
_dbContext.LLMChannels.Add(channel);
await _dbContext.SaveChangesAsync();
diff --git a/TelegramSearchBot.LLM.Test/Service/AI/LLM/ModelCapabilityServiceTests.cs b/TelegramSearchBot.LLM.Test/Service/AI/LLM/ModelCapabilityServiceTests.cs
index a649e8db..da4ef579 100644
--- a/TelegramSearchBot.LLM.Test/Service/AI/LLM/ModelCapabilityServiceTests.cs
+++ b/TelegramSearchBot.LLM.Test/Service/AI/LLM/ModelCapabilityServiceTests.cs
@@ -67,8 +67,12 @@ public async Task GetModelCapabilities_NotFound_ReturnsNull() {
public async Task GetModelCapabilities_WithCapabilities_ReturnsCorrectModel() {
// Arrange
var channel = new LLMChannel {
- Name = "test", Gateway = "gw", ApiKey = "key",
- Provider = LLMProvider.OpenAI, Parallel = 1, Priority = 1
+ Name = "test",
+ Gateway = "gw",
+ ApiKey = "key",
+ Provider = LLMProvider.OpenAI,
+ Parallel = 1,
+ Priority = 1
};
_dbContext.LLMChannels.Add(channel);
await _dbContext.SaveChangesAsync();
@@ -106,8 +110,12 @@ public async Task GetModelCapabilities_WithCapabilities_ReturnsCorrectModel() {
public async Task GetToolCallingSupportedModels_ReturnsCorrectModels() {
// Arrange
var channel = new LLMChannel {
- Name = "test", Gateway = "gw", ApiKey = "key",
- Provider = LLMProvider.OpenAI, Parallel = 1, Priority = 1
+ Name = "test",
+ Gateway = "gw",
+ ApiKey = "key",
+ Provider = LLMProvider.OpenAI,
+ Parallel = 1,
+ Priority = 1
};
_dbContext.LLMChannels.Add(channel);
await _dbContext.SaveChangesAsync();
@@ -138,7 +146,7 @@ public async Task GetToolCallingSupportedModels_ReturnsCorrectModels() {
await _dbContext.SaveChangesAsync();
// Act
- var result = (await _service.GetToolCallingSupportedModels()).ToList();
+ var result = ( await _service.GetToolCallingSupportedModels() ).ToList();
// Assert
Assert.Single(result);
@@ -149,8 +157,12 @@ public async Task GetToolCallingSupportedModels_ReturnsCorrectModels() {
public async Task GetVisionSupportedModels_ReturnsCorrectModels() {
// Arrange
var channel = new LLMChannel {
- Name = "test", Gateway = "gw", ApiKey = "key",
- Provider = LLMProvider.OpenAI, Parallel = 1, Priority = 1
+ Name = "test",
+ Gateway = "gw",
+ ApiKey = "key",
+ Provider = LLMProvider.OpenAI,
+ Parallel = 1,
+ Priority = 1
};
_dbContext.LLMChannels.Add(channel);
await _dbContext.SaveChangesAsync();
@@ -170,7 +182,7 @@ public async Task GetVisionSupportedModels_ReturnsCorrectModels() {
await _dbContext.SaveChangesAsync();
// Act
- var result = (await _service.GetVisionSupportedModels()).ToList();
+ var result = ( await _service.GetVisionSupportedModels() ).ToList();
// Assert
Assert.Single(result);
@@ -181,8 +193,12 @@ public async Task GetVisionSupportedModels_ReturnsCorrectModels() {
public async Task GetEmbeddingModels_ReturnsCorrectModels() {
// Arrange
var channel = new LLMChannel {
- Name = "test", Gateway = "gw", ApiKey = "key",
- Provider = LLMProvider.OpenAI, Parallel = 1, Priority = 1
+ Name = "test",
+ Gateway = "gw",
+ ApiKey = "key",
+ Provider = LLMProvider.OpenAI,
+ Parallel = 1,
+ Priority = 1
};
_dbContext.LLMChannels.Add(channel);
await _dbContext.SaveChangesAsync();
@@ -202,7 +218,7 @@ public async Task GetEmbeddingModels_ReturnsCorrectModels() {
await _dbContext.SaveChangesAsync();
// Act
- var result = (await _service.GetEmbeddingModels()).ToList();
+ var result = ( await _service.GetEmbeddingModels() ).ToList();
// Assert
Assert.Single(result);
@@ -213,8 +229,12 @@ public async Task GetEmbeddingModels_ReturnsCorrectModels() {
public async Task CleanupOldCapabilities_RemovesOldEntries() {
// Arrange
var channel = new LLMChannel {
- Name = "test", Gateway = "gw", ApiKey = "key",
- Provider = LLMProvider.OpenAI, Parallel = 1, Priority = 1
+ Name = "test",
+ Gateway = "gw",
+ ApiKey = "key",
+ Provider = LLMProvider.OpenAI,
+ Parallel = 1,
+ Priority = 1
};
_dbContext.LLMChannels.Add(channel);
await _dbContext.SaveChangesAsync();
diff --git a/TelegramSearchBot.LLM.Test/Service/AI/LLM/OpenAIProviderHistorySerializationTests.cs b/TelegramSearchBot.LLM.Test/Service/AI/LLM/OpenAIProviderHistorySerializationTests.cs
index 3d5b003c..0529dbb2 100644
--- a/TelegramSearchBot.LLM.Test/Service/AI/LLM/OpenAIProviderHistorySerializationTests.cs
+++ b/TelegramSearchBot.LLM.Test/Service/AI/LLM/OpenAIProviderHistorySerializationTests.cs
@@ -79,7 +79,7 @@ public void SerializeProviderHistory_WithToolCallHistory_PreservesContent() {
};
var serialized = OpenAIService.SerializeProviderHistory(history);
-
+
Assert.Equal(5, serialized.Count);
Assert.Contains("tool_call", serialized[2].Content);
Assert.Contains("bash", serialized[3].Content);
diff --git a/TelegramSearchBot.LLM/Service/AI/LLM/GeminiService.cs b/TelegramSearchBot.LLM/Service/AI/LLM/GeminiService.cs
index b6c62aaf..2eecde60 100644
--- a/TelegramSearchBot.LLM/Service/AI/LLM/GeminiService.cs
+++ b/TelegramSearchBot.LLM/Service/AI/LLM/GeminiService.cs
@@ -341,7 +341,7 @@ public async IAsyncEnumerable ExecAsync(
executionContext.IterationLimitReached = true;
executionContext.SnapshotData = new LlmContinuationSnapshot {
ChatId = ChatId,
- OriginalMessageId = (int)message.MessageId,
+ OriginalMessageId = ( int ) message.MessageId,
UserId = message.FromUserId,
ModelName = modelName,
Provider = "Gemini",
diff --git a/TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs b/TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs
index e4595ad2..be5862d2 100644
--- a/TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs
+++ b/TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs
@@ -170,7 +170,7 @@ private static string RegisterToolsAndGetPromptString(List assemblies)
var builtInParamAttr = param.GetCustomAttribute();
var mcpParamAttr = param.GetCustomAttribute();
var paramDescription = builtInParamAttr?.Description ?? mcpParamAttr?.Description ?? $"Parameter '{param.Name}'";
- var paramIsRequired = builtInParamAttr?.IsRequired ?? mcpParamAttr?.IsRequired ?? (!param.IsOptional && !param.HasDefaultValue);
+ var paramIsRequired = builtInParamAttr?.IsRequired ?? mcpParamAttr?.IsRequired ?? ( !param.IsOptional && !param.HasDefaultValue );
var paramType = MapToJsonSchemaType(param.ParameterType);
properties[param.Name] = new Dictionary {
@@ -511,7 +511,7 @@ private static (string toolName, Dictionary arguments) ParseTool
}
}
- if (toolName == null || (!ToolRegistry.ContainsKey(toolName) && !ExternalToolRegistry.ContainsKey(toolName))) {
+ if (toolName == null || ( !ToolRegistry.ContainsKey(toolName) && !ExternalToolRegistry.ContainsKey(toolName) )) {
_sLogger?.LogWarning($"ParseToolElement: Unregistered tool '{element.Name.LocalName}'");
return (null, null);
}
@@ -703,8 +703,7 @@ public static async Task