diff --git a/Services/AnthropicService.Cli.cs b/Services/AnthropicService.Cli.cs
index fd8a99a..d16e9fa 100644
--- a/Services/AnthropicService.Cli.cs
+++ b/Services/AnthropicService.Cli.cs
@@ -366,6 +366,176 @@ public partial class AnthropicService
}
}
+ ///
+ /// Non-Streaming Chat über die Claude CLI für Telegram (und andere Caller, die kein SSE brauchen).
+ /// Sammelt den finalen Text und gibt ihn zurück. Persistiert Text, Tool-Use und Tool-Result in der DB,
+ /// damit die History im Web-UI korrekt rekonstruiert werden kann.
+ ///
+ public async Task ChatNonStreamingViaCli(
+ ChatContext ctx,
+ List messages,
+ Func onIteration = null,
+ CancellationToken cancellationToken = default)
+ {
+ if (_cliService == null || !_cliService.IsAvailable)
+ return "";
+
+ var sw = System.Diagnostics.Stopwatch.StartNew();
+ var usage = new TokenUsage();
+ var tempImagePaths = new List();
+
+ var chain = new ChainRecord
+ {
+ ChainId = ctx.ChainId,
+ SessionId = ctx.SessionId,
+ Model = ctx.ModelId
+ };
+
+ try
+ {
+ // System-Prompt als Text
+ var systemText = new StringBuilder(ctx.SystemPromptText ?? "");
+
+ // Auto-Context (analog Streaming-Pfad)
+ if ((ctx.Agent?.UseAutoContext ?? true) && messages.Count > 0)
+ {
+ try
+ {
+ var autoCtx = await BuildAutoContextAsync(_vectorSearch, messages, ctx.ChainId, ctx.AgentId);
+ if (!string.IsNullOrEmpty(autoCtx))
+ {
+ systemText.AppendLine(autoCtx);
+ await _db.SaveMessage(new MessageRecord
+ {
+ ChainId = ctx.ChainId,
+ Role = "system",
+ Type = "auto_context",
+ Content = autoCtx
+ });
+ }
+ }
+ catch { /* silent – Auto-Context optional */ }
+ }
+
+ // Prompt aus letzter User-Message + Bild-/PDF-Refs + History-Kontext
+ var prompt = ExtractLastUserText(messages) ?? "";
+ var imageRefs = ExtractLastUserImageRefs(messages, tempImagePaths);
+ if (!string.IsNullOrEmpty(imageRefs))
+ prompt = imageRefs + "\n" + prompt;
+
+ var historyContext = BuildHistoryContext(messages);
+ if (!string.IsNullOrEmpty(historyContext))
+ prompt = $"\n{historyContext}\n \n\n\n{prompt}\n ";
+
+ var fullText = new StringBuilder();
+ if (onIteration != null) await onIteration(1);
+
+ var result = await _cliService.ChatStreamingAsync(
+ prompt,
+ async evt =>
+ {
+ switch (evt.Type)
+ {
+ case CliEventType.TextDelta:
+ fullText.Append(evt.Text);
+ break;
+ case CliEventType.ToolStart:
+ // Text-Puffer flushen bevor Tool-Use gespeichert wird (korrekte Reihenfolge in DB)
+ if (fullText.Length > 0)
+ {
+ var textForEmbed = fullText.ToString();
+ var preMsgId = await _db.SaveMessage(new MessageRecord
+ {
+ ChainId = ctx.ChainId,
+ Iteration = 1,
+ Role = "assistant",
+ Type = "text",
+ Content = textForEmbed
+ });
+ _embeddingBg?.Enqueue(preMsgId, ctx.ChainId, ctx.SessionId, ctx.AgentId, "assistant", textForEmbed);
+ fullText.Clear();
+ }
+ if (onIteration != null) await onIteration(1);
+ break;
+ case CliEventType.ToolEnd:
+ {
+ var mappedName = MapCliToolName(evt.ToolName);
+ var mappedInput = MapCliToolInput(evt.ToolName, evt.ToolInput);
+ await _db.SaveMessage(new MessageRecord
+ {
+ ChainId = ctx.ChainId,
+ Iteration = 1,
+ Role = "assistant",
+ Type = "tool_use",
+ ToolName = mappedName,
+ ToolCallId = evt.ToolId,
+ Content = mappedInput
+ });
+ break;
+ }
+ case CliEventType.ToolResult:
+ await _db.SaveMessage(new MessageRecord
+ {
+ ChainId = ctx.ChainId,
+ Iteration = 1,
+ Role = "user",
+ Type = "tool_result",
+ ToolCallId = evt.ToolId,
+ Content = evt.ToolResultContent ?? "",
+ IsError = evt.ToolResultIsError
+ });
+ break;
+ }
+ },
+ modelId: ctx.ModelId,
+ systemPrompt: systemText.ToString(),
+ workspace: ctx.Workspace,
+ hasWorkspace: !string.IsNullOrEmpty(ctx.Workspace),
+ cancellationToken: cancellationToken
+ );
+
+ // Finalen Text persistieren
+ var finalText = fullText.ToString();
+ if (finalText.Length > 0)
+ {
+ var msgId = await _db.SaveMessage(new MessageRecord
+ {
+ ChainId = ctx.ChainId,
+ Iteration = 1,
+ Role = "assistant",
+ Type = "text",
+ Content = finalText
+ });
+ _embeddingBg?.Enqueue(msgId, ctx.ChainId, ctx.SessionId, ctx.AgentId, "assistant", finalText);
+ }
+
+ usage.InputTokens = result.InputTokens;
+ usage.OutputTokens = result.OutputTokens;
+ usage.CacheReadTokens = result.CacheReadTokens;
+ usage.CacheCreationTokens = result.CacheCreationTokens;
+
+ var stopReason = result.Success ? "end_turn" : "error";
+ await FinalizeChain(chain, usage, result.NumTurns > 0 ? result.NumTurns : 1, stopReason, sw);
+
+ return finalText;
+ }
+ catch (Exception ex)
+ {
+ await _db.SaveMessage(new MessageRecord
+ {
+ ChainId = ctx.ChainId, Iteration = 1,
+ Role = "system", Type = "error",
+ Content = $"CLI-Fehler: {ex.Message}", IsError = true
+ });
+ await FinalizeChain(chain, usage, 1, "error", sw);
+ return "";
+ }
+ finally
+ {
+ CleanupTempImages(tempImagePaths);
+ }
+ }
+
///
/// Mappt CLI-Tool-Namen (Edit, Write, Bash, etc.) auf gott-Tool-Namen (edit_file, write_file, etc.).
///
diff --git a/Services/TelegramService.cs b/Services/TelegramService.cs
index 683d4d2..8cc28a2 100644
--- a/Services/TelegramService.cs
+++ b/Services/TelegramService.cs
@@ -173,12 +173,37 @@ public class TelegramService
// API-Key des Bot-Owners für diesen Agent auflösen
var userApiKey = await _db.GetAgentApiKeyRecord(botUserId, ctx.AgentId);
+ bool useCliFallback = false;
+
if (userApiKey == null)
{
- await SendMessage(chatId, botToken, "❌ Kein API-Key für diesen Agent zugewiesen. Der Bot-Besitzer muss im Web-UI einen Key zuweisen.");
- return;
+ // Kein Key → CLI-Fallback prüfen (analog ChatEndpoint).
+ // Nur für Anthropic-Agents möglich (CLI = Claude). OpenAI-Provider haben kein CLI-Äquivalent.
+ if (!string.IsNullOrEmpty(ctx.Provider))
+ {
+ await SendMessage(chatId, botToken, "❌ Kein API-Key für diesen Agent zugewiesen. Für OpenAI-kompatible Provider ist kein CLI-Fallback möglich – der Bot-Besitzer muss im Web-UI einen Key zuweisen.");
+ return;
+ }
+
+ var botOwner = await _db.GetUser(botUserId);
+ if (botOwner != null && !botOwner.CanUseCli && !botOwner.IsAdmin)
+ {
+ await SendMessage(chatId, botToken, "❌ Kein API-Key für diesen Agent zugewiesen und der Bot-Besitzer hat keine CLI-Berechtigung.");
+ return;
+ }
+
+ if (!_anthropic.IsCliAvailable)
+ {
+ await SendMessage(chatId, botToken, "❌ Kein API-Key für diesen Agent zugewiesen und Claude CLI nicht verfügbar. Der Bot-Besitzer muss im Web-UI einen Key zuweisen oder `claude login` auf dem Server ausführen.");
+ return;
+ }
+
+ useCliFallback = true;
+ }
+ else
+ {
+ ctx.ApiToken = userApiKey.ApiKey;
}
- ctx.ApiToken = userApiKey.ApiKey;
await SendChatAction(chatId, botToken, "typing");
@@ -211,6 +236,13 @@ public class TelegramService
await SendChatAction(chatId, botToken, "typing");
});
}
+ else if (useCliFallback)
+ {
+ reply = await _anthropic.ChatNonStreamingViaCli(ctx, history, onIteration: async (iteration) =>
+ {
+ await SendChatAction(chatId, botToken, "typing");
+ });
+ }
else
{
reply = await _anthropic.ChatNonStreaming(ctx, history, onIteration: async (iteration) =>
@@ -220,6 +252,9 @@ public class TelegramService
});
}
+ if (string.IsNullOrEmpty(reply))
+ reply = "⚠️ Keine Antwort vom Modell erhalten.";
+
await SendLongMessage(chatId, botToken, reply);
}
catch (Exception ex)