From 294edf72146c792d0bcd22adc2f052ba51917ed2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 29 Oct 2025 17:38:15 +0000 Subject: [PATCH 1/7] feat: Add agentic llms.txt generator tool Co-authored-by: william.holmberg --- .../tools/llms-txt-generator/ARCHITECTURE.md | 268 +++++++++++++++ .github/tools/llms-txt-generator/Agent.cs | 175 ++++++++++ .github/tools/llms-txt-generator/Program.cs | 143 ++++++++ .../Prompts/SystemPrompt.cs | 151 +++++++++ .../tools/llms-txt-generator/QUICKSTART.md | 168 ++++++++++ .github/tools/llms-txt-generator/README.md | 305 ++++++++++++++++++ .../Tools/FileSystemTools.cs | 284 ++++++++++++++++ .../Tools/WriteLlmsTxtTool.cs | 56 ++++ .../llms-txt-generator.csproj | 19 ++ .github/tools/llms-txt-generator/run.sh | 49 +++ 10 files changed, 1618 insertions(+) create mode 100644 .github/tools/llms-txt-generator/ARCHITECTURE.md create mode 100644 .github/tools/llms-txt-generator/Agent.cs create mode 100644 .github/tools/llms-txt-generator/Program.cs create mode 100644 .github/tools/llms-txt-generator/Prompts/SystemPrompt.cs create mode 100644 .github/tools/llms-txt-generator/QUICKSTART.md create mode 100644 .github/tools/llms-txt-generator/README.md create mode 100644 .github/tools/llms-txt-generator/Tools/FileSystemTools.cs create mode 100644 .github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs create mode 100644 .github/tools/llms-txt-generator/llms-txt-generator.csproj create mode 100755 .github/tools/llms-txt-generator/run.sh diff --git a/.github/tools/llms-txt-generator/ARCHITECTURE.md b/.github/tools/llms-txt-generator/ARCHITECTURE.md new file mode 100644 index 0000000..36602c5 --- /dev/null +++ b/.github/tools/llms-txt-generator/ARCHITECTURE.md @@ -0,0 +1,268 @@ +# πŸ—οΈ Architecture Overview + +## System Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Program.cs β”‚ +β”‚ (CLI Entry Point) β”‚ +β”‚ β”‚ +β”‚ β€’ Parse command line arguments β”‚ +β”‚ β€’ Initialize OpenRouterClient β”‚ +β”‚ β€’ Create Agent instance β”‚ +β”‚ β€’ Handle errors and output β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Agent.cs β”‚ +β”‚ (Agent Orchestration) β”‚ +β”‚ β”‚ +β”‚ β€’ Manages conversation history β”‚ +β”‚ β€’ Registers tools with OpenRouterClient β”‚ +β”‚ β€’ Runs agentic loop (up to max iterations) β”‚ +β”‚ β€’ Streams responses and handles tool calls β”‚ +β”‚ β€’ Monitors for completion (WriteLlmsTxt called) β”‚ +β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”‚ β”‚ + β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ FileSystemTools β”‚ β”‚ WriteLlmsTxtTool β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ πŸ“ ListDirectory β”‚ β”‚ ✍️ WriteLlmsTxt β”‚ +β”‚ πŸ“„ ReadFile β”‚ β”‚ (called once) β”‚ +β”‚ πŸ“š ReadFiles β”‚ β”‚ β”‚ +β”‚ πŸ” SearchFiles β”‚ β”‚ β€’ Validates content β”‚ +β”‚ 🌲 GetDirectoryTreeβ”‚ β”‚ β€’ Writes output file β”‚ +β”‚ πŸ“Š GetCodebaseStatsβ”‚ β”‚ β€’ Marks completion β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Agent Flow + +``` + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ START β”‚ + β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Load System Prompt β”‚ + β”‚ (Instructions to β”‚ + β”‚ explore & document) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ AGENTIC LOOP β”‚ + β”‚ (Iteration 1 to MaxIterations) β”‚ + β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ 1. LLM decides next action β”‚ β”‚ + β”‚ β”‚ (via OpenRouter.NET) β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ β”‚ + β”‚ β–Ό β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ 2. Calls tool (if needed) β”‚ β”‚ + β”‚ β”‚ β€’ ListDirectory β”‚ β”‚ + β”‚ β”‚ β€’ ReadFile β”‚ β”‚ + β”‚ β”‚ β€’ SearchFiles β”‚ β”‚ + β”‚ β”‚ β€’ etc. β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ β”‚ + β”‚ β–Ό β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ 3. Tool executes & returns β”‚ β”‚ + β”‚ β”‚ result to LLM β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ β”‚ + β”‚ β–Ό β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ 4. LLM processes result β”‚ β”‚ + β”‚ β”‚ (builds understanding) β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ β”‚ + β”‚ β–Ό β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ WriteLlmsTxt called?β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ No β”‚ Yes β”‚ + β”‚ β”‚ β”‚ β”‚ + β”‚ β”€β”€β”€β”€β”€β”˜ └────────────▢│ + β”‚ β”‚ β”‚ + β”‚ └─ Continue loop β”‚ + β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ WriteLlmsTxt called β”‚ + β”‚ Output file created β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ SUCCESS! β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Tool Calling Flow + +``` +Agent + β”‚ + β”‚ RegisterTool(fileTools, "ReadFile") + β–Ό +OpenRouterClient + β”‚ + β”‚ ChatCompletionRequest with tools[] + β–Ό +OpenRouter API + β”‚ + β”‚ Response with tool_calls[] + β–Ό +SDK Tool Execution + β”‚ + β”‚ Execute: fileTools.ReadFile(path) + β–Ό +Tool Returns Result + β”‚ + β”‚ Result sent back to LLM + β–Ό +LLM Continues + β”‚ + └─▢ Next iteration or completion +``` + +## Data Flow + +``` +User Input (--path) + β”‚ + β–Ό + File System ─────▢ FileSystemTools + β”‚ β”‚ + β”‚ β”‚ Read/List/Search + β”‚ β”‚ + β”‚ β–Ό + β”‚ OpenRouterClient + β”‚ β”‚ + β”‚ β”‚ API Request + β”‚ β”‚ + β”‚ β–Ό + β”‚ OpenRouter API + β”‚ β”‚ + β”‚ β”‚ LLM Processing + β”‚ β”‚ + β”‚ β–Ό + β”‚ Tool Calls + Responses + β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ + β”‚ (Loop until (When ready) + β”‚ complete) β”‚ + β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–Ό + WriteLlmsTxtTool + β”‚ + β”‚ Write + β–Ό + llms.txt file +``` + +## Key Components + +### 1. Program.cs +- **Purpose**: CLI interface +- **Responsibilities**: + - Parse arguments + - Validate inputs + - Initialize components + - Display results + +### 2. Agent.cs +- **Purpose**: Agent orchestration +- **Responsibilities**: + - Manage conversation state + - Register tools + - Run agentic loop + - Stream and handle responses + - Detect completion + +### 3. FileSystemTools.cs +- **Purpose**: Codebase exploration +- **Tools**: + - `ListDirectory` - Browse files + - `ReadFile` - Read single file + - `ReadFiles` - Read multiple files + - `SearchFiles` - Find by pattern + - `GetDirectoryTree` - Tree view + - `GetCodebaseStats` - Statistics + +### 4. WriteLlmsTxtTool.cs +- **Purpose**: Final output +- **Responsibilities**: + - Validate content + - Write file + - Signal completion + +### 5. SystemPrompt.cs +- **Purpose**: Agent instructions +- **Content**: + - Mission statement + - Tool descriptions + - Process steps + - Output format + - Requirements + +## Extension Points + +### Adding New Tools + +```csharp +// 1. Add method to FileSystemTools.cs +[ToolMethod("Description")] +public string MyNewTool([ToolParameter("param")] string param) +{ + // Implementation +} + +// 2. Register in Agent.cs +_client.RegisterTool(_fileTools, nameof(_fileTools.MyNewTool)); +``` + +### Adding New Modes + +```csharp +// Future: git-diff mode +public class GitTools +{ + [ToolMethod("Get git diff")] + public string GetDiff(string since) { ... } +} + +// Register conditionally based on mode +if (mode == "diff") +{ + var gitTools = new GitTools(); + _client.RegisterTool(gitTools, nameof(gitTools.GetDiff)); +} +``` + +## Performance Considerations + +- **Max Iterations**: Balance between thoroughness and cost +- **Model Selection**: Faster models = quicker runs, may be less thorough +- **Tool Efficiency**: ReadFiles() is more efficient than multiple ReadFile() calls +- **Caching**: Future enhancement to cache file reads + +## Cost Analysis + +Per run (typical SDK): +- **Context**: ~10k-20k tokens (files read, conversation) +- **Output**: ~10k tokens (generated docs) +- **Total**: ~$0.10-0.30 with Claude 3.5 Sonnet + +Very affordable for maintaining documentation! diff --git a/.github/tools/llms-txt-generator/Agent.cs b/.github/tools/llms-txt-generator/Agent.cs new file mode 100644 index 0000000..b27dd5d --- /dev/null +++ b/.github/tools/llms-txt-generator/Agent.cs @@ -0,0 +1,175 @@ +using OpenRouter.NET; +using OpenRouter.NET.Models; +using OpenRouter.NET.Tools; +using LlmsTxtGenerator.Tools; +using LlmsTxtGenerator.Prompts; +using System.Text; + +namespace LlmsTxtGenerator; + +public class Agent +{ + private readonly OpenRouterClient _client; + private readonly FileSystemTools _fileTools; + private readonly WriteLlmsTxtTool _writeTool; + private readonly List _conversationHistory; + private readonly string _model; + private readonly int _maxIterations; + + public Agent( + OpenRouterClient client, + string basePath, + string outputPath, + string model = "anthropic/claude-3.5-sonnet", + int maxIterations = 30) + { + _client = client; + _fileTools = new FileSystemTools(basePath); + _writeTool = new WriteLlmsTxtTool(outputPath); + _model = model; + _maxIterations = maxIterations; + + _conversationHistory = new List + { + Message.FromSystem(SystemPrompt.GetPrompt(basePath)) + }; + + RegisterTools(); + } + + private void RegisterTools() + { + _client.RegisterTool(_fileTools, nameof(_fileTools.ListDirectory)); + _client.RegisterTool(_fileTools, nameof(_fileTools.ReadFile)); + _client.RegisterTool(_fileTools, nameof(_fileTools.ReadFiles)); + _client.RegisterTool(_fileTools, nameof(_fileTools.SearchFiles)); + _client.RegisterTool(_fileTools, nameof(_fileTools.GetDirectoryTree)); + _client.RegisterTool(_fileTools, nameof(_fileTools.GetCodebaseStats)); + _client.RegisterTool(_writeTool, nameof(_writeTool.WriteLlmsTxt)); + } + + public async Task RunAsync() + { + Console.WriteLine("πŸ€– Agent Starting..."); + Console.WriteLine($"πŸ“¦ Model: {_model}"); + Console.WriteLine($"πŸ”„ Max iterations: {_maxIterations}"); + Console.WriteLine(); + + _conversationHistory.Add(Message.FromUser( + "Begin your analysis. Start by exploring the codebase structure, then read relevant files, " + + "and finally generate the comprehensive llms.txt documentation.")); + + int iteration = 0; + + while (iteration < _maxIterations && !_writeTool.HasWritten) + { + iteration++; + Console.WriteLine($"\n{'='​,60}"); + Console.WriteLine($"πŸ”„ Iteration {iteration}/{_maxIterations}"); + Console.WriteLine($"{'='​,60}\n"); + + var request = new ChatCompletionRequest + { + Model = _model, + Messages = _conversationHistory, + Temperature = 0.3f, + MaxTokens = 4000 + }; + + var response = new StringBuilder(); + var toolCalls = new List<(string name, string result)>(); + bool hasContent = false; + + try + { + await foreach (var chunk in _client.StreamAsync(request)) + { + if (chunk.TextDelta != null) + { + response.Append(chunk.TextDelta); + Console.Write(chunk.TextDelta); + hasContent = true; + } + + if (chunk.ServerTool != null) + { + switch (chunk.ServerTool.State) + { + case ToolCallState.Executing: + Console.WriteLine($"\n\nπŸ”§ Executing tool: {chunk.ServerTool.ToolName}"); + Console.WriteLine($" Arguments: {TruncateForDisplay(chunk.ServerTool.Arguments, 100)}"); + break; + + case ToolCallState.Completed: + var result = chunk.ServerTool.Result ?? "No result"; + Console.WriteLine($"βœ… Tool completed in {chunk.ServerTool.ExecutionTime?.TotalMilliseconds:F0}ms"); + Console.WriteLine($" Result preview: {TruncateForDisplay(result, 200)}"); + toolCalls.Add((chunk.ServerTool.ToolName, result)); + break; + + case ToolCallState.Error: + Console.WriteLine($"❌ Tool error: {chunk.ServerTool.Error}"); + break; + } + } + + if (chunk.Completion != null) + { + Console.WriteLine($"\n\nπŸ“Š Finish reason: {chunk.Completion.FinishReason}"); + if (chunk.Completion.Usage != null) + { + Console.WriteLine($" Tokens - Input: {chunk.Completion.Usage.PromptTokens}, " + + $"Output: {chunk.Completion.Usage.CompletionTokens}, " + + $"Total: {chunk.Completion.Usage.TotalTokens}"); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"\n\n❌ Error during streaming: {ex.Message}"); + return false; + } + + var responseText = response.ToString(); + + if (hasContent || toolCalls.Any()) + { + _conversationHistory.Add(Message.FromAssistant(responseText)); + } + + if (_writeTool.HasWritten) + { + Console.WriteLine("\n\n" + "="​.PadRight(60, '=')); + Console.WriteLine("βœ… AGENT COMPLETED SUCCESSFULLY!"); + Console.WriteLine("="​.PadRight(60, '=')); + Console.WriteLine($"\nπŸ“ Files read during analysis: {_fileTools.GetReadFiles().Count()}"); + Console.WriteLine($"πŸ”„ Iterations used: {iteration}/{_maxIterations}"); + Console.WriteLine($"\nπŸŽ‰ llms.txt has been generated!"); + return true; + } + + if (iteration >= _maxIterations) + { + Console.WriteLine("\n\n⚠️ Reached maximum iterations without completing."); + Console.WriteLine("The agent may need more iterations or there might be an issue."); + return false; + } + } + + return false; + } + + private static string TruncateForDisplay(string text, int maxLength) + { + if (string.IsNullOrEmpty(text)) + return "(empty)"; + + text = text.Replace("\n", " ").Replace("\r", ""); + + if (text.Length <= maxLength) + return text; + + return text.Substring(0, maxLength) + "..."; + } +} diff --git a/.github/tools/llms-txt-generator/Program.cs b/.github/tools/llms-txt-generator/Program.cs new file mode 100644 index 0000000..487013e --- /dev/null +++ b/.github/tools/llms-txt-generator/Program.cs @@ -0,0 +1,143 @@ +using LlmsTxtGenerator; +using System.CommandLine; + +var pathOption = new Option( + name: "--path", + description: "Path to the codebase to analyze (e.g., /path/to/sdk or .)") +{ + IsRequired = true +}; + +var outputOption = new Option( + name: "--output", + description: "Output path for llms.txt (default: llms.txt in current directory)", + getDefaultValue: () => Path.Combine(Directory.GetCurrentDirectory(), "llms.txt")); + +var modelOption = new Option( + name: "--model", + description: "OpenRouter model to use", + getDefaultValue: () => "anthropic/claude-3.5-sonnet"); + +var maxIterationsOption = new Option( + name: "--max-iterations", + description: "Maximum iterations for the agent", + getDefaultValue: () => 30); + +var apiKeyOption = new Option( + name: "--api-key", + description: "OpenRouter API key (or set OPENROUTER_API_KEY env var)"); + +var rootCommand = new RootCommand("πŸ€– LLMs.txt Generator - Agentic documentation generator for codebases") +{ + pathOption, + outputOption, + modelOption, + maxIterationsOption, + apiKeyOption +}; + +rootCommand.SetHandler(async (path, output, model, maxIterations, apiKey) => +{ + try + { + Console.WriteLine("╔════════════════════════════════════════════════════════════╗"); + Console.WriteLine("β•‘ πŸ€– LLMs.txt Generator - Agentic Edition β•‘"); + Console.WriteLine("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + Console.WriteLine(); + + var resolvedApiKey = apiKey ?? Environment.GetEnvironmentVariable("OPENROUTER_API_KEY"); + + if (string.IsNullOrEmpty(resolvedApiKey)) + { + Console.WriteLine("❌ Error: OpenRouter API key not provided!"); + Console.WriteLine(); + Console.WriteLine("Please either:"); + Console.WriteLine(" 1. Set OPENROUTER_API_KEY environment variable"); + Console.WriteLine(" 2. Use --api-key option"); + Console.WriteLine(); + Console.WriteLine("Example:"); + Console.WriteLine(" export OPENROUTER_API_KEY='your-key-here'"); + Console.WriteLine(" dotnet run -- --path ./src"); + Environment.Exit(1); + return; + } + + var fullPath = Path.GetFullPath(path); + var fullOutput = Path.GetFullPath(output); + + Console.WriteLine($"πŸ“‚ Analyzing: {fullPath}"); + Console.WriteLine($"πŸ“ Output to: {fullOutput}"); + Console.WriteLine($"πŸ€– Model: {model}"); + Console.WriteLine($"πŸ”„ Max iterations: {maxIterations}"); + Console.WriteLine(); + + if (!Directory.Exists(fullPath)) + { + Console.WriteLine($"❌ Error: Directory does not exist: {fullPath}"); + Environment.Exit(1); + return; + } + + Console.WriteLine("πŸš€ Initializing agent..."); + Console.WriteLine(); + + using var client = new OpenRouter.NET.OpenRouterClient(resolvedApiKey); + var agent = new Agent( + client, + fullPath, + fullOutput, + model, + maxIterations); + + var success = await agent.RunAsync(); + + Console.WriteLine(); + + if (success) + { + Console.WriteLine("╔════════════════════════════════════════════════════════════╗"); + Console.WriteLine("β•‘ βœ… SUCCESS! β•‘"); + Console.WriteLine("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + Console.WriteLine(); + Console.WriteLine($"πŸ“„ Generated: {fullOutput}"); + + if (File.Exists(fullOutput)) + { + var fileInfo = new FileInfo(fullOutput); + Console.WriteLine($"πŸ“Š Size: {fileInfo.Length:N0} bytes"); + + var lines = File.ReadLines(fullOutput).Count(); + Console.WriteLine($"πŸ“ Lines: {lines:N0}"); + } + + Console.WriteLine(); + Console.WriteLine("πŸŽ‰ Your llms.txt is ready!"); + Environment.Exit(0); + } + else + { + Console.WriteLine("╔════════════════════════════════════════════════════════════╗"); + Console.WriteLine("β•‘ ⚠️ WARNING β•‘"); + Console.WriteLine("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + Console.WriteLine(); + Console.WriteLine("❌ Agent did not complete successfully."); + Console.WriteLine("Consider increasing --max-iterations or checking for errors above."); + Environment.Exit(1); + } + } + catch (Exception ex) + { + Console.WriteLine(); + Console.WriteLine("╔════════════════════════════════════════════════════════════╗"); + Console.WriteLine("β•‘ ❌ ERROR β•‘"); + Console.WriteLine("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"); + Console.WriteLine(); + Console.WriteLine($"Error: {ex.Message}"); + Console.WriteLine(); + Console.WriteLine("Stack trace:"); + Console.WriteLine(ex.StackTrace); + Environment.Exit(1); + } +}, pathOption, outputOption, modelOption, maxIterationsOption, apiKeyOption); + +return await rootCommand.InvokeAsync(args); diff --git a/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs new file mode 100644 index 0000000..f8df7b0 --- /dev/null +++ b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs @@ -0,0 +1,151 @@ +namespace LlmsTxtGenerator.Prompts; + +public static class SystemPrompt +{ + public static string GetPrompt(string basePath) => $@"You are an expert technical documentation writer specializing in creating comprehensive LLM-friendly documentation files (llms.txt). + +# YOUR MISSION + +Analyze the codebase at: {basePath} + +Generate a COMPLETE, COMPREHENSIVE llms.txt file that serves as a reference guide for LLMs to understand and use this SDK. + +# AVAILABLE TOOLS + +You have these tools to explore the codebase: + +1. **ListDirectory(path)** - List files/folders in a directory +2. **ReadFile(filePath)** - Read a single file's content +3. **ReadFiles(filePathsJson)** - Read multiple files at once (JSON array) +4. **SearchFiles(pattern)** - Find files matching a pattern (e.g., '*.cs') +5. **GetDirectoryTree(path, maxDepth)** - Get tree view of directory structure +6. **GetCodebaseStats(path)** - Get statistics about file types and lines +7. **WriteLlmsTxt(content)** - OUTPUT the final llms.txt (CALL THIS LAST!) + +# YOUR PROCESS + +1. **EXPLORE** the codebase systematically: + - Start with GetDirectoryTree to understand structure + - Use GetCodebaseStats to see what types of files exist + - Read key files: README, .csproj files, main source files + - Explore Models, Extensions, Tools directories + - Look at sample code if available + +2. **UNDERSTAND** the SDK: + - What is the SDK for? (read README) + - What are the main classes and their purposes? + - What patterns does it support? (streaming, tools, etc.) + - What are the public APIs? + - What are common usage patterns? + - What are the dependencies and requirements? + +3. **GENERATE** comprehensive documentation: + - Complete usage guide with all patterns + - Configuration options + - Request/response models + - Error handling + - Streaming patterns + - Advanced features + - Code examples (real, working examples) + - Best practices + - Common issues and solutions + +4. **OUTPUT** using WriteLlmsTxt tool with XML-structured sections + +# DOCUMENTATION STRUCTURE + +Your llms.txt should follow this structure: + +```xml + + + SDK Name - Comprehensive Usage Guide + X.Y.Z + .NET version + Root namespace + + +
+ Getting Started + + Required using statements, installation, basic setup... + +
+ +
+ Core Client Usage + + Configuration + + All configuration patterns with examples... + + + + Public Methods + + Documentation of all public methods... + + +
+ +
+ Usage Patterns + + Streaming, tools, artifacts, conversation management... + +
+ +
+ Advanced Features + + Advanced usage, edge cases, performance tips... + +
+ +
+ Best Practices + + Important notes, gotchas, recommendations... + +
+
+``` + +# CRITICAL REQUIREMENTS + +βœ… **BE COMPREHENSIVE**: This is for LLMs to understand the ENTIRE SDK +βœ… **INCLUDE REAL EXAMPLES**: Use actual code patterns from the codebase +βœ… **BE SPECIFIC**: Include exact class names, method signatures, namespaces +βœ… **SHOW COMMON PATTERNS**: How do developers actually use this? +βœ… **DOCUMENT GOTCHAS**: Common mistakes, error messages, solutions +βœ… **USE XML STRUCTURE**: Wrap sections in XML tags for easy parsing +βœ… **BE THOROUGH**: Read enough files to truly understand the SDK +βœ… **WORKING CODE**: All examples should be syntactically correct + +❌ **DON'T RUSH**: Take time to explore thoroughly before writing +❌ **DON'T GUESS**: Read the actual code to understand behavior +❌ **DON'T BE VAGUE**: Provide concrete examples and specific details +❌ **DON'T SKIP FEATURES**: Document everything important + +# EXPLORATION STRATEGY + +I recommend this approach: + +1. GetDirectoryTree() to see overall structure +2. ReadFile(""README.md"") to understand the SDK +3. ReadFile(""*.csproj"") to see dependencies and version +4. SearchFiles(""*.cs"") to list all source files +5. Read main client class files +6. Read model/request/response files +7. Read extension method files +8. Read any tool or helper files +9. Look at samples if available +10. THEN write comprehensive llms.txt + +Take your time. Explore thoroughly. When ready, call WriteLlmsTxt with the complete documentation. + +# START NOW + +Begin by exploring the codebase. Use the tools to understand what you're documenting. +"; +} diff --git a/.github/tools/llms-txt-generator/QUICKSTART.md b/.github/tools/llms-txt-generator/QUICKSTART.md new file mode 100644 index 0000000..03cff29 --- /dev/null +++ b/.github/tools/llms-txt-generator/QUICKSTART.md @@ -0,0 +1,168 @@ +# πŸš€ Quick Start Guide + +## Step 1: Set Your API Key + +```bash +export OPENROUTER_API_KEY="sk-or-v1-your-key-here" +``` + +Get your API key from: https://openrouter.ai/keys + +## Step 2: Run the Generator + +### Option A: Using the quick script (easiest) + +```bash +cd .github/tools/llms-txt-generator +./run.sh +``` + +This will: +- Restore dependencies +- Build the project +- Analyze `src/` directory +- Generate `llms.txt` in project root + +### Option B: Manual run with custom options + +```bash +cd .github/tools/llms-txt-generator + +# Restore and build +dotnet restore +dotnet build + +# Run with custom path +dotnet run -- --path ../../../src --output ../../../llms.txt +``` + +### Option C: Analyze a different path + +```bash +# Analyze the react-sdk package instead +./run.sh ../../../packages/react-sdk react-sdk-llms.txt +``` + +## Step 3: Watch It Work! + +The agent will: + +1. **πŸ” Explore** - List directories and files +2. **πŸ“– Read** - Read source files, README, examples +3. **🧠 Understand** - Build mental model of SDK +4. **✍️ Generate** - Create comprehensive llms.txt +5. **βœ… Done!** - Output saved + +Example output: +``` +πŸ€– Agent Starting... +πŸ“¦ Model: anthropic/claude-3.5-sonnet +πŸ”„ Max iterations: 30 + +============================================================ +πŸ”„ Iteration 1/30 +============================================================ + +Let me start by exploring the codebase structure... + +πŸ”§ Executing tool: GetDirectoryTree + Arguments: {"path":".","maxDepth":3} +βœ… Tool completed in 45ms + +πŸ”§ Executing tool: ReadFile + Arguments: {"filePath":"README.md"} +βœ… Tool completed in 12ms + +[... continues exploring ...] + +βœ… AGENT COMPLETED SUCCESSFULLY! + +πŸ“„ Generated: /workspace/llms.txt +πŸ“Š Size: 127,584 bytes +πŸ“ Lines: 2,341 +``` + +## What Gets Generated? + +The tool creates a comprehensive `llms.txt` with: + +- βœ… Complete API reference +- βœ… Usage patterns and examples +- βœ… Configuration options +- βœ… Error handling guide +- βœ… Best practices +- βœ… Common issues and solutions +- βœ… XML-structured sections + +## Customization + +### Use a Different Model + +```bash +# Faster, cheaper +dotnet run -- --path ./src --model openai/gpt-4o-mini + +# Best quality +dotnet run -- --path ./src --model anthropic/claude-3-opus +``` + +### Increase Iterations + +If agent doesn't finish: + +```bash +dotnet run -- --path ./src --max-iterations 50 +``` + +### Custom Output Location + +```bash +dotnet run -- --path ./src --output ./docs/llms.txt +``` + +## Troubleshooting + +### "API key not set" +```bash +# Check it's set +echo $OPENROUTER_API_KEY + +# If not, set it +export OPENROUTER_API_KEY="sk-or-v1-..." +``` + +### "Directory does not exist" +```bash +# Use absolute path +dotnet run -- --path /workspace/src + +# Or relative from tool directory +dotnet run -- --path ../../../src +``` + +### Agent runs out of iterations + +Increase max iterations: +```bash +dotnet run -- --path ./src --max-iterations 50 +``` + +Or use a more capable model: +```bash +dotnet run -- --path ./src --model anthropic/claude-3-opus +``` + +## Next Steps + +1. βœ… Review the generated `llms.txt` +2. βœ… Test with your LLM of choice +3. βœ… Add to your repository +4. βœ… Set up GitHub Action (coming soon!) + +## Need Help? + +See the full [README.md](README.md) for: +- Detailed architecture +- Adding custom tools +- Future roadmap +- Development guide diff --git a/.github/tools/llms-txt-generator/README.md b/.github/tools/llms-txt-generator/README.md new file mode 100644 index 0000000..a0cf80f --- /dev/null +++ b/.github/tools/llms-txt-generator/README.md @@ -0,0 +1,305 @@ +# πŸ€– LLMs.txt Generator - Agentic Edition + +An intelligent agent that explores your codebase and generates comprehensive LLM-friendly documentation (`llms.txt`). + +## What Does It Do? + +Instead of manually maintaining `llms.txt`, this tool: + +1. **Explores** your codebase autonomously using filesystem tools +2. **Understands** your SDK by reading source files, README, examples +3. **Generates** a comprehensive, structured `llms.txt` documentation file +4. **Uses your own OpenRouter.NET SDK** with tool calling (dogfooding!) + +## Features + +βœ… **Agentic Exploration** - The LLM agent explores your codebase systematically +βœ… **Smart Analysis** - Understands code structure, patterns, and usage +βœ… **Tool Calling** - Uses real tools to read files, search, and analyze +βœ… **XML Structure** - Outputs structured XML for easy section updates +βœ… **Comprehensive** - Generates complete documentation, not just summaries +βœ… **Extensible** - Easy to add new modes (git-diff, incremental updates) + +## Quick Start + +### 1. Set API Key + +```bash +export OPENROUTER_API_KEY="sk-or-v1-..." +``` + +### 2. Run the Generator + +```bash +cd .github/tools/llms-txt-generator +dotnet run -- --path ../../../src +``` + +That's it! The agent will explore your codebase and generate `llms.txt`. + +## Usage + +### Basic Usage + +```bash +dotnet run -- --path +``` + +### Full Options + +```bash +dotnet run -- \ + --path ./src \ + --output ./llms.txt \ + --model anthropic/claude-3.5-sonnet \ + --max-iterations 30 \ + --api-key sk-or-v1-... +``` + +### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--path` | Path to codebase (required) | - | +| `--output` | Output file path | `./llms.txt` | +| `--model` | OpenRouter model to use | `anthropic/claude-3.5-sonnet` | +| `--max-iterations` | Max agent iterations | `30` | +| `--api-key` | OpenRouter API key | `$OPENROUTER_API_KEY` | + +## How It Works + +### 1. Agent Initialization + +The agent starts with a comprehensive system prompt that instructs it to: +- Explore the codebase systematically +- Read relevant source files +- Understand the SDK's purpose and patterns +- Generate complete documentation + +### 2. Tool Calling + +The agent has access to these tools: + +- **`ListDirectory(path)`** - List files/folders +- **`ReadFile(filePath)`** - Read a single file +- **`ReadFiles(filePathsJson)`** - Read multiple files efficiently +- **`SearchFiles(pattern)`** - Find files by pattern (e.g., `*.cs`) +- **`GetDirectoryTree(path, maxDepth)`** - Get tree view of structure +- **`GetCodebaseStats(path)`** - Get file statistics +- **`WriteLlmsTxt(content)`** - Output the final documentation (called last) + +### 3. Agentic Loop + +The agent iteratively: +1. Calls tools to explore and understand the codebase +2. Builds up knowledge about the SDK +3. When ready, generates and outputs the complete `llms.txt` + +### 4. Output + +The generated `llms.txt` uses XML structure for easy parsing: + +```xml + + ... +
...
+
...
+ ... +
+``` + +## Example Run + +```bash +$ cd .github/tools/llms-txt-generator +$ export OPENROUTER_API_KEY="sk-or-v1-..." +$ dotnet run -- --path ../../../src + +╔════════════════════════════════════════════════════════════╗ +β•‘ πŸ€– LLMs.txt Generator - Agentic Edition β•‘ +β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• + +πŸ“‚ Analyzing: /workspace/src +πŸ“ Output to: /workspace/llms.txt +πŸ€– Model: anthropic/claude-3.5-sonnet +πŸ”„ Max iterations: 30 + +πŸš€ Initializing agent... + +πŸ€– Agent Starting... +πŸ“¦ Model: anthropic/claude-3.5-sonnet +πŸ”„ Max iterations: 30 + +============================================================ +πŸ”„ Iteration 1/30 +============================================================ + +Let me start by exploring the codebase structure... + +πŸ”§ Executing tool: GetDirectoryTree +βœ… Tool completed in 45ms + +πŸ”§ Executing tool: ReadFile +βœ… Tool completed in 12ms + +[... agent continues exploring ...] + +============================================================ +βœ… AGENT COMPLETED SUCCESSFULLY! +============================================================ + +πŸ“ Files read during analysis: 23 +πŸ”„ Iterations used: 12/30 + +πŸŽ‰ llms.txt has been generated! + +╔════════════════════════════════════════════════════════════╗ +β•‘ βœ… SUCCESS! β•‘ +β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• + +πŸ“„ Generated: /workspace/llms.txt +πŸ“Š Size: 127,584 bytes +πŸ“ Lines: 2,341 + +πŸŽ‰ Your llms.txt is ready! +``` + +## Recommended Models + +| Model | Speed | Quality | Cost | Use Case | +|-------|-------|---------|------|----------| +| `anthropic/claude-3.5-sonnet` | Medium | Excellent | $$$ | **Recommended** - Best quality | +| `openai/gpt-4o` | Fast | Excellent | $$ | Great balance | +| `openai/gpt-4o-mini` | Very Fast | Good | $ | Quick iterations | +| `anthropic/claude-3-opus` | Slow | Best | $$$$ | Maximum quality | + +## Cost Estimation + +Typical run for a medium SDK (~50 source files): +- **Input tokens**: ~15,000 (reading files, context) +- **Output tokens**: ~10,000 (generating docs) +- **Total cost**: ~$0.10-0.30 per run (with Claude 3.5 Sonnet) + +Very affordable for automated documentation! + +## Future Modes + +This tool is designed to be extended with additional modes: + +### Coming Soon + +- **`--mode diff`** - Generate updates based on git diff +- **`--mode incremental`** - Update only changed sections +- **`--mode validate`** - Check if existing llms.txt is up to date +- **`--format markdown`** - Output in markdown instead of XML + +### Example Future Usage + +```bash +# Generate based on recent changes only +dotnet run -- --path ./src --mode diff --since HEAD~5 + +# Update specific sections only +dotnet run -- --path ./src --mode incremental --sections "tools,streaming" + +# Validate current llms.txt +dotnet run -- --path ./src --mode validate --input llms.txt +``` + +## Troubleshooting + +### "Agent did not complete successfully" + +The agent might need more iterations. Try: +```bash +dotnet run -- --path ./src --max-iterations 50 +``` + +### "Directory does not exist" + +Make sure the path is correct: +```bash +dotnet run -- --path $(pwd)/src +``` + +### API Key Issues + +Verify your API key: +```bash +echo $OPENROUTER_API_KEY +``` + +Should start with `sk-or-v1-` + +### Agent is too slow + +Try a faster model: +```bash +dotnet run -- --path ./src --model openai/gpt-4o-mini +``` + +## Development + +### Project Structure + +``` +.github/tools/llms-txt-generator/ +β”œβ”€β”€ Program.cs # CLI entry point +β”œβ”€β”€ Agent.cs # Agent orchestration +β”œβ”€β”€ Tools/ +β”‚ β”œβ”€β”€ FileSystemTools.cs # File reading/exploring tools +β”‚ └── WriteLlmsTxtTool.cs # Output generation tool +β”œβ”€β”€ Prompts/ +β”‚ └── SystemPrompt.cs # Agent instructions +β”œβ”€β”€ llms-txt-generator.csproj # Project file +└── README.md # This file +``` + +### Adding New Tools + +1. Create tool method in `Tools/FileSystemTools.cs` +2. Add `[ToolMethod]` attribute with description +3. Add `[ToolParameter]` attributes to parameters +4. Register tool in `Agent.cs` constructor + +Example: +```csharp +[ToolMethod("Get git commit history")] +public string GetGitLog( + [ToolParameter("Number of commits")] int count = 10) +{ + // Implementation +} +``` + +Then register: +```csharp +_client.RegisterTool(_fileTools, nameof(_fileTools.GetGitLog)); +``` + +## Why This Approach? + +### Traditional Approach ❌ +- Manual updates required +- Easy to forget or skip +- Inconsistent documentation +- Time-consuming maintenance + +### Agentic Approach βœ… +- **Autonomous** - Agent explores and understands +- **Comprehensive** - Doesn't miss important features +- **Consistent** - Same quality every time +- **Extensible** - Easy to add new capabilities + +## Contributing + +Ideas for improvements: +- Add more filesystem tools +- Implement git-diff mode +- Add validation mode +- Support multiple output formats +- Add caching to reduce API calls + +## License + +Same as OpenRouter.NET (MIT) diff --git a/.github/tools/llms-txt-generator/Tools/FileSystemTools.cs b/.github/tools/llms-txt-generator/Tools/FileSystemTools.cs new file mode 100644 index 0000000..68ef2c4 --- /dev/null +++ b/.github/tools/llms-txt-generator/Tools/FileSystemTools.cs @@ -0,0 +1,284 @@ +using OpenRouter.NET.Tools; +using System.Text.Json; + +namespace LlmsTxtGenerator.Tools; + +public class FileSystemTools +{ + private readonly string _basePath; + private readonly HashSet _readFiles = new(); + + public FileSystemTools(string basePath) + { + _basePath = Path.GetFullPath(basePath); + + if (!Directory.Exists(_basePath)) + { + throw new DirectoryNotFoundException($"Base path does not exist: {_basePath}"); + } + } + + [ToolMethod("List all files and directories in a given path. Returns names only for overview.")] + public string ListDirectory( + [ToolParameter("Relative path from the base directory (use '.' for root)")] string path = ".") + { + try + { + var fullPath = GetFullPath(path); + + if (!Directory.Exists(fullPath)) + { + return $"❌ Directory not found: {path}"; + } + + var items = new List(); + + var dirs = Directory.GetDirectories(fullPath) + .Select(d => $"πŸ“ {Path.GetFileName(d)}/") + .OrderBy(x => x); + + var files = Directory.GetFiles(fullPath) + .Select(f => $"πŸ“„ {Path.GetFileName(f)}") + .OrderBy(x => x); + + items.AddRange(dirs); + items.AddRange(files); + + if (!items.Any()) + { + return $"πŸ“‚ {path}: (empty directory)"; + } + + return $"πŸ“‚ {path}:\n" + string.Join("\n", items); + } + catch (Exception ex) + { + return $"❌ Error listing directory: {ex.Message}"; + } + } + + [ToolMethod("Read the complete content of a single file")] + public string ReadFile( + [ToolParameter("Relative path to the file from base directory")] string filePath) + { + try + { + var fullPath = GetFullPath(filePath); + + if (!File.Exists(fullPath)) + { + return $"❌ File not found: {filePath}"; + } + + var content = File.ReadAllText(fullPath); + _readFiles.Add(filePath); + + var lines = content.Split('\n').Length; + return $"πŸ“„ File: {filePath} ({lines} lines)\n" + + $"---\n{content}\n---"; + } + catch (Exception ex) + { + return $"❌ Error reading file: {ex.Message}"; + } + } + + [ToolMethod("Read multiple files at once. Efficient for reading several related files.")] + public string ReadFiles( + [ToolParameter("Array of relative file paths as JSON array, e.g. [\"file1.cs\", \"dir/file2.cs\"]")] string filePathsJson) + { + try + { + var filePaths = JsonSerializer.Deserialize(filePathsJson); + + if (filePaths == null || filePaths.Length == 0) + { + return "❌ No file paths provided or invalid JSON format"; + } + + var results = new List(); + + foreach (var filePath in filePaths) + { + var fullPath = GetFullPath(filePath); + + if (!File.Exists(fullPath)) + { + results.Add($"❌ {filePath}: File not found"); + continue; + } + + var content = File.ReadAllText(fullPath); + _readFiles.Add(filePath); + + var lines = content.Split('\n').Length; + results.Add($"πŸ“„ {filePath} ({lines} lines)\n---\n{content}\n---"); + } + + return string.Join("\n\n", results); + } + catch (Exception ex) + { + return $"❌ Error reading files: {ex.Message}"; + } + } + + [ToolMethod("Search for files matching a pattern (e.g., '*.cs', '**/Models/*.cs')")] + public string SearchFiles( + [ToolParameter("Glob pattern to search for files")] string pattern) + { + try + { + var searchPath = _basePath; + var searchPattern = pattern; + + if (pattern.Contains('/')) + { + var parts = pattern.Split('/', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length > 1) + { + searchPath = Path.Combine(_basePath, string.Join('/', parts.Take(parts.Length - 1))); + searchPattern = parts.Last(); + } + } + + var files = Directory.GetFiles(searchPath, searchPattern, SearchOption.AllDirectories) + .Select(f => Path.GetRelativePath(_basePath, f)) + .OrderBy(f => f) + .ToList(); + + if (!files.Any()) + { + return $"No files found matching pattern: {pattern}"; + } + + return $"πŸ” Found {files.Count} file(s) matching '{pattern}':\n" + + string.Join("\n", files.Select(f => $" β€’ {f}")); + } + catch (Exception ex) + { + return $"❌ Error searching files: {ex.Message}"; + } + } + + [ToolMethod("Get a tree view of the directory structure")] + public string GetDirectoryTree( + [ToolParameter("Relative path from base (use '.' for root)")] string path = ".", + [ToolParameter("Maximum depth to traverse (default 3)")] int maxDepth = 3) + { + try + { + var fullPath = GetFullPath(path); + + if (!Directory.Exists(fullPath)) + { + return $"❌ Directory not found: {path}"; + } + + var tree = new List { $"πŸ“‚ {path}/" }; + BuildTree(fullPath, "", 0, maxDepth, tree); + + return string.Join("\n", tree); + } + catch (Exception ex) + { + return $"❌ Error building tree: {ex.Message}"; + } + } + + [ToolMethod("Get summary statistics about the codebase")] + public string GetCodebaseStats( + [ToolParameter("Relative path to analyze (use '.' for entire codebase)")] string path = ".") + { + try + { + var fullPath = GetFullPath(path); + + if (!Directory.Exists(fullPath)) + { + return $"❌ Directory not found: {path}"; + } + + var allFiles = Directory.GetFiles(fullPath, "*.*", SearchOption.AllDirectories); + + var stats = allFiles + .GroupBy(f => Path.GetExtension(f).ToLowerInvariant()) + .Select(g => new + { + Extension = string.IsNullOrEmpty(g.Key) ? "(no extension)" : g.Key, + Count = g.Count(), + TotalLines = g.Sum(f => File.ReadLines(f).Count()) + }) + .OrderByDescending(x => x.Count) + .ToList(); + + var result = new List + { + $"πŸ“Š Codebase Statistics for: {path}", + $"Total files: {allFiles.Length}", + "", + "Breakdown by file type:" + }; + + foreach (var stat in stats) + { + result.Add($" {stat.Extension}: {stat.Count} files, {stat.TotalLines:N0} lines"); + } + + return string.Join("\n", result); + } + catch (Exception ex) + { + return $"❌ Error calculating stats: {ex.Message}"; + } + } + + public IEnumerable GetReadFiles() => _readFiles; + + private string GetFullPath(string relativePath) + { + if (relativePath == ".") + return _basePath; + + var fullPath = Path.Combine(_basePath, relativePath); + var normalizedPath = Path.GetFullPath(fullPath); + + if (!normalizedPath.StartsWith(_basePath)) + { + throw new UnauthorizedAccessException("Access outside base path is not allowed"); + } + + return normalizedPath; + } + + private void BuildTree(string path, string indent, int depth, int maxDepth, List output) + { + if (depth >= maxDepth) + return; + + try + { + var dirs = Directory.GetDirectories(path).OrderBy(d => d).ToList(); + var files = Directory.GetFiles(path).OrderBy(f => f).ToList(); + + for (int i = 0; i < dirs.Count; i++) + { + var isLast = i == dirs.Count - 1 && files.Count == 0; + var dirName = Path.GetFileName(dirs[i]); + output.Add($"{indent}{(isLast ? "└── " : "β”œβ”€β”€ ")}πŸ“ {dirName}/"); + BuildTree(dirs[i], indent + (isLast ? " " : "β”‚ "), depth + 1, maxDepth, output); + } + + for (int i = 0; i < files.Count; i++) + { + var isLast = i == files.Count - 1; + var fileName = Path.GetFileName(files[i]); + output.Add($"{indent}{(isLast ? "└── " : "β”œβ”€β”€ ")}πŸ“„ {fileName}"); + } + } + catch + { + // Skip directories we can't access + } + } +} diff --git a/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs b/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs new file mode 100644 index 0000000..2630e93 --- /dev/null +++ b/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs @@ -0,0 +1,56 @@ +using OpenRouter.NET.Tools; + +namespace LlmsTxtGenerator.Tools; + +public class WriteLlmsTxtTool +{ + private readonly string _outputPath; + private bool _hasWritten = false; + + public WriteLlmsTxtTool(string outputPath) + { + _outputPath = outputPath; + } + + [ToolMethod("Write the final llms.txt content. Call this ONLY when you have completed your analysis and are ready to generate the complete documentation.")] + public string WriteLlmsTxt( + [ToolParameter("The complete llms.txt content with all sections, examples, and documentation")] string content) + { + try + { + if (_hasWritten) + { + return "❌ llms.txt has already been written. Tool can only be called once per execution."; + } + + if (string.IsNullOrWhiteSpace(content)) + { + return "❌ Content cannot be empty"; + } + + if (content.Length < 1000) + { + return "❌ Content seems too short. llms.txt should be comprehensive documentation (at least 1000 characters). Did you complete your analysis?"; + } + + File.WriteAllText(_outputPath, content); + _hasWritten = true; + + var lines = content.Split('\n').Length; + var chars = content.Length; + + return $"βœ… Successfully wrote llms.txt!\n" + + $" Location: {_outputPath}\n" + + $" Lines: {lines:N0}\n" + + $" Characters: {chars:N0}\n" + + $"\n" + + $"The documentation has been generated successfully."; + } + catch (Exception ex) + { + return $"❌ Error writing llms.txt: {ex.Message}"; + } + } + + public bool HasWritten => _hasWritten; +} diff --git a/.github/tools/llms-txt-generator/llms-txt-generator.csproj b/.github/tools/llms-txt-generator/llms-txt-generator.csproj new file mode 100644 index 0000000..b873e26 --- /dev/null +++ b/.github/tools/llms-txt-generator/llms-txt-generator.csproj @@ -0,0 +1,19 @@ + + + + Exe + net9.0 + LlmsTxtGenerator + enable + enable + + + + + + + + + + + diff --git a/.github/tools/llms-txt-generator/run.sh b/.github/tools/llms-txt-generator/run.sh new file mode 100755 index 0000000..a9b423b --- /dev/null +++ b/.github/tools/llms-txt-generator/run.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Quick setup and run script for llms.txt generator + +set -e + +echo "╔════════════════════════════════════════════════════════════╗" +echo "β•‘ πŸ€– LLMs.txt Generator - Quick Start β•‘" +echo "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•" +echo "" + +# Check if API key is set +if [ -z "$OPENROUTER_API_KEY" ]; then + echo "❌ Error: OPENROUTER_API_KEY environment variable not set!" + echo "" + echo "Please set it first:" + echo " export OPENROUTER_API_KEY='your-key-here'" + echo "" + exit 1 +fi + +# Default path to analyze +TARGET_PATH="${1:-../../../src}" +OUTPUT_PATH="${2:-../../../llms.txt}" +MODEL="${3:-anthropic/claude-3.5-sonnet}" + +echo "πŸ“‚ Target path: $TARGET_PATH" +echo "πŸ“ Output path: $OUTPUT_PATH" +echo "πŸ€– Model: $MODEL" +echo "" + +# Navigate to tool directory +cd "$(dirname "$0")" + +echo "πŸ”§ Restoring dependencies..." +dotnet restore + +echo "" +echo "πŸ—οΈ Building project..." +dotnet build -c Release + +echo "" +echo "πŸš€ Running agent..." +echo "" + +dotnet run -c Release -- \ + --path "$TARGET_PATH" \ + --output "$OUTPUT_PATH" \ + --model "$MODEL" From a36701e748cdb23dc2ea3120247c35823fae205b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 29 Oct 2025 18:10:41 +0000 Subject: [PATCH 2/7] Checkpoint before follow-up message Co-authored-by: william.holmberg --- .github/tools/llms-txt-generator/Agent.cs | 12 +++- .../Prompts/SystemPrompt.cs | 55 ++++++++++++++----- .../tools/llms-txt-generator/QUICKSTART.md | 27 +++++++-- .github/tools/llms-txt-generator/README.md | 19 ++++++- .github/tools/llms-txt-generator/run.sh | 4 +- 5 files changed, 91 insertions(+), 26 deletions(-) diff --git a/.github/tools/llms-txt-generator/Agent.cs b/.github/tools/llms-txt-generator/Agent.cs index b27dd5d..7625ecf 100644 --- a/.github/tools/llms-txt-generator/Agent.cs +++ b/.github/tools/llms-txt-generator/Agent.cs @@ -56,8 +56,16 @@ public async Task RunAsync() Console.WriteLine(); _conversationHistory.Add(Message.FromUser( - "Begin your analysis. Start by exploring the codebase structure, then read relevant files, " + - "and finally generate the comprehensive llms.txt documentation.")); + "Begin your analysis. Follow this approach:\n\n" + + "1. FIRST: Read llms.txt if it exists to understand expected depth and style\n" + + "2. Explore the codebase structure (GetDirectoryTree)\n" + + "3. Read README.md and .csproj files\n" + + "4. Read ALL files in src/Extensions/ - these are critical!\n" + + "5. Read ALL files in src/Sse/ if it exists\n" + + "6. Explore samples/ directory for real-world usage\n" + + "7. Read other important source files\n" + + "8. Finally, generate comprehensive llms.txt documentation (2000+ lines)\n\n" + + "Take your time and be thorough. Quality over speed!")); int iteration = 0; diff --git a/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs index f8df7b0..bbdbc67 100644 --- a/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs +++ b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs @@ -129,20 +129,47 @@ Documentation of all public methods... # EXPLORATION STRATEGY -I recommend this approach: - -1. GetDirectoryTree() to see overall structure -2. ReadFile(""README.md"") to understand the SDK -3. ReadFile(""*.csproj"") to see dependencies and version -4. SearchFiles(""*.cs"") to list all source files -5. Read main client class files -6. Read model/request/response files -7. Read extension method files -8. Read any tool or helper files -9. Look at samples if available -10. THEN write comprehensive llms.txt - -Take your time. Explore thoroughly. When ready, call WriteLlmsTxt with the complete documentation. +**CRITICAL**: Follow this systematic approach for comprehensive documentation: + +## Phase 1: Get Context & Reference (MUST DO FIRST!) +1. **GetDirectoryTree(""."", 3)** - Understand overall structure +2. **ReadFile(""README.md"")** - Understand SDK purpose and features +3. **ReadFile(""llms.txt"")** - IF IT EXISTS, read it as a REFERENCE for: + - Expected depth and detail level + - Formatting style and tone + - Common gotchas and issues to document + - What users actually need to know +4. **ReadFile(""*.csproj"")** - Get version, dependencies, .NET requirements +5. **GetCodebaseStats(""."")** - Understand codebase scale + +## Phase 2: Deep Source Analysis (THOROUGH!) +6. **SearchFiles(""*.cs"")** - List ALL source files +7. **ListDirectory(""src/Extensions"")** then **ReadFiles([all extension files])** - CRITICAL! +8. **ListDirectory(""src/Sse"")** then **ReadFiles([all SSE files])** - Important feature! +9. **ReadFile(""src/OpenRouterClient.cs"")** - Main client class +10. **ListDirectory(""src/Models"")** then **ReadFiles([key model files])** +11. **ListDirectory(""src/Tools"")** then **ReadFiles([tool files])** +12. Read any other critical source files + +## Phase 3: Real-World Usage Patterns (DON'T SKIP!) +13. **ListDirectory(""samples"")** - See what samples exist +14. **Read sample Program.cs files** - Real usage patterns matter! +15. **Read sample README files** - Common use cases and setup + +## Phase 4: Generate Comprehensive Documentation +16. Synthesize everything learned +17. Compare depth with existing llms.txt (if present) +18. **WriteLlmsTxt(content)** - Generate COMPLETE documentation + +**CRITICAL PRIORITIES**: +- βœ… If llms.txt exists, READ IT FIRST to understand expected quality +- βœ… Read ALL Extension files - they contain the most useful user-facing features +- βœ… Read samples/ directory - shows real-world usage patterns +- βœ… Document EVERY public method and extension method +- βœ… Include common error messages and solutions +- βœ… Don't stop at basics - dig deep into advanced features + +Take your time. Explore thoroughly. Aim for 2000+ line comprehensive documentation. # START NOW diff --git a/.github/tools/llms-txt-generator/QUICKSTART.md b/.github/tools/llms-txt-generator/QUICKSTART.md index 03cff29..e396282 100644 --- a/.github/tools/llms-txt-generator/QUICKSTART.md +++ b/.github/tools/llms-txt-generator/QUICKSTART.md @@ -20,7 +20,7 @@ cd .github/tools/llms-txt-generator This will: - Restore dependencies - Build the project -- Analyze `src/` directory +- Analyze **entire workspace** (src/, samples/, llms.txt, README.md) - Generate `llms.txt` in project root ### Option B: Manual run with custom options @@ -32,15 +32,32 @@ cd .github/tools/llms-txt-generator dotnet restore dotnet build -# Run with custom path +# Run - analyze entire workspace (RECOMMENDED) +dotnet run -- --path ../../.. --output ../../../llms.txt + +# Or analyze just src/ (will miss samples and existing llms.txt) dotnet run -- --path ../../../src --output ../../../llms.txt ``` -### Option C: Analyze a different path +**πŸ’‘ Important**: For best results, point to the **workspace root**, not just `src/`. +This gives the agent access to: +- βœ… Source code (`src/`) +- βœ… Sample projects (`samples/`) +- βœ… Existing `llms.txt` (for reference) +- βœ… README.md (for overview) +- βœ… .csproj files (for dependencies) + +### Option C: Analyze a specific package ```bash -# Analyze the react-sdk package instead -./run.sh ../../../packages/react-sdk react-sdk-llms.txt +# Analyze just the react-sdk package (will miss broader context) +cd .github/tools/llms-txt-generator +dotnet run -- --path ../../../packages/react-sdk --output react-sdk-llms.txt + +# Better: Analyze workspace but focus output on react-sdk +# (Agent can see everything for context, but documents react-sdk) +dotnet run -- --path ../../.. --output react-sdk-llms.txt +# Then instruct in prompt which package to focus on ``` ## Step 3: Watch It Work! diff --git a/.github/tools/llms-txt-generator/README.md b/.github/tools/llms-txt-generator/README.md index a0cf80f..514eae2 100644 --- a/.github/tools/llms-txt-generator/README.md +++ b/.github/tools/llms-txt-generator/README.md @@ -42,20 +42,33 @@ That's it! The agent will explore your codebase and generate `llms.txt`. ### Basic Usage ```bash -dotnet run -- --path +# Analyze entire workspace (RECOMMENDED) +dotnet run -- --path /path/to/workspace + +# Or from tool directory, workspace root +dotnet run -- --path ../../.. ``` ### Full Options ```bash dotnet run -- \ - --path ./src \ - --output ./llms.txt \ + --path ../../.. \ + --output ../../../llms.txt \ --model anthropic/claude-3.5-sonnet \ --max-iterations 30 \ --api-key sk-or-v1-... ``` +**πŸ’‘ Pro Tip**: Always point to the **workspace root**, not just `src/`. +The agent needs access to: +- Source code (`src/`) +- Sample projects (`samples/`) +- Existing documentation (`llms.txt`, `README.md`) +- Project files (`.csproj`) + +The agent will explore intelligently and focus on what matters. + ### Options | Option | Description | Default | diff --git a/.github/tools/llms-txt-generator/run.sh b/.github/tools/llms-txt-generator/run.sh index a9b423b..0b0c821 100755 --- a/.github/tools/llms-txt-generator/run.sh +++ b/.github/tools/llms-txt-generator/run.sh @@ -19,8 +19,8 @@ if [ -z "$OPENROUTER_API_KEY" ]; then exit 1 fi -# Default path to analyze -TARGET_PATH="${1:-../../../src}" +# Default path to analyze - workspace root for full context +TARGET_PATH="${1:-../../..}" OUTPUT_PATH="${2:-../../../llms.txt}" MODEL="${3:-anthropic/claude-3.5-sonnet}" From 11a9f4bd5150e2ddc426fa53be3b2512f1a47c3f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 29 Oct 2025 18:12:58 +0000 Subject: [PATCH 3/7] Checkpoint before follow-up message Co-authored-by: william.holmberg --- .github/tools/llms-txt-generator/Agent.cs | 67 ++++- .github/tools/llms-txt-generator/CHANGELOG.md | 251 ++++++++++++++++++ .../Prompts/SystemPrompt.cs | 15 +- .../Tools/WriteLlmsTxtTool.cs | 37 ++- 4 files changed, 348 insertions(+), 22 deletions(-) create mode 100644 .github/tools/llms-txt-generator/CHANGELOG.md diff --git a/.github/tools/llms-txt-generator/Agent.cs b/.github/tools/llms-txt-generator/Agent.cs index 7625ecf..ad39858 100644 --- a/.github/tools/llms-txt-generator/Agent.cs +++ b/.github/tools/llms-txt-generator/Agent.cs @@ -56,15 +56,17 @@ public async Task RunAsync() Console.WriteLine(); _conversationHistory.Add(Message.FromUser( - "Begin your analysis. Follow this approach:\n\n" + + "You are documenting the OpenRouter.NET SDK (the C#/.NET SDK in the src/ directory).\n\n" + + "Follow this approach:\n\n" + "1. FIRST: Read llms.txt if it exists to understand expected depth and style\n" + "2. Explore the codebase structure (GetDirectoryTree)\n" + - "3. Read README.md and .csproj files\n" + + "3. Read README.md and OpenRouter.NET.csproj\n" + "4. Read ALL files in src/Extensions/ - these are critical!\n" + "5. Read ALL files in src/Sse/ if it exists\n" + - "6. Explore samples/ directory for real-world usage\n" + - "7. Read other important source files\n" + - "8. Finally, generate comprehensive llms.txt documentation (2000+ lines)\n\n" + + "6. Explore samples/ directory for real-world C# usage patterns\n" + + "7. Read other important source files in src/\n" + + "8. Finally, call WriteLlmsTxt ONCE with comprehensive documentation (2000+ lines)\n\n" + + "IMPORTANT: After you call WriteLlmsTxt successfully, you are DONE. Do not continue exploring or call it again.\n\n" + "Take your time and be thorough. Quality over speed!")); int iteration = 0; @@ -105,18 +107,49 @@ public async Task RunAsync() { case ToolCallState.Executing: Console.WriteLine($"\n\nπŸ”§ Executing tool: {chunk.ServerTool.ToolName}"); - Console.WriteLine($" Arguments: {TruncateForDisplay(chunk.ServerTool.Arguments, 100)}"); + + // Special handling for WriteLlmsTxt + if (chunk.ServerTool.ToolName == "WriteLlmsTxt") + { + Console.WriteLine(" ⚠️ WRITING FINAL OUTPUT - This may take a moment..."); + } + else + { + Console.WriteLine($" Arguments: {TruncateForDisplay(chunk.ServerTool.Arguments, 100)}"); + } break; case ToolCallState.Completed: var result = chunk.ServerTool.Result ?? "No result"; Console.WriteLine($"βœ… Tool completed in {chunk.ServerTool.ExecutionTime?.TotalMilliseconds:F0}ms"); - Console.WriteLine($" Result preview: {TruncateForDisplay(result, 200)}"); + + // Special handling for WriteLlmsTxt success + if (chunk.ServerTool.ToolName == "WriteLlmsTxt") + { + Console.WriteLine("\n" + "="​.PadRight(60, '=')); + Console.WriteLine("πŸŽ‰ WriteLlmsTxt SUCCEEDED!"); + Console.WriteLine("="​.PadRight(60, '=')); + Console.WriteLine(result); + + // Force break on next check + } + else + { + Console.WriteLine($" Result preview: {TruncateForDisplay(result, 200)}"); + } + toolCalls.Add((chunk.ServerTool.ToolName, result)); break; case ToolCallState.Error: Console.WriteLine($"❌ Tool error: {chunk.ServerTool.Error}"); + + // If WriteLlmsTxt failed, show prominent error + if (chunk.ServerTool.ToolName == "WriteLlmsTxt") + { + Console.WriteLine("\n⚠️⚠️⚠️ WriteLlmsTxt FAILED! ⚠️⚠️⚠️"); + Console.WriteLine("The agent will likely retry. Check the error above."); + } break; } } @@ -154,9 +187,29 @@ public async Task RunAsync() Console.WriteLine($"\nπŸ“ Files read during analysis: {_fileTools.GetReadFiles().Count()}"); Console.WriteLine($"πŸ”„ Iterations used: {iteration}/{_maxIterations}"); Console.WriteLine($"\nπŸŽ‰ llms.txt has been generated!"); + + // Give the agent one more chance to see the success message + if (!string.IsNullOrEmpty(responseText) || hasContent) + { + Console.WriteLine($"\nπŸ“‹ Final agent response: {TruncateForDisplay(responseText, 300)}"); + } + return true; } + // Check if we're stuck (agent keeps trying after WriteLlmsTxt was called) + if (iteration > 5 && toolCalls.Any(t => t.name == "WriteLlmsTxt")) + { + Console.WriteLine("\n⚠️ Warning: WriteLlmsTxt was called but agent hasn't stopped."); + Console.WriteLine("Checking if file was actually written..."); + + if (_writeTool.HasWritten) + { + Console.WriteLine("βœ… File WAS written successfully. Stopping agent."); + return true; + } + } + if (iteration >= _maxIterations) { Console.WriteLine("\n\n⚠️ Reached maximum iterations without completing."); diff --git a/.github/tools/llms-txt-generator/CHANGELOG.md b/.github/tools/llms-txt-generator/CHANGELOG.md new file mode 100644 index 0000000..97a7cac --- /dev/null +++ b/.github/tools/llms-txt-generator/CHANGELOG.md @@ -0,0 +1,251 @@ +# πŸ”§ Updates Made to Fix Issues + +## Issue 1: Clarify Target - OpenRouter.NET SDK βœ… + +**Problem**: Agent had access to entire workspace but wasn't clear it should focus on the .NET SDK in `src/`, not other packages like `packages/react-sdk/`. + +**Solution**: Updated prompts to be crystal clear: + +### SystemPrompt.cs +```csharp +You are documenting the **OpenRouter.NET SDK** - a .NET SDK for the OpenRouter API. + +**FOCUS**: The .NET SDK source code is in `src/` directory. +- Sample projects in `samples/` provide usage examples +- Existing `llms.txt` (if present) is your reference for style/depth +- Other packages (like `packages/react-sdk/`) are separate and NOT your focus +``` + +### Agent.cs Initial Message +```csharp +"You are documenting the OpenRouter.NET SDK (the C#/.NET SDK in the src/ directory).\n\n" +``` + +### Search Pattern Updated +Changed from `SearchFiles("*.cs")` to `SearchFiles("src/*.cs")` to focus on .NET SDK only. + +--- + +## Issue 2: WriteLlmsTxt Failures & Agent Continuing βœ… + +**Problem**: WriteLlmsTxt tool was being called, but either: +- Failed silently and agent didn't realize +- Succeeded but agent didn't understand it should STOP +- Agent continued exploring and calling tools after writing + +**Solutions Applied**: + +### 1. More Explicit Tool Description +```csharp +[ToolMethod("Write the final llms.txt content. Call this ONLY ONCE when you have completed +your analysis and are ready to output the complete documentation. After calling this +successfully, your task is COMPLETE and you should stop.")] +``` + +### 2. Much Clearer Success Message +```csharp +return $"πŸŽ‰ SUCCESS! llms.txt has been written successfully!\n" + + $"\n" + + $"πŸ“„ Location: {_outputPath}\n" + + $"πŸ“ Lines: {lines:N0}\n" + + $"πŸ“ Characters: {chars:N0}\n" + + $"πŸ’Ύ File size: {fileSize:N0} bytes\n" + + $"\n" + + $"βœ… Your task is COMPLETE. The documentation has been generated.\n" + + $"βœ… You should now STOP. Do not continue exploring or call tools.\n" + + $"βœ… Do not call WriteLlmsTxt again.\n" + + $"\n" + + $"The agent can now terminate successfully."; +``` + +Multiple "STOP" messages to make it crystal clear! + +### 3. Better Error Messages +```csharp +if (content.Length < 1000) +{ + return $"❌ ERROR: Content is too short ({content.Length} characters). " + + "llms.txt should be comprehensive documentation (at least 10,000 characters + recommended, 50,000+ for thorough docs). " + + "Did you complete your analysis? Have you read all the extension methods, + samples, and existing llms.txt?"; +} +``` + +Now tells agent exactly what's wrong and what to do. + +### 4. Special Handling in Agent Loop +```csharp +// Special handling for WriteLlmsTxt +if (chunk.ServerTool.ToolName == "WriteLlmsTxt") +{ + Console.WriteLine(" ⚠️ WRITING FINAL OUTPUT - This may take a moment..."); +} + +// On success +if (chunk.ServerTool.ToolName == "WriteLlmsTxt") +{ + Console.WriteLine("\n" + "="​.PadRight(60, '=')); + Console.WriteLine("πŸŽ‰ WriteLlmsTxt SUCCEEDED!"); + Console.WriteLine("="​.PadRight(60, '=')); + Console.WriteLine(result); +} +``` + +### 5. Stuck Detection +```csharp +// Check if we're stuck (agent keeps trying after WriteLlmsTxt was called) +if (iteration > 5 && toolCalls.Any(t => t.name == "WriteLlmsTxt")) +{ + Console.WriteLine("\n⚠️ Warning: WriteLlmsTxt was called but agent hasn't stopped."); + Console.WriteLine("Checking if file was actually written..."); + + if (_writeTool.HasWritten) + { + Console.WriteLine("βœ… File WAS written successfully. Stopping agent."); + return true; + } +} +``` + +Forces stop if file was written but agent is confused. + +### 6. Better Initial Instructions +```csharp +"8. Finally, call WriteLlmsTxt ONCE with comprehensive documentation (2000+ lines)\n\n" + +"IMPORTANT: After you call WriteLlmsTxt successfully, you are DONE. +Do not continue exploring or call it again.\n\n" +``` + +--- + +## Updated Usage Instructions + +### Correct Usage (Workspace Root) +```bash +cd .github/tools/llms-txt-generator + +# Option 1: Quick run (default to workspace root) +./run.sh + +# Option 2: Explicit workspace root +dotnet run -- --path ../../.. + +# Option 3: With more iterations for thorough analysis +dotnet run -- --path ../../.. --max-iterations 50 +``` + +### What the Agent Now Does + +1. βœ… Reads existing `llms.txt` for reference +2. βœ… Focuses on `src/` directory (OpenRouter.NET SDK) +3. βœ… Reads ALL extension methods (critical!) +4. βœ… Reads samples for real-world usage +5. βœ… Generates comprehensive documentation +6. βœ… Calls WriteLlmsTxt ONCE with complete content +7. βœ… STOPS after successful write (no more loops!) + +--- + +## Expected Behavior Now + +### During Execution: +``` +πŸ”§ Executing tool: WriteLlmsTxt + ⚠️ WRITING FINAL OUTPUT - This may take a moment... +βœ… Tool completed in 1250ms + +============================================================ +πŸŽ‰ WriteLlmsTxt SUCCEEDED! +============================================================ +πŸŽ‰ SUCCESS! llms.txt has been written successfully! + +πŸ“„ Location: /workspace/llms.txt +πŸ“ Lines: 2,341 +πŸ“ Characters: 127,584 +πŸ’Ύ File size: 127,584 bytes + +βœ… Your task is COMPLETE. The documentation has been generated. +βœ… You should now STOP. Do not continue exploring or call tools. +βœ… Do not call WriteLlmsTxt again. + +============================================================ +βœ… AGENT COMPLETED SUCCESSFULLY! +============================================================ + +πŸ“ Files read during analysis: 23 +πŸ”„ Iterations used: 12/30 + +πŸŽ‰ llms.txt has been generated! +``` + +### If Agent Gets Stuck: +``` +⚠️ Warning: WriteLlmsTxt was called but agent hasn't stopped. +Checking if file was actually written... +βœ… File WAS written successfully. Stopping agent. +``` + +The system now forcibly stops the agent if file was written. + +--- + +## Testing Recommendations + +### Test 1: Basic Run +```bash +cd .github/tools/llms-txt-generator +export OPENROUTER_API_KEY="your-key" +./run.sh +``` + +Expected: Should complete in 15-30 iterations, generating comprehensive llms.txt. + +### Test 2: Verify Focus +After run, check that: +- βœ… `llms.txt` mentions OpenRouter.NET SDK specifically +- βœ… Includes extension methods (StreamingExtensions, ResponseExtensions, etc.) +- βœ… Includes SSE support documentation +- βœ… References samples from `samples/` directory +- ❌ Does NOT heavily document `packages/react-sdk/` (wrong SDK) + +### Test 3: Verify Completion +- βœ… Agent should stop immediately after WriteLlmsTxt succeeds +- βœ… No repeated tool calls after write +- βœ… Clear success message + +--- + +## Debugging + +If WriteLlmsTxt still fails: + +1. **Check the error message** - Now much more detailed +2. **Verify content length** - Should be 10,000+ characters minimum +3. **Check file permissions** - Can the tool write to output path? +4. **Review iterations** - Is agent exploring enough before writing? + +If agent continues after successful write: + +1. **Check console output** - Should see "WriteLlmsTxt SUCCEEDED!" +2. **Look for stuck detection** - Should auto-stop after iteration 5 +3. **Increase verbosity** - Tool now logs all steps + +--- + +## Summary of Changes + +| File | Changes | +|------|---------| +| `SystemPrompt.cs` | Clarified focus on OpenRouter.NET SDK, updated exploration strategy | +| `Agent.cs` | Added initial instructions, WriteLlmsTxt special handling, stuck detection | +| `WriteLlmsTxtTool.cs` | Much clearer success/error messages, better validation, explicit STOP signals | +| `QUICKSTART.md` | Updated to recommend workspace root, explain what agent can access | +| `README.md` | Updated usage examples to use workspace root | +| `run.sh` | Changed default from `src/` to `../../..` (workspace root) | + +All changes focused on: +1. βœ… Clarity - Agent knows exactly what to document +2. βœ… Thoroughness - Agent explores all necessary files +3. βœ… Completion - Agent knows when to stop +4. βœ… Debugging - Clear output for troubleshooting diff --git a/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs index bbdbc67..b4a275a 100644 --- a/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs +++ b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs @@ -6,9 +6,16 @@ public static string GetPrompt(string basePath) => $@"You are an expert technica # YOUR MISSION -Analyze the codebase at: {basePath} +You are documenting the **OpenRouter.NET SDK** - a .NET SDK for the OpenRouter API. -Generate a COMPLETE, COMPREHENSIVE llms.txt file that serves as a reference guide for LLMs to understand and use this SDK. +The workspace is at: {basePath} + +**FOCUS**: The .NET SDK source code is in `src/` directory. +- Sample projects in `samples/` provide usage examples +- Existing `llms.txt` (if present) is your reference for style/depth +- Other packages (like `packages/react-sdk/`) are separate and NOT your focus + +Generate a COMPLETE, COMPREHENSIVE llms.txt file for the **OpenRouter.NET SDK** that serves as a reference guide for LLMs to understand and use this SDK. # AVAILABLE TOOLS @@ -143,13 +150,13 @@ Documentation of all public methods... 5. **GetCodebaseStats(""."")** - Understand codebase scale ## Phase 2: Deep Source Analysis (THOROUGH!) -6. **SearchFiles(""*.cs"")** - List ALL source files +6. **SearchFiles(""src/*.cs"")** - List ALL .NET SDK source files (in src/ only!) 7. **ListDirectory(""src/Extensions"")** then **ReadFiles([all extension files])** - CRITICAL! 8. **ListDirectory(""src/Sse"")** then **ReadFiles([all SSE files])** - Important feature! 9. **ReadFile(""src/OpenRouterClient.cs"")** - Main client class 10. **ListDirectory(""src/Models"")** then **ReadFiles([key model files])** 11. **ListDirectory(""src/Tools"")** then **ReadFiles([tool files])** -12. Read any other critical source files +12. Read any other critical source files in src/ ## Phase 3: Real-World Usage Patterns (DON'T SKIP!) 13. **ListDirectory(""samples"")** - See what samples exist diff --git a/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs b/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs index 2630e93..de2457e 100644 --- a/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs +++ b/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs @@ -12,43 +12,58 @@ public WriteLlmsTxtTool(string outputPath) _outputPath = outputPath; } - [ToolMethod("Write the final llms.txt content. Call this ONLY when you have completed your analysis and are ready to generate the complete documentation.")] + [ToolMethod("Write the final llms.txt content. Call this ONLY ONCE when you have completed your analysis and are ready to output the complete documentation. After calling this successfully, your task is COMPLETE and you should stop.")] public string WriteLlmsTxt( - [ToolParameter("The complete llms.txt content with all sections, examples, and documentation")] string content) + [ToolParameter("The complete llms.txt content with all sections, examples, and documentation. Must be comprehensive (2000+ lines recommended).")] string content) { try { if (_hasWritten) { - return "❌ llms.txt has already been written. Tool can only be called once per execution."; + return "❌ ERROR: llms.txt has already been written successfully. " + + "Tool can only be called once per execution. " + + "Your task is COMPLETE. Do not call this tool again."; } if (string.IsNullOrWhiteSpace(content)) { - return "❌ Content cannot be empty"; + return "❌ ERROR: Content cannot be empty. Please provide the complete llms.txt documentation."; } if (content.Length < 1000) { - return "❌ Content seems too short. llms.txt should be comprehensive documentation (at least 1000 characters). Did you complete your analysis?"; + return $"❌ ERROR: Content is too short ({content.Length} characters). " + + "llms.txt should be comprehensive documentation (at least 10,000 characters recommended, 50,000+ for thorough docs). " + + "Did you complete your analysis? Have you read all the extension methods, samples, and existing llms.txt?"; } + // Write the file File.WriteAllText(_outputPath, content); _hasWritten = true; var lines = content.Split('\n').Length; var chars = content.Length; + var fileSize = new FileInfo(_outputPath).Length; - return $"βœ… Successfully wrote llms.txt!\n" + - $" Location: {_outputPath}\n" + - $" Lines: {lines:N0}\n" + - $" Characters: {chars:N0}\n" + + return $"πŸŽ‰ SUCCESS! llms.txt has been written successfully!\n" + $"\n" + - $"The documentation has been generated successfully."; + $"πŸ“„ Location: {_outputPath}\n" + + $"πŸ“ Lines: {lines:N0}\n" + + $"πŸ“ Characters: {chars:N0}\n" + + $"πŸ’Ύ File size: {fileSize:N0} bytes\n" + + $"\n" + + $"βœ… Your task is COMPLETE. The documentation has been generated.\n" + + $"βœ… You should now STOP. Do not continue exploring or call tools.\n" + + $"βœ… Do not call WriteLlmsTxt again.\n" + + $"\n" + + $"The agent can now terminate successfully."; } catch (Exception ex) { - return $"❌ Error writing llms.txt: {ex.Message}"; + _hasWritten = false; // Allow retry if there was an error + return $"❌ ERROR writing llms.txt: {ex.Message}\n" + + $"Stack trace: {ex.StackTrace}\n" + + $"Please try again after fixing the issue."; } } From b3ff6b3d98c7dc8d1d5d855d58e1d71fd594f76b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 29 Oct 2025 18:16:01 +0000 Subject: [PATCH 4/7] feat: Implement hard crash with full diagnostics for WriteLlmsTxt Co-authored-by: william.holmberg --- .github/tools/llms-txt-generator/Agent.cs | 35 +- .github/tools/llms-txt-generator/CHANGELOG.md | 70 +++- .../llms-txt-generator/CRASH-DIAGNOSTICS.md | 321 ++++++++++++++++++ .../Tools/WriteLlmsTxtTool.cs | 138 +++++++- 4 files changed, 551 insertions(+), 13 deletions(-) create mode 100644 .github/tools/llms-txt-generator/CRASH-DIAGNOSTICS.md diff --git a/.github/tools/llms-txt-generator/Agent.cs b/.github/tools/llms-txt-generator/Agent.cs index ad39858..bc2d47c 100644 --- a/.github/tools/llms-txt-generator/Agent.cs +++ b/.github/tools/llms-txt-generator/Agent.cs @@ -168,8 +168,39 @@ public async Task RunAsync() } catch (Exception ex) { - Console.WriteLine($"\n\n❌ Error during streaming: {ex.Message}"); - return false; + Console.WriteLine("\n\n" + "=".PadRight(70, '=')); + Console.WriteLine("πŸ’₯ FATAL ERROR DURING AGENT EXECUTION"); + Console.WriteLine("=".PadRight(70, '=')); + Console.WriteLine($"Exception Type: {ex.GetType().Name}"); + Console.WriteLine($"Message: {ex.Message}"); + Console.WriteLine($"\nStack Trace:"); + Console.WriteLine(ex.StackTrace ?? "(no stack trace)"); + + if (ex.InnerException != null) + { + Console.WriteLine($"\nInner Exception: {ex.InnerException.GetType().Name}"); + Console.WriteLine($"Inner Message: {ex.InnerException.Message}"); + Console.WriteLine($"Inner Stack Trace:"); + Console.WriteLine(ex.InnerException.StackTrace ?? "(no stack trace)"); + } + + Console.WriteLine("\nCONTEXT:"); + Console.WriteLine($"Iteration: {iteration}/{_maxIterations}"); + Console.WriteLine($"Model: {_model}"); + Console.WriteLine($"Files read so far: {_fileTools.GetReadFiles().Count()}"); + Console.WriteLine($"Has written llms.txt: {_writeTool.HasWritten}"); + Console.WriteLine($"Conversation messages: {_conversationHistory.Count}"); + + Console.WriteLine("\nLAST TOOL CALLS:"); + foreach (var (name, result) in toolCalls.TakeLast(5)) + { + Console.WriteLine($" - {name}: {TruncateForDisplay(result, 100)}"); + } + + Console.WriteLine("\n" + "=".PadRight(70, '=')); + + // Re-throw to crash the application + throw; } var responseText = response.ToString(); diff --git a/.github/tools/llms-txt-generator/CHANGELOG.md b/.github/tools/llms-txt-generator/CHANGELOG.md index 97a7cac..7f6c7d0 100644 --- a/.github/tools/llms-txt-generator/CHANGELOG.md +++ b/.github/tools/llms-txt-generator/CHANGELOG.md @@ -233,19 +233,85 @@ If agent continues after successful write: --- +## Issue 3: Hard Crash on WriteLlmsTxt Failure βœ… + +**Problem**: When WriteLlmsTxt failed, it was unclear what went wrong. Agent might continue or retry without fixing the underlying issue. + +**Solution**: Tool now **CRASHES HARD** with maximum diagnostic information. + +### Changes Made: + +#### WriteLlmsTxtTool.cs - Maximum Diagnostics +```csharp +// Collects full diagnostic info before attempting write +var diagnosticInfo = new StringBuilder(); +diagnosticInfo.AppendLine("Output path: {_outputPath}"); +diagnosticInfo.AppendLine("Content length: {content?.Length}"); +diagnosticInfo.AppendLine("Directory exists: {Directory.Exists(outputDir)}"); +// ... and much more + +// On ANY failure, dumps complete diagnostic +Console.WriteLine(diagnosticInfo.ToString()); + +// Saves error log +File.WriteAllText("llms-txt-error.log", fullDiagnostic); + +// RE-THROWS exception (crashes app) +throw new Exception($"WriteLlmsTxt FAILED!\n\n{fullDiagnostic}", ex); +``` + +#### What Gets Logged: +- βœ… Output path and directory status +- βœ… Content length and line count +- βœ… Current directory +- βœ… Validation failure details +- βœ… Content preview (first 1000 chars) +- βœ… Full exception stack trace +- βœ… Inner exception details +- βœ… Environment info (OS, User, .NET version) +- βœ… Saved to `llms-txt-error.log` + +#### Agent.cs - Crash Context +```csharp +catch (Exception ex) +{ + Console.WriteLine("πŸ’₯ FATAL ERROR DURING AGENT EXECUTION"); + // Dumps full context: + // - Iteration number + // - Files read count + // - Last 5 tool calls + // - Full stack trace + + throw; // Re-throw to crash +} +``` + +#### Benefits: +- βœ… **NO SILENT FAILURES** - App crashes immediately +- βœ… **FULL DIAGNOSTICS** - See exactly what went wrong +- βœ… **ERROR LOG SAVED** - Review later if needed +- βœ… **CONTENT PREVIEW** - See what was attempted +- βœ… **EASY DEBUGGING** - All info in one place + +See [CRASH-DIAGNOSTICS.md](CRASH-DIAGNOSTICS.md) for full details. + +--- + ## Summary of Changes | File | Changes | |------|---------| | `SystemPrompt.cs` | Clarified focus on OpenRouter.NET SDK, updated exploration strategy | -| `Agent.cs` | Added initial instructions, WriteLlmsTxt special handling, stuck detection | -| `WriteLlmsTxtTool.cs` | Much clearer success/error messages, better validation, explicit STOP signals | +| `Agent.cs` | Added initial instructions, WriteLlmsTxt special handling, stuck detection, crash diagnostics | +| `WriteLlmsTxtTool.cs` | Much clearer success/error messages, better validation, explicit STOP signals, **HARD CRASH with full diagnostics** | | `QUICKSTART.md` | Updated to recommend workspace root, explain what agent can access | | `README.md` | Updated usage examples to use workspace root | | `run.sh` | Changed default from `src/` to `../../..` (workspace root) | +| `CRASH-DIAGNOSTICS.md` | **NEW** - Complete guide to crash behavior and debugging | All changes focused on: 1. βœ… Clarity - Agent knows exactly what to document 2. βœ… Thoroughness - Agent explores all necessary files 3. βœ… Completion - Agent knows when to stop 4. βœ… Debugging - Clear output for troubleshooting +5. βœ… **FAIL LOUD** - Immediate crash with full diagnostic info diff --git a/.github/tools/llms-txt-generator/CRASH-DIAGNOSTICS.md b/.github/tools/llms-txt-generator/CRASH-DIAGNOSTICS.md new file mode 100644 index 0000000..8c86eec --- /dev/null +++ b/.github/tools/llms-txt-generator/CRASH-DIAGNOSTICS.md @@ -0,0 +1,321 @@ +# πŸ’₯ Crash Diagnostics - WriteLlmsTxt Failures + +## Updated Behavior: FAIL LOUD πŸ”Š + +The tool now **crashes hard** with **maximum diagnostic information** if WriteLlmsTxt fails. + +No more silent failures or confusing behavior! + +## What Happens on Failure + +### 1. Validation Failures + +If content is invalid, the tool throws an exception immediately: + +``` +❌ CRITICAL ERROR: Content is too short (523 characters). +llms.txt should be comprehensive documentation (at least 10,000 characters +recommended, 50,000+ for thorough docs). +``` + +**Throws**: `ArgumentException` with full diagnostic info + +### 2. File System Failures + +If file cannot be written: + +``` +πŸ’₯ FATAL ERROR IN WriteLlmsTxt +Exception Type: IOException +Message: Access denied to path '/workspace/llms.txt' +``` + +**Throws**: `Exception` with wrapped inner exception + +### 3. Diagnostic Information Dumped + +On ANY failure, you'll see: + +``` +====================================================================== +WriteLlmsTxt DIAGNOSTIC INFORMATION +====================================================================== +Output path: /workspace/llms.txt +Already written: False +Content provided: True +Content length: 523 characters +Content lines: 15 +Current directory: /workspace/.github/tools/llms-txt-generator +Output directory: /workspace +Directory exists: True + +❌ CRITICAL ERROR: Content is too short (523 characters) + +CONTENT PREVIEW (first 500 chars): +--- +# OpenRouter.NET SDK + +This is incomplete documentation... +--- + +====================================================================== +πŸ’₯ FATAL ERROR IN WriteLlmsTxt +====================================================================== +Exception Type: ArgumentException +Message: ❌ CRITICAL ERROR: Content is too short... + +Stack Trace: + at LlmsTxtGenerator.Tools.WriteLlmsTxtTool.WriteLlmsTxt(String content) + at ... + +CONTENT PREVIEW (first 1000 chars): +--- +[full content preview] +--- + +ENVIRONMENT INFO: +OS: Unix 6.1.147.0 +User: ubuntu +.NET Version: 9.0.0 + +πŸ“ Diagnostic log saved to: /workspace/llms-txt-error.log +``` + +### 4. Error Log File + +A detailed log is saved to `llms-txt-error.log` in the output directory with: +- Full stack trace +- Content preview +- Environment information +- All diagnostic data + +### 5. Application Crash + +The exception is **re-thrown** and **not caught**, causing: +- Agent stops immediately +- Application exits with error code +- Full stack trace printed to console + +## Example Failure Output + +### Too Short Content + +``` +πŸ”§ Executing tool: WriteLlmsTxt + ⚠️ WRITING FINAL OUTPUT - This may take a moment... + +====================================================================== +WriteLlmsTxt DIAGNOSTIC INFORMATION +====================================================================== +Output path: /workspace/llms.txt +Already written: False +Content provided: True +Content length: 523 characters +Content lines: 15 +Current directory: /workspace/.github/tools/llms-txt-generator +Output directory: /workspace +Directory exists: True + +ERROR: ❌ CRITICAL ERROR: Content is too short (523 characters). +llms.txt should be comprehensive documentation (at least 10,000 characters +recommended, 50,000+ for thorough docs). Did you complete your analysis? +Have you read all the extension methods, samples, and existing llms.txt? + +CONTENT PREVIEW (first 500 chars): +--- +# OpenRouter.NET SDK + +Basic usage... +--- + +====================================================================== +πŸ’₯ FATAL ERROR IN WriteLlmsTxt +====================================================================== +Exception Type: ArgumentException +Message: ❌ CRITICAL ERROR: Content is too short (523 characters)... + +Stack Trace: + at LlmsTxtGenerator.Tools.WriteLlmsTxtTool.WriteLlmsTxt(String content) + at OpenRouter.NET.Tools.ToolRegistry.ExecuteTool... + +πŸ“ Diagnostic log saved to: /workspace/llms-txt-error.log + +====================================================================== +πŸ’₯ FATAL ERROR DURING AGENT EXECUTION +====================================================================== +Exception Type: Exception +Message: WriteLlmsTxt FAILED! + +[full diagnostic info repeated] + +CONTEXT: +Iteration: 8/30 +Model: anthropic/claude-3.5-sonnet +Files read so far: 12 +Has written llms.txt: False +Conversation messages: 17 + +LAST TOOL CALLS: + - ReadFile: [content of file] + - ListDirectory: [directory listing] + - WriteLlmsTxt: [error] + +====================================================================== + +Unhandled exception. System.Exception: WriteLlmsTxt FAILED! +[application exits with error code] +``` + +## Why This is Better + +### Before ❌ +- Tool returned error string +- Agent might ignore it or misunderstand +- Agent continued exploring +- Hard to debug what went wrong +- Unclear if content was too short or file permission issue + +### After βœ… +- **Immediate crash** - no confusion +- **Full diagnostic dump** - see exactly what happened +- **Error log saved** - easy to review later +- **Content preview** - see what was attempted +- **Environment info** - helpful for debugging +- **Clear error messages** - know exactly what to fix + +## Common Failure Scenarios + +### 1. Content Too Short + +**Cause**: Agent tried to write before completing analysis + +**Diagnostic Shows**: +``` +Content length: 523 characters +Content lines: 15 + +CONTENT PREVIEW: +# OpenRouter.NET SDK +Basic usage... +``` + +**Solution**: Increase `--max-iterations` or improve prompt to ensure thorough exploration + +### 2. Permission Denied + +**Cause**: Cannot write to output path + +**Diagnostic Shows**: +``` +Exception Type: UnauthorizedAccessException +Message: Access to the path '/root/llms.txt' is denied. +Output directory: /root +``` + +**Solution**: Change output path or fix permissions + +### 3. Already Written + +**Cause**: Tool called twice (shouldn't happen but defensive) + +**Diagnostic Shows**: +``` +Already written: True +ERROR: llms.txt has already been written successfully +``` + +**Solution**: This is a bug in agent logic - shouldn't call twice + +### 4. Disk Full / No Space + +**Cause**: Cannot create file + +**Diagnostic Shows**: +``` +Exception Type: IOException +Message: There is not enough space on the disk +Output directory: /workspace +Directory exists: True +``` + +**Solution**: Free up disk space + +## Debugging Tips + +### Check the Error Log + +```bash +cat /workspace/llms-txt-error.log +``` + +Contains the complete diagnostic dump. + +### Check Content Length + +Look for: +``` +Content length: X characters +Content lines: Y +``` + +Should be 10,000+ characters minimum, ideally 50,000+. + +### Check File Permissions + +```bash +ls -la /workspace/llms.txt +# Should be writable +``` + +### Check Agent Progress + +Look for: +``` +Files read so far: X +``` + +Should be 15-30+ files for thorough analysis. + +### Verify Output Path + +``` +Output path: /workspace/llms.txt +Output directory: /workspace +Directory exists: True +``` + +All should be valid. + +## Recovery After Failure + +### If Content Too Short + +Run again with more iterations: +```bash +dotnet run -- --path ../../.. --max-iterations 50 +``` + +### If Permission Issue + +Change output path: +```bash +dotnet run -- --path ../../.. --output /tmp/llms.txt +``` + +### If Agent Confused + +Add explicit guidance or check the error log for what was attempted. + +## Summary + +**The tool now CRASHES HARD on any WriteLlmsTxt failure.** + +Benefits: +- βœ… No silent failures +- βœ… Complete diagnostic information +- βœ… Error log saved for review +- βœ… Clear error messages +- βœ… Application exits immediately +- βœ… Easy to debug and fix + +You'll know EXACTLY what went wrong and how to fix it! πŸ’ͺ diff --git a/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs b/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs index de2457e..aa631ae 100644 --- a/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs +++ b/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs @@ -1,4 +1,5 @@ using OpenRouter.NET.Tools; +using System.Text; namespace LlmsTxtGenerator.Tools; @@ -16,36 +17,103 @@ public WriteLlmsTxtTool(string outputPath) public string WriteLlmsTxt( [ToolParameter("The complete llms.txt content with all sections, examples, and documentation. Must be comprehensive (2000+ lines recommended).")] string content) { + // First, validate and save diagnostics + var diagnosticInfo = new StringBuilder(); + diagnosticInfo.AppendLine("=".PadRight(70, '=')); + diagnosticInfo.AppendLine("WriteLlmsTxt DIAGNOSTIC INFORMATION"); + diagnosticInfo.AppendLine("=".PadRight(70, '=')); + diagnosticInfo.AppendLine($"Output path: {_outputPath}"); + diagnosticInfo.AppendLine($"Already written: {_hasWritten}"); + diagnosticInfo.AppendLine($"Content provided: {content != null}"); + diagnosticInfo.AppendLine($"Content length: {content?.Length ?? 0} characters"); + diagnosticInfo.AppendLine($"Content lines: {(content?.Split('\n').Length ?? 0)}"); + diagnosticInfo.AppendLine($"Current directory: {Directory.GetCurrentDirectory()}"); + + var outputDir = Path.GetDirectoryName(_outputPath); + if (!string.IsNullOrEmpty(outputDir)) + { + diagnosticInfo.AppendLine($"Output directory: {outputDir}"); + diagnosticInfo.AppendLine($"Directory exists: {Directory.Exists(outputDir)}"); + } + try { + // Validation checks if (_hasWritten) { - return "❌ ERROR: llms.txt has already been written successfully. " + + var errorMsg = "❌ CRITICAL ERROR: llms.txt has already been written successfully. " + "Tool can only be called once per execution. " + "Your task is COMPLETE. Do not call this tool again."; + + diagnosticInfo.AppendLine($"\nERROR: {errorMsg}"); + Console.WriteLine(diagnosticInfo.ToString()); + + throw new InvalidOperationException(errorMsg + "\n\n" + diagnosticInfo.ToString()); } if (string.IsNullOrWhiteSpace(content)) { - return "❌ ERROR: Content cannot be empty. Please provide the complete llms.txt documentation."; + var errorMsg = "❌ CRITICAL ERROR: Content cannot be empty. Please provide the complete llms.txt documentation."; + + diagnosticInfo.AppendLine($"\nERROR: {errorMsg}"); + Console.WriteLine(diagnosticInfo.ToString()); + + throw new ArgumentException(errorMsg + "\n\n" + diagnosticInfo.ToString()); } if (content.Length < 1000) { - return $"❌ ERROR: Content is too short ({content.Length} characters). " + + var errorMsg = $"❌ CRITICAL ERROR: Content is too short ({content.Length} characters). " + "llms.txt should be comprehensive documentation (at least 10,000 characters recommended, 50,000+ for thorough docs). " + "Did you complete your analysis? Have you read all the extension methods, samples, and existing llms.txt?"; + + diagnosticInfo.AppendLine($"\nERROR: {errorMsg}"); + diagnosticInfo.AppendLine("\nCONTENT PREVIEW (first 500 chars):"); + diagnosticInfo.AppendLine("---"); + diagnosticInfo.AppendLine(content.Length > 500 ? content.Substring(0, 500) + "..." : content); + diagnosticInfo.AppendLine("---"); + + Console.WriteLine(diagnosticInfo.ToString()); + + throw new ArgumentException(errorMsg + "\n\n" + diagnosticInfo.ToString()); + } + + diagnosticInfo.AppendLine("\nβœ… Validation passed. Attempting to write file..."); + + // Try to create directory if it doesn't exist + if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir)) + { + diagnosticInfo.AppendLine($"Creating directory: {outputDir}"); + Directory.CreateDirectory(outputDir); } // Write the file + diagnosticInfo.AppendLine($"Writing to: {_outputPath}"); File.WriteAllText(_outputPath, content); + + // Verify it was written + if (!File.Exists(_outputPath)) + { + throw new IOException($"File was not created at: {_outputPath}"); + } + + var fileInfo = new FileInfo(_outputPath); + if (fileInfo.Length == 0) + { + throw new IOException($"File was created but is empty at: {_outputPath}"); + } + _hasWritten = true; var lines = content.Split('\n').Length; var chars = content.Length; - var fileSize = new FileInfo(_outputPath).Length; + var fileSize = fileInfo.Length; + + diagnosticInfo.AppendLine($"βœ… File written successfully!"); + diagnosticInfo.AppendLine($" Size: {fileSize:N0} bytes"); + diagnosticInfo.AppendLine($" Lines: {lines:N0}"); - return $"πŸŽ‰ SUCCESS! llms.txt has been written successfully!\n" + + var successMsg = $"πŸŽ‰ SUCCESS! llms.txt has been written successfully!\n" + $"\n" + $"πŸ“„ Location: {_outputPath}\n" + $"πŸ“ Lines: {lines:N0}\n" + @@ -57,13 +125,65 @@ public string WriteLlmsTxt( $"βœ… Do not call WriteLlmsTxt again.\n" + $"\n" + $"The agent can now terminate successfully."; + + diagnosticInfo.AppendLine(successMsg); + Console.WriteLine(diagnosticInfo.ToString()); + + return successMsg; } catch (Exception ex) { - _hasWritten = false; // Allow retry if there was an error - return $"❌ ERROR writing llms.txt: {ex.Message}\n" + - $"Stack trace: {ex.StackTrace}\n" + - $"Please try again after fixing the issue."; + // CRASH HARD with maximum diagnostic info + diagnosticInfo.AppendLine("\n" + "=".PadRight(70, '=')); + diagnosticInfo.AppendLine("πŸ’₯ FATAL ERROR IN WriteLlmsTxt"); + diagnosticInfo.AppendLine("=".PadRight(70, '=')); + diagnosticInfo.AppendLine($"Exception Type: {ex.GetType().Name}"); + diagnosticInfo.AppendLine($"Message: {ex.Message}"); + diagnosticInfo.AppendLine($"\nStack Trace:"); + diagnosticInfo.AppendLine(ex.StackTrace ?? "(no stack trace)"); + + if (ex.InnerException != null) + { + diagnosticInfo.AppendLine($"\nInner Exception: {ex.InnerException.GetType().Name}"); + diagnosticInfo.AppendLine($"Inner Message: {ex.InnerException.Message}"); + diagnosticInfo.AppendLine($"Inner Stack Trace:"); + diagnosticInfo.AppendLine(ex.InnerException.StackTrace ?? "(no stack trace)"); + } + + diagnosticInfo.AppendLine("\nCONTENT PREVIEW (first 1000 chars):"); + diagnosticInfo.AppendLine("---"); + if (content != null) + { + diagnosticInfo.AppendLine(content.Length > 1000 ? content.Substring(0, 1000) + "..." : content); + } + else + { + diagnosticInfo.AppendLine("(content is null)"); + } + diagnosticInfo.AppendLine("---"); + + diagnosticInfo.AppendLine("\nENVIRONMENT INFO:"); + diagnosticInfo.AppendLine($"OS: {Environment.OSVersion}"); + diagnosticInfo.AppendLine($"User: {Environment.UserName}"); + diagnosticInfo.AppendLine($".NET Version: {Environment.Version}"); + + var fullDiagnostic = diagnosticInfo.ToString(); + Console.WriteLine(fullDiagnostic); + + // Save diagnostic to file for debugging + try + { + var diagPath = Path.Combine(Path.GetDirectoryName(_outputPath) ?? ".", "llms-txt-error.log"); + File.WriteAllText(diagPath, fullDiagnostic); + Console.WriteLine($"\nπŸ“ Diagnostic log saved to: {diagPath}"); + } + catch + { + // If we can't even save the log, just continue + } + + // Re-throw with all diagnostic info + throw new Exception($"WriteLlmsTxt FAILED!\n\n{fullDiagnostic}", ex); } } From b2d587c1d8b8e67e6c2ad1c5eaab44c4671606dd Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 30 Oct 2025 09:52:50 +0000 Subject: [PATCH 5/7] feat: Implement artifact support for documentation generation Co-authored-by: william.holmberg --- .github/tools/llms-txt-generator/Agent.cs | 45 +++++++++++- .../Prompts/SystemPrompt.cs | 35 ++++++++- .../Tools/WriteLlmsTxtTool.cs | 73 +++++++++++++++---- 3 files changed, 131 insertions(+), 22 deletions(-) diff --git a/.github/tools/llms-txt-generator/Agent.cs b/.github/tools/llms-txt-generator/Agent.cs index bc2d47c..e974753 100644 --- a/.github/tools/llms-txt-generator/Agent.cs +++ b/.github/tools/llms-txt-generator/Agent.cs @@ -16,6 +16,8 @@ public class Agent private readonly string _model; private readonly int _maxIterations; + private readonly List _artifacts = new(); + public Agent( OpenRouterClient client, string basePath, @@ -25,7 +27,7 @@ public Agent( { _client = client; _fileTools = new FileSystemTools(basePath); - _writeTool = new WriteLlmsTxtTool(outputPath); + _writeTool = new WriteLlmsTxtTool(outputPath, () => _artifacts); _model = model; _maxIterations = maxIterations; @@ -65,8 +67,14 @@ public async Task RunAsync() "5. Read ALL files in src/Sse/ if it exists\n" + "6. Explore samples/ directory for real-world C# usage patterns\n" + "7. Read other important source files in src/\n" + - "8. Finally, call WriteLlmsTxt ONCE with comprehensive documentation (2000+ lines)\n\n" + - "IMPORTANT: After you call WriteLlmsTxt successfully, you are DONE. Do not continue exploring or call it again.\n\n" + + "8. Generate the complete documentation as an ARTIFACT using tags\n" + + "9. Finally, call WriteLlmsTxt with the artifact ID\n\n" + + "CRITICAL OUTPUT FORMAT:\n" + + "\n" + + "[Your complete 50,000+ character documentation here]\n" + + "\n\n" + + "Then: WriteLlmsTxt(artifactId: \"artifact-id\")\n\n" + + "After successfully calling WriteLlmsTxt, you are DONE. Do not continue.\n\n" + "Take your time and be thorough. Quality over speed!")); int iteration = 0; @@ -83,8 +91,11 @@ public async Task RunAsync() Model = _model, Messages = _conversationHistory, Temperature = 0.3f, - MaxTokens = 4000 + MaxTokens = 8000 // Increased for artifact generation }; + + // Enable artifact support for documentation generation + request.EnableArtifactSupport(); var response = new StringBuilder(); var toolCalls = new List<(string name, string result)>(); @@ -100,6 +111,32 @@ public async Task RunAsync() Console.Write(chunk.TextDelta); hasContent = true; } + + // Handle artifacts + if (chunk.Artifact != null) + { + if (chunk.Artifact is ArtifactStarted started) + { + Console.WriteLine($"\n\nπŸ“¦ Artifact started: {started.Title} (Type: {started.Type})"); + } + else if (chunk.Artifact is ArtifactCompleted completed) + { + Console.WriteLine($"\n\nβœ… Artifact completed: {completed.Title}"); + Console.WriteLine($" ID: {completed.ArtifactId}"); + Console.WriteLine($" Size: {completed.Content?.Length ?? 0} characters"); + + // Store artifact + var artifact = new Artifact + { + Id = completed.ArtifactId, + Type = completed.Type, + Title = completed.Title, + Content = completed.Content ?? "", + Language = completed.Language + }; + _artifacts.Add(artifact); + } + } if (chunk.ServerTool != null) { diff --git a/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs index b4a275a..a392f7f 100644 --- a/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs +++ b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs @@ -27,7 +27,26 @@ public static string GetPrompt(string basePath) => $@"You are an expert technica 4. **SearchFiles(pattern)** - Find files matching a pattern (e.g., '*.cs') 5. **GetDirectoryTree(path, maxDepth)** - Get tree view of directory structure 6. **GetCodebaseStats(path)** - Get statistics about file types and lines -7. **WriteLlmsTxt(content)** - OUTPUT the final llms.txt (CALL THIS LAST!) +7. **WriteLlmsTxt(artifactId)** - OUTPUT the final llms.txt from an artifact (CALL THIS LAST!) + +**CRITICAL**: You MUST generate the documentation as an ARTIFACT first, then reference it! + +## How to Output Documentation + +1. Generate the complete llms.txt content wrapped in artifact tags: +``` + +[Your complete 50,000+ character documentation here] + +``` + +2. Then call WriteLlmsTxt with the artifact ID: +``` +WriteLlmsTxt(artifactId: "artifact_abc123") +``` + +**DO NOT** try to pass the content directly to WriteLlmsTxt - it won't work! +You MUST use artifacts for large content. # YOUR PROCESS @@ -166,7 +185,19 @@ 12. Read any other critical source files in src/ ## Phase 4: Generate Comprehensive Documentation 16. Synthesize everything learned 17. Compare depth with existing llms.txt (if present) -18. **WriteLlmsTxt(content)** - Generate COMPLETE documentation +18. **Generate llms.txt as an ARTIFACT** - Wrap in tags! +19. **WriteLlmsTxt(artifactId)** - Pass the artifact ID to write to file + +**CRITICAL OUTPUT FORMAT**: +``` + +OPENROUTER.NET SDK - COMPREHENSIVE USAGE GUIDE +================================================ +[50,000+ characters of complete documentation] + +``` + +Then call: `WriteLlmsTxt(artifactId: "the-artifact-id")` **CRITICAL PRIORITIES**: - βœ… If llms.txt exists, READ IT FIRST to understand expected quality diff --git a/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs b/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs index aa631ae..eeceb3f 100644 --- a/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs +++ b/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs @@ -1,4 +1,5 @@ using OpenRouter.NET.Tools; +using OpenRouter.NET.Models; using System.Text; namespace LlmsTxtGenerator.Tools; @@ -7,15 +8,17 @@ public class WriteLlmsTxtTool { private readonly string _outputPath; private bool _hasWritten = false; + private readonly Func> _getArtifacts; - public WriteLlmsTxtTool(string outputPath) + public WriteLlmsTxtTool(string outputPath, Func> getArtifacts) { _outputPath = outputPath; + _getArtifacts = getArtifacts; } - [ToolMethod("Write the final llms.txt content. Call this ONLY ONCE when you have completed your analysis and are ready to output the complete documentation. After calling this successfully, your task is COMPLETE and you should stop.")] + [ToolMethod("Write the final llms.txt content from an artifact. First generate the documentation as an artifact (using tags), then call this tool with the artifact ID. After calling this successfully, your task is COMPLETE and you should stop.")] public string WriteLlmsTxt( - [ToolParameter("The complete llms.txt content with all sections, examples, and documentation. Must be comprehensive (2000+ lines recommended).")] string content) + [ToolParameter("The artifact ID containing the complete llms.txt documentation. You must generate an artifact first before calling this.")] string artifactId) { // First, validate and save diagnostics var diagnosticInfo = new StringBuilder(); @@ -24,9 +27,7 @@ public string WriteLlmsTxt( diagnosticInfo.AppendLine("=".PadRight(70, '=')); diagnosticInfo.AppendLine($"Output path: {_outputPath}"); diagnosticInfo.AppendLine($"Already written: {_hasWritten}"); - diagnosticInfo.AppendLine($"Content provided: {content != null}"); - diagnosticInfo.AppendLine($"Content length: {content?.Length ?? 0} characters"); - diagnosticInfo.AppendLine($"Content lines: {(content?.Split('\n').Length ?? 0)}"); + diagnosticInfo.AppendLine($"Artifact ID provided: {artifactId}"); diagnosticInfo.AppendLine($"Current directory: {Directory.GetCurrentDirectory()}"); var outputDir = Path.GetDirectoryName(_outputPath); @@ -51,9 +52,49 @@ public string WriteLlmsTxt( throw new InvalidOperationException(errorMsg + "\n\n" + diagnosticInfo.ToString()); } + if (string.IsNullOrWhiteSpace(artifactId)) + { + var errorMsg = "❌ CRITICAL ERROR: artifactId cannot be empty. " + + "You must first generate the documentation as an artifact using tags, " + + "then call this tool with the artifact ID."; + + diagnosticInfo.AppendLine($"\nERROR: {errorMsg}"); + Console.WriteLine(diagnosticInfo.ToString()); + + throw new ArgumentException(errorMsg + "\n\n" + diagnosticInfo.ToString()); + } + + // Get artifacts + var artifacts = _getArtifacts(); + diagnosticInfo.AppendLine($"Available artifacts: {artifacts.Count}"); + foreach (var art in artifacts) + { + diagnosticInfo.AppendLine($" - {art.Id}: {art.Title} ({art.Content?.Length ?? 0} chars)"); + } + + // Find the artifact + var artifact = artifacts.FirstOrDefault(a => a.Id == artifactId); + + if (artifact == null) + { + var errorMsg = $"❌ CRITICAL ERROR: Artifact '{artifactId}' not found. " + + $"Available artifacts: {string.Join(", ", artifacts.Select(a => a.Id))}. " + + "Did you generate the artifact first?"; + + diagnosticInfo.AppendLine($"\nERROR: {errorMsg}"); + Console.WriteLine(diagnosticInfo.ToString()); + + throw new ArgumentException(errorMsg + "\n\n" + diagnosticInfo.ToString()); + } + + var content = artifact.Content; + diagnosticInfo.AppendLine($"\nβœ… Found artifact: {artifact.Title}"); + diagnosticInfo.AppendLine($" Content length: {content?.Length ?? 0} characters"); + diagnosticInfo.AppendLine($" Content lines: {(content?.Split('\n').Length ?? 0)}"); + if (string.IsNullOrWhiteSpace(content)) { - var errorMsg = "❌ CRITICAL ERROR: Content cannot be empty. Please provide the complete llms.txt documentation."; + var errorMsg = "❌ CRITICAL ERROR: Artifact content is empty."; diagnosticInfo.AppendLine($"\nERROR: {errorMsg}"); Console.WriteLine(diagnosticInfo.ToString()); @@ -119,6 +160,7 @@ public string WriteLlmsTxt( $"πŸ“ Lines: {lines:N0}\n" + $"πŸ“ Characters: {chars:N0}\n" + $"πŸ’Ύ File size: {fileSize:N0} bytes\n" + + $"πŸ“¦ Source artifact: {artifact.Title} ({artifactId})\n" + $"\n" + $"βœ… Your task is COMPLETE. The documentation has been generated.\n" + $"βœ… You should now STOP. Do not continue exploring or call tools.\n" + @@ -150,17 +192,16 @@ public string WriteLlmsTxt( diagnosticInfo.AppendLine(ex.InnerException.StackTrace ?? "(no stack trace)"); } - diagnosticInfo.AppendLine("\nCONTENT PREVIEW (first 1000 chars):"); - diagnosticInfo.AppendLine("---"); - if (content != null) - { - diagnosticInfo.AppendLine(content.Length > 1000 ? content.Substring(0, 1000) + "..." : content); - } - else + diagnosticInfo.AppendLine("\nAVAILABLE ARTIFACTS:"); + var artifacts = _getArtifacts(); + foreach (var art in artifacts) { - diagnosticInfo.AppendLine("(content is null)"); + diagnosticInfo.AppendLine($" - {art.Id}: {art.Title}"); + if (art.Content != null) + { + diagnosticInfo.AppendLine($" Content preview: {(art.Content.Length > 200 ? art.Content.Substring(0, 200) + "..." : art.Content)}"); + } } - diagnosticInfo.AppendLine("---"); diagnosticInfo.AppendLine("\nENVIRONMENT INFO:"); diagnosticInfo.AppendLine($"OS: {Environment.OSVersion}"); From d2b3eea8c736d75c13b0c5ba9b99cf7662d9520f Mon Sep 17 00:00:00 2001 From: William Holmberg Date: Thu, 30 Oct 2025 11:04:20 +0100 Subject: [PATCH 6/7] fix: Correct formatting issues in console output and update default model in llms-txt generator - Fixed formatting in console output to ensure proper alignment and readability. - Updated the default model in the command-line options from "anthropic/claude-3.5-sonnet" to "anthropic/claude-haiku-4.5". - Added new llms-1.txt and llms.txt files containing comprehensive documentation for the OpenRouter.NET SDK. - Enhanced README instructions for running the llms-txt generator tool. --- .github/tools/llms-txt-generator/Agent.cs | 12 +- .github/tools/llms-txt-generator/Program.cs | 4 +- .../Prompts/SystemPrompt.cs | 12 +- .github/tools/llms-txt-generator/README.md | 2 +- .github/tools/llms-txt-generator/llms-1.txt | 334 +++++++++++++ .github/tools/llms-txt-generator/llms.txt | 455 ++++++++++++++++++ 6 files changed, 804 insertions(+), 15 deletions(-) create mode 100644 .github/tools/llms-txt-generator/llms-1.txt create mode 100644 .github/tools/llms-txt-generator/llms.txt diff --git a/.github/tools/llms-txt-generator/Agent.cs b/.github/tools/llms-txt-generator/Agent.cs index e974753..a2d6661 100644 --- a/.github/tools/llms-txt-generator/Agent.cs +++ b/.github/tools/llms-txt-generator/Agent.cs @@ -82,9 +82,9 @@ public async Task RunAsync() while (iteration < _maxIterations && !_writeTool.HasWritten) { iteration++; - Console.WriteLine($"\n{'='​,60}"); + Console.WriteLine($"\n{'=',60}"); Console.WriteLine($"πŸ”„ Iteration {iteration}/{_maxIterations}"); - Console.WriteLine($"{'='​,60}\n"); + Console.WriteLine($"{'=',60}\n"); var request = new ChatCompletionRequest { @@ -163,9 +163,9 @@ public async Task RunAsync() // Special handling for WriteLlmsTxt success if (chunk.ServerTool.ToolName == "WriteLlmsTxt") { - Console.WriteLine("\n" + "="​.PadRight(60, '=')); + Console.WriteLine("\n" + "=".PadRight(60, '=')); Console.WriteLine("πŸŽ‰ WriteLlmsTxt SUCCEEDED!"); - Console.WriteLine("="​.PadRight(60, '=')); + Console.WriteLine("=".PadRight(60, '=')); Console.WriteLine(result); // Force break on next check @@ -249,9 +249,9 @@ public async Task RunAsync() if (_writeTool.HasWritten) { - Console.WriteLine("\n\n" + "="​.PadRight(60, '=')); + Console.WriteLine("\n\n" + "=".PadRight(60, '=')); Console.WriteLine("βœ… AGENT COMPLETED SUCCESSFULLY!"); - Console.WriteLine("="​.PadRight(60, '=')); + Console.WriteLine("=".PadRight(60, '=')); Console.WriteLine($"\nπŸ“ Files read during analysis: {_fileTools.GetReadFiles().Count()}"); Console.WriteLine($"πŸ”„ Iterations used: {iteration}/{_maxIterations}"); Console.WriteLine($"\nπŸŽ‰ llms.txt has been generated!"); diff --git a/.github/tools/llms-txt-generator/Program.cs b/.github/tools/llms-txt-generator/Program.cs index 487013e..3265f84 100644 --- a/.github/tools/llms-txt-generator/Program.cs +++ b/.github/tools/llms-txt-generator/Program.cs @@ -16,7 +16,7 @@ var modelOption = new Option( name: "--model", description: "OpenRouter model to use", - getDefaultValue: () => "anthropic/claude-3.5-sonnet"); + getDefaultValue: () => "anthropic/claude-haiku-4.5"); var maxIterationsOption = new Option( name: "--max-iterations", @@ -81,7 +81,7 @@ Console.WriteLine("πŸš€ Initializing agent..."); Console.WriteLine(); - using var client = new OpenRouter.NET.OpenRouterClient(resolvedApiKey); + var client = new OpenRouter.NET.OpenRouterClient(resolvedApiKey); var agent = new Agent( client, fullPath, diff --git a/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs index a392f7f..6336177 100644 --- a/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs +++ b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs @@ -35,14 +35,14 @@ public static string GetPrompt(string basePath) => $@"You are an expert technica 1. Generate the complete llms.txt content wrapped in artifact tags: ``` - + [Your complete 50,000+ character documentation here] ``` 2. Then call WriteLlmsTxt with the artifact ID: ``` -WriteLlmsTxt(artifactId: "artifact_abc123") +WriteLlmsTxt(artifactId: ""artifact_abc123"") ``` **DO NOT** try to pass the content directly to WriteLlmsTxt - it won't work! @@ -158,7 +158,7 @@ Documentation of all public methods... **CRITICAL**: Follow this systematic approach for comprehensive documentation: ## Phase 1: Get Context & Reference (MUST DO FIRST!) -1. **GetDirectoryTree(""."", 3)** - Understand overall structure +1. **GetDirectoryTree('.', 3)** - Understand overall structure 2. **ReadFile(""README.md"")** - Understand SDK purpose and features 3. **ReadFile(""llms.txt"")** - IF IT EXISTS, read it as a REFERENCE for: - Expected depth and detail level @@ -166,7 +166,7 @@ Documentation of all public methods... - Common gotchas and issues to document - What users actually need to know 4. **ReadFile(""*.csproj"")** - Get version, dependencies, .NET requirements -5. **GetCodebaseStats(""."")** - Understand codebase scale +5. **GetCodebaseStats('.')** - Understand codebase scale ## Phase 2: Deep Source Analysis (THOROUGH!) 6. **SearchFiles(""src/*.cs"")** - List ALL .NET SDK source files (in src/ only!) @@ -190,14 +190,14 @@ 17. Compare depth with existing llms.txt (if present) **CRITICAL OUTPUT FORMAT**: ``` - + OPENROUTER.NET SDK - COMPREHENSIVE USAGE GUIDE ================================================ [50,000+ characters of complete documentation] ``` -Then call: `WriteLlmsTxt(artifactId: "the-artifact-id")` +Then call: `WriteLlmsTxt(artifactId: ""the-artifact-id"")` **CRITICAL PRIORITIES**: - βœ… If llms.txt exists, READ IT FIRST to understand expected quality diff --git a/.github/tools/llms-txt-generator/README.md b/.github/tools/llms-txt-generator/README.md index 514eae2..73c379d 100644 --- a/.github/tools/llms-txt-generator/README.md +++ b/.github/tools/llms-txt-generator/README.md @@ -32,7 +32,7 @@ export OPENROUTER_API_KEY="sk-or-v1-..." ```bash cd .github/tools/llms-txt-generator -dotnet run -- --path ../../../src +dotnet run -- --path ../../.. ``` That's it! The agent will explore your codebase and generate `llms.txt`. diff --git a/.github/tools/llms-txt-generator/llms-1.txt b/.github/tools/llms-txt-generator/llms-1.txt new file mode 100644 index 0000000..39a68a0 --- /dev/null +++ b/.github/tools/llms-txt-generator/llms-1.txt @@ -0,0 +1,334 @@ +# OpenRouter.NET SDK - Comprehensive Documentation + + + + OpenRouter.NET - .NET SDK for OpenRouter API + 0.1.0 + .NET 6.0 or later + OpenRouter.NET + + +
+ Overview + + OpenRouter.NET is a comprehensive .NET SDK for interacting with the OpenRouter API. It provides: + - Streaming and non-streaming chat completions + - Function calling and tool integration + - Artifact handling + - Conversation management + - Type-safe request/response models + - Extensive configuration options + +
+ +
+ Installation + + Install via NuGet: + ```bash + dotnet add package OpenRouter.NET + ``` + + Required using statements: + ```csharp + using OpenRouter.NET; + using OpenRouter.NET.Models; + ``` + +
+ +
+ Basic Usage + + Create a client and send a basic chat completion request: + + ```csharp + var client = new OpenRouterClient("your-api-key"); + + var request = new ChatCompletionRequest + { + Messages = new List + { + Message.FromUser("Hello, how are you?") + }, + Model = "gpt-3.5-turbo" + }; + + var response = await client.CreateChatCompletionAsync(request); + var reply = response.Choices[0].Message.Content; + ``` + +
+ +
+ Client Configuration + + The client can be configured with various options: + + ```csharp + var options = new OpenRouterClientOptions + { + ApiKey = "your-api-key", + SiteUrl = "https://your-site.com", // Optional: HTTP referer + SiteName = "Your App", // Optional: X-Title header + HttpClient = customHttpClient, // Optional: Custom HttpClient + OnLogMessage = (msg) => Console.WriteLine(msg), // Optional: Logging + BaseUrl = "https://openrouter.ai/api/v1" // Optional: API endpoint + }; + + var client = new OpenRouterClient(options); + ``` + +
+ +
+ Streaming Support + + Multiple streaming patterns are supported: + + 1. Basic text streaming: + ```csharp + await client.StreamTextAsync(request, + text => Console.Write(text)); + ``` + + 2. Advanced streaming with events: + ```csharp + await client.StreamTextAsync(request, + onText: text => Console.Write(text), + onFirstChunk: chunk => Console.WriteLine($"Model: {chunk.Raw.Model}"), + onComplete: completion => Console.WriteLine($"Finished: {completion.FinishReason}")); + ``` + + 3. Raw chunk streaming: + ```csharp + await foreach (var chunk in client.StreamAsync(request)) + { + if (chunk.TextDelta != null) + Console.Write(chunk.TextDelta); + if (chunk.ToolCallDelta != null) + HandleToolCall(chunk.ToolCallDelta); + if (chunk.Artifact != null) + HandleArtifact(chunk.Artifact); + } + ``` + + 4. Accumulate streamed response: + ```csharp + var message = await client.StreamAndAccumulateAsync(request, + onChunk: chunk => UpdateUI(chunk)); + ``` + +
+ +
+ Function Calling and Tools + + The SDK supports function calling with automatic tool execution: + + 1. Register tools: + ```csharp + client.RegisterTool( + name: "get_weather", + implementation: (args) => GetWeather(args), + description: "Get weather for a location", + parameters: new + { + type = "object", + properties = new + { + location = new + { + type = "string", + description = "City name" + } + }, + required = new[] { "location" } + } + ); + ``` + + 2. Tool modes: + ```csharp + // Auto-execute mode (default) + client.RegisterTool("server_tool", + implementation: HandleServerSide, + description: "Server-side tool", + parameters: schema, + mode: ToolMode.AutoExecute); + + // Client-side execution + client.RegisterTool("client_tool", + implementation: null, + description: "Client-side tool", + parameters: schema, + mode: ToolMode.ClientSide); + ``` + + 3. Tool loop configuration: + ```csharp + var request = new ChatCompletionRequest + { + Messages = messages, + ToolLoopConfig = new ToolLoopConfig + { + Enabled = true, + MaxIterations = 5, + TimeoutPerCall = TimeSpan.FromSeconds(30) + } + }; + ``` + +
+ +
+ Artifact Handling + + The SDK supports collecting and processing artifacts from responses: + + ```csharp + var artifacts = await client.CollectArtifactsAsync(request, + onText: text => Console.Write(text)); + + foreach (var artifact in artifacts) + { + Console.WriteLine($""" + Artifact: {artifact.Id} + Type: {artifact.Type} + Title: {artifact.Title} + Language: {artifact.Language} + Content: {artifact.Content} + """); + } + ``` + + Artifacts are automatically parsed from XML-like tags in the response: + ```xml + + public class Example { } + + ``` + +
+ +
+ Models and Responses + + 1. List available models: + ```csharp + var models = await client.GetModelsAsync(); + foreach (var model in models) + { + Console.WriteLine($"{model.Id}: {model.Description}"); + } + ``` + + 2. Check usage limits: + ```csharp + var limits = await client.GetLimitsAsync(); + Console.WriteLine($"RPM: {limits.RateLimit.RequestsPerMinute}"); + ``` + + 3. Get generation info: + ```csharp + var info = await client.GetGenerationAsync(generationId); + Console.WriteLine($"Status: {info.Status}"); + ``` + +
+ +
+ Error Handling + + The SDK provides specific exception types for different error cases: + + ```csharp + try + { + var response = await client.CreateChatCompletionAsync(request); + } + catch (OpenRouterAuthException ex) + { + // Invalid API key or authentication error + } + catch (OpenRouterRateLimitException ex) + { + // Rate limit exceeded + var retryAfterSeconds = ex.RetryAfter; + } + catch (OpenRouterBadRequestException ex) + { + // Invalid request parameters + } + catch (OpenRouterModelNotFoundException ex) + { + // Requested model not found + } + catch (OpenRouterServerException ex) + { + // Server-side error (5xx) + } + catch (OpenRouterException ex) + { + // Generic API error + } + ``` + +
+ +
+ Best Practices + + 1. Client Lifecycle: + - Create one client instance and reuse it + - Dispose the client when done if you created it with default HttpClient + - Use dependency injection in ASP.NET applications + + 2. Error Handling: + - Always handle OpenRouterException and its derived types + - Implement retry logic for rate limits using RetryAfter value + - Log error responses for debugging + + 3. Streaming: + - Use StreamTextAsync for simple text streaming + - Use StreamAndAccumulateAsync when you need the final message + - Handle cancellation tokens appropriately + + 4. Tools: + - Register tools before sending requests + - Use appropriate ToolMode based on execution context + - Implement tool error handling + - Set reasonable MaxIterations in ToolLoopConfig + + 5. Performance: + - Reuse HttpClient instances + - Use streaming for long responses + - Implement timeouts for tool executions + +
+ +
+ Common Issues and Solutions + + 1. Authentication Errors: + - Ensure API key is set correctly + - Check if API key has required permissions + - Verify API key format + + 2. Rate Limits: + - Implement exponential backoff + - Use RetryAfter value from OpenRouterRateLimitException + - Monitor usage with GetLimitsAsync + + 3. Streaming Issues: + - Handle network interruptions + - Implement timeout logic + - Use cancellation tokens + + 4. Tool Execution: + - Validate tool arguments + - Handle tool timeouts + - Implement proper error responses + +
+
\ No newline at end of file diff --git a/.github/tools/llms-txt-generator/llms.txt b/.github/tools/llms-txt-generator/llms.txt new file mode 100644 index 0000000..136e322 --- /dev/null +++ b/.github/tools/llms-txt-generator/llms.txt @@ -0,0 +1,455 @@ + +OPENROUTER.NET SDK - COMPREHENSIVE USAGE GUIDE +================================================ + +METADATA +-------- +Title: OpenRouter.NET SDK - Complete Reference Guide +Version: 1.0.0+ +.NET Requirements: .NET 6.0 or higher +Primary Namespace: OpenRouter.NET +Repository: OpenRouter.NET (C# SDK for OpenRouter API) + +OVERVIEW +-------- +OpenRouter.NET is a comprehensive C# SDK for interacting with the OpenRouter API. It provides: +- Full support for chat completions with streaming +- Tool/function calling capabilities +- Server-Sent Events (SSE) streaming +- Artifact support for code, text, and images +- Conversation management +- Flexible model selection and routing +- Request/response validation +- Error handling and retry logic + +The SDK is designed to be intuitive for .NET developers while providing access to all OpenRouter API features. + +GETTING STARTED +=============== + +Installation +----------- +Install via NuGet: + dotnet add package OpenRouter.NET + +Required Using Statements +-------------------------- +using OpenRouter.NET; +using OpenRouter.NET.Models; +using OpenRouter.NET.Sse; +using OpenRouter.NET.Tools; +using OpenRouter.NET.Extensions; + +Basic Setup +----------- +The SDK requires an OpenRouter API key. Set it as an environment variable: + export OPENROUTER_API_KEY="your-api-key-here" + +Or pass it directly when creating the client: + var client = new OpenRouterClient("your-api-key-here"); + +CORE CLIENT USAGE +================= + +OpenRouterClient Class +--------------------- +The main entry point for all SDK functionality. + +Constructor: + public OpenRouterClient(string? apiKey = null) + + - If apiKey is null, reads from OPENROUTER_API_KEY environment variable + - Throws ArgumentException if no API key is provided or found + +Configuration +----------- +The client uses sensible defaults but can be customized: + +Default Configuration: + - Base URL: https://openrouter.ai/api/v1 + - Timeout: 30 seconds + - Retry Policy: Exponential backoff with max 3 retries + - Model: gpt-3.5-turbo (can be overridden per request) + +Example: Basic Chat Completion +------------------------------ +var client = new OpenRouterClient(); + +var response = await client.CreateChatCompletionAsync(new ChatCompletionRequest +{ + Model = "openai/gpt-4", + Messages = new[] + { + new ChatMessage { Role = "user", Content = "What is 2+2?" } + } +}); + +Console.WriteLine(response.Choices[0].Message.Content); + +CHAT COMPLETION REQUESTS +======================== + +ChatCompletionRequest Class +--------------------------- +Represents a request to create a chat completion. + +Key Properties: + - Model (string): The model to use (e.g., "openai/gpt-4", "anthropic/claude-3-opus") + - Messages (ChatMessage[]): Array of messages in the conversation + - Temperature (float?): Controls randomness (0.0-2.0, default 1.0) + - MaxTokens (int?): Maximum tokens in the response + - TopP (float?): Nucleus sampling parameter (0.0-1.0) + - TopK (int?): Top-K sampling parameter + - FrequencyPenalty (float?): Reduces repetition (-2.0 to 2.0) + - PresencePenalty (float?): Encourages new topics (-2.0 to 2.0) + - Stop (string[]?): Stop sequences + - Tools (Tool[]?): Available tools for function calling + - ToolChoice (string?): "auto", "required", or specific tool name + - ResponseFormat (ResponseFormat?): For structured outputs + - Seed (int?): For reproducible outputs + - Provider (ProviderConfig?): Provider-specific settings + +ChatMessage Class +----------------- +Represents a single message in a conversation. + +Properties: + - Role (string): "system", "user", "assistant", or "tool" + - Content (string?): The message content + - ToolCalls (ToolCall[]?): Tool calls made by the assistant + - ToolCallId (string?): ID of the tool call this message responds to + - Name (string?): Name of the tool (for tool messages) + +Example: Multi-turn Conversation +-------------------------------- +var client = new OpenRouterClient(); + +var messages = new List +{ + new ChatMessage { Role = "system", Content = "You are a helpful assistant." }, + new ChatMessage { Role = "user", Content = "What is the capital of France?" }, +}; + +var response = await client.CreateChatCompletionAsync(new ChatCompletionRequest +{ + Model = "openai/gpt-4", + Messages = messages.ToArray() +}); + +var assistantMessage = response.Choices[0].Message; +messages.Add(assistantMessage); + +// Continue conversation +messages.Add(new ChatMessage { Role = "user", Content = "What is its population?" }); + +var response2 = await client.CreateChatCompletionAsync(new ChatCompletionRequest +{ + Model = "openai/gpt-4", + Messages = messages.ToArray() +}); + +ChatCompletionResponse Class +---------------------------- +Represents the response from a chat completion request. + +Key Properties: + - Id (string): Unique identifier for the completion + - Object (string): Always "chat.completion" + - Created (long): Unix timestamp of creation + - Model (string): The model used + - Choices (Choice[]): Array of completion choices + - Usage (Usage): Token usage information + - SystemFingerprint (string?): System fingerprint for reproducibility + +Choice Class +----------- +Represents a single completion choice. + +Properties: + - Index (int): Index in the choices array + - Message (ChatMessage): The generated message + - FinishReason (string): "stop", "length", "tool_calls", or "content_filter" + - LogProbs (object?): Log probabilities (if requested) + +Usage Class +----------- +Token usage information. + +Properties: + - PromptTokens (int): Tokens in the prompt + - CompletionTokens (int): Tokens in the completion + - TotalTokens (int): Total tokens used + +STREAMING +========= + +Streaming is a critical feature for real-time responses. The SDK provides two approaches: + +Approach 1: Async Enumerable (Recommended) +------------------------------------------ +var client = new OpenRouterClient(); + +await foreach (var chunk in client.CreateChatCompletionStreamAsync(new ChatCompletionRequest +{ + Model = "openai/gpt-4", + Messages = new[] + { + new ChatMessage { Role = "user", Content = "Write a short poem about coding" } + } +})) +{ + if (chunk.Choices[0].Delta?.Content != null) + { + Console.Write(chunk.Choices[0].Delta.Content); + } +} + +StreamingChatCompletionResponse Class +------------------------------------- +Represents a streaming chunk from the API. + +Key Properties: + - Id (string): Unique identifier for the stream + - Object (string): Always "chat.completion.chunk" + - Created (long): Unix timestamp + - Model (string): The model used + - Choices (StreamingChoice[]): Array of streaming choices + +StreamingChoice Class +-------------------- +Properties: + - Index (int): Index in the choices array + - Delta (Delta): The incremental content + - FinishReason (string?): "stop" when stream ends + - LogProbs (object?): Log probabilities + +Delta Class +----------- +Represents incremental content in a stream. + +Properties: + - Role (string?): Role of the message (usually only in first chunk) + - Content (string?): Incremental text content + - ToolCalls (ToolCall[]?): Tool calls (if any) + +Example: Streaming with Cancellation +------------------------------------ +var client = new OpenRouterClient(); +var cts = new CancellationTokenSource(); +cts.CancelAfter(TimeSpan.FromSeconds(30)); + +try +{ + await foreach (var chunk in client.CreateChatCompletionStreamAsync( + new ChatCompletionRequest + { + Model = "openai/gpt-4", + Messages = new[] + { + new ChatMessage { Role = "user", Content = "Tell me a long story" } + } + }, + cts.Token)) + { + if (chunk.Choices[0].Delta?.Content != null) + { + Console.Write(chunk.Choices[0].Delta.Content); + } + } +} +catch (OperationCanceledException) +{ + Console.WriteLine("Stream cancelled"); +} + +TOOL/FUNCTION CALLING +==================== + +Tool calling allows the model to request specific functions be executed. This is powerful for: +- Retrieving real-time data +- Performing calculations +- Accessing external APIs +- Creating structured workflows + +Tool Class +--------- +Defines a tool the model can call. + +Properties: + - Type (string): Always "function" + - Function (ToolFunction): The function definition + +ToolFunction Class +----------------- +Properties: + - Name (string): Function name (alphanumeric, underscores, hyphens) + - Description (string): What the function does + - Parameters (ToolParameters): Function parameters schema + +ToolParameters Class +------------------- +Properties: + - Type (string): Always "object" + - Properties (Dictionary): Parameter definitions + - Required (string[]?): Required parameter names + +ToolProperty Class +----------------- +Properties: + - Type (string): "string", "number", "integer", "boolean", "array", "object" + - Description (string): Parameter description + - Enum (string[]?): Allowed values + - Items (ToolProperty?): For array types + - Properties (Dictionary?): For object types + +ToolCall Class +-------------- +Represents a tool call made by the model. + +Properties: + - Id (string): Unique identifier for the tool call + - Type (string): Always "function" + - Function (ToolCallFunction): The function call details + +ToolCallFunction Class +--------------------- +Properties: + - Name (string): Function name + - Arguments (string): JSON string of arguments + +Example: Weather Tool +-------------------- +var client = new OpenRouterClient(); + +var tools = new[] +{ + new Tool + { + Type = "function", + Function = new ToolFunction + { + Name = "get_weather", + Description = "Get the current weather for a location", + Parameters = new ToolParameters + { + Type = "object", + Properties = new Dictionary + { + ["location"] = new ToolProperty + { + Type = "string", + Description = "City name" + }, + ["unit"] = new ToolProperty + { + Type = "string", + Description = "Temperature unit", + Enum = new[] { "celsius", "fahrenheit" } + } + }, + Required = new[] { "location" } + } + } + } +}; + +var response = await client.CreateChatCompletionAsync(new ChatCompletionRequest +{ + Model = "openai/gpt-4", + Messages = new[] + { + new ChatMessage { Role = "user", Content = "What's the weather in Paris?" } + }, + Tools = tools, + ToolChoice = "auto" +}); + +// Check if model wants to call a tool +if (response.Choices[0].Message.ToolCalls != null) +{ + foreach (var toolCall in response.Choices[0].Message.ToolCalls) + { + Console.WriteLine($"Tool: {toolCall.Function.Name}"); + Console.WriteLine($"Arguments: {toolCall.Function.Arguments}"); + + // Parse arguments and execute tool + var args = JsonSerializer.Deserialize>( + toolCall.Function.Arguments); + + // Execute your tool logic here + var result = ExecuteWeatherTool(args); + + // Send result back to model + var messages = new List + { + new ChatMessage { Role = "user", Content = "What's the weather in Paris?" }, + response.Choices[0].Message, + new ChatMessage + { + Role = "tool", + Content = result, + ToolCallId = toolCall.Id, + Name = toolCall.Function.Name + } + }; + + var finalResponse = await client.CreateChatCompletionAsync(new ChatCompletionRequest + { + Model = "openai/gpt-4", + Messages = messages.ToArray(), + Tools = tools + }); + + Console.WriteLine(finalResponse.Choices[0].Message.Content); + } +} + +Tool Calling Best Practices +--------------------------- +1. Always validate tool arguments before execution +2. Use specific, descriptive function names +3. Provide clear descriptions of what each tool does +4. Include examples in descriptions for complex tools +5. Handle tool call failures gracefully +6. Set ToolChoice = "required" if you want guaranteed tool use +7. Set ToolChoice = "auto" for optional tool use +8. Set ToolChoice = "tool_name" to force a specific tool +9. Always include tool results in the conversation for context + +ARTIFACTS +========= + +Artifacts are a special feature for handling large content blocks like code, text, or images. + +Artifact Class +-------------- +Properties: + - Type (string): "code", "text", "image", or "html" + - Language (string?): Programming language for code artifacts + - Title (string?): Display title + - Content (string): The artifact content + +Example: Code Artifact +--------------------- +var client = new OpenRouterClient(); + +var response = await client.CreateChatCompletionAsync(new ChatCompletionRequest +{ + Model = "openai/gpt-4", + Messages = new[] + { + new ChatMessage + { + Role = "user", + Content = "Write a C# function to calculate fibonacci numbers" + } + } +}); + +var content = response.Choices[0].Message.Content; + +// Check if response contains artifacts +if (content.Contains("]*type=""([^""]+)""[^>]*>(.+?) \ No newline at end of file From 2686645a3169407ba4cb2281ff097dd54381352d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 30 Oct 2025 10:07:40 +0000 Subject: [PATCH 7/7] Refactor: Improve system prompt and artifact handling Update system prompt to focus on practical usage for LLMs. Enhance artifact ID handling in WriteLlmsTxtTool for better robustness and user experience. Co-authored-by: william.holmberg --- .../Prompts/SystemPrompt.cs | 115 ++++++++++++++---- .../Tools/WriteLlmsTxtTool.cs | 76 ++++++++---- 2 files changed, 147 insertions(+), 44 deletions(-) diff --git a/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs index 6336177..5855e60 100644 --- a/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs +++ b/.github/tools/llms-txt-generator/Prompts/SystemPrompt.cs @@ -6,7 +6,27 @@ public static string GetPrompt(string basePath) => $@"You are an expert technica # YOUR MISSION -You are documenting the **OpenRouter.NET SDK** - a .NET SDK for the OpenRouter API. +You are creating a **PRACTICAL USAGE GUIDE** for the **OpenRouter.NET SDK** - a .NET SDK for the OpenRouter API. + +**PURPOSE**: This llms.txt is for OTHER LLMs to learn how to USE the SDK in real projects. + +**FOCUS ON**: +- βœ… How to install and set up +- βœ… Required using statements (CRITICAL!) +- βœ… Working code examples that can be copy-pasted +- βœ… Common patterns developers actually use +- βœ… How to accomplish specific tasks +- βœ… Common gotchas and how to avoid them +- βœ… Error messages and solutions + +**DO NOT FOCUS ON**: +- ❌ Internal implementation details +- ❌ How the SDK works internally +- ❌ Class inheritance hierarchies +- ❌ Private methods +- ❌ Architecture documentation + +Think: "If an LLM reads this, can it help a developer write working code?" The workspace is at: {basePath} @@ -15,7 +35,7 @@ public static string GetPrompt(string basePath) => $@"You are an expert technica - Existing `llms.txt` (if present) is your reference for style/depth - Other packages (like `packages/react-sdk/`) are separate and NOT your focus -Generate a COMPLETE, COMPREHENSIVE llms.txt file for the **OpenRouter.NET SDK** that serves as a reference guide for LLMs to understand and use this SDK. +Generate a COMPLETE, COMPREHENSIVE llms.txt file for the **OpenRouter.NET SDK** that teaches LLMs how to USE this SDK effectively. # AVAILABLE TOOLS @@ -65,16 +85,18 @@ public static string GetPrompt(string basePath) => $@"You are an expert technica - What are common usage patterns? - What are the dependencies and requirements? -3. **GENERATE** comprehensive documentation: - - Complete usage guide with all patterns - - Configuration options - - Request/response models - - Error handling - - Streaming patterns - - Advanced features - - Code examples (real, working examples) +3. **GENERATE** comprehensive PRACTICAL documentation: + - Installation and setup steps + - Required using statements (VERY IMPORTANT!) + - Configuration patterns with examples + - Request/response examples + - Streaming examples (basic and advanced) + - Tool calling examples (with attributes!) + - Error handling patterns + - Common usage patterns from samples + - Troubleshooting common issues - Best practices - - Common issues and solutions + - Complete working examples 4. **OUTPUT** using WriteLlmsTxt tool with XML-structured sections @@ -139,20 +161,23 @@ Documentation of all public methods... # CRITICAL REQUIREMENTS -βœ… **BE COMPREHENSIVE**: This is for LLMs to understand the ENTIRE SDK -βœ… **INCLUDE REAL EXAMPLES**: Use actual code patterns from the codebase -βœ… **BE SPECIFIC**: Include exact class names, method signatures, namespaces +βœ… **BE PRACTICAL**: Show HOW to use, not HOW it works internally +βœ… **INCLUDE REAL EXAMPLES**: Working code that can be copy-pasted +βœ… **BE SPECIFIC**: Exact using statements, class names, method signatures βœ… **SHOW COMMON PATTERNS**: How do developers actually use this? -βœ… **DOCUMENT GOTCHAS**: Common mistakes, error messages, solutions -βœ… **USE XML STRUCTURE**: Wrap sections in XML tags for easy parsing -βœ… **BE THOROUGH**: Read enough files to truly understand the SDK -βœ… **WORKING CODE**: All examples should be syntactically correct +βœ… **DOCUMENT GOTCHAS**: Common mistakes, error messages, solutions ("Missing using statement" etc.) +βœ… **USE CLEAR STRUCTURE**: Easy to navigate and find information +βœ… **BE THOROUGH**: Cover ALL major features with examples +βœ… **WORKING CODE**: All examples should be syntactically correct and runnable +❌ **DON'T DOCUMENT INTERNALS**: Skip private methods, implementation details ❌ **DON'T RUSH**: Take time to explore thoroughly before writing ❌ **DON'T GUESS**: Read the actual code to understand behavior ❌ **DON'T BE VAGUE**: Provide concrete examples and specific details ❌ **DON'T SKIP FEATURES**: Document everything important +**REMEMBER**: This is for LLMs to learn "HOW TO USE" not "HOW IT WORKS" + # EXPLORATION STRATEGY **CRITICAL**: Follow this systematic approach for comprehensive documentation: @@ -191,13 +216,59 @@ 17. Compare depth with existing llms.txt (if present) **CRITICAL OUTPUT FORMAT**: ``` -OPENROUTER.NET SDK - COMPREHENSIVE USAGE GUIDE -================================================ -[50,000+ characters of complete documentation] +OPENROUTER.NET SDK - PRACTICAL USAGE GUIDE FOR LLMs +==================================================== + +# PURPOSE +This guide teaches LLMs how to USE the OpenRouter.NET SDK effectively. +Focus: Practical usage, working examples, common patterns. + +# INSTALLATION +[How to install...] + +# REQUIRED USING STATEMENTS +⚠️ CRITICAL - These are required for the SDK to work: +using OpenRouter.NET; +using OpenRouter.NET.Models; +... + +# QUICK START +[Simple working example...] + +# CONFIGURATION +[All configuration patterns with examples...] + +# BASIC USAGE +[Request/response examples...] + +# STREAMING +[Streaming examples with code...] + +# TOOL CALLING +[Tool registration examples with attributes...] + +# COMMON PATTERNS +[Real-world usage from samples...] + +# TROUBLESHOOTING +[Common errors and solutions...] + +# BEST PRACTICES +[Tips and recommendations...] + +[50,000+ characters of PRACTICAL, USAGE-FOCUSED documentation] ``` -Then call: `WriteLlmsTxt(artifactId: ""the-artifact-id"")` +**KEY POINTS**: +- Focus on HOW TO USE, not internal architecture +- Include COMPLETE working examples +- Show required using statements upfront +- Document common mistakes and solutions +- Reference real patterns from samples/ + +Then call: `WriteLlmsTxt(artifactId: ""the-artifact-id"")` +(Use the EXACT artifact ID from the artifact you generated!) **CRITICAL PRIORITIES**: - βœ… If llms.txt exists, READ IT FIRST to understand expected quality diff --git a/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs b/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs index eeceb3f..7b431d6 100644 --- a/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs +++ b/.github/tools/llms-txt-generator/Tools/WriteLlmsTxtTool.cs @@ -18,7 +18,7 @@ public WriteLlmsTxtTool(string outputPath, Func> getArtifacts) [ToolMethod("Write the final llms.txt content from an artifact. First generate the documentation as an artifact (using tags), then call this tool with the artifact ID. After calling this successfully, your task is COMPLETE and you should stop.")] public string WriteLlmsTxt( - [ToolParameter("The artifact ID containing the complete llms.txt documentation. You must generate an artifact first before calling this.")] string artifactId) + [ToolParameter("The artifact ID containing the complete llms.txt documentation. You must generate an artifact first before calling this. Check the artifact ID carefully!")] string artifactId) { // First, validate and save diagnostics var diagnosticInfo = new StringBuilder(); @@ -52,18 +52,6 @@ public string WriteLlmsTxt( throw new InvalidOperationException(errorMsg + "\n\n" + diagnosticInfo.ToString()); } - if (string.IsNullOrWhiteSpace(artifactId)) - { - var errorMsg = "❌ CRITICAL ERROR: artifactId cannot be empty. " + - "You must first generate the documentation as an artifact using tags, " + - "then call this tool with the artifact ID."; - - diagnosticInfo.AppendLine($"\nERROR: {errorMsg}"); - Console.WriteLine(diagnosticInfo.ToString()); - - throw new ArgumentException(errorMsg + "\n\n" + diagnosticInfo.ToString()); - } - // Get artifacts var artifacts = _getArtifacts(); diagnosticInfo.AppendLine($"Available artifacts: {artifacts.Count}"); @@ -72,19 +60,63 @@ public string WriteLlmsTxt( diagnosticInfo.AppendLine($" - {art.Id}: {art.Title} ({art.Content?.Length ?? 0} chars)"); } - // Find the artifact - var artifact = artifacts.FirstOrDefault(a => a.Id == artifactId); + // If artifactId is empty but there's exactly one artifact, use it + Artifact? artifact = null; - if (artifact == null) + if (string.IsNullOrWhiteSpace(artifactId)) + { + if (artifacts.Count == 1) + { + artifact = artifacts[0]; + diagnosticInfo.AppendLine($"\nβœ… No artifact ID provided, but found exactly one artifact. Using: {artifact.Id}"); + } + else + { + var errorMsg = "❌ CRITICAL ERROR: artifactId cannot be empty. " + + "You must first generate the documentation as an artifact using tags, " + + "then call this tool with the artifact ID."; + + diagnosticInfo.AppendLine($"\nERROR: {errorMsg}"); + Console.WriteLine(diagnosticInfo.ToString()); + + throw new ArgumentException(errorMsg + "\n\n" + diagnosticInfo.ToString()); + } + } + else { - var errorMsg = $"❌ CRITICAL ERROR: Artifact '{artifactId}' not found. " + - $"Available artifacts: {string.Join(", ", artifacts.Select(a => a.Id))}. " + - "Did you generate the artifact first?"; + // Try exact match first + artifact = artifacts.FirstOrDefault(a => a.Id == artifactId); - diagnosticInfo.AppendLine($"\nERROR: {errorMsg}"); - Console.WriteLine(diagnosticInfo.ToString()); + // If not found, try case-insensitive match + if (artifact == null) + { + artifact = artifacts.FirstOrDefault(a => + string.Equals(a.Id, artifactId, StringComparison.OrdinalIgnoreCase)); + + if (artifact != null) + { + diagnosticInfo.AppendLine($"\n⚠️ Artifact ID mismatch (case difference). Found: {artifact.Id}"); + } + } - throw new ArgumentException(errorMsg + "\n\n" + diagnosticInfo.ToString()); + // If still not found but there's only one artifact, use it + if (artifact == null && artifacts.Count == 1) + { + artifact = artifacts[0]; + diagnosticInfo.AppendLine($"\n⚠️ Artifact ID '{artifactId}' not found, but only one artifact exists. Using: {artifact.Id}"); + } + + if (artifact == null) + { + var errorMsg = $"❌ CRITICAL ERROR: Artifact '{artifactId}' not found. " + + $"Available artifacts: {string.Join(", ", artifacts.Select(a => a.Id))}. " + + "Check the artifact ID carefully!"; + + diagnosticInfo.AppendLine($"\nERROR: {errorMsg}"); + Console.WriteLine(diagnosticInfo.ToString()); + + throw new ArgumentException(errorMsg + "\n\n" + diagnosticInfo.ToString()); + } } var content = artifact.Content;