diff --git a/Endpoints/ChatEndpoint.cs b/Endpoints/ChatEndpoint.cs
index ecdc5a4..56153f1 100644
--- a/Endpoints/ChatEndpoint.cs
+++ b/Endpoints/ChatEndpoint.cs
@@ -142,14 +142,14 @@ public static class ChatEndpoint
if (ctx.Preparer != null && !isChoiceResponse)
{
var preparerApiToken = userApiKey?.ApiKey;
- // Anthropic-Preparer braucht zwingend einen Key, OpenAI-Provider können ohne auskommen
- if (string.IsNullOrEmpty(ctx.Preparer.Provider) && string.IsNullOrEmpty(preparerApiToken))
- {
- res.StatusCode = 400;
- await res.WriteAsJsonAsync(new { error = "Kein API-Key für diesen Agent zugewiesen. Bitte weise einen Key zu." });
- return;
- }
+ // Kein Key und kein CLI → Preparer überspringen (main agent wird auch CLI nutzen oder fehlermelden)
+ bool skipPreparer = string.IsNullOrEmpty(ctx.Preparer.Provider)
+ && string.IsNullOrEmpty(preparerApiToken)
+ && !anthropic.IsCliAvailable;
+
+ if (!skipPreparer)
+ {
// Preparer bekommt History MIT seinen eigenen vorherigen Messages (Tag "preparer"),
// damit er den Kontext seiner Rückfragen kennt
var preparerHistory = await chatService.BuildHistoryFromDb(sessionId, includeTag: "preparer", promptBlocks: ctx.PromptBlocks);
@@ -182,6 +182,7 @@ public static class ChatEndpoint
Content = $"[Vorbereitet von {ctx.Preparer.Agent?.Name ?? "Vorbereiter-Agent"}]:\n{preparerResult.ForwardedPrompt}"
});
}
+ } // !skipPreparer
}
// History für den Session-Agent: OHNE Sub-Agent-Messages (saubere History)
diff --git a/Services/AnthropicService.Agents.cs b/Services/AnthropicService.Agents.cs
index 20ba09e..d14acf8 100644
--- a/Services/AnthropicService.Agents.cs
+++ b/Services/AnthropicService.Agents.cs
@@ -63,6 +63,13 @@ public partial class AnthropicService
text = await _openAiService.ChatNonStreaming(subCtx, preparerMessages);
iterations = 1;
}
+ else if (string.IsNullOrEmpty(apiToken) && _cliService != null && _cliService.IsAvailable)
+ {
+ // Kein API-Key → CLI-Fallback
+ (text, iterations) = await RunSubAgentViaCli(
+ response, preparer, preparerMessages, chainId, sessionId,
+ "preparer", cancellationToken, cancellationToken);
+ }
else
{
(text, iterations) = await RunAgentChat(
@@ -164,6 +171,13 @@ public partial class AnthropicService
text = await _openAiService.ChatNonStreaming(subCtx, reviewMessages);
iterations = 1;
}
+ else if (string.IsNullOrEmpty(apiToken) && _cliService != null && _cliService.IsAvailable)
+ {
+ // Kein API-Key → CLI-Fallback
+ (text, iterations) = await RunSubAgentViaCli(
+ response, review, reviewMessages, chainId, sessionId,
+ "review", workCt, sseCt);
+ }
else
{
(text, iterations) = await RunAgentChat(
@@ -228,6 +242,13 @@ public partial class AnthropicService
text = await _openAiService.ChatNonStreaming(subCtx, finalizerMessages);
iterations = 1;
}
+ else if (string.IsNullOrEmpty(apiToken) && _cliService != null && _cliService.IsAvailable)
+ {
+ // Kein API-Key → CLI-Fallback
+ (text, iterations) = await RunSubAgentViaCli(
+ response, finalizer, finalizerMessages, chainId, sessionId,
+ "finalizer", workCt, sseCt);
+ }
else
{
(text, iterations) = await RunAgentChat(
@@ -624,6 +645,88 @@ public partial class AnthropicService
});
}
+ ///
+ /// Führt einen Sub-Agent (Preparer/Review/Finalizer) über die Claude CLI aus.
+ /// Sammelt den gesamten Text und gibt ihn zurück. Wird als Fallback genutzt,
+ /// wenn kein API-Key vorhanden ist.
+ ///
+ private async Task<(string text, int iterations)> RunSubAgentViaCli(
+ HttpResponse response,
+ SubAgentContext sub,
+ List