Enable GLM web search and 5.2+ effort over the z.ai CLI path

GLM models cannot run Anthropic's server-side WebSearch/WebFetch, so when a
GLM model has web search enabled and ZaiApiKey is set, GenerateMcpConfig
registers z.ai's web_search_prime HTTP MCP server and CreateCliStartInfo
allows mcp__web-search-prime__* while disallowing the native WebSearch/WebFetch
tools — otherwise the model tries the (headless-ungranted) native tool first
and only falls back to the MCP search on the second attempt.

Effort is now passed through for GLM-5.2 and newer (version compare, not a
prefix match, so 5.3/6 are picked up automatically and glm-5.2[1m]/glm-5-turbo
parse correctly); older GLM models still drop CLAUDE_CODE_EFFORT_LEVEL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MelekiandClaude Opus 4.8 committed 2026-06-20 14:30:59 +02:00
1 parent 0f3d658fd3
commit 5418dc9225
2 files changed
+82 -7

No files matched your search

+23 -2
View File
@@ -73,7 +73,7 @@ public partial class AnthropicService
var remoteConn = remoteMgr != null && !string.IsNullOrEmpty(sessionId) ? remoteMgr.GetBySessionId(sessionId) : null;
var hasRemote = remoteConn != null;
var remoteOs = remoteConn?.OsVersion ?? "";
mcpConfigPath = GenerateMcpConfig(sessionId, agentId, hasRemote, unityMcpUrl, unityMcpToken, remoteOs);
mcpConfigPath = GenerateMcpConfig(sessionId, agentId, hasRemote, unityMcpUrl, unityMcpToken, remoteOs, modelId, webSearch);
// Python-Verfügbarkeit prüfen und ggf. warnen
if (FindPythonCommand() == null)
@@ -903,7 +903,7 @@ public partial class AnthropicService
/// Generiert eine temporäre MCP-Config-Datei für die Claude CLI.
/// Enthält den MCP-Server für remote_execute.
/// </summary>
private string GenerateMcpConfig(string sessionId, string agentId = null, bool hasRemote = false, string unityMcpUrl = null, string unityMcpToken = null, string remoteOs = null)
private string GenerateMcpConfig(string sessionId, string agentId = null, bool hasRemote = false, string unityMcpUrl = null, string unityMcpToken = null, string remoteOs = null, string modelId = null, bool webSearch = false)
{
// Port aus Konfiguration oder Default
var port = 19000;
@@ -963,6 +963,27 @@ public partial class AnthropicService
mcpServers["unity"] = unityConfig;
}
// z.ai Web-Search-Prime-MCP: GLM-Modelle können Anthropics native WebSearch nicht nutzen.
// Läuft ein GLM-Modell mit aktivierter Web-Suche und ist der z.ai-Key gesetzt, registrieren
// wir z.ais HTTP-MCP-Suchserver (Bearer-Auth mit demselben Key). Tool wird in den
// --allowed-tools der CLI (ClaudeCliService) als "mcp__web-search-prime__*" freigegeben.
if (webSearch && ClaudeCliService.IsZaiModel(modelId))
{
var zaiKey = _settings?.Get("ZaiApiKey");
if (!string.IsNullOrEmpty(zaiKey))
{
mcpServers["web-search-prime"] = new Dictionary<string, object>
{
["type"] = "http",
["url"] = "https://api.z.ai/api/mcp/web_search_prime/mcp",
["headers"] = new Dictionary<string, string>
{
["Authorization"] = $"Bearer {zaiKey}"
}
};
}
}
var config = new { mcpServers };
var json = System.Text.Json.JsonSerializer.Serialize(config, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
+59 -5
View File
@@ -65,6 +65,31 @@ public class ClaudeCliService
public static bool IsZaiModel(string modelId)
=> !string.IsNullOrEmpty(modelId) && modelId.StartsWith("glm-", System.StringComparison.OrdinalIgnoreCase);
/// <summary>
/// GLM-5.2 (und höher) hat (laut z.ai-Doku) ein Reasoning-Effort-Mapping:
/// Claude Code mappt low/medium/high → GLM-"high", xhigh/max → GLM-"max". Ältere GLM-Modelle
/// (4.x, 5, 5-turbo, 5.1) kennen kein Effort. Bewusst ein Versionsvergleich statt Prefix-Match,
/// damit künftige Releases (5.3, 6, …) automatisch mitgenommen werden. Erfasst auch
/// Varianten-Suffixe wie "glm-5.2[1m]" oder "glm-5-turbo".
/// </summary>
private static bool IsZaiEffortModel(string modelId)
{
if (string.IsNullOrEmpty(modelId) || !modelId.StartsWith("glm-", System.StringComparison.OrdinalIgnoreCase))
return false;
// Führende Versionsziffern nach "glm-" extrahieren: "5.2[1m]" → 5.2, "5-turbo" → 5(.0).
var rest = modelId.Substring(4);
int i = 0;
while (i < rest.Length && (char.IsDigit(rest[i]) || rest[i] == '.')) i++;
var versionStr = rest.Substring(0, i).Trim('.');
if (versionStr.Length == 0) return false;
var parts = versionStr.Split('.');
if (!int.TryParse(parts[0], out var major)) return false;
var minor = 0;
if (parts.Length > 1) int.TryParse(parts[1], out minor);
// GLM-5.2 oder höher.
return major > 5 || (major == 5 && minor >= 2);
}
private ProcessStartInfo CreateCliStartInfo(List<string> args, bool redirectInput = false, string effort = null, string workingDirectory = null, string modelId = null)
{
var psi = new ProcessStartInfo
@@ -112,8 +137,12 @@ public class ClaudeCliService
psi.Environment["ANTHROPIC_DEFAULT_SONNET_MODEL"] = modelId;
psi.Environment["ANTHROPIC_DEFAULT_OPUS_MODEL"] = modelId;
psi.Environment["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = "glm-4.5-air";
// Effort/Reasoning-Budget ist Claude-spezifisch und für z.ai nicht definiert → nicht senden.
psi.Environment.Remove("CLAUDE_CODE_EFFORT_LEVEL");
// Effort/Reasoning: NUR GLM-5.2 hat ein definiertes Effort-Mapping bei z.ai
// (Claude Code mappt es selbst: low/medium/high → "high", xhigh/max → "max").
// Für 5.2 reichen wir CLAUDE_CODE_EFFORT_LEVEL daher durch; alle anderen GLM-Modelle
// kennen kein Effort → Env-Var entfernen, damit nichts Undefiniertes gesendet wird.
if (!IsZaiEffortModel(modelId))
psi.Environment.Remove("CLAUDE_CODE_EFFORT_LEVEL");
// Ein evtl. global gesetzter Anthropic-API-Key darf den z.ai-Token nicht überschreiben.
psi.Environment.Remove("ANTHROPIC_API_KEY");
}
@@ -207,10 +236,12 @@ public class ClaudeCliService
"--output-format", "stream-json",
"--include-partial-messages",
"--no-session-persistence",
"--permission-mode", permissionMode,
"--disallowed-tools", "AskUserQuestion"
"--permission-mode", permissionMode
};
// Tools, die das Modell gar nicht erst anbieten/versuchen soll.
var disallowedTools = new List<string> { "AskUserQuestion" };
// Tools konfigurieren
var tools = new List<string>();
if (hasWorkspace && planMode)
@@ -228,7 +259,27 @@ public class ClaudeCliService
// Kein Workspace: Read für Temp-Dateien (z.B. gesendete Bilder)
tools.Add("Read");
}
if (webSearch) { tools.Add("WebFetch"); tools.Add("WebSearch"); }
if (webSearch)
{
// GLM/z.ai kann Anthropics server-seitige WebSearch/WebFetch nicht ausführen →
// stattdessen z.ais Web-Search-Prime-MCP-Tool zulassen. Der zugehörige MCP-Server
// wird in GenerateMcpConfig injiziert, sobald GLM + Web-Suche + ZaiApiKey gesetzt sind.
if (IsZaiModel(modelId) && !string.IsNullOrEmpty(_settings?.Get("ZaiApiKey")))
{
tools.Add("mcp__web-search-prime__*");
// Die nativen Web-Tools komplett verbieten: sonst versucht das Modell zuerst
// eine (über z.ai nicht ausführbare) WebSearch, scheitert headless an der
// Berechtigung und greift erst im zweiten Anlauf zum Prime-MCP. Mit dem Verbot
// werden sie dem Modell gar nicht angeboten → direkt das Prime-Tool.
disallowedTools.Add("WebSearch");
disallowedTools.Add("WebFetch");
}
else
{
tools.Add("WebFetch");
tools.Add("WebSearch");
}
}
// MCP-Tools erlauben (wenn MCP-Config vorhanden)
if (!string.IsNullOrEmpty(mcpConfigPath))
{
@@ -246,6 +297,9 @@ public class ClaudeCliService
args.Add("--allowed-tools");
args.Add(tools.Count > 0 ? string.Join(" ", tools) : "\"\"");
args.Add("--disallowed-tools");
args.Add(string.Join(" ", disallowedTools));
if (!string.IsNullOrEmpty(workspace))
{
args.Add("--add-dir");