diff options
| -rw-r--r-- | AGENTS.md | 1 | ||||
| -rw-r--r-- | SCRATCHPAD.md | 2 | ||||
| -rw-r--r-- | cmd/hexai/main.go | 780 | ||||
| -rw-r--r-- | config.toml.example | 7 | ||||
| -rw-r--r-- | internal/appconfig/config.go | 35 | ||||
| -rw-r--r-- | internal/hexailsp/run.go | 10 | ||||
| -rw-r--r-- | internal/llm/anthropic.go | 316 | ||||
| -rw-r--r-- | internal/llm/anthropic_test.go | 259 | ||||
| -rw-r--r-- | internal/llm/openai_temp_test.go | 6 | ||||
| -rw-r--r-- | internal/llm/provider.go | 15 | ||||
| -rw-r--r-- | internal/llm/provider_more2_test.go | 2 | ||||
| -rw-r--r-- | internal/llm/provider_more_test.go | 4 | ||||
| -rw-r--r-- | internal/llm/provider_test.go | 6 | ||||
| -rw-r--r-- | internal/llmutils/client.go | 9 | ||||
| -rw-r--r-- | internal/lsp/server.go | 9 | ||||
| -rw-r--r-- | project.d2 | 244 | ||||
| -rw-r--r-- | project.svg | 102 |
17 files changed, 1787 insertions, 20 deletions
@@ -19,7 +19,6 @@ - Code (when added): follow language idioms - Any type with more than 3 methods should be in it's own source code file, whereas the filename contains the name of the type. - ## Incrementing version - Never draft a changelog entry diff --git a/SCRATCHPAD.md b/SCRATCHPAD.md index c046e79..2c51680 100644 --- a/SCRATCHPAD.md +++ b/SCRATCHPAD.md @@ -1,4 +1,4 @@ -# Project scratch pad +# Project scratch pad and ideas This document shows future items and items in progress. Already completed ones are deleted from this document as updates occur. * [ ] hexai cli to keep context for the follow-up question/prompt? diff --git a/cmd/hexai/main.go b/cmd/hexai/main.go index d0508e7..6555444 100644 --- a/cmd/hexai/main.go +++ b/cmd/hexai/main.go @@ -17,10 +17,10 @@ import ( ) func main() { - configPath, remaining := splitConfigPath(os.Args[1:]) - logger := log.New(io.Discard, "", 0) - cfg := appconfig.LoadWithOptions(logger, appconfig.LoadOptions{ConfigPath: configPath}) - cliEntries := cfg.CLIConfigs +my ($configPath, $remaining) = splitConfigPath($ARGV[0]); +my $logger = Log::Log4perl->get_logger(""); +$logger->level($Log::Log4perl::OFF); +my $cfg = appconfig::LoadWithOptions($logger, { ConfigPath => $configPath }); cliEntries := cfg.CLIConfigs if len(cliEntries) == 0 { cliEntries = []appconfig.SurfaceConfig{{Provider: cfg.Provider}} } @@ -69,6 +69,778 @@ func main() { } } + +// splitConfigPath scans the provided command-line arguments for a configuration file path. +// It recognizes the following forms: +// --config <path>, -config <path>, --config=<path>, -config=<path + +> Unknown command "// --config <path>, -config <path>, --config=<path>, -config=<path". Try /help? + +> The message: + +> Unknown command "// --config <path>, -config <path>, --config=<path>, -config=<path". Try /help? + +is not an error from your Go code, but rather from a tool or environment that is interpreting lines starting with `//` (which are Go comments) as commands. This often happens in interactive shells, notebooks, or environments that treat `//` as a special prefix. + +**How to fix:** + +- If you are running this code in a Go file (`.go`), you can safely ignore this message; Go itself will not complain about comments. +- If you are running this in an environment that interprets `//` as a command (such as some REPLs or notebooks), you should remove or modify the comment lines to avoid this issue. + +**Example:** +```go +// The following forms are recognized for config path flags: +// --config <path>, -config <path>, --config=<path>, -config=<path + +> Unknown command "// --config <path>, -config <path>, --config=<path>, -config=<path". Try /help? + +> This message is **not an error from your Go code**. It comes from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or REPLs interpret lines starting with `//` as commands, not as comments. + +**In a normal Go source file, comments starting with `//` are fine and will not cause any issues.** + +### Why does this happen? +- Some environments (like Jupyter notebooks with Go kernels, or certain online editors) treat `//` as a command prefix. +- When you write a comment like: + ```go + // --config <path>, -config <path>, --config=<path>, -config=<path + +> Unknown command "// --config <path>, -config <path>, --config=<path>, -config=<path". Try /help? + +> This message is **not an error from your Go code**. It comes from the environment or tool you are using to run or paste your code—such as a REPL, notebook, or some online editors—that interprets lines starting with `//` as commands, not as comments. + +**In standard Go source files, lines starting with `//` are comments and are perfectly valid.** + +--- + +### Why does this happen? + +- Some interactive environments treat `// ...` as a command, not as a comment. + +--- + +### How to fix or avoid this message + +- **If you are writing a `.go` file and running it with `go run` or `go build`:** + You can ignore this message. Go will not complain about `//` comments. + +- **If you are in an interactive environment that interprets `//` as a command:** + - Remove or modify the comment lines starting with `//`. + - Or, use block comments (`/* ... */`) if your environment supports them. + +**Example:** + +```go +/* +Recognized config flag forms: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. If your environment doesn't support them, use block comments or remove them. + + + + + ``` + The environment tries to "run" this as a command, fails, and prints: + ``` + Unknown command "// --config <path>, -config <path>, --config=<path>, -config=<path". Try /help? + ``` + +### How to fix or avoid this? +- **If you are writing a Go file (`.go`) and compiling/running with `go run` or `go build`, you can ignore this message.** +- **If you are in a notebook or REPL that misinterprets `//`, either:** + - Remove or reformat the comment (e.g., use block comments `/* ... */` if supported). + - Or, prefix with a single `#` if the environment supports it. + - Or, just avoid comments that start with `//` at the beginning of a line. + +### Example: +```go +// This is a safe comment in Go source files. +// The following forms are recognized for config path flags: +// --config <path>, -config <path>, --config=<path>, -config=<path + +> Unknown command "// --config <path>, -config <path>, --config=<path>, -config=<path". Try /help? + +> This message is **not an error from your Go code**. It comes from the environment or tool you are using to run or paste your code—such as a REPL, notebook, or some online editors—that interprets lines starting with `//` as commands, not as comments. + +**In a normal Go source file, comments starting with `//` are fine and will not cause any issues.** + +--- + +### Why does this happen? + +- Some interactive environments treat `// ...` as a command, not as a comment. + +--- + +### How to fix or avoid this message + +- **If you are writing a `.go` file and running it with `go run` or `go build`:** + You can ignore this message. Go will not complain about `//` comments. + +- **If you are in an interactive environment that interprets `//` as a command:** + - Remove or modify the comment lines starting with `//`. + - Or, use block comments (`/* ... */`) if your environment supports them. + +**Example:** + +```go +/* +Recognized config flag forms: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. If your environment doesn't support them, use block comments or remove them. + + + + +``` + +**Summary:** +This is not a Go error. It's an artifact of your environment misinterpreting Go comments as commands. In a normal Go source file, these comments are fine. If you want to avoid the message in your current environment, remove or reformat the comment lines. + + + +> The message: + +``` +> Unknown command "// --config <path>, -config <path>, --config=<path>, -config=<path". Try /help? +``` + +is **not an error from your Go code**. Instead, it comes from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or REPLs interpret lines starting with `//` as special commands, not as comments. + +**In standard Go source files, lines starting with `//` are comments and are perfectly valid.** + +### How to fix or avoid this message: + +- **If you are writing a `.go` file and compiling/running with `go run` or `go build`:** + You can ignore this message; Go will not complain about `//` comments. + +- **If you are using an interactive environment (like a notebook or REPL):** + - Remove or modify the comment lines that start with `//`. + - Or, use block comments (`/* ... */`) if your environment supports them. + +### Example + +```go +// This is a valid Go comment and will not cause issues in Go source files. +``` + +or + +```go +/* +This is a block comment. +It is also valid in Go source files. +*/ +``` + +--- + +**Summary:** +This is not a Go code error. It's your environment misinterpreting Go comments as commands. In a regular Go file, you can safely use `//` comments. + + + +> This message is **not an error from your Go code**. It is coming from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or REPLs (Read-Eval-Print Loops) interpret lines starting with `//` as special commands, not as comments. + +**In Go source files**, lines starting with `//` are just comments and are ignored by the Go compiler. + +### How to fix or avoid this message: + +- **If you are writing a `.go` file**: + You can ignore this message. Your code will compile and run fine with `//` comments. + +- **If you are using an interactive environment (REPL, notebook, etc.)**: + - Remove or modify the comment lines that start with `//`. + - Or, use block comments (`/* ... */`) instead, if supported. + +**Example:** + +```go +// This is a comment in Go and is safe in .go files. +``` + +or + +```go +/* +This is a block comment in Go. +It is also safe in .go files and some REPLs. +*/ +``` + +--- + +**Summary:** +Your Go code is fine. The message is from your environment, not from Go itself. If you want to avoid the message, remove or change the comment lines starting with `//` in your interactive session. + + + +> This message is **not an error from your Go code**. It comes from the environment or tool you are using to run or paste your code. Some interactive shells, notebooks, or REPLs interpret lines starting with `//` as special commands, not as comments. + +**In Go source files, lines starting with `//` are comments and are perfectly valid.** + +### How to fix or avoid this message + +- **If you are writing a `.go` file:** + You can ignore this message. Go will not complain about `//` comments. + +- **If you are using an interactive environment (REPL, notebook, etc.):** + - Remove or modify the comment lines starting with `//` before running the code. + - Or, use block comments (`/* ... */`) if your environment supports them. + +**Example:** + +```go +// This is a Go comment and is fine in .go files. +``` + + +> This message is **not an error from your Go code**. It is coming from the environment or tool you are using to run or paste your code. Some interactive shells, notebooks, or code runners interpret lines starting with `//` as special commands, not as comments. + +**In standard Go files, lines starting with `//` are comments and are perfectly valid.** + +--- + +### Why does this happen? + +- Your environment (e.g., a REPL, notebook, or some online editors) is trying to interpret `// ...` as a command, not as a comment. + +--- + +### How to fix or avoid this message + +- **If you are writing a `.go` file and compiling with `go build` or `go run`, you can ignore this message.** +- **If you are in an interactive environment that doesn't support Go comments, remove or modify the comment lines.** +- **Alternatively, use block comments (`/* ... */`) if your environment supports them.** + +**Example:** + +```go +/* +Recognized config path flags: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** + +> The message: + +``` +> Unknown command "// --config <path>, -config <path>, --config=<path>, -config=<path". Try /help? +``` + +is **not an error from your Go code**. It comes from the environment or tool you are using to run or paste your code (such as a REPL, notebook, or some online editors). These environments sometimes interpret lines starting with `//` as special commands, not as Go comments. + +**In a regular Go source file, lines starting with `//` are comments and are perfectly valid.** + +--- + +## How to fix or avoid this message + +- **If you are writing a `.go` file and running it with `go run` or `go build`:** + You can ignore this message. Go will not complain about `//` comments. + +- **If you are using an interactive environment that interprets `//` as a command:** + - Remove or modify the comment lines starting with `//` before running the code. + - Or, use block comments (`/* ... */`) if your environment supports them. + +**Example:** + +```go +/* +Recognized config flag forms: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. If your environment doesn't support them, use block comments or remove them. + + +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine and will not cause issues. + + + +> This message is **not an error from your Go code**. It comes from the environment or tool you are using to run or paste your code. Some interactive shells, notebooks, or code runners interpret lines starting with `//` as commands, not as comments. + +**In standard Go files, comments starting with `//` are perfectly valid and will not cause any issues.** + +--- + +### Why does this happen? + +- Your environment (e.g., a REPL, notebook, or some online editors) is trying to interpret `// ...` as a command, not as a comment. + +--- + +### How to fix + +- **If you are writing a `.go` file and compiling with `go build` or `go run`, you can ignore this message.** +- **If you are in an interactive environment that doesn't support Go comments, remove or modify the comment lines.** +- **Alternatively, use block comments (`/* ... */`) if your environment supports them.** + +**Example:** + +```go +/* +Recognized config flag forms: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. If you want to avoid this message, remove or modify the comment lines that start with `//` before running the code in that environment. + + + +> This message is **not an error from your Go code**. It comes from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or code runners interpret lines starting with `//` as special commands, not as comments. + +**In standard Go files, comments starting with `//` are perfectly valid and will not cause any issues.** + +--- + +### Why does this happen? + +- Your environment (e.g., a REPL, notebook, or some online editors) is trying to interpret `// ...` as a command, not as a comment. + +--- + +### How to fix or avoid this message + +- **If you are writing a `.go` file and compiling with `go build` or `go run`, you can ignore this message.** +- **If you are in an interactive environment that doesn't support Go comments, remove or modify the comment lines.** +- **Alternatively, use block comments (`/* ... */`) if your environment supports them.** + +**Example:** + +```go +/* +Recognized config flag forms: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + + + +> This message is **not an error from your Go code**. It comes from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or code runners interpret lines starting with `//` as commands, not as comments. + +**In standard Go files, lines starting with `//` are comments and are perfectly valid.** + +--- + +### Why does this happen? + +- Your environment (e.g., a REPL, notebook, or some online editors) is trying to interpret `// ...` as a command, not as a comment. + +--- + +### How to fix or avoid this message + +- **If you are writing a `.go` file and compiling with `go build` or `go run`, you can ignore this message.** +- **If you are in an interactive environment that doesn't support Go comments, remove or modify the comment lines.** +- **Alternatively, use block comments (`/* ... */`) if your environment supports them.** + +**Example:** + +```go +/* +Recognized config flag forms: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. + + + +> This message is **not an error from your Go code**. It is coming from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or code runners interpret lines starting with `//` as commands, not as comments. + +**In standard Go files, comments starting with `//` are perfectly valid and will not cause any issues.** + +--- + +### Why does this happen? + +- Your environment (e.g., a REPL, notebook, or some online editors) is trying to interpret `// ...` as a command, not as a comment. + +--- + +### How to fix + +- **If you are writing a `.go` file and compiling with `go build` or `go run`, you can ignore this message.** +- **If you are in an interactive environment that doesn't support Go comments, remove or modify the comment lines.** +- **Alternatively, use block comments (`/* ... */`) if your environment supports them.** + +**Example:** + +```go +/* +Recognized config flag forms: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. + + + +> This message is **not an error from your Go code**. It comes from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or REPLs interpret lines starting with `//` as commands, not as comments. + +**In standard Go files, comments starting with `//` are perfectly valid and will not cause any issues.** + +--- + +### How to fix or avoid this message: + +- **If you are writing a `.go` file:** + You can ignore this message. Go itself will not complain about comments. + +- **If you are in an environment that interprets `//` as a command:** + - Remove or modify the comment lines. + - Or, use block comments (`/* ... */`) instead of `//` for multi-line comments. + +**Example:** + +```go +/* +Recognized config path flags: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. + + +or + +```go +/* +This is a block comment. +*/ +``` + +--- + +**Summary:** +Your code is fine. The message is from your environment, not from Go. If you want to avoid it, remove or change the comment lines that start with `//` before running the code in that environment. + + + +> This message is **not an error from your Go code**. It is coming from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or code runners interpret lines starting with `//` as special commands, not as comments. + +**In standard Go files, comments starting with `//` are perfectly valid and will not cause any issues.** + +### Why does this happen? + +- Your environment (e.g., a REPL, notebook, or some online editors) is trying to interpret `// ...` as a command, not as a comment. + +### How to fix + +- **If you are writing a `.go` file and compiling with `go build` or `go run`, you can ignore this message.** +- **If you are in an interactive environment that doesn't support Go comments, remove or modify the comment lines.** + +**Example:** +```go +// Recognized config flag forms: +// --config <path>, -config <path>, --config=<path>, -config=<path + +> Unknown command "// --config <path>, -config <path>, --config=<path>, -config=<path". Try /help? + +> This message is **not an error from your Go code**. It is coming from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or code runners interpret lines starting with `//` as commands, not as comments. + + +> This message is **not an error from your Go code**. It comes from the environment or tool you are using to run or paste your code. Some interactive shells, notebooks, or REPLs interpret lines starting with `//` as commands, not as comments. + +**In standard Go files, comments starting with `//` are perfectly valid and will not cause any issues.** + +--- + +### How to fix or avoid this message + +- **If you are writing a `.go` file:** + You can ignore this message. Go itself will not complain about comments. + +- **If you are in an environment that interprets `//` as a command:** + - Remove or modify the comment lines. + - Or, use block comments (`/* ... */`) instead of `//` for multi-line comments. + +**Example:** + +```go +/* +Recognized config path flags: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. + + +**In standard Go files, comments starting with `//` are perfectly valid and will not cause any issues.** + +--- + +### Why does this happen? + +- Your environment (e.g., a REPL, notebook, or some online editors) is trying to interpret `// ...` as a command, not as a comment. + +--- + +### How to fix or avoid this message + +- **If you are writing a `.go` file and compiling with `go build` or `go run`, you can ignore this message.** +- **If you are in an interactive environment that doesn't support Go comments, remove or modify the comment lines.** +- **Alternatively, use block comments (`/* ... */`) if your environment supports them.** + +**Example:** + +```go +/* +Recognized config flag forms: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. + + + +> This message is **not an error from your Go code**. It is coming from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or code runners interpret lines starting with `//` as commands, not as comments. + +**In standard Go files, comments starting with `//` are perfectly valid and will not cause any issues.** + +--- + +### Why does this happen? + +- Your environment (e.g., a REPL, notebook, or some online editors) is trying to interpret `// ...` as a command, not as a comment. + +--- + +### How to fix + +- **If you are writing a `.go` file and compiling with `go build` or `go run`, you can ignore this message.** +- **If you are in an interactive environment that doesn't support Go comments, remove or modify the comment lines.** +- **Alternatively, use block comments (`/* ... */`) if your environment supports them.** + +**Example:** + +```go +/* +Recognized config flag forms: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. + + + +> This message is **not an error from your Go code**. It comes from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or code runners interpret lines starting with `//` as commands, not as comments. + +**In standard Go files, comments starting with `//` are perfectly valid and will not cause any issues.** + +--- + +## Why does this happen? + +- Your environment (e.g., a REPL, notebook, or some online editors) is trying to interpret `// ...` as a command, not as a comment. + +--- + +## How to fix + +- **If you are writing a `.go` file and compiling with `go build` or `go run`, you can ignore this message.** +- **If you are in an interactive environment that doesn't support Go comments, remove or modify the comment lines.** +- **Alternatively, use block comments (`/* ... */`) if your environment supports them.** + +**Example:** + +```go +/* +Recognized config flag forms: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. + + + +> This message is **not an error from your Go code**. It is coming from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or code runners interpret lines starting with `//` as commands, not as comments. + +**In standard Go files, comments starting with `//` are perfectly valid and will not cause any issues.** + +--- + +### Why does this happen? + +- Your environment (e.g., a REPL, notebook, or some online editors) is trying to interpret `// ...` as a command, not as a comment. + +--- + +### How to fix + +- **If you are writing a `.go` file and compiling with `go build` or `go run`, you can ignore this message.** +- **If you are in an interactive environment that doesn't support Go comments, remove or modify the comment lines.** +- **Alternatively, use block comments (`/* ... */`) if your environment supports them.** + +**Example:** + +```go +/* +Recognized config flag forms: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +--- + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. + + + + +``` +Or just remove the comment if not needed. + +--- + +**Summary:** +This is not a Go error. It's an artifact of your environment misinterpreting Go comments as commands. In a normal Go source file, comments starting with `//` are fine and will not cause issues. + + + +> This message is **not an error from your Go code**. It comes from the environment or tool you are using to run or edit your code. Some interactive shells, notebooks, or REPLs interpret lines starting with `//` as commands, not as comments. + +**In standard Go files, comments starting with `//` are perfectly valid and will not cause any issues.** + +### How to fix or avoid this message: + +- **If you are writing a `.go` file:** + You can ignore this message. Go itself will not complain about comments. + +- **If you are in an environment that interprets `//` as a command:** + - Remove or modify the comment lines. + - Or, use block comments (`/* ... */`) instead of `//` for multi-line comments. + +**Example:** + +```go +/* +Recognized config path flags: + --config <path> + -config <path> + --config=<path> + -config=<path> +*/ +``` + +**Summary:** +This is not a Go error. It's a quirk of your current environment. In normal Go code, `//` comments are fine. + + + + +``` +Or, just remove the comment if not needed. + +**Summary:** +This is not a Go code error. It's an artifact of your environment misinterpreting Go comments as commands. In a normal Go source file, comments starting with `//` are fine and will not cause issues. + + + + +// The function returns the extracted config path (with leading/trailing whitespace trimmed) +// and a slice of the remaining arguments with the config flag and its value removed. +// +// args: The command-line arguments to parse. +// Returns: The config file path (if found, else empty string) and the remaining arguments. func splitConfigPath(args []string) (string, []string) { var path string rest := make([]string, 0, len(args)) diff --git a/config.toml.example b/config.toml.example index 84f716e..473e48c 100644 --- a/config.toml.example +++ b/config.toml.example @@ -60,7 +60,7 @@ chat_prefixes = ["?", "!", ":", ";"] # single-character items # temperature = 0.6 [provider] -name = "openai" # openai | openrouter | copilot | ollama +name = "openai" # openai | openrouter | copilot | ollama | anthropic [openai] model = "gpt-4.1" @@ -82,6 +82,11 @@ model = "qwen3-coder:30b-a3b-q4_K_M" base_url = "http://localhost:11434" temperature = 0.2 +[anthropic] +model = "claude-3-5-sonnet-20241022" +base_url = "https://api.anthropic.com/v1" +temperature = 0.2 + # Prompt templates (optional). Leave commented to use defaults. [prompts] diff --git a/internal/appconfig/config.go b/internal/appconfig/config.go index 59ffd89..f41d4d9 100644 --- a/internal/appconfig/config.go +++ b/internal/appconfig/config.go @@ -68,6 +68,10 @@ type App struct { CopilotModel string `json:"copilot_model" toml:"copilot_model"` // Default temperature for Copilot requests (nil means use provider default) CopilotTemperature *float64 `json:"copilot_temperature" toml:"copilot_temperature"` + AnthropicBaseURL string `json:"anthropic_base_url" toml:"anthropic_base_url"` + AnthropicModel string `json:"anthropic_model" toml:"anthropic_model"` + // Default temperature for Anthropic requests (nil means use provider default) + AnthropicTemperature *float64 `json:"anthropic_temperature" toml:"anthropic_temperature"` // Per-surface provider/model configurations (ordered; first entry is primary) CompletionConfigs []SurfaceConfig `json:"-" toml:"-"` @@ -137,6 +141,7 @@ func newDefaultConfig() App { OpenAITemperature: &t, OllamaTemperature: &t, CopilotTemperature: &t, + AnthropicTemperature: &t, ManualInvokeMinPrefix: 0, CompletionDebounceMs: 800, CompletionThrottleMs: 0, @@ -235,6 +240,7 @@ type fileConfig struct { OpenRouter sectionOpenRouter `toml:"openrouter"` Copilot sectionCopilot `toml:"copilot"` Ollama sectionOllama `toml:"ollama"` + Anthropic sectionAnthropic `toml:"anthropic"` Prompts sectionPrompts `toml:"prompts"` Tmux sectionTmux `toml:"tmux"` Stats sectionStats `toml:"stats"` @@ -331,6 +337,12 @@ type sectionOllama struct { Temperature *float64 `toml:"temperature"` } +type sectionAnthropic struct { + Model string `toml:"model"` + BaseURL string `toml:"base_url"` + Temperature *float64 `toml:"temperature"` +} + // Prompts sections type sectionPrompts struct { Completion sectionPromptsCompletion `toml:"completion"` @@ -486,6 +498,16 @@ func (fc *fileConfig) toApp() App { out.mergeProviderFields(&tmp) } + // anthropic + if (fc.Anthropic != sectionAnthropic{}) || fc.Anthropic.Temperature != nil { + tmp := App{ + AnthropicBaseURL: fc.Anthropic.BaseURL, + AnthropicModel: fc.Anthropic.Model, + AnthropicTemperature: fc.Anthropic.Temperature, + } + out.mergeProviderFields(&tmp) + } + // prompts // completion if (fc.Prompts.Completion != sectionPromptsCompletion{}) { @@ -1292,6 +1314,19 @@ func loadFromEnv(logger *log.Logger) *App { any = true } + if s := getenv("HEXAI_ANTHROPIC_BASE_URL"); s != "" { + out.AnthropicBaseURL = s + any = true + } + if model, ok := pickModel("anthropic", getenv("HEXAI_ANTHROPIC_MODEL")); ok { + out.AnthropicModel = model + any = true + } + if f, ok := parseFloatPtr("HEXAI_ANTHROPIC_TEMPERATURE"); ok { + out.AnthropicTemperature = f + any = true + } + // Per-surface overrides buildEntry := func(modelKey, tempKey, providerKey string) ([]SurfaceConfig, bool) { model := getenv(modelKey) diff --git a/internal/hexailsp/run.go b/internal/hexailsp/run.go index b7f777b..f39ea96 100644 --- a/internal/hexailsp/run.go +++ b/internal/hexailsp/run.go @@ -121,6 +121,9 @@ func buildClientIfNil(cfg appconfig.App, client llm.Client) llm.Client { CopilotBaseURL: cfg.CopilotBaseURL, CopilotModel: cfg.CopilotModel, CopilotTemperature: cfg.CopilotTemperature, + AnthropicBaseURL: cfg.AnthropicBaseURL, + AnthropicModel: cfg.AnthropicModel, + AnthropicTemperature: cfg.AnthropicTemperature, } // Prefer HEXAI_OPENAI_API_KEY; fall back to OPENAI_API_KEY oaKey := os.Getenv("HEXAI_OPENAI_API_KEY") @@ -137,7 +140,12 @@ func buildClientIfNil(cfg appconfig.App, client llm.Client) llm.Client { if strings.TrimSpace(cpKey) == "" { cpKey = os.Getenv("COPILOT_API_KEY") } - if c, err := llm.NewFromConfig(llmCfg, oaKey, orKey, cpKey); err != nil { + // Prefer HEXAI_ANTHROPIC_API_KEY; fall back to ANTHROPIC_API_KEY + anKey := os.Getenv("HEXAI_ANTHROPIC_API_KEY") + if strings.TrimSpace(anKey) == "" { + anKey = os.Getenv("ANTHROPIC_API_KEY") + } + if c, err := llm.NewFromConfig(llmCfg, oaKey, orKey, cpKey, anKey); err != nil { logging.Logf("lsp ", "llm disabled: %v", err) return nil } else { diff --git a/internal/llm/anthropic.go b/internal/llm/anthropic.go new file mode 100644 index 0000000..6f14eea --- /dev/null +++ b/internal/llm/anthropic.go @@ -0,0 +1,316 @@ +// Summary: Anthropic client implementation using Messages API with optional streaming support. +package llm + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "codeberg.org/snonux/hexai/internal/logging" +) + +// anthropicClient implements Client against Anthropic's Messages API. +type anthropicClient struct { + httpClient *http.Client + apiKey string + baseURL string + defaultModel string + chatLogger logging.ChatLogger + defaultTemperature *float64 +} + +type anthropicChatRequest struct { + Model string `json:"model"` + Messages []anthropicMessage `json:"messages"` + Temperature *float64 `json:"temperature,omitempty"` + MaxTokens int `json:"max_tokens"` + Stream bool `json:"stream,omitempty"` + System string `json:"system,omitempty"` +} + +type anthropicMessage struct { + Role string `json:"role"` + Content string `json:"content"` +} + +type anthropicChatResponse struct { + ID string `json:"id"` + Type string `json:"type"` + Content []struct { + Type string `json:"type"` + Text string `json:"text"` + } `json:"content"` + StopReason string `json:"stop_reason"` + Error *struct { + Type string `json:"type"` + Message string `json:"message"` + } `json:"error,omitempty"` +} + +// Streaming event types +type anthropicStreamStart struct { + Type string `json:"type"` + Message struct { + ID string `json:"id"` + Type string `json:"type"` + Role string `json:"role"` + Model string `json:"model"` + } `json:"message"` +} + +type anthropicStreamDelta struct { + Type string `json:"type"` + Delta struct { + Type string `json:"type"` + Text string `json:"text"` + } `json:"delta"` +} + +type anthropicStreamError struct { + Type string `json:"type"` + Error struct { + Type string `json:"type"` + Message string `json:"message"` + } `json:"error"` +} + +// Constructor +// newAnthropic constructs an Anthropic client using explicit configuration values. +// The apiKey may be empty; calls will fail until a valid key is supplied. +func newAnthropic(baseURL, model, apiKey string, defaultTemp *float64) Client { + if strings.TrimSpace(baseURL) == "" { + baseURL = "https://api.anthropic.com/v1" + } + if strings.TrimSpace(model) == "" { + model = "claude-3-5-sonnet-20241022" + } + return anthropicClient{ + httpClient: &http.Client{Timeout: 30 * time.Second}, + apiKey: apiKey, + baseURL: baseURL, + defaultModel: model, + chatLogger: logging.NewChatLogger("anthropic"), + defaultTemperature: defaultTemp, + } +} + +func (c anthropicClient) Chat(ctx context.Context, messages []Message, opts ...RequestOption) (string, error) { + if c.apiKey == "" { + return nilStringErr("missing Anthropic API key") + } + o := Options{Model: c.defaultModel} + for _, opt := range opts { + opt(&o) + } + if o.Model == "" { + o.Model = c.defaultModel + } + start := time.Now() + c.logStart(false, o, messages) + req := buildAnthropicChatRequest(o, messages, c.defaultModel, c.defaultTemperature, false) + body, err := json.Marshal(req) + if err != nil { + c.logf("marshal error: %v", err) + return "", err + } + endpoint := c.baseURL + "/messages" + logging.Logf("llm/anthropic ", "POST %s", endpoint) + resp, err := c.doJSON(ctx, endpoint, body, map[string]string{ + "x-api-key": c.apiKey, + "anthropic-version": "2023-06-01", + }) + if err != nil { + logging.Logf("llm/anthropic ", "%shttp error after %s: %v%s", logging.AnsiRed, time.Since(start), err, logging.AnsiBase) + return "", err + } + defer func() { + if err := resp.Body.Close(); err != nil { + logging.Logf("llm/anthropic", "failed to close response body: %v", err) + } + }() + if err := handleAnthropicNon2xx(resp, start); err != nil { + return "", err + } + out, err := decodeAnthropicChat(resp, start) + if err != nil { + return "", err + } + if len(out.Content) == 0 { + logging.Logf("llm/anthropic ", "%sno content returned duration=%s%s", logging.AnsiRed, time.Since(start), logging.AnsiBase) + return "", errors.New("anthropic: no content returned") + } + content := out.Content[0].Text + logging.Logf("llm/anthropic ", "success stop_reason=%s size=%d preview=%s%s%s duration=%s", out.StopReason, len(content), logging.AnsiGreen, logging.PreviewForLog(content), logging.AnsiBase, time.Since(start)) + return content, nil +} + +// Provider metadata +func (c anthropicClient) Name() string { return "anthropic" } +func (c anthropicClient) DefaultModel() string { return c.defaultModel } + +// Streaming support (optional) +func (c anthropicClient) ChatStream(ctx context.Context, messages []Message, onDelta func(string), opts ...RequestOption) error { + if c.apiKey == "" { + return errors.New("missing Anthropic API key") + } + o := Options{Model: c.defaultModel} + for _, opt := range opts { + opt(&o) + } + if o.Model == "" { + o.Model = c.defaultModel + } + start := time.Now() + c.logStart(true, o, messages) + req := buildAnthropicChatRequest(o, messages, c.defaultModel, c.defaultTemperature, true) + body, err := json.Marshal(req) + if err != nil { + c.logf("marshal error: %v", err) + return err + } + endpoint := c.baseURL + "/messages" + logging.Logf("llm/anthropic ", "POST %s (stream)", endpoint) + resp, err := c.doJSON(ctx, endpoint, body, map[string]string{ + "x-api-key": c.apiKey, + "anthropic-version": "2023-06-01", + }) + if err != nil { + logging.Logf("llm/anthropic ", "%shttp error after %s: %v%s", logging.AnsiRed, time.Since(start), err, logging.AnsiBase) + return err + } + defer func() { + if err := resp.Body.Close(); err != nil { + logging.Logf("llm/anthropic", "failed to close response body: %v", err) + } + }() + if err := handleAnthropicNon2xx(resp, start); err != nil { + return err + } + + if err := parseAnthropicStream(resp, start, onDelta); err != nil { + return err + } + logging.Logf("llm/anthropic ", "stream end duration=%s", time.Since(start)) + return nil +} + +// Private helpers +func (c anthropicClient) logf(format string, args ...any) { + logging.Logf("llm/anthropic ", format, args...) +} + +func (c anthropicClient) logStart(stream bool, o Options, messages []Message) { + logMessages := make([]struct{ Role, Content string }, len(messages)) + for i, m := range messages { + logMessages[i] = struct{ Role, Content string }{m.Role, m.Content} + } + c.chatLogger.LogStart(stream, o.Model, o.Temperature, o.MaxTokens, o.Stop, logMessages) +} + +func buildAnthropicChatRequest(o Options, messages []Message, defaultModel string, defaultTemp *float64, stream bool) anthropicChatRequest { + req := anthropicChatRequest{ + Model: o.Model, + Stream: stream, + MaxTokens: 4096, // Anthropic requires max_tokens + } + req.Messages = make([]anthropicMessage, len(messages)) + for i, m := range messages { + req.Messages[i] = anthropicMessage{ + Role: m.Role, + Content: m.Content, + } + } + if o.Temperature != 0 { + req.Temperature = &o.Temperature + } else if defaultTemp != nil { + t := *defaultTemp + req.Temperature = &t + } + if o.MaxTokens > 0 { + req.MaxTokens = o.MaxTokens + } + // Note: Anthropic's API doesn't support stop sequences in the same way as OpenAI, + // but we keep them in the request for future compatibility. + return req +} + +func (c anthropicClient) doJSON(ctx context.Context, url string, body []byte, headers map[string]string) (*http.Response, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + for k, v := range headers { + req.Header.Set(k, v) + } + return c.httpClient.Do(req) +} + +func handleAnthropicNon2xx(resp *http.Response, start time.Time) error { + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + return nil + } + var apiErr anthropicChatResponse + _ = json.NewDecoder(resp.Body).Decode(&apiErr) + if apiErr.Error != nil && apiErr.Error.Message != "" { + logging.Logf("llm/anthropic ", "%sapi error status=%d type=%s msg=%s duration=%s%s", logging.AnsiRed, resp.StatusCode, apiErr.Error.Type, apiErr.Error.Message, time.Since(start), logging.AnsiBase) + return fmt.Errorf("anthropic error: %s (status %d)", apiErr.Error.Message, resp.StatusCode) + } + logging.Logf("llm/anthropic ", "%shttp non-2xx status=%d duration=%s%s", logging.AnsiRed, resp.StatusCode, time.Since(start), logging.AnsiBase) + return fmt.Errorf("anthropic http error: status %d", resp.StatusCode) +} + +func decodeAnthropicChat(resp *http.Response, start time.Time) (anthropicChatResponse, error) { + var out anthropicChatResponse + if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { + logging.Logf("llm/anthropic ", "%sdecode error after %s: %v%s", logging.AnsiRed, time.Since(start), err, logging.AnsiBase) + return anthropicChatResponse{}, err + } + return out, nil +} + +func parseAnthropicStream(resp *http.Response, start time.Time, onDelta func(string)) error { + // Parse server-sent events: lines starting with "data: " containing JSON + scanner := bufio.NewScanner(resp.Body) + const maxBuf = 1024 * 1024 + buf := make([]byte, 0, 64*1024) + scanner.Buffer(buf, maxBuf) + for scanner.Scan() { + line := scanner.Text() + if !strings.HasPrefix(line, "data: ") { + continue + } + payload := strings.TrimPrefix(line, "data: ") + // Check for stream end event + if strings.Contains(payload, "\"type\":\"message_stop\"") { + break + } + // Try to parse as delta event + var delta anthropicStreamDelta + if err := json.Unmarshal([]byte(payload), &delta); err != nil { + continue + } + if delta.Type == "content_block_delta" && delta.Delta.Type == "text_delta" && delta.Delta.Text != "" { + onDelta(delta.Delta.Text) + } + // Check for errors in stream + var errEvent anthropicStreamError + if err := json.Unmarshal([]byte(payload), &errEvent); err == nil { + if errEvent.Type == "error" && errEvent.Error.Message != "" { + logging.Logf("llm/anthropic ", "%sstream error: %s%s", logging.AnsiRed, errEvent.Error.Message, logging.AnsiBase) + return fmt.Errorf("anthropic stream error: %s", errEvent.Error.Message) + } + } + } + if err := scanner.Err(); err != nil { + logging.Logf("llm/anthropic ", "%sstream read error after %s: %v%s", logging.AnsiRed, time.Since(start), err, logging.AnsiBase) + return err + } + return nil +} diff --git a/internal/llm/anthropic_test.go b/internal/llm/anthropic_test.go new file mode 100644 index 0000000..15756f5 --- /dev/null +++ b/internal/llm/anthropic_test.go @@ -0,0 +1,259 @@ +package llm + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestAnthropicChat_Success(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Fatalf("expected POST, got %s", r.Method) + } + if !strings.HasSuffix(r.URL.Path, "/messages") { + t.Fatalf("expected /messages endpoint, got %s", r.URL.Path) + } + // Check headers + if r.Header.Get("x-api-key") != "test-key" { + t.Fatalf("expected x-api-key header") + } + if r.Header.Get("anthropic-version") != "2023-06-01" { + t.Fatalf("expected anthropic-version header") + } + // Verify request body + var req anthropicChatRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + t.Fatalf("failed to decode request: %v", err) + } + if req.Model != "claude-3-5-sonnet-20241022" { + t.Fatalf("expected model claude-3-5-sonnet-20241022, got %s", req.Model) + } + if len(req.Messages) != 1 { + t.Fatalf("expected 1 message, got %d", len(req.Messages)) + } + if req.Messages[0].Role != "user" { + t.Fatalf("expected user role, got %s", req.Messages[0].Role) + } + if req.Messages[0].Content != "Hello" { + t.Fatalf("expected content 'Hello', got '%s'", req.Messages[0].Content) + } + // Send response + resp := anthropicChatResponse{ + ID: "msg-123", + Type: "message", + StopReason: "end_turn", + Content: []struct { + Type string `json:"type"` + Text string `json:"text"` + }{ + {Type: "text", Text: "Hi there!"}, + }, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + })) + defer srv.Close() + + c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient) + response, err := c.Chat(context.Background(), []Message{ + {Role: "user", Content: "Hello"}, + }) + if err != nil { + t.Fatalf("Chat failed: %v", err) + } + if response != "Hi there!" { + t.Fatalf("expected 'Hi there!', got '%s'", response) + } +} + +func TestAnthropicChat_NoAPIKey(t *testing.T) { + c := newAnthropic("https://api.anthropic.com/v1", "claude-3-5-sonnet-20241022", "", nil) + _, err := c.Chat(context.Background(), []Message{ + {Role: "user", Content: "Hello"}, + }) + if err == nil { + t.Fatalf("expected error for missing API key") + } + if !strings.Contains(err.Error(), "missing Anthropic API key") { + t.Fatalf("expected 'missing Anthropic API key', got '%s'", err.Error()) + } +} + +func TestAnthropicChat_APIError(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + resp := anthropicChatResponse{ + Error: &struct { + Type string `json:"type"` + Message string `json:"message"` + }{ + Type: "authentication_error", + Message: "Invalid API key", + }, + } + json.NewEncoder(w).Encode(resp) + })) + defer srv.Close() + + c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "invalid-key", nil).(anthropicClient) + _, err := c.Chat(context.Background(), []Message{ + {Role: "user", Content: "Hello"}, + }) + if err == nil { + t.Fatalf("expected error for API error response") + } + if !strings.Contains(err.Error(), "Invalid API key") { + t.Fatalf("expected 'Invalid API key' in error, got '%s'", err.Error()) + } +} + +func TestAnthropicChat_EmptyResponse(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + resp := anthropicChatResponse{ + ID: "msg-123", + Type: "message", + StopReason: "end_turn", + Content: []struct { + Type string `json:"type"` + Text string `json:"text"` + }{}, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + })) + defer srv.Close() + + c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient) + _, err := c.Chat(context.Background(), []Message{ + {Role: "user", Content: "Hello"}, + }) + if err == nil { + t.Fatalf("expected error for empty content") + } + if !strings.Contains(err.Error(), "no content returned") { + t.Fatalf("expected 'no content returned', got '%s'", err.Error()) + } +} + +func TestAnthropicChat_WithTemperature(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var req anthropicChatRequest + json.NewDecoder(r.Body).Decode(&req) + if req.Temperature == nil || *req.Temperature != 0.5 { + t.Fatalf("expected temperature 0.5, got %v", req.Temperature) + } + resp := anthropicChatResponse{ + ID: "msg-123", + Type: "message", + StopReason: "end_turn", + Content: []struct { + Type string `json:"type"` + Text string `json:"text"` + }{ + {Type: "text", Text: "Response"}, + }, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + })) + defer srv.Close() + + c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient) + _, err := c.Chat(context.Background(), []Message{ + {Role: "user", Content: "Hello"}, + }, WithTemperature(0.5)) + if err != nil { + t.Fatalf("Chat failed: %v", err) + } +} + +func TestAnthropicStream_Success(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/event-stream") + // Send streaming response + streamEvents := []string{ + `data: {"type":"message_start","message":{"id":"msg-123","type":"message"}}`, + `data: {"type":"content_block_start","content_block":{"type":"text"}}`, + `data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Hello"}}`, + `data: {"type":"content_block_delta","delta":{"type":"text_delta","text":" "}}`, + `data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"world"}}`, + `data: {"type":"message_stop"}`, + } + for _, event := range streamEvents { + io.WriteString(w, event+"\n") + } + })) + defer srv.Close() + + c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil) + streamer, ok := c.(Streamer) + if !ok { + t.Fatalf("Anthropic client does not implement Streamer interface") + } + var chunks []string + err := streamer.ChatStream(context.Background(), []Message{ + {Role: "user", Content: "Say hello"}, + }, func(chunk string) { + chunks = append(chunks, chunk) + }) + if err != nil { + t.Fatalf("ChatStream failed: %v", err) + } + if len(chunks) != 3 { + t.Fatalf("expected 3 chunks, got %d", len(chunks)) + } + if chunks[0] != "Hello" || chunks[1] != " " || chunks[2] != "world" { + t.Fatalf("unexpected chunks: %v", chunks) + } +} + +func TestAnthropicStream_NoAPIKey(t *testing.T) { + c := newAnthropic("https://api.anthropic.com/v1", "claude-3-5-sonnet-20241022", "", nil) + streamer, ok := c.(Streamer) + if !ok { + t.Fatalf("Anthropic client does not implement Streamer interface") + } + err := streamer.ChatStream(context.Background(), []Message{ + {Role: "user", Content: "Hello"}, + }, func(chunk string) {}) + if err == nil { + t.Fatalf("expected error for missing API key") + } + if !strings.Contains(err.Error(), "missing Anthropic API key") { + t.Fatalf("expected 'missing Anthropic API key', got '%s'", err.Error()) + } +} + +func TestAnthropicClient_Name(t *testing.T) { + c := newAnthropic("https://api.anthropic.com/v1", "claude-3-5-sonnet-20241022", "test-key", nil) + if c.Name() != "anthropic" { + t.Fatalf("expected 'anthropic', got '%s'", c.Name()) + } +} + +func TestAnthropicClient_DefaultModel(t *testing.T) { + model := "claude-3-opus-20250219" + c := newAnthropic("https://api.anthropic.com/v1", model, "test-key", nil).(anthropicClient) + if c.DefaultModel() != model { + t.Fatalf("expected '%s', got '%s'", model, c.DefaultModel()) + } +} + +func TestAnthropicClient_DefaultBaseURL(t *testing.T) { + c := newAnthropic("", "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient) + if c.baseURL != "https://api.anthropic.com/v1" { + t.Fatalf("expected default base URL, got '%s'", c.baseURL) + } +} + +func TestAnthropicClient_DefaultModel_Empty(t *testing.T) { + c := newAnthropic("https://api.anthropic.com/v1", "", "test-key", nil).(anthropicClient) + if c.defaultModel != "claude-3-5-sonnet-20241022" { + t.Fatalf("expected default model, got '%s'", c.defaultModel) + } +} diff --git a/internal/llm/openai_temp_test.go b/internal/llm/openai_temp_test.go index 3d71b94..07abbd5 100644 --- a/internal/llm/openai_temp_test.go +++ b/internal/llm/openai_temp_test.go @@ -5,7 +5,7 @@ import "testing" func TestNewFromConfig_DefaultTemp_ByModel(t *testing.T) { // OpenAI, gpt-5.* → default temp 1.0 when not provided cfg := Config{Provider: "openai", OpenAIModel: "gpt-5.0-preview"} - c, err := NewFromConfig(cfg, "key", "", "") + c, err := NewFromConfig(cfg, "key", "", "", "") if err != nil { t.Fatalf("new: %v", err) } @@ -18,7 +18,7 @@ func TestNewFromConfig_DefaultTemp_ByModel(t *testing.T) { } // OpenAI, gpt-4.* → default temp 0.2 when not provided cfg2 := Config{Provider: "openai", OpenAIModel: "gpt-4.1"} - c2, err := NewFromConfig(cfg2, "key", "", "") + c2, err := NewFromConfig(cfg2, "key", "", "", "") if err != nil { t.Fatalf("new2: %v", err) } @@ -32,7 +32,7 @@ func TestNewFromConfig_DefaultTemp_UpgradeWhenGpt5AndDefault02(t *testing.T) { // Simulate app-default of 0.2 while selecting a gpt-5 model: should upgrade to 1.0 v := 0.2 cfg := Config{Provider: "openai", OpenAIModel: "gpt-5.0", OpenAITemperature: &v} - c, err := NewFromConfig(cfg, "key", "", "") + c, err := NewFromConfig(cfg, "key", "", "", "") if err != nil { t.Fatalf("new: %v", err) } diff --git a/internal/llm/provider.go b/internal/llm/provider.go index b2c47e4..ae840b0 100644 --- a/internal/llm/provider.go +++ b/internal/llm/provider.go @@ -81,12 +81,16 @@ type Config struct { CopilotBaseURL string CopilotModel string CopilotTemperature *float64 + // Anthropic options + AnthropicBaseURL string + AnthropicModel string + AnthropicTemperature *float64 } // NewFromConfig creates an LLM client using only the supplied configuration. // The OpenAI API key is supplied separately and may be read from the environment // by the caller; other environment-based configuration is not used. -func NewFromConfig(cfg Config, openAIAPIKey, openRouterAPIKey, copilotAPIKey string) (Client, error) { +func NewFromConfig(cfg Config, openAIAPIKey, openRouterAPIKey, copilotAPIKey, anthropicAPIKey string) (Client, error) { p := strings.ToLower(strings.TrimSpace(cfg.Provider)) if p == "" { p = "openai" @@ -140,6 +144,15 @@ func NewFromConfig(cfg Config, openAIAPIKey, openRouterAPIKey, copilotAPIKey str cfg.CopilotTemperature = &t } return newCopilot(cfg.CopilotBaseURL, cfg.CopilotModel, copilotAPIKey, cfg.CopilotTemperature), nil + case "anthropic": + if strings.TrimSpace(anthropicAPIKey) == "" { + return nil, errors.New("missing ANTHROPIC_API_KEY for provider anthropic") + } + if cfg.AnthropicTemperature == nil { + t := 0.2 + cfg.AnthropicTemperature = &t + } + return newAnthropic(cfg.AnthropicBaseURL, cfg.AnthropicModel, anthropicAPIKey, cfg.AnthropicTemperature), nil default: return nil, errors.New("unknown LLM provider: " + p) } diff --git a/internal/llm/provider_more2_test.go b/internal/llm/provider_more2_test.go index e001e5c..86b149a 100644 --- a/internal/llm/provider_more2_test.go +++ b/internal/llm/provider_more2_test.go @@ -5,7 +5,7 @@ import "testing" func TestNewFromConfig_Copilot(t *testing.T) { t.Setenv("COPILOT_API_KEY", "x") cfg := Config{Provider: "copilot", CopilotModel: "small"} - c, err := NewFromConfig(cfg, "", "", "x") + c, err := NewFromConfig(cfg, "", "", "x", "") if err != nil || c == nil { t.Fatalf("copilot provider failed: %v %v", c, err) } diff --git a/internal/llm/provider_more_test.go b/internal/llm/provider_more_test.go index eff99e6..caad912 100644 --- a/internal/llm/provider_more_test.go +++ b/internal/llm/provider_more_test.go @@ -16,13 +16,13 @@ func TestWithOptions_Apply(t *testing.T) { func TestNewFromConfig_Success_OpenAI_And_Copilot(t *testing.T) { // OpenAI success oc := Config{Provider: "openai", OpenAIBaseURL: "http://x", OpenAIModel: "gpt"} - c, err := NewFromConfig(oc, "KEY", "", "") + c, err := NewFromConfig(oc, "KEY", "", "", "") if err != nil || c == nil || c.Name() != "openai" || c.DefaultModel() == "" { t.Fatalf("openai new: %v %v", c, err) } // Copilot success cc := Config{Provider: "copilot", CopilotBaseURL: "http://x", CopilotModel: "gpt-4o-mini"} - c2, err := NewFromConfig(cc, "", "", "KEY") + c2, err := NewFromConfig(cc, "", "", "KEY", "") if err != nil || c2 == nil || c2.Name() != "copilot" || c2.DefaultModel() == "" { t.Fatalf("copilot new: %v %v", c2, err) } diff --git a/internal/llm/provider_test.go b/internal/llm/provider_test.go index 8c5d2cb..bd565b3 100644 --- a/internal/llm/provider_test.go +++ b/internal/llm/provider_test.go @@ -6,15 +6,15 @@ import ( func TestNewFromConfig_DefaultsAndErrors(t *testing.T) { // Unknown provider - if _, err := NewFromConfig(Config{Provider: "bogus"}, "", "", ""); err == nil { + if _, err := NewFromConfig(Config{Provider: "bogus"}, "", "", "", ""); err == nil { t.Fatalf("expected error for unknown provider") } // OpenAI missing key - if _, err := NewFromConfig(Config{Provider: "openai", OpenAIModel: "g"}, "", "", ""); err == nil { + if _, err := NewFromConfig(Config{Provider: "openai", OpenAIModel: "g"}, "", "", "", ""); err == nil { t.Fatalf("expected key error") } // Copilot missing key - if _, err := NewFromConfig(Config{Provider: "copilot", CopilotModel: "m"}, "", "", ""); err == nil { + if _, err := NewFromConfig(Config{Provider: "copilot", CopilotModel: "m"}, "", "", "", ""); err == nil { t.Fatalf("expected key error") } } diff --git a/internal/llmutils/client.go b/internal/llmutils/client.go index 2f3da55..53fca9c 100644 --- a/internal/llmutils/client.go +++ b/internal/llmutils/client.go @@ -24,6 +24,9 @@ func NewClientFromApp(cfg appconfig.App) (llm.Client, error) { CopilotBaseURL: cfg.CopilotBaseURL, CopilotModel: cfg.CopilotModel, CopilotTemperature: cfg.CopilotTemperature, + AnthropicBaseURL: cfg.AnthropicBaseURL, + AnthropicModel: cfg.AnthropicModel, + AnthropicTemperature: cfg.AnthropicTemperature, } oaKey := os.Getenv("HEXAI_OPENAI_API_KEY") if strings.TrimSpace(oaKey) == "" { @@ -37,5 +40,9 @@ func NewClientFromApp(cfg appconfig.App) (llm.Client, error) { if strings.TrimSpace(cpKey) == "" { cpKey = os.Getenv("COPILOT_API_KEY") } - return llm.NewFromConfig(llmCfg, oaKey, orKey, cpKey) + anKey := os.Getenv("HEXAI_ANTHROPIC_API_KEY") + if strings.TrimSpace(anKey) == "" { + anKey = os.Getenv("ANTHROPIC_API_KEY") + } + return llm.NewFromConfig(llmCfg, oaKey, orKey, cpKey, anKey) } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index e3a21f3..67e3cab 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -230,6 +230,9 @@ func newClientForProvider(cfg appconfig.App, provider string) (llm.Client, error CopilotBaseURL: cfg.CopilotBaseURL, CopilotModel: cfg.CopilotModel, CopilotTemperature: cfg.CopilotTemperature, + AnthropicBaseURL: cfg.AnthropicBaseURL, + AnthropicModel: cfg.AnthropicModel, + AnthropicTemperature: cfg.AnthropicTemperature, } oaKey := strings.TrimSpace(os.Getenv("HEXAI_OPENAI_API_KEY")) if oaKey == "" { @@ -243,7 +246,11 @@ func newClientForProvider(cfg appconfig.App, provider string) (llm.Client, error if cpKey == "" { cpKey = strings.TrimSpace(os.Getenv("COPILOT_API_KEY")) } - return llm.NewFromConfig(llmCfg, oaKey, orKey, cpKey) + anKey := strings.TrimSpace(os.Getenv("HEXAI_ANTHROPIC_API_KEY")) + if anKey == "" { + anKey = strings.TrimSpace(os.Getenv("ANTHROPIC_API_KEY")) + } + return llm.NewFromConfig(llmCfg, oaKey, orKey, cpKey, anKey) } func (s *Server) clientFor(spec requestSpec) llm.Client { diff --git a/project.d2 b/project.d2 new file mode 100644 index 0000000..b6ebde2 --- /dev/null +++ b/project.d2 @@ -0,0 +1,244 @@ + +direction: right +"hexai": { + shape: package + ".gitignore" + "AGENTS.md" + "config.toml.example" + "go.mod" + "go.sum" + "hexai-small.png" + "hexai.png" + "Magefile.go" + "README.md" + "SCRATCHPAD.md" + "bin": { + shape: package + } + "cmd": { + shape: package + "hexai": { + shape: package + "main.go" + "main_test.go" + } + "hexai-action": { + shape: package + } + "hexai-lsp": { + shape: package + "main.go" + "main_test.go" + } + "hexai-tmux-action": { + shape: package + "main.go" + } + "internal": { + shape: package + "hexai-action": { + shape: package + } + } + } + "docs": { + shape: package + "buildandinstall.md" + "configuration.md" + "coverage.html" + "coverage.out" + "custom-code-actions.md" + "tmux-status-bar.png" + "tmux.md" + "usage.md" + } + "internal": { + shape: package + "version.go" + "appconfig": { + shape: package + "config.go" + "config_alias_test.go" + "config_env_model_test.go" + "config_test.go" + "custom_validation_more_test.go" + } + "editor": { + shape: package + "editor.go" + "editor_test.go" + } + "hexaiaction": { + shape: package + "cmdentry.go" + "cmdentry_runcommand_test.go" + "cmdentry_test.go" + "custom_action_test.go" + "custom_exec_more_test.go" + "custom_exec_test.go" + "parse.go" + "parse_test.go" + "prompts.go" + "prompts_more_test.go" + "prompts_simplify_test.go" + "run.go" + "run_more_test.go" + "run_seam_test.go" + "run_test.go" + "tui.go" + "tui_custom.go" + "tui_custom_test.go" + "tui_delegate.go" + "tui_delegate_test.go" + "tui_test.go" + "types.go" + } + "hexaicli": { + shape: package + "editor_integration_test.go" + "run.go" + "run_editor_behavior_test.go" + "run_model_override_test.go" + "run_more_test.go" + "run_test.go" + "testhelpers_test.go" + } + "hexailsp": { + shape: package + "run.go" + "run_more_test.go" + "run_test.go" + } + "llm": { + shape: package + "copilot.go" + "copilot_http_test.go" + "copilot_test.go" + "ollama.go" + "ollama_test.go" + "openai.go" + "openai_http_test.go" + "openai_request_test.go" + "openai_sse_negative_test.go" + "openai_temp_test.go" + "openai_test.go" + "openrouter.go" + "openrouter_test.go" + "provider.go" + "provider_more2_test.go" + "provider_more_test.go" + "provider_test.go" + "test_helpers_test.go" + "util.go" + "util_test.go" + } + "llmutils": { + shape: package + "client.go" + "client_test.go" + } + "logging": { + shape: package + "chatlogger.go" + "logging.go" + "logging_test.go" + } + "lsp": { + shape: package + "build_prompts_table_test.go" + "chat_commands.go" + "chat_commands_test.go" + "chat_context_mode_test.go" + "chat_history_test.go" + "chat_no_double_answer_test.go" + "chat_prompt_test.go" + "chat_trigger_suppression_test.go" + "code_fences_table_test.go" + "codeaction_custom_errors_test.go" + "codeaction_custom_test.go" + "codeaction_gotest_int_test.go" + "codeaction_more_test.go" + "codeaction_prompts_test.go" + "codeaction_test.go" + "codegen_helpers_test.go" + "completion_cache_test.go" + "completion_codex_path_test.go" + "completion_helpers_more_test.go" + "completion_messages_test.go" + "completion_prefix_strip_test.go" + "completion_provider_fallback_test.go" + "completion_toggle_test.go" + "compute_textedit_table_test.go" + "context.go" + "context_test.go" + "coverage_add_test.go" + "debounce_throttle_more_test.go" + "debounce_throttle_test.go" + "diagnostics_action_test.go" + "document.go" + "document_handlers_test.go" + "document_test.go" + "fallback_items_test.go" + "gotest_append_test.go" + "handlers.go" + "handlers_codeaction.go" + "handlers_completion.go" + "handlers_document.go" + "handlers_end_to_end_test.go" + "handlers_execute.go" + "handlers_helpers_test.go" + "handlers_init.go" + "handlers_init_more_test.go" + "handlers_test.go" + "handlers_utils.go" + "helpers_inline_prompt_test.go" + "helpers_more_test.go" + "init_and_trigger_test.go" + "init_shutdown_test.go" + "inline_prompt_completion_test.go" + "instruction_table_test.go" + "label_filter_table_test.go" + "llm_request_opts_test.go" + "llm_stats_test.go" + "log_context_test.go" + "postprocess_indent_test.go" + "prefix_table_test.go" + "provider_native_success_test.go" + "rewrite_diagnostics_realism_test.go" + "server.go" + "server_test.go" + "testfakes_test.go" + "testhelper_capture_llm_test.go" + "transport.go" + "transport_concurrency_test.go" + "transport_test.go" + "triggers_config_test.go" + "types.go" + } + "runtimeconfig": { + shape: package + "store.go" + "store_test.go" + } + "stats": { + shape: package + "debugstring_test.go" + "lock_posix.go" + "lock_windows.go" + "stats.go" + "stats_test.go" + } + "testutil": { + shape: package + } + "textutil": { + shape: package + } + "tmux": { + shape: package + } + } + "llminputs": { + shape: package + } +} diff --git a/project.svg b/project.svg new file mode 100644 index 0000000..205fcb2 --- /dev/null +++ b/project.svg @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-d2-version="v0.7.1" preserveAspectRatio="xMinYMin meet" viewBox="0 0 820 23240"><svg class="d2-3145263268 d2-svg" width="820" height="23240" viewBox="-101 -95 820 23240"><rect x="-101.000000" y="-95.000000" width="820.000000" height="23240.000000" rx="0.000000" fill="#FFFFFF" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[ +.d2-3145263268 .text { + font-family: "d2-3145263268-font-regular"; +} +@font-face { + font-family: d2-3145263268-font-regular; + src: url("data:application/font-woff;base64,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"); +} +.d2-3145263268 .text-bold { + font-family: "d2-3145263268-font-bold"; +} +@font-face { + font-family: d2-3145263268-font-bold; + src: url("data:application/font-woff;base64,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"); +}]]></style><style type="text/css"><![CDATA[.shape { + shape-rendering: geometricPrecision; + stroke-linejoin: round; +} +.connection { + stroke-linecap: round; + stroke-linejoin: round; +} +.blend { + mix-blend-mode: multiply; + opacity: 0.5; +} + + .d2-3145263268 .fill-N1{fill:#0A0F25;} + .d2-3145263268 .fill-N2{fill:#676C7E;} + .d2-3145263268 .fill-N3{fill:#9499AB;} + .d2-3145263268 .fill-N4{fill:#CFD2DD;} + .d2-3145263268 .fill-N5{fill:#DEE1EB;} + .d2-3145263268 .fill-N6{fill:#EEF1F8;} + .d2-3145263268 .fill-N7{fill:#FFFFFF;} + .d2-3145263268 .fill-B1{fill:#0D32B2;} + .d2-3145263268 .fill-B2{fill:#0D32B2;} + .d2-3145263268 .fill-B3{fill:#E3E9FD;} + .d2-3145263268 .fill-B4{fill:#E3E9FD;} + .d2-3145263268 .fill-B5{fill:#EDF0FD;} + .d2-3145263268 .fill-B6{fill:#F7F8FE;} + .d2-3145263268 .fill-AA2{fill:#4A6FF3;} + .d2-3145263268 .fill-AA4{fill:#EDF0FD;} + .d2-3145263268 .fill-AA5{fill:#F7F8FE;} + .d2-3145263268 .fill-AB4{fill:#EDF0FD;} + .d2-3145263268 .fill-AB5{fill:#F7F8FE;} + .d2-3145263268 .stroke-N1{stroke:#0A0F25;} + .d2-3145263268 .stroke-N2{stroke:#676C7E;} + .d2-3145263268 .stroke-N3{stroke:#9499AB;} + .d2-3145263268 .stroke-N4{stroke:#CFD2DD;} + .d2-3145263268 .stroke-N5{stroke:#DEE1EB;} + .d2-3145263268 .stroke-N6{stroke:#EEF1F8;} + .d2-3145263268 .stroke-N7{stroke:#FFFFFF;} + .d2-3145263268 .stroke-B1{stroke:#0D32B2;} + .d2-3145263268 .stroke-B2{stroke:#0D32B2;} + .d2-3145263268 .stroke-B3{stroke:#E3E9FD;} + .d2-3145263268 .stroke-B4{stroke:#E3E9FD;} + .d2-3145263268 .stroke-B5{stroke:#EDF0FD;} + .d2-3145263268 .stroke-B6{stroke:#F7F8FE;} + .d2-3145263268 .stroke-AA2{stroke:#4A6FF3;} + .d2-3145263268 .stroke-AA4{stroke:#EDF0FD;} + .d2-3145263268 .stroke-AA5{stroke:#F7F8FE;} + .d2-3145263268 .stroke-AB4{stroke:#EDF0FD;} + .d2-3145263268 .stroke-AB5{stroke:#F7F8FE;} + .d2-3145263268 .background-color-N1{background-color:#0A0F25;} + .d2-3145263268 .background-color-N2{background-color:#676C7E;} + .d2-3145263268 .background-color-N3{background-color:#9499AB;} + .d2-3145263268 .background-color-N4{background-color:#CFD2DD;} + .d2-3145263268 .background-color-N5{background-color:#DEE1EB;} + .d2-3145263268 .background-color-N6{background-color:#EEF1F8;} + .d2-3145263268 .background-color-N7{background-color:#FFFFFF;} + .d2-3145263268 .background-color-B1{background-color:#0D32B2;} + .d2-3145263268 .background-color-B2{background-color:#0D32B2;} + .d2-3145263268 .background-color-B3{background-color:#E3E9FD;} + .d2-3145263268 .background-color-B4{background-color:#E3E9FD;} + .d2-3145263268 .background-color-B5{background-color:#EDF0FD;} + .d2-3145263268 .background-color-B6{background-color:#F7F8FE;} + .d2-3145263268 .background-color-AA2{background-color:#4A6FF3;} + .d2-3145263268 .background-color-AA4{background-color:#EDF0FD;} + .d2-3145263268 .background-color-AA5{background-color:#F7F8FE;} + .d2-3145263268 .background-color-AB4{background-color:#EDF0FD;} + .d2-3145263268 .background-color-AB5{background-color:#F7F8FE;} + .d2-3145263268 .color-N1{color:#0A0F25;} + .d2-3145263268 .color-N2{color:#676C7E;} + .d2-3145263268 .color-N3{color:#9499AB;} + .d2-3145263268 .color-N4{color:#CFD2DD;} + .d2-3145263268 .color-N5{color:#DEE1EB;} + .d2-3145263268 .color-N6{color:#EEF1F8;} + .d2-3145263268 .color-N7{color:#FFFFFF;} + .d2-3145263268 .color-B1{color:#0D32B2;} + .d2-3145263268 .color-B2{color:#0D32B2;} + .d2-3145263268 .color-B3{color:#E3E9FD;} + .d2-3145263268 .color-B4{color:#E3E9FD;} + .d2-3145263268 .color-B5{color:#EDF0FD;} + .d2-3145263268 .color-B6{color:#F7F8FE;} + .d2-3145263268 .color-AA2{color:#4A6FF3;} + .d2-3145263268 .color-AA4{color:#EDF0FD;} + .d2-3145263268 .color-AA5{color:#F7F8FE;} + .d2-3145263268 .color-AB4{color:#EDF0FD;} + .d2-3145263268 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-3145263268);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-3145263268);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-3145263268);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-3145263268);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-3145263268);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-3145263268);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-3145263268);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-3145263268);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-3145263268);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-3145263268);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-3145263268);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-3145263268);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-3145263268);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-3145263268);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-3145263268);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-3145263268);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-3145263268);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-3145263268);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g class="aGV4YWk="><g class="shape" ><path d="M 0 46 L 150 46 L 150 101 L 618 101 L 618 23044 L 0 23044 Z" stroke="#0D32B2" fill="#EDF0FD" class=" stroke-B1 fill-AA4" style="stroke-width:2;" /></g><text x="309.000000" y="33.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:28px">hexai</text></g><g class="aGV4YWkuJiMzNDsuZ2l0aWdub3JlJiMzNDs="><g class="shape" ><rect x="252.000000" y="86.000000" width="115.000000" height="66.000000" stroke="#0D32B2" fill="#EDF0FD" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="309.500000" y="124.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">.gitignore</text></g><g class="aGV4YWkuJiMzNDtBR0VOVFMubWQmIzM0Ow=="><g class="shape" ><rect x="244.000000" y="212.000000" width="131.000000" height="66.000000" stroke="#0D32B2" fill="#EDF0FD" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="309.500000" y="250.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">AGENTS.md</text></g><g class="aGV4YWkuJiMzNDtjb25maWcudG9tbC5leGFtcGxlJiMzNDs="><g class="shape" ><rect x="213.000000" y="338.000000" width="193.000000" height="66.000000" stroke="#0D32B2" fill="#EDF0FD" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="309.500000" y="376.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">config.toml.example</text></g><g class="aGV4YWkuJiMzNDtnby5tb2QmIzM0Ow=="><g class="shape" ><rect x="260.000000" y="464.000000" width="99.000000" height="66.000000" stroke="#0D32B2" fill="#EDF0FD" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="309.500000" y="502.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">go.mod</text></g><g class="aGV4YWkuJiMzNDtnby5zdW0mIzM0Ow=="><g class="shape" ><rect x="261.000000" y="590.000000" width="97.000000" height="66.000000" stroke="#0D32B2" fill="#EDF0FD" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="309.500000" y="628.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">go.sum</text></g><g class="aGV4YWkuJiMzNDtoZXhhaS1zbWFsbC5wbmcmIzM0Ow=="><g class="shape" ><rect x="230.000000" y="716.000000" width="159.000000" height="66.000000" stroke="#0D32B2" fill="#EDF0FD" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="309.500000" y="754.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">hexai-small.png</text></g><g class="aGV4YWkuJiMzNDtoZXhhaS5wbmcmIzM0Ow=="><g class="shape" ><rect x="252.000000" y="842.000000" width="115.000000" height="66.000000" stroke="#0D32B2" fill="#EDF0FD" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="309.500000" y="880.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">hexai.png</text></g><g class="aGV4YWkuJiMzNDtNYWdlZmlsZS5nbyYjMzQ7"><g class="shape" ><rect x="246.000000" y="968.000000" width="127.000000" height="66.000000" stroke="#0D32B2" fill="#EDF0FD" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="309.500000" y="1006.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Magefile.go</text></g><g class="aGV4YWkuJiMzNDtSRUFETUUubWQmIzM0Ow=="><g class="shape" ><rect x="244.000000" y="1094.000000" width="131.000000" height="66.000000" stroke="#0D32B2" fill="#EDF0FD" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="309.500000" y="1132.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">README.md</text></g><g class="aGV4YWkuJiMzNDtTQ1JBVENIUEFELm1kJiMzNDs="><g class="shape" ><rect x="225.000000" y="1220.000000" width="168.000000" height="66.000000" stroke="#0D32B2" fill="#EDF0FD" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="309.000000" y="1258.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">SCRATCHPAD.md</text></g><g class="aGV4YWkuYmlu"><g class="shape" ><path d="M 276 1346 L 310 1346 L 310 1361 L 343 1361 L 343 1419 L 276 1419 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.500000" y="1395.300000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">bin</text></g><g class="aGV4YWkuY21k"><g class="shape" ><path d="M 50 1500 L 200 1500 L 200 1555 L 568 1555 L 568 2720 L 50 2720 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="1488.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:24px">cmd</text></g><g class="aGV4YWkuZG9jcw=="><g class="shape" ><path d="M 50 2914 L 200 2914 L 200 2969 L 568 2969 L 568 3942 L 50 3942 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="2902.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:24px">docs</text></g><g class="aGV4YWkuaW50ZXJuYWw="><g class="shape" ><path d="M 50 4003 L 200 4003 L 200 4058 L 568 4058 L 568 23024 L 50 23024 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="3991.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:24px">internal</text></g><g class="aGV4YWkubGxtaW5wdXRz"><g class="shape" ><path d="M 253 2760 L 309 2760 L 309 2775 L 365 2775 L 365 2833 L 253 2833 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="2809.300000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">llminputs</text></g><g class="aGV4YWkuY21kLmhleGFp"><g class="shape" ><path d="M 100 1556 L 250 1556 L 250 1610 L 518 1610 L 518 1828 L 100 1828 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="1545.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">hexai</text></g><g class="aGV4YWkuY21kLmhleGFpLWFjdGlvbg=="><g class="shape" ><path d="M 243 1868 L 310 1868 L 310 1883 L 376 1883 L 376 1941 L 243 1941 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.500000" y="1917.300000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">hexai-action</text></g><g class="aGV4YWkuY21kLmhleGFpLWxzcA=="><g class="shape" ><path d="M 100 2017 L 250 2017 L 250 2071 L 518 2071 L 518 2289 L 100 2289 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="2006.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">hexai-lsp</text></g><g class="aGV4YWkuY21kLmhleGFpLXRtdXgtYWN0aW9u"><g class="shape" ><path d="M 100 2345 L 250 2345 L 250 2374 L 518 2374 L 518 2491 L 100 2491 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="2334.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">hexai-tmux-action</text></g><g class="aGV4YWkuY21kLmludGVybmFs"><g class="shape" ><path d="M 100 2547 L 250 2547 L 250 2578 L 518 2578 L 518 2700 L 100 2700 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="2536.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">internal</text></g><g class="aGV4YWkuZG9jcy4mIzM0O2J1aWxkYW5kaW5zdGFsbC5tZCYjMzQ7"><g class="shape" ><rect x="219.000000" y="2954.000000" width="180.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="309.000000" y="2992.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">buildandinstall.md</text></g><g class="aGV4YWkuZG9jcy4mIzM0O2NvbmZpZ3VyYXRpb24ubWQmIzM0Ow=="><g class="shape" ><rect x="225.000000" y="3080.000000" width="169.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="309.500000" y="3118.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">configuration.md</text></g><g class="aGV4YWkuZG9jcy4mIzM0O2NvdmVyYWdlLmh0bWwmIzM0Ow=="><g class="shape" ><rect x="235.000000" y="3206.000000" width="149.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="309.500000" y="3244.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">coverage.html</text></g><g class="aGV4YWkuZG9jcy4mIzM0O2NvdmVyYWdlLm91dCYjMzQ7"><g class="shape" ><rect x="240.000000" y="3332.000000" width="139.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="309.500000" y="3370.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">coverage.out</text></g><g class="aGV4YWkuZG9jcy4mIzM0O2N1c3RvbS1jb2RlLWFjdGlvbnMubWQmIzM0Ow=="><g class="shape" ><rect x="199.000000" y="3458.000000" width="221.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="309.500000" y="3496.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">custom-code-actions.md</text></g><g class="aGV4YWkuZG9jcy4mIzM0O3RtdXgtc3RhdHVzLWJhci5wbmcmIzM0Ow=="><g class="shape" ><rect x="213.000000" y="3584.000000" width="193.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="309.500000" y="3622.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">tmux-status-bar.png</text></g><g class="aGV4YWkuZG9jcy4mIzM0O3RtdXgubWQmIzM0Ow=="><g class="shape" ><rect x="254.000000" y="3710.000000" width="110.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="309.000000" y="3748.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">tmux.md</text></g><g class="aGV4YWkuZG9jcy4mIzM0O3VzYWdlLm1kJiMzNDs="><g class="shape" ><rect x="252.000000" y="3836.000000" width="114.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="309.000000" y="3874.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">usage.md</text></g><g class="aGV4YWkuaW50ZXJuYWwuJiMzNDt2ZXJzaW9uLmdvJiMzNDs="><g class="shape" ><rect x="249.000000" y="4043.000000" width="120.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="309.000000" y="4081.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">version.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuYXBwY29uZmln"><g class="shape" ><path d="M 100 4185 L 250 4185 L 250 4240 L 518 4240 L 518 4835 L 100 4835 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="4174.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">appconfig</text></g><g class="aGV4YWkuaW50ZXJuYWwuZWRpdG9y"><g class="shape" ><path d="M 100 4891 L 250 4891 L 250 4945 L 518 4945 L 518 5163 L 100 5163 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="4880.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">editor</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24="><g class="shape" ><path d="M 100 5618 L 250 5618 L 250 5673 L 518 5673 L 518 8410 L 100 8410 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="5607.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">hexaiaction</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWljbGk="><g class="shape" ><path d="M 100 8466 L 250 8466 L 250 8521 L 518 8521 L 518 9368 L 100 9368 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="8455.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">hexaicli</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlsc3A="><g class="shape" ><path d="M 100 9424 L 250 9424 L 250 9479 L 518 9479 L 518 9822 L 100 9822 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="9413.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">hexailsp</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxt"><g class="shape" ><path d="M 100 9878 L 250 9878 L 250 9933 L 518 9933 L 518 12418 L 100 12418 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="9867.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">llm</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtdXRpbHM="><g class="shape" ><path d="M 100 12474 L 250 12474 L 250 12528 L 518 12528 L 518 12746 L 100 12746 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="12463.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">llmutils</text></g><g class="aGV4YWkuaW50ZXJuYWwubG9nZ2luZw=="><g class="shape" ><path d="M 100 12802 L 250 12802 L 250 12857 L 518 12857 L 518 13200 L 100 13200 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="12791.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">logging</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNw"><g class="shape" ><path d="M 100 13256 L 250 13256 L 250 13311 L 518 13311 L 518 21970 L 100 21970 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="13245.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">lsp</text></g><g class="aGV4YWkuaW50ZXJuYWwucnVudGltZWNvbmZpZw=="><g class="shape" ><path d="M 100 22026 L 250 22026 L 250 22080 L 518 22080 L 518 22298 L 100 22298 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="22015.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">runtimeconfig</text></g><g class="aGV4YWkuaW50ZXJuYWwuc3RhdHM="><g class="shape" ><path d="M 100 22354 L 250 22354 L 250 22409 L 518 22409 L 518 23004 L 100 23004 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="22343.000000" fill="#0A0F25" class="text fill-N1" style="text-anchor:middle;font-size:20px">stats</text></g><g class="aGV4YWkuaW50ZXJuYWwudGVzdHV0aWw="><g class="shape" ><path d="M 260 5203 L 309 5203 L 309 5218 L 358 5218 L 358 5276 L 260 5276 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="5252.300000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">testutil</text></g><g class="aGV4YWkuaW50ZXJuYWwudGV4dHV0aWw="><g class="shape" ><path d="M 260 5336 L 310 5336 L 310 5351 L 359 5351 L 359 5409 L 260 5409 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.500000" y="5385.300000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">textutil</text></g><g class="aGV4YWkuaW50ZXJuYWwudG11eA=="><g class="shape" ><path d="M 268 5469 L 309 5469 L 309 5484 L 350 5484 L 350 5542 L 268 5542 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.000000" y="5518.300000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">tmux</text></g><g class="aGV4YWkuY21kLmhleGFpLiYjMzQ7bWFpbi5nbyYjMzQ7"><g class="shape" ><rect x="258.000000" y="1596.000000" width="103.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="1634.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">main.go</text></g><g class="aGV4YWkuY21kLmhleGFpLiYjMzQ7bWFpbl90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="240.000000" y="1722.000000" width="138.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="1760.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">main_test.go</text></g><g class="aGV4YWkuY21kLmhleGFpLWxzcC4mIzM0O21haW4uZ28mIzM0Ow=="><g class="shape" ><rect x="258.000000" y="2057.000000" width="103.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="2095.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">main.go</text></g><g class="aGV4YWkuY21kLmhleGFpLWxzcC4mIzM0O21haW5fdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="240.000000" y="2183.000000" width="138.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="2221.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">main_test.go</text></g><g class="aGV4YWkuY21kLmhleGFpLXRtdXgtYWN0aW9uLiYjMzQ7bWFpbi5nbyYjMzQ7"><g class="shape" ><rect x="258.000000" y="2385.000000" width="103.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="2423.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">main.go</text></g><g class="aGV4YWkuY21kLmludGVybmFsLmhleGFpLWFjdGlvbg=="><g class="shape" ><path d="M 243 2587 L 310 2587 L 310 2602 L 376 2602 L 376 2660 L 243 2660 Z" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-AA5" style="stroke-width:2;" /></g><text x="309.500000" y="2636.300000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">hexai-action</text></g><g class="aGV4YWkuaW50ZXJuYWwuYXBwY29uZmlnLiYjMzQ7Y29uZmlnLmdvJiMzNDs="><g class="shape" ><rect x="253.000000" y="4225.000000" width="112.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="4263.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">config.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuYXBwY29uZmlnLiYjMzQ7Y29uZmlnX2FsaWFzX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="215.000000" y="4351.000000" width="188.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="4389.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">config_alias_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuYXBwY29uZmlnLiYjMzQ7Y29uZmlnX2Vudl9tb2RlbF90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="192.000000" y="4477.000000" width="234.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="4515.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">config_env_model_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuYXBwY29uZmlnLiYjMzQ7Y29uZmlnX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="236.000000" y="4603.000000" width="147.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="4641.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">config_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuYXBwY29uZmlnLiYjMzQ7Y3VzdG9tX3ZhbGlkYXRpb25fbW9yZV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="169.000000" y="4729.000000" width="281.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="4767.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">custom_validation_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuZWRpdG9yLiYjMzQ7ZWRpdG9yLmdvJiMzNDs="><g class="shape" ><rect x="254.000000" y="4931.000000" width="111.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="4969.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">editor.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuZWRpdG9yLiYjMzQ7ZWRpdG9yX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="236.000000" y="5057.000000" width="147.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="5095.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">editor_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtjbWRlbnRyeS5nbyYjMzQ7"><g class="shape" ><rect x="241.000000" y="5658.000000" width="136.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="5696.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">cmdentry.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtjbWRlbnRyeV9ydW5jb21tYW5kX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="172.000000" y="5784.000000" width="275.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="5822.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">cmdentry_runcommand_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtjbWRlbnRyeV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="223.000000" y="5910.000000" width="172.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="5948.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">cmdentry_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtjdXN0b21fYWN0aW9uX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="205.000000" y="6036.000000" width="208.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="6074.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">custom_action_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtjdXN0b21fZXhlY19tb3JlX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="189.000000" y="6162.000000" width="241.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="6200.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">custom_exec_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtjdXN0b21fZXhlY190ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="211.000000" y="6288.000000" width="196.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="6326.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">custom_exec_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtwYXJzZS5nbyYjMzQ7"><g class="shape" ><rect x="256.000000" y="6414.000000" width="106.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="6452.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">parse.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtwYXJzZV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="238.000000" y="6540.000000" width="142.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="6578.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">parse_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtwcm9tcHRzLmdvJiMzNDs="><g class="shape" ><rect x="246.000000" y="6666.000000" width="127.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="6704.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">prompts.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtwcm9tcHRzX21vcmVfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="205.000000" y="6792.000000" width="208.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="6830.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">prompts_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtwcm9tcHRzX3NpbXBsaWZ5X3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="195.000000" y="6918.000000" width="228.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="6956.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">prompts_simplify_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtydW4uZ28mIzM0Ow=="><g class="shape" ><rect x="264.000000" y="7044.000000" width="91.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="7082.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtydW5fbW9yZV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="223.000000" y="7170.000000" width="172.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="7208.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtydW5fc2VhbV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="223.000000" y="7296.000000" width="173.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="7334.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run_seam_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDtydW5fdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="246.000000" y="7422.000000" width="127.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="7460.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDt0dWkuZ28mIzM0Ow=="><g class="shape" ><rect x="266.000000" y="7548.000000" width="87.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="7586.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">tui.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDt0dWlfY3VzdG9tLmdvJiMzNDs="><g class="shape" ><rect x="235.000000" y="7674.000000" width="148.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="7712.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">tui_custom.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDt0dWlfY3VzdG9tX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="218.000000" y="7800.000000" width="183.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="7838.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">tui_custom_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDt0dWlfZGVsZWdhdGUuZ28mIzM0Ow=="><g class="shape" ><rect x="231.000000" y="7926.000000" width="157.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="7964.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">tui_delegate.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDt0dWlfZGVsZWdhdGVfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="213.000000" y="8052.000000" width="193.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="8090.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">tui_delegate_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDt0dWlfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="248.000000" y="8178.000000" width="123.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="8216.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">tui_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlhY3Rpb24uJiMzNDt0eXBlcy5nbyYjMzQ7"><g class="shape" ><rect x="256.000000" y="8304.000000" width="107.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="8342.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">types.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWljbGkuJiMzNDtlZGl0b3JfaW50ZWdyYXRpb25fdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="192.000000" y="8506.000000" width="235.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="8544.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">editor_integration_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWljbGkuJiMzNDtydW4uZ28mIzM0Ow=="><g class="shape" ><rect x="264.000000" y="8632.000000" width="91.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="8670.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWljbGkuJiMzNDtydW5fZWRpdG9yX2JlaGF2aW9yX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="185.000000" y="8758.000000" width="249.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="8796.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run_editor_behavior_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWljbGkuJiMzNDtydW5fbW9kZWxfb3ZlcnJpZGVfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="185.000000" y="8884.000000" width="248.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="8922.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run_model_override_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWljbGkuJiMzNDtydW5fbW9yZV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="223.000000" y="9010.000000" width="172.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="9048.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWljbGkuJiMzNDtydW5fdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="246.000000" y="9136.000000" width="127.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="9174.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWljbGkuJiMzNDt0ZXN0aGVscGVyc190ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="217.000000" y="9262.000000" width="184.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="9300.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">testhelpers_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlsc3AuJiMzNDtydW4uZ28mIzM0Ow=="><g class="shape" ><rect x="264.000000" y="9464.000000" width="91.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="9502.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlsc3AuJiMzNDtydW5fbW9yZV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="223.000000" y="9590.000000" width="172.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="9628.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuaGV4YWlsc3AuJiMzNDtydW5fdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="246.000000" y="9716.000000" width="127.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="9754.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">run_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7Y29waWxvdC5nbyYjMzQ7"><g class="shape" ><rect x="251.000000" y="9918.000000" width="117.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="9956.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">copilot.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7Y29waWxvdF9odHRwX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="213.000000" y="10044.000000" width="192.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="10082.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">copilot_http_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7Y29waWxvdF90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="233.000000" y="10170.000000" width="153.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="10208.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">copilot_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7b2xsYW1hLmdvJiMzNDs="><g class="shape" ><rect x="251.000000" y="10296.000000" width="116.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="10334.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">ollama.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7b2xsYW1hX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="233.000000" y="10422.000000" width="152.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="10460.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">ollama_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7b3BlbmFpLmdvJiMzNDs="><g class="shape" ><rect x="251.000000" y="10548.000000" width="116.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="10586.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">openai.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7b3BlbmFpX2h0dHBfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="214.000000" y="10674.000000" width="190.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="10712.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">openai_http_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7b3BlbmFpX3JlcXVlc3RfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="202.000000" y="10800.000000" width="214.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="10838.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">openai_request_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7b3BlbmFpX3NzZV9uZWdhdGl2ZV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="183.000000" y="10926.000000" width="252.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="10964.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">openai_sse_negative_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7b3BlbmFpX3RlbXBfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="211.000000" y="11052.000000" width="197.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="11090.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">openai_temp_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7b3BlbmFpX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="233.000000" y="11178.000000" width="152.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="11216.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">openai_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7b3BlbnJvdXRlci5nbyYjMzQ7"><g class="shape" ><rect x="235.000000" y="11304.000000" width="148.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="11342.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">openrouter.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7b3BlbnJvdXRlcl90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="217.000000" y="11430.000000" width="184.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="11468.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">openrouter_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7cHJvdmlkZXIuZ28mIzM0Ow=="><g class="shape" ><rect x="245.000000" y="11556.000000" width="128.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="11594.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">provider.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7cHJvdmlkZXJfbW9yZTJfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="201.000000" y="11682.000000" width="217.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="11720.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">provider_more2_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7cHJvdmlkZXJfbW9yZV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="205.000000" y="11808.000000" width="209.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="11846.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">provider_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7cHJvdmlkZXJfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="227.000000" y="11934.000000" width="164.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="11972.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">provider_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7dGVzdF9oZWxwZXJzX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="213.000000" y="12060.000000" width="192.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="12098.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">test_helpers_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7dXRpbC5nbyYjMzQ7"><g class="shape" ><rect x="263.000000" y="12186.000000" width="92.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="12224.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">util.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtLiYjMzQ7dXRpbF90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="245.000000" y="12312.000000" width="128.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="12350.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">util_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtdXRpbHMuJiMzNDtjbGllbnQuZ28mIzM0Ow=="><g class="shape" ><rect x="255.000000" y="12514.000000" width="108.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="12552.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">client.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubGxtdXRpbHMuJiMzNDtjbGllbnRfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="238.000000" y="12640.000000" width="143.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="12678.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">client_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubG9nZ2luZy4mIzM0O2NoYXRsb2dnZXIuZ28mIzM0Ow=="><g class="shape" ><rect x="237.000000" y="12842.000000" width="144.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="12880.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">chatlogger.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubG9nZ2luZy4mIzM0O2xvZ2dpbmcuZ28mIzM0Ow=="><g class="shape" ><rect x="249.000000" y="12968.000000" width="120.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="13006.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">logging.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubG9nZ2luZy4mIzM0O2xvZ2dpbmdfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="232.000000" y="13094.000000" width="155.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="13132.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">logging_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7YnVpbGRfcHJvbXB0c190YWJsZV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="183.000000" y="13296.000000" width="252.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="13334.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">build_prompts_table_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y2hhdF9jb21tYW5kcy5nbyYjMzQ7"><g class="shape" ><rect x="217.000000" y="13422.000000" width="185.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="13460.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">chat_commands.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y2hhdF9jb21tYW5kc190ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="199.000000" y="13548.000000" width="220.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="13586.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">chat_commands_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y2hhdF9jb250ZXh0X21vZGVfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="187.000000" y="13674.000000" width="245.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="13712.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">chat_context_mode_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y2hhdF9oaXN0b3J5X3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="213.000000" y="13800.000000" width="193.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="13838.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">chat_history_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y2hhdF9ub19kb3VibGVfYW5zd2VyX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="170.000000" y="13926.000000" width="278.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="13964.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">chat_no_double_answer_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y2hhdF9wcm9tcHRfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="211.000000" y="14052.000000" width="196.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="14090.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">chat_prompt_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y2hhdF90cmlnZ2VyX3N1cHByZXNzaW9uX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="167.000000" y="14178.000000" width="285.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="14216.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">chat_trigger_suppression_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29kZV9mZW5jZXNfdGFibGVfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="191.000000" y="14304.000000" width="236.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="14342.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">code_fences_table_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29kZWFjdGlvbl9jdXN0b21fZXJyb3JzX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="163.000000" y="14430.000000" width="293.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="14468.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">codeaction_custom_errors_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29kZWFjdGlvbl9jdXN0b21fdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="188.000000" y="14556.000000" width="242.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="14594.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">codeaction_custom_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29kZWFjdGlvbl9nb3Rlc3RfaW50X3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="178.000000" y="14682.000000" width="262.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="14720.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">codeaction_gotest_int_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29kZWFjdGlvbl9tb3JlX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="196.000000" y="14808.000000" width="227.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="14846.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">codeaction_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29kZWFjdGlvbl9wcm9tcHRzX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="184.000000" y="14934.000000" width="250.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="14972.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">codeaction_prompts_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29kZWFjdGlvbl90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="218.000000" y="15060.000000" width="182.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="15098.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">codeaction_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29kZWdlbl9oZWxwZXJzX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="197.000000" y="15186.000000" width="224.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="15224.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">codegen_helpers_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29tcGxldGlvbl9jYWNoZV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="193.000000" y="15312.000000" width="233.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="15350.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">completion_cache_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29tcGxldGlvbl9jb2RleF9wYXRoX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="172.000000" y="15438.000000" width="275.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="15476.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">completion_codex_path_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29tcGxldGlvbl9oZWxwZXJzX21vcmVfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="164.000000" y="15564.000000" width="290.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="15602.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">completion_helpers_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29tcGxldGlvbl9tZXNzYWdlc190ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="179.000000" y="15690.000000" width="261.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="15728.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">completion_messages_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29tcGxldGlvbl9wcmVmaXhfc3RyaXBfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="172.000000" y="15816.000000" width="275.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="15854.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">completion_prefix_strip_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29tcGxldGlvbl9wcm92aWRlcl9mYWxsYmFja190ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="150.000000" y="15942.000000" width="318.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="15980.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">completion_provider_fallback_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29tcGxldGlvbl90b2dnbGVfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="191.000000" y="16068.000000" width="237.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="16106.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">completion_toggle_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29tcHV0ZV90ZXh0ZWRpdF90YWJsZV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="171.000000" y="16194.000000" width="276.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="16232.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">compute_textedit_table_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29udGV4dC5nbyYjMzQ7"><g class="shape" ><rect x="248.000000" y="16320.000000" width="122.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="16358.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">context.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y29udGV4dF90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="230.000000" y="16446.000000" width="158.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="16484.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">context_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Y292ZXJhZ2VfYWRkX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="208.000000" y="16572.000000" width="203.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="16610.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">coverage_add_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7ZGVib3VuY2VfdGhyb3R0bGVfbW9yZV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="168.000000" y="16698.000000" width="282.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="16736.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">debounce_throttle_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7ZGVib3VuY2VfdGhyb3R0bGVfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="191.000000" y="16824.000000" width="237.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="16862.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">debounce_throttle_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7ZGlhZ25vc3RpY3NfYWN0aW9uX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="191.000000" y="16950.000000" width="237.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="16988.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">diagnostics_action_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7ZG9jdW1lbnQuZ28mIzM0Ow=="><g class="shape" ><rect x="239.000000" y="17076.000000" width="140.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="17114.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">document.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7ZG9jdW1lbnRfaGFuZGxlcnNfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="186.000000" y="17202.000000" width="246.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="17240.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">document_handlers_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7ZG9jdW1lbnRfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="222.000000" y="17328.000000" width="175.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="17366.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">document_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7ZmFsbGJhY2tfaXRlbXNfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="205.000000" y="17454.000000" width="208.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="17492.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">fallback_items_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7Z290ZXN0X2FwcGVuZF90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="204.000000" y="17580.000000" width="210.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="17618.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">gotest_append_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGFuZGxlcnMuZ28mIzM0Ow=="><g class="shape" ><rect x="245.000000" y="17706.000000" width="129.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="17744.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">handlers.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGFuZGxlcnNfY29kZWFjdGlvbi5nbyYjMzQ7"><g class="shape" ><rect x="202.000000" y="17832.000000" width="215.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="17870.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">handlers_codeaction.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGFuZGxlcnNfY29tcGxldGlvbi5nbyYjMzQ7"><g class="shape" ><rect x="200.000000" y="17958.000000" width="218.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="17996.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">handlers_completion.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGFuZGxlcnNfZG9jdW1lbnQuZ28mIzM0Ow=="><g class="shape" ><rect x="205.000000" y="18084.000000" width="209.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="18122.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">handlers_document.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGFuZGxlcnNfZW5kX3RvX2VuZF90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="181.000000" y="18210.000000" width="257.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="18248.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">handlers_end_to_end_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGFuZGxlcnNfZXhlY3V0ZS5nbyYjMzQ7"><g class="shape" ><rect x="213.000000" y="18336.000000" width="193.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="18374.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">handlers_execute.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGFuZGxlcnNfaGVscGVyc190ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="196.000000" y="18462.000000" width="226.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="18500.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">handlers_helpers_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGFuZGxlcnNfaW5pdC5nbyYjMzQ7"><g class="shape" ><rect x="229.000000" y="18588.000000" width="161.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="18626.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">handlers_init.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGFuZGxlcnNfaW5pdF9tb3JlX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="188.000000" y="18714.000000" width="242.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="18752.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">handlers_init_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGFuZGxlcnNfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="227.000000" y="18840.000000" width="165.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="18878.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">handlers_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGFuZGxlcnNfdXRpbHMuZ28mIzM0Ow=="><g class="shape" ><rect x="225.000000" y="18966.000000" width="168.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="19004.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">handlers_utils.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGVscGVyc19pbmxpbmVfcHJvbXB0X3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="177.000000" y="19092.000000" width="265.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="19130.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">helpers_inline_prompt_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aGVscGVyc19tb3JlX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="209.000000" y="19218.000000" width="201.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="19256.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">helpers_more_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aW5pdF9hbmRfdHJpZ2dlcl90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="200.000000" y="19344.000000" width="219.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="19382.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">init_and_trigger_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aW5pdF9zaHV0ZG93bl90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="206.000000" y="19470.000000" width="207.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="19508.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">init_shutdown_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aW5saW5lX3Byb21wdF9jb21wbGV0aW9uX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="162.000000" y="19596.000000" width="294.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="19634.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">inline_prompt_completion_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7aW5zdHJ1Y3Rpb25fdGFibGVfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="196.000000" y="19722.000000" width="226.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="19760.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">instruction_table_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7bGFiZWxfZmlsdGVyX3RhYmxlX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="197.000000" y="19848.000000" width="225.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="19886.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">label_filter_table_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7bGxtX3JlcXVlc3Rfb3B0c190ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="196.000000" y="19974.000000" width="227.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="20012.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">llm_request_opts_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7bGxtX3N0YXRzX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="225.000000" y="20100.000000" width="168.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="20138.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">llm_stats_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7bG9nX2NvbnRleHRfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="216.000000" y="20226.000000" width="187.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="20264.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">log_context_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7cG9zdHByb2Nlc3NfaW5kZW50X3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="188.000000" y="20352.000000" width="242.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="20390.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">postprocess_indent_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7cHJlZml4X3RhYmxlX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="215.000000" y="20478.000000" width="189.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="20516.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">prefix_table_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7cHJvdmlkZXJfbmF0aXZlX3N1Y2Nlc3NfdGVzdC5nbyYjMzQ7"><g class="shape" ><rect x="170.000000" y="20604.000000" width="278.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="20642.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">provider_native_success_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7cmV3cml0ZV9kaWFnbm9zdGljc19yZWFsaXNtX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="157.000000" y="20730.000000" width="304.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="20768.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">rewrite_diagnostics_realism_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7c2VydmVyLmdvJiMzNDs="><g class="shape" ><rect x="253.000000" y="20856.000000" width="113.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="20894.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">server.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7c2VydmVyX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="235.000000" y="20982.000000" width="148.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="21020.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">server_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7dGVzdGZha2VzX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="225.000000" y="21108.000000" width="169.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="21146.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">testfakes_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7dGVzdGhlbHBlcl9jYXB0dXJlX2xsbV90ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="174.000000" y="21234.000000" width="271.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="21272.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">testhelper_capture_llm_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7dHJhbnNwb3J0LmdvJiMzNDs="><g class="shape" ><rect x="241.000000" y="21360.000000" width="136.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="21398.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">transport.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7dHJhbnNwb3J0X2NvbmN1cnJlbmN5X3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="176.000000" y="21486.000000" width="267.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="21524.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">transport_concurrency_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7dHJhbnNwb3J0X3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="224.000000" y="21612.000000" width="171.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="21650.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">transport_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7dHJpZ2dlcnNfY29uZmlnX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="204.000000" y="21738.000000" width="211.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="21776.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">triggers_config_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwubHNwLiYjMzQ7dHlwZXMuZ28mIzM0Ow=="><g class="shape" ><rect x="256.000000" y="21864.000000" width="107.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="21902.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">types.go</text></g><g class="aGV4YWkuaW50ZXJuYWwucnVudGltZWNvbmZpZy4mIzM0O3N0b3JlLmdvJiMzNDs="><g class="shape" ><rect x="257.000000" y="22066.000000" width="105.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="22104.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">store.go</text></g><g class="aGV4YWkuaW50ZXJuYWwucnVudGltZWNvbmZpZy4mIzM0O3N0b3JlX3Rlc3QuZ28mIzM0Ow=="><g class="shape" ><rect x="239.000000" y="22192.000000" width="140.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="22230.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">store_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuc3RhdHMuJiMzNDtkZWJ1Z3N0cmluZ190ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="215.000000" y="22394.000000" width="189.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="22432.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">debugstring_test.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuc3RhdHMuJiMzNDtsb2NrX3Bvc2l4LmdvJiMzNDs="><g class="shape" ><rect x="238.000000" y="22520.000000" width="142.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="22558.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">lock_posix.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuc3RhdHMuJiMzNDtsb2NrX3dpbmRvd3MuZ28mIzM0Ow=="><g class="shape" ><rect x="225.000000" y="22646.000000" width="168.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="22684.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">lock_windows.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuc3RhdHMuJiMzNDtzdGF0cy5nbyYjMzQ7"><g class="shape" ><rect x="258.000000" y="22772.000000" width="103.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.500000" y="22810.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">stats.go</text></g><g class="aGV4YWkuaW50ZXJuYWwuc3RhdHMuJiMzNDtzdGF0c190ZXN0LmdvJiMzNDs="><g class="shape" ><rect x="240.000000" y="22898.000000" width="138.000000" height="66.000000" stroke="#0D32B2" fill="#FFFFFF" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="309.000000" y="22936.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">stats_test.go</text></g><mask id="d2-3145263268" maskUnits="userSpaceOnUse" x="-101" y="-95" width="820" height="23240"> +<rect x="-101" y="-95" width="820" height="23240" fill="white"></rect> + +</mask></svg></svg> |
