# ClawdDotNet – Prompt-Anhang: Internet-Tools Dieser Abschnitt ergänzt die bestehenden Prompt-Anhänge und definiert drei unabhängige Internet-Tools sowie ein spezialisiertes Monitoring-Tool für strukturierte Webseiten (z.B. Capitol Trades). Alle Tools folgen den bekannten Prinzipien: IAgentTool implementiert, Konfiguration ausschließlich aus AgentToolContext, keine Tool-zu-Tool-Abhängigkeiten. --- ## Pflicht-Regel: Timestamp in jedem ToolResult **Diese Regel gilt für ALLE Internet-Tools ohne Ausnahme.** Jedes `ToolResult.Content` das Finanzdaten, Nachrichten oder externe Daten enthält, muss ein JSON-Objekt zurückgeben das mindestens enthält: ```json { "fetchedAt": "2026-05-13T07:42:00Z", "dataAsOf": "2026-05-13T07:40:00Z", "source": "https://...", "data": { ... } } ``` - `fetchedAt` = Zeitpunkt des HTTP-Requests (UTC, immer `DateTime.UtcNow`) - `dataAsOf` = Zeitpunkt der Daten laut Quelle (aus Response-Header, HTML oder API-Feld) Falls nicht ermittelbar: `null` — NIEMALS schätzen oder weglassen - `source` = exakte URL die abgerufen wurde Der System-Prompt jedes Agenten der Internet-Tools nutzt MUSS enthalten: > "Verwende niemals Daten ohne `fetchedAt`-Feld. Wenn `dataAsOf` null ist, > teile dem Nutzer mit dass der Datenzeitpunkt unbekannt ist. > Erfinde niemals Kurse, Preise oder Daten aus dem Gedächtnis." --- ## Tool 1: DirectAPI — Echtzeit-Finanzdaten **Datei: `ClawdDotNet.Tools.DirectAPI/DirectApiTool.cs`** Direkter HTTP-Zugriff auf Finanz-APIs. Kein HTML-Parsing. Strukturiertes JSON mit verifizierbaren Timestamps. ### AgentConfig-Beispiel ```json "DirectAPI": { "providers": { "twelvedata": { "apiKey": "your-key-here", "baseUrl": "https://api.twelvedata.com" }, "alphavantage": { "apiKey": "your-key-here", "baseUrl": "https://www.alphavantage.co" }, "coingecko": { "baseUrl": "https://api.coingecko.com/api/v3" }, "yahoo": { "baseUrl": "https://query1.finance.yahoo.com" } }, "defaultProvider": "twelvedata", "cacheTtlSeconds": 60 } ``` ### Tool-Implementierung ```csharp namespace ClawdDotNet.Tools.DirectAPI; public sealed class DirectApiTool : IAgentTool { public string Name => "DirectAPI"; public string Description => """ Ruft Echtzeit-Finanzdaten von verifizierten APIs ab. Alle Antworten enthalten fetchedAt und dataAsOf Timestamps. Aktionen: quote, history, crypto, forex, search """; public JsonElement InputSchema => JsonDocument.Parse(""" { "type": "object", "required": ["action", "symbol"], "properties": { "action": { "type": "string", "enum": ["quote", "history", "crypto", "forex", "search"], "description": "quote=aktueller Kurs, history=Kursverlauf, crypto=Krypto, forex=Wechselkurs, search=Symbol suchen" }, "symbol": { "type": "string", "description": "z.B. NVDA, BTC, EUR/USD" }, "provider": { "type": "string", "description": "optional: twelvedata|alphavantage|coingecko|yahoo" }, "interval": { "type": "string", "description": "für history: 1min|5min|1h|1day" }, "outputsize":{ "type": "integer","description": "für history: Anzahl Datenpunkte, max 500" } } } """).RootElement; public async Task ExecuteAsync( JsonElement input, AgentToolContext ctx, CancellationToken ct) { // Config aus Context lesen — nie aus statischen Feldern var config = ctx.ToolConfig["DirectAPI"] as Dictionary ?? throw new InvalidOperationException("DirectAPI config missing"); var providers = config["providers"] as Dictionary ?? new(); var cacheTtl = Convert.ToInt32(config.GetValueOrDefault("cacheTtlSeconds") ?? 60); var action = input.GetProperty("action").GetString()!; var symbol = input.GetProperty("symbol").GetString()!; var provider = input.TryGetProperty("provider", out var p) ? p.GetString() : config.GetValueOrDefault("defaultProvider")?.ToString() ?? "twelvedata"; // Cache-Check: agentId + symbol + action als Key var cacheKey = $"directapi:{ctx.AgentId}:{provider}:{action}:{symbol}"; // (Cache-Implementierung über IMemoryCache oder Redis aus Core) return action switch { "quote" => await FetchQuoteAsync(symbol, provider, providers, ct), "history" => await FetchHistoryAsync(input, symbol, provider, providers, ct), "crypto" => await FetchCryptoAsync(symbol, providers, ct), "forex" => await FetchForexAsync(symbol, provider, providers, ct), "search" => await SearchSymbolAsync(symbol, provider, providers, ct), _ => new ToolResult(false, "", $"Unknown action: {action}") }; } private async Task FetchQuoteAsync( string symbol, string provider, Dictionary providers, CancellationToken ct) { // Jeder Provider hat eigene URL-Struktur // Gemeinsam: immer Cache-Control: no-cache Header setzen // Gemeinsam: dataAsOf aus Response extrahieren (nicht schätzen) // Twelve Data Quote: // GET https://api.twelvedata.com/quote?symbol={symbol}&apikey={key} // Response enthält: "datetime" → das ist dataAsOf // Response enthält: "timestamp" (Unix) → ebenfalls verwertbar // Yahoo Finance Quote (kein Key nötig): // GET https://query1.finance.yahoo.com/v8/finance/chart/{symbol}?interval=1m&range=1d // Response: result[0].meta.regularMarketTime (Unix timestamp) → dataAsOf // Alpha Vantage Quote: // GET https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={key} // Response: "Global Quote"."07. latest trading day" → dataAsOf (nur Datum, keine Zeit) // IMPLEMENTIERUNGSREGEL: dataAsOf IMMER aus der API-Antwort lesen. // Wenn das Feld fehlt oder leer ist → dataAsOf = null, NICHT DateTime.UtcNow. throw new NotImplementedException("Implement per provider"); } // history, crypto, forex, search analog implementieren } ``` ### NuGet Keine externen HTTP-Bibliotheken. Nur `System.Net.Http.HttpClient` via `IHttpClientFactory`. --- ## Tool 2: WebFetch — Nachrichten & strukturiertes HTML **Datei: `ClawdDotNet.Tools.WebFetch/WebFetchTool.cs`** HTTP-Abruf mit Whitelist, Timestamp-Extraktion und HTML-zu-Text-Konvertierung. Kein JavaScript-Rendering (statisches HTML only). ### AgentConfig-Beispiel ```json "WebFetch": { "allowedDomains": [ "reuters.com", "bloomberg.com", "sec.gov", "feeds.finance.yahoo.com", "reddit.com", "capitoltrades.com" ], "maxResponseKb": 512, "timeoutSeconds": 15, "userAgent": "ClawdDotNet-Agent/1.0 (Research Bot)" } ``` ### Tool-Implementierung ```csharp namespace ClawdDotNet.Tools.WebFetch; public sealed class WebFetchTool : IAgentTool { public string Name => "WebFetch"; public string Description => """ Ruft statische Webseiten oder RSS-Feeds ab und extrahiert Text + Timestamps. Nur Domains aus der Whitelist erlaubt. Kein JavaScript-Rendering. Aktionen: fetch, rss """; public JsonElement InputSchema => JsonDocument.Parse(""" { "type": "object", "required": ["action", "url"], "properties": { "action": { "type": "string", "enum": ["fetch", "rss"], "description": "fetch=HTML-Seite abrufen und zu Text konvertieren, rss=RSS/Atom-Feed parsen" }, "url": { "type": "string" }, "selector":{ "type": "string", "description": "optional: CSS-ähnlicher Hint welcher Teil relevant ist, z.B. 'table', 'article'" } } } """).RootElement; public async Task ExecuteAsync( JsonElement input, AgentToolContext ctx, CancellationToken ct) { var config = ctx.ToolConfig["WebFetch"] as Dictionary ?? new(); var allowedDomains = (config.GetValueOrDefault("allowedDomains") as List) ?? new List(); var url = input.GetProperty("url").GetString()!; var action = input.GetProperty("action").GetString()!; // Domain-Whitelist prüfen var host = new Uri(url).Host.Replace("www.", ""); if (!allowedDomains.Any(d => host == d || host.EndsWith("." + d))) return new ToolResult(false, "", $"Domain '{host}' nicht in der Whitelist dieses Agenten."); return action switch { "fetch" => await FetchPageAsync(url, input, config, ct), "rss" => await FetchRssAsync(url, ct), _ => new ToolResult(false, "", $"Unknown action: {action}") }; } private async Task FetchPageAsync( string url, JsonElement input, Dictionary config, CancellationToken ct) { using var http = CreateHttpClient(config); using var request = new HttpRequestMessage(HttpMethod.Get, url); // Kein Cache — immer frische Daten anfordern request.Headers.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue { NoCache = true, NoStore = true }; using var response = await http.SendAsync(request, ct); response.EnsureSuccessStatusCode(); // dataAsOf aus HTTP-Headern extrahieren (Reihenfolge: Last-Modified > Date) DateTimeOffset? dataAsOf = response.Content.Headers.LastModified ?? response.Headers.Date; var html = await response.Content.ReadAsStringAsync(ct); var maxKb = Convert.ToInt32(config.GetValueOrDefault("maxResponseKb") ?? 512); if (html.Length > maxKb * 1024) html = html[..(maxKb * 1024)]; // HTML → lesbarer Text (einfache Implementierung ohne externe Libs) var text = StripHtml(html); // Versuche dataAsOf aus HTML-Meta-Tags zu verfeinern falls Header fehlt if (dataAsOf == null) dataAsOf = ExtractDateFromHtml(html); var result = new { fetchedAt = DateTime.UtcNow, dataAsOf = dataAsOf?.UtcDateTime, source = url, data = new { text } }; return new ToolResult(true, JsonSerializer.Serialize(result)); } private async Task FetchRssAsync(string url, CancellationToken ct) { // XML parsen mit System.Xml.Linq // Einträge: title, link, pubDate (→ dataAsOf), description // Neueste Einträge zuerst, max 20 throw new NotImplementedException(); } private static string StripHtml(string html) { // Einfaches Regex-basiertes Stripping // Script- und Style-Tags zuerst entfernen, dann alle anderen Tags // Anschließend HTML-Entities dekodieren (System.Net.WebUtility.HtmlDecode) // Mehrfache Leerzeilen auf max. 2 reduzieren throw new NotImplementedException(); } private static DateTime? ExtractDateFromHtml(string html) { // Suche nach: