Merge branch 'main' into ui

Ayman Bagabas created

Change summary

.github/cla-signatures.json                                                        |  40 
README.md                                                                          | 122 
Taskfile.yaml                                                                      |   8 
go.mod                                                                             |  57 
go.sum                                                                             | 120 
internal/agent/agent.go                                                            |  13 
internal/agent/agent_test.go                                                       |   8 
internal/agent/common_test.go                                                      |  30 
internal/agent/coordinator.go                                                      |  15 
internal/agent/prompts.go                                                          |  14 
internal/agent/recorder_test.go                                                    | 116 
internal/agent/templates/coder.md.tpl                                              |   1 
internal/agent/templates/initialize.md.tpl                                         |   6 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/bash_tool.yaml             |  25 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/download_tool.yaml         |  25 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/fetch_tool.yaml            |  27 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/glob_tool.yaml             |  25 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/grep_tool.yaml             |  22 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/ls_tool.yaml               |  22 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/multiedit_tool.yaml        |  21 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/parallel_tool_calls.yaml   |  26 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/read_a_file.yaml           |  20 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/simple_test.yaml           |  24 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/sourcegraph_tool.yaml      |  26 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/update_a_file.yaml         |  20 
internal/agent/testdata/TestCoderAgent/anthropic-sonnet/write_tool.yaml            |  26 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/bash_tool.yaml                 |  22 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/download_tool.yaml             |  24 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/fetch_tool.yaml                |  30 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/glob_tool.yaml                 |  28 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/grep_tool.yaml                 |  32 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/ls_tool.yaml                   |  30 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/multiedit_tool.yaml            |  34 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/parallel_tool_calls.yaml       |  28 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/read_a_file.yaml               |  18 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/simple_test.yaml               |  14 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/sourcegraph_tool.yaml          |  34 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/update_a_file.yaml             |  30 
internal/agent/testdata/TestCoderAgent/openai-gpt-5/write_tool.yaml                |  22 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/bash_tool.yaml           |  22 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/download_tool.yaml       |  24 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/fetch_tool.yaml          |   2 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/glob_tool.yaml           |  20 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/grep_tool.yaml           |  26 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/ls_tool.yaml             |  22 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/multiedit_tool.yaml      |  20 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/parallel_tool_calls.yaml |  22 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/read_a_file.yaml         |  18 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/simple_test.yaml         |  12 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/sourcegraph_tool.yaml    |  30 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/update_a_file.yaml       |  26 
internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/write_tool.yaml          |  16 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/bash_tool.yaml                   |  20 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/download_tool.yaml               |  22 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/fetch_tool.yaml                  |   2 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/glob_tool.yaml                   |   3 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/grep_tool.yaml                   |  22 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/ls_tool.yaml                     |   2 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/multiedit_tool.yaml              |   3 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/parallel_tool_calls.yaml         |  18 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/read_a_file.yaml                 |  16 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/simple_test.yaml                 |  12 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/sourcegraph_tool.yaml            |   2 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/update_a_file.yaml               |  24 
internal/agent/testdata/TestCoderAgent/zai-glm4.6/write_tool.yaml                  |  20 
internal/agent/tools/bash.go                                                       | 247 
internal/agent/tools/bash.tpl                                                      |  44 
internal/agent/tools/job_kill.go                                                   |  59 
internal/agent/tools/job_kill.md                                                   |  18 
internal/agent/tools/job_output.go                                                 |  85 
internal/agent/tools/job_output.md                                                 |  19 
internal/agent/tools/job_test.go                                                   | 329 
internal/agent/tools/mcp/init.go                                                   |   9 
internal/agent/tools/mcp/tools.go                                                  |   6 
internal/app/app.go                                                                |   4 
internal/config/attribution_migration_test.go                                      |  95 
internal/config/config.go                                                          |  75 
internal/config/load.go                                                            |  19 
internal/config/load_test.go                                                       |   6 
internal/config/recent_models_test.go                                              | 253 
internal/db/connect.go                                                             |   2 
internal/message/message.go                                                        |   1 
internal/shell/background.go                                                       | 201 
internal/shell/background_test.go                                                  | 276 
internal/shell/doc.go                                                              |   7 
internal/shell/persistent.go                                                       |  43 
internal/shell/shell.go                                                            |  70 
internal/stringext/string.go                                                       |  10 
internal/tui/components/chat/messages/renderer.go                                  | 158 
internal/tui/components/chat/splash/splash.go                                      |  11 
internal/tui/components/dialogs/commands/commands.go                               |  10 
internal/tui/components/dialogs/models/list.go                                     |  80 
internal/tui/components/dialogs/models/list_recent_test.go                         | 369 
internal/tui/components/dialogs/permissions/permissions.go                         |  40 
internal/tui/exp/list/list.go                                                      |   3 
internal/tui/keys.go                                                               |   5 
internal/tui/page/chat/chat.go                                                     |  13 
internal/tui/styles/charmtone.go                                                   |   1 
internal/tui/styles/theme.go                                                       |   1 
internal/tui/tui.go                                                                |  59 
schema.json                                                                        |  35 
101 files changed, 3,272 insertions(+), 1,022 deletions(-)

Detailed changes

.github/cla-signatures.json πŸ”—

@@ -823,6 +823,46 @@
       "created_at": "2025-11-05T17:59:51Z",
       "repoId": 987670088,
       "pullRequestNo": 1387
+    },
+    {
+      "name": "nanvenomous",
+      "id": 43622197,
+      "comment_id": 3497693075,
+      "created_at": "2025-11-06T15:05:00Z",
+      "repoId": 987670088,
+      "pullRequestNo": 1392
+    },
+    {
+      "name": "novalis78",
+      "id": 190390,
+      "comment_id": 3506836115,
+      "created_at": "2025-11-08T19:53:10Z",
+      "repoId": 987670088,
+      "pullRequestNo": 1408
+    },
+    {
+      "name": "alewtschuk",
+      "id": 19924911,
+      "comment_id": 3520127241,
+      "created_at": "2025-11-12T05:44:54Z",
+      "repoId": 987670088,
+      "pullRequestNo": 1429
+    },
+    {
+      "name": "Iflgit",
+      "id": 24231648,
+      "comment_id": 3536325469,
+      "created_at": "2025-11-15T10:46:24Z",
+      "repoId": 987670088,
+      "pullRequestNo": 1450
+    },
+    {
+      "name": "iainlane",
+      "id": 321014,
+      "comment_id": 3539345738,
+      "created_at": "2025-11-16T20:55:47Z",
+      "repoId": 987670088,
+      "pullRequestNo": 1457
     }
   ]
 }

README.md πŸ”—

@@ -335,73 +335,53 @@ permissions. Use this with care.
 You can also skip all permission prompts entirely by running Crush with the
 `--yolo` flag. Be very, very careful with this feature.
 
-### Attribution Settings
+### Initialization
 
-By default, Crush adds attribution information to Git commits and pull requests
-it creates. You can customize this behavior with the `attribution` option:
+When you initialize a project, Crush analyzes your codebase and creates
+a context file that helps it work more effectively in future sessions.
+By default, this file is named `AGENTS.md`, but you can customize the
+name and location with the `initialize_as` option:
 
 ```json
 {
   "$schema": "https://charm.land/crush.json",
   "options": {
-    "attribution": {
-      "co_authored_by": true,
-      "generated_with": true
-    }
+    "initialize_as": "AGENTS.md"
   }
 }
 ```
 
-- `co_authored_by`: When true (default), adds `Co-Authored-By: Crush <crush@charm.land>` to commit messages
-- `generated_with`: When true (default), adds `πŸ’˜ Generated with Crush` line to commit messages and PR descriptions
+This is useful if you prefer a different naming convention or want to
+place the file in a specific directory (e.g., `CRUSH.md` or
+`docs/LLMs.md`). Crush will fill the file with project-specific context
+like build commands, code patterns, and conventions it discovered during
+initialization.
 
-### Local Models
-
-Local models can also be configured via OpenAI-compatible API. Here are two common examples:
+### Attribution Settings
 
-#### Ollama
+By default, Crush adds attribution information to Git commits and pull requests
+it creates. You can customize this behavior with the `attribution` option:
 
 ```json
 {
-  "providers": {
-    "ollama": {
-      "name": "Ollama",
-      "base_url": "http://localhost:11434/v1/",
-      "type": "openai-compat",
-      "models": [
-        {
-          "name": "Qwen 3 30B",
-          "id": "qwen3:30b",
-          "context_window": 256000,
-          "default_max_tokens": 20000
-        }
-      ]
+  "$schema": "https://charm.land/crush.json",
+  "options": {
+    "attribution": {
+      "trailer_style": "co-authored-by",
+      "generated_with": true
     }
   }
 }
 ```
 
-#### LM Studio
-
-```json
-{
-  "providers": {
-    "lmstudio": {
-      "name": "LM Studio",
-      "base_url": "http://localhost:1234/v1/",
-      "type": "openai-compat",
-      "models": [
-        {
-          "name": "Qwen 3 30B",
-          "id": "qwen/qwen3-30b-a3b-2507",
-          "context_window": 256000,
-          "default_max_tokens": 20000
-        }
-      ]
-    }
-  }
-}
-```
+- `trailer_style`: Controls the attribution trailer added to commit messages
+  (default: `assisted-by`)
+	- `assisted-by`: Adds `Assisted-by: [Model Name] via Crush <crush@charm.land>`
+	  (includes the model name)
+	- `co-authored-by`: Adds `Co-Authored-By: Crush <crush@charm.land>`
+	- `none`: No attribution trailer
+- `generated_with`: When true (default), adds `πŸ’˜ Generated with Crush` line to
+  commit messages and PR descriptions
 
 ### Custom Providers
 
@@ -521,6 +501,54 @@ To add specific models to the configuration, configure as such:
 }
 ```
 
+### Local Models
+
+Local models can also be configured via OpenAI-compatible API. Here are two common examples:
+
+#### Ollama
+
+```json
+{
+  "providers": {
+    "ollama": {
+      "name": "Ollama",
+      "base_url": "http://localhost:11434/v1/",
+      "type": "openai-compat",
+      "models": [
+        {
+          "name": "Qwen 3 30B",
+          "id": "qwen3:30b",
+          "context_window": 256000,
+          "default_max_tokens": 20000
+        }
+      ]
+    }
+  }
+}
+```
+
+#### LM Studio
+
+```json
+{
+  "providers": {
+    "lmstudio": {
+      "name": "LM Studio",
+      "base_url": "http://localhost:1234/v1/",
+      "type": "openai-compat",
+      "models": [
+        {
+          "name": "Qwen 3 30B",
+          "id": "qwen/qwen3-30b-a3b-2507",
+          "context_window": 256000,
+          "default_max_tokens": 20000
+        }
+      ]
+    }
+  }
+}
+```
+
 ## Logging
 
 Sometimes you need to look at logs. Luckily, Crush logs all sorts of

Taskfile.yaml πŸ”—

@@ -51,6 +51,13 @@ tasks:
     cmds:
       - go test ./... {{.CLI_ARGS}}
 
+  test:record:
+    desc: Run tests and record all VCR cassettes again
+    aliases: [record]
+    cmds:
+      - rm -r internal/agent/testdata
+      - go test -v -count=1 -timeout=1h ./internal/agent
+
   fmt:
     desc: Run gofumpt
     cmds:
@@ -68,6 +75,7 @@ tasks:
     vars:
       LDFLAGS: '{{if .VERSION}}-ldflags="-X github.com/charmbracelet/crush/internal/version.Version={{.VERSION}}"{{end}}'
     cmds:
+      - task: fetch-tags
       - go install {{.LDFLAGS}} -v .
 
   profile:cpu:

go.mod πŸ”—

@@ -4,9 +4,10 @@ go 1.25.0
 
 require (
 	charm.land/bubbles/v2 v2.0.0-beta.1.0.20251104200223-da0b892d1759
-	charm.land/bubbletea/v2 v2.0.0-rc.1.0.20251105182244-3138f1cd1bf8
-	charm.land/fantasy v0.1.6
-	charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251104200114-3aae28661422
+	charm.land/bubbletea/v2 v2.0.0-rc.1.0.20251117161017-15f884bd2973
+	charm.land/fantasy v0.3.1
+	charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410
+	charm.land/x/vcr v0.1.1
 	github.com/JohannesKaufmann/html-to-markdown v1.6.0
 	github.com/MakeNowJust/heredoc v1.0.0
 	github.com/PuerkitoBio/goquery v1.10.3
@@ -17,15 +18,15 @@ require (
 	github.com/charlievieth/fastwalk v1.0.14
 	github.com/charmbracelet/catwalk v0.8.2
 	github.com/charmbracelet/colorprofile v0.3.3
-	github.com/charmbracelet/fang v0.4.3
-	github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018
-	github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706
-	github.com/charmbracelet/ultraviolet v0.0.0-20251105181648-75d1e37ff1bb
-	github.com/charmbracelet/x/ansi v0.10.3
+	github.com/charmbracelet/fang v0.4.4
+	github.com/charmbracelet/glamour/v2 v2.0.0-20251106195642-800eb8175930
+	github.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0
+	github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38
+	github.com/charmbracelet/x/ansi v0.11.1
 	github.com/charmbracelet/x/exp/charmtone v0.0.0-20250708181618-a60a724ba6c3
 	github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f
 	github.com/charmbracelet/x/exp/ordered v0.1.0
-	github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5
+	github.com/charmbracelet/x/exp/slice v0.0.0-20251113172435-cef867b85f6a
 	github.com/charmbracelet/x/powernap v0.0.0-20251015113943-25f979b54ad4
 	github.com/charmbracelet/x/term v0.2.2
 	github.com/denisbrodbeck/machineid v1.0.1
@@ -36,7 +37,7 @@ require (
 	github.com/lucasb-eyer/go-colorful v1.3.0
 	github.com/modelcontextprotocol/go-sdk v1.1.0
 	github.com/muesli/termenv v0.16.0
-	github.com/ncruces/go-sqlite3 v0.29.1
+	github.com/ncruces/go-sqlite3 v0.30.1
 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
 	github.com/nxadm/tail v1.4.11
 	github.com/openai/openai-go/v2 v2.7.1
@@ -52,10 +53,8 @@ require (
 	github.com/stretchr/testify v1.11.1
 	github.com/tidwall/sjson v1.2.5
 	github.com/zeebo/xxh3 v1.0.2
-	go.yaml.in/yaml/v4 v4.0.0-rc.2
-	golang.org/x/sync v0.17.0
-	golang.org/x/text v0.30.0
-	gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20250923044825-7b4892dd3117
+	golang.org/x/sync v0.18.0
+	golang.org/x/text v0.31.0
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 	mvdan.cc/sh/moreinterp v0.0.0-20250902163504-3cf4fd5717a5
 	mvdan.cc/sh/v3 v3.12.1-0.20250902163504-3cf4fd5717a5
@@ -68,8 +67,9 @@ require (
 	cloud.google.com/go/compute/metadata v0.8.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
+	github.com/RealAlexandreAI/json-repair v0.0.14 // indirect
 	github.com/andybalholm/cascadia v1.3.3 // indirect
-	github.com/aws/aws-sdk-go-v2 v1.39.5 // indirect
+	github.com/aws/aws-sdk-go-v2 v1.39.6 // indirect
 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
 	github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
 	github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
@@ -82,18 +82,16 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
 	github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
-	github.com/aws/smithy-go v1.23.1 // indirect
+	github.com/aws/smithy-go v1.23.2 // indirect
 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/bahlo/generic-list-go v0.2.0 // indirect
 	github.com/buger/jsonparser v1.1.1 // indirect
 	github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251024181547-21d6f3d9a904 // indirect
-	github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20251103214348-d3032608aa74 // indirect
-	github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a // indirect
 	github.com/charmbracelet/x/json v0.2.0 // indirect
 	github.com/charmbracelet/x/termios v0.1.1 // indirect
 	github.com/charmbracelet/x/windows v0.2.2 // indirect
-	github.com/clipperhouse/displaywidth v0.4.1 // indirect
+	github.com/clipperhouse/displaywidth v0.5.0 // indirect
 	github.com/clipperhouse/stringish v0.1.1 // indirect
 	github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
@@ -102,10 +100,12 @@ require (
 	github.com/dustin/go-humanize v1.0.1 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/fsnotify/fsnotify v1.9.0 // indirect
+	github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e // indirect
 	github.com/go-logfmt/logfmt v0.6.0 // indirect
 	github.com/go-logr/logr v1.4.3 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
+	github.com/goccy/go-yaml v1.18.0 // indirect
 	github.com/google/go-cmp v0.7.0 // indirect
 	github.com/google/jsonschema-go v0.3.0 // indirect
 	github.com/google/s2a-go v0.1.9 // indirect
@@ -115,6 +115,9 @@ require (
 	github.com/gorilla/websocket v1.5.3 // indirect
 	github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/kaptinlin/go-i18n v0.2.0 // indirect
+	github.com/kaptinlin/jsonschema v0.5.2 // indirect
+	github.com/kaptinlin/messageformat-go v0.4.6 // indirect
 	github.com/klauspost/compress v1.18.0 // indirect
 	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
 	github.com/klauspost/pgzip v1.2.6 // indirect
@@ -136,7 +139,7 @@ require (
 	github.com/sethvargo/go-retry v0.3.0 // indirect
 	github.com/sourcegraph/jsonrpc2 v0.2.1 // indirect
 	github.com/spf13/pflag v1.0.9 // indirect
-	github.com/tetratelabs/wazero v1.9.0 // indirect
+	github.com/tetratelabs/wazero v1.10.0 // indirect
 	github.com/tidwall/gjson v1.18.0 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.1 // indirect
@@ -154,19 +157,21 @@ require (
 	go.opentelemetry.io/otel/metric v1.37.0 // indirect
 	go.opentelemetry.io/otel/trace v1.37.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/crypto v0.42.0 // indirect
+	go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
+	golang.org/x/crypto v0.43.0 // indirect
 	golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
 	golang.org/x/image v0.27.0 // indirect
-	golang.org/x/net v0.44.0 // indirect
-	golang.org/x/oauth2 v0.32.0 // indirect
-	golang.org/x/sys v0.37.0 // indirect
-	golang.org/x/term v0.35.0 // indirect
+	golang.org/x/net v0.45.0 // indirect
+	golang.org/x/oauth2 v0.33.0 // indirect
+	golang.org/x/sys v0.38.0 // indirect
+	golang.org/x/term v0.36.0 // indirect
 	golang.org/x/time v0.12.0 // indirect
 	google.golang.org/api v0.239.0 // indirect
-	google.golang.org/genai v1.33.1-0.20251103191629-d15baab4f79e // indirect
+	google.golang.org/genai v1.34.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
 	google.golang.org/grpc v1.74.2 // indirect
 	google.golang.org/protobuf v1.36.10 // indirect
+	gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20251110073552-01de4eb40290 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )

go.sum πŸ”—

@@ -1,11 +1,13 @@
 charm.land/bubbles/v2 v2.0.0-beta.1.0.20251104200223-da0b892d1759 h1:P1MxkVl8ZeI9tHmmrn9UzV/5Mz7heoiTgqECHRFsUcs=
 charm.land/bubbles/v2 v2.0.0-beta.1.0.20251104200223-da0b892d1759/go.mod h1:G7JWaj3kDT0BDB+h5BLDUhhBLpDoRLKrpOp5QrA2SQs=
-charm.land/bubbletea/v2 v2.0.0-rc.1.0.20251105182244-3138f1cd1bf8 h1:A1y0nyy7ykH1judtnD36sgpepLBrU4y7mN6QlZEVhZk=
-charm.land/bubbletea/v2 v2.0.0-rc.1.0.20251105182244-3138f1cd1bf8/go.mod h1:oR2A+f83vzDY0hALwW4eh90fKXdranRWnH/vfwJL1lU=
-charm.land/fantasy v0.1.6 h1:laomMUqUaniQoLx7UOb+MLUpIGJPoNwsXvw1PbzgnB8=
-charm.land/fantasy v0.1.6/go.mod h1:JpFcJ5zs/1CjmYYGAZ7GaFmeBv0mPaTzEPRG6Eic5pc=
-charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251104200114-3aae28661422 h1:LcW3SSv1EZvlb9pfaVZIZyHrPVRJdb0adgX+tWPYl0k=
-charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251104200114-3aae28661422/go.mod h1:0EJAlA1PDGb+2RyyC02yDSPDwvpegDefu74HC9Blg5o=
+charm.land/bubbletea/v2 v2.0.0-rc.1.0.20251117161017-15f884bd2973 h1:Ay8VWyn/CbwltswomzWXj0m5KKfSJavFfCDCxI+j8qo=
+charm.land/bubbletea/v2 v2.0.0-rc.1.0.20251117161017-15f884bd2973/go.mod h1:IXFmnCnMLTWw/KQ9rEatSYqbAPAYi8kA3Yqwa1SFnLk=
+charm.land/fantasy v0.3.1 h1:YeMoLnaOHM3hdXq+SByxIKZxdm/2CHgKIS7HE0k/G6I=
+charm.land/fantasy v0.3.1/go.mod h1:sV8Ns/JTJHOaYOHPgVRDugMheAyxsW/nmdpVGrycYEk=
+charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 h1:D9PbaszZYpB4nj+d6HTWr1onlmlyuGVNfL9gAi8iB3k=
+charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410/go.mod h1:1qZyvvVCenJO2M1ac2mX0yyiIZJoZmDM4DG4s0udJkU=
+charm.land/x/vcr v0.1.1 h1:PXCFMUG0rPtyk35rhfzYCJEduOzWXCIbrXTFq4OF/9Q=
+charm.land/x/vcr v0.1.1/go.mod h1:eByq2gqzWvcct/8XE2XO5KznoWEBiXH56+y2gphbltM=
 cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
 cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
 cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
@@ -29,6 +31,8 @@ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6
 github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
 github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
 github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
+github.com/RealAlexandreAI/json-repair v0.0.14 h1:4kTqotVonDVTio5n2yweRUELVcNe2x518wl0bCsw0t0=
+github.com/RealAlexandreAI/json-repair v0.0.14/go.mod h1:GKJi5borR78O8c7HCVbgqjhoiVibZ6hJldxbc6dGrAI=
 github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
 github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
 github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
@@ -40,8 +44,8 @@ github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kk
 github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
 github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
 github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
-github.com/aws/aws-sdk-go-v2 v1.39.5 h1:e/SXuia3rkFtapghJROrydtQpfQaaUgd1cUvyO1mp2w=
-github.com/aws/aws-sdk-go-v2 v1.39.5/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM=
+github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk=
+github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
 github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
@@ -66,8 +70,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrA
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
 github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
 github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
-github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M=
-github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
+github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
+github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
 github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
@@ -88,28 +92,24 @@ github.com/charmbracelet/catwalk v0.8.2 h1:J7xq/ft/ZByJCHl3JpgvxlCd59bzZPugy66Xu
 github.com/charmbracelet/catwalk v0.8.2/go.mod h1:ReU4SdrLfe63jkEjWMdX2wlZMV3k9r11oQAmzN0m+KY=
 github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
 github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
-github.com/charmbracelet/fang v0.4.3 h1:qXeMxnL4H6mSKBUhDefHu8NfikFbP/MBNTfqTrXvzmY=
-github.com/charmbracelet/fang v0.4.3/go.mod h1:wHJKQYO5ReYsxx+yZl+skDtrlKO/4LLEQ6EXsdHhRhg=
-github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018 h1:PU4Zvpagsk5sgaDxn5W4sxHuLp9QRMBZB3bFSk40A4w=
-github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018/go.mod h1:Z/GLmp9fzaqX4ze3nXG7StgWez5uBM5XtlLHK8V/qSk=
-github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20251103214348-d3032608aa74 h1:2N+CxpUFM6Rrx+xT7XaqM9pp/psOFlxKWa5R7rP/lck=
-github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20251103214348-d3032608aa74/go.mod h1:RfXmCdNs2F4MVJjBVQp5RZYXR05MiRAHN4GHwWmsNIA=
-github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 h1:WkwO6Ks3mSIGnGuSdKl9qDSyfbYK50z2wc2gGMggegE=
-github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706/go.mod h1:mjJGp00cxcfvD5xdCa+bso251Jt4owrQvuimJtVmEmM=
-github.com/charmbracelet/ultraviolet v0.0.0-20251105181648-75d1e37ff1bb h1:KZnKSrGjarKScpekDuPAVnlMSMtA7mdzmoUD0AhAZC0=
-github.com/charmbracelet/ultraviolet v0.0.0-20251105181648-75d1e37ff1bb/go.mod h1:G7cNuWgmuugx6ApJv4kDGfnFanoDAz8AWazH9lSoWdw=
-github.com/charmbracelet/x/ansi v0.10.3 h1:3WoV9XN8uMEnFRZZ+vBPRy59TaIWa+gJodS4Vg5Fut0=
-github.com/charmbracelet/x/ansi v0.10.3/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE=
-github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a h1:zYSNtEJM9jwHbJts2k+Hroj+xQwsW1yxc4Wopdv7KaI=
-github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a/go.mod h1:rc2bsPC6MWae3LdOxNO1mOb443NlMrrDL0xEya48NNc=
+github.com/charmbracelet/fang v0.4.4 h1:G4qKxF6or/eTPgmAolwPuRNyuci3hTUGGX1rj1YkHJY=
+github.com/charmbracelet/fang v0.4.4/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo=
+github.com/charmbracelet/glamour/v2 v2.0.0-20251106195642-800eb8175930 h1:+47Z2jVAWPSLGjPRbfZizW3OpcAYsu7EUk2DR+66FyM=
+github.com/charmbracelet/glamour/v2 v2.0.0-20251106195642-800eb8175930/go.mod h1:izs11tnkYaT3DTEH2E0V/lCb18VGZ7k9HLYEGuvgXGA=
+github.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0 h1:lxHzxsHd4P7o7+5D5OcEItYkQ1xY3ovNg8Dc5ftd3rI=
+github.com/charmbracelet/log/v2 v2.0.0-20251106192421-eb64aaa963a0/go.mod h1:Q7oMtlboDPnnrYiJDXNwdWmJblOmuOnycPKczlVju6I=
+github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 h1:7Rs87fbKJoIIxsQS8YKJYGYa0tlsDwwb0twQjV1KB+g=
+github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38/go.mod h1:6lfcr3MNP+kZR25sF1nQwJFuQnNYBlFy3PGX5rvslXc=
+github.com/charmbracelet/x/ansi v0.11.1 h1:iXAC8SyMQDJgtcz9Jnw+HU8WMEctHzoTAETIeA3JXMk=
+github.com/charmbracelet/x/ansi v0.11.1/go.mod h1:M49wjzpIujwPceJ+t5w3qh2i87+HRtHohgb5iTyepL0=
 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250708181618-a60a724ba6c3 h1:1xwHZg6eMZ9Wv5TE1UGub6ARubyOd1Lo5kPUI/6VL50=
 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250708181618-a60a724ba6c3/go.mod h1:T9jr8CzFpjhFVHjNjKwbAD7KwBNyFnj2pntAO7F2zw0=
 github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
 github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
 github.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE=
 github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8=
-github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5 h1:DTSZxdV9qQagD4iGcAt9RgaRBZtJl01bfKgdLzUzUPI=
-github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms=
+github.com/charmbracelet/x/exp/slice v0.0.0-20251113172435-cef867b85f6a h1:+mXWbAiS5wNq8VvUd+/P4STqdu2dLtCe9sFr9IqdPDk=
+github.com/charmbracelet/x/exp/slice v0.0.0-20251113172435-cef867b85f6a/go.mod h1:vqEfX6xzqW1pKKZUUiFOKg0OQ7bCh54Q2vR/tserrRA=
 github.com/charmbracelet/x/json v0.2.0 h1:DqB+ZGx2h+Z+1s98HOuOyli+i97wsFQIxP2ZQANTPrQ=
 github.com/charmbracelet/x/json v0.2.0/go.mod h1:opFIflx2YgXgi49xVUu8gEQ21teFAxyMwvOiZhIvWNM=
 github.com/charmbracelet/x/powernap v0.0.0-20251015113943-25f979b54ad4 h1:i/XilBPYK4L1Yo/mc9FPx0SyJzIsN0y4sj1MWq9Sscc=
@@ -120,8 +120,8 @@ github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8
 github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
 github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
 github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
-github.com/clipperhouse/displaywidth v0.4.1 h1:uVw9V8UDfnggg3K2U84VWY1YLQ/x2aKSCtkRyYozfoU=
-github.com/clipperhouse/displaywidth v0.4.1/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
+github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
+github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
 github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
 github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
 github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
@@ -147,6 +147,8 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
 github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
 github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
+github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
 github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
 github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -158,6 +160,8 @@ github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7
 github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
 github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
 github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
+github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
 github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
 github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -191,6 +195,12 @@ github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uO
 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/kaptinlin/go-i18n v0.2.0 h1:8iwjAERQbCVF78c3HxC4MxUDxDRFvQVQlMDvlsO43hU=
+github.com/kaptinlin/go-i18n v0.2.0/go.mod h1:gRHEMrTHtQLsAFwulPbJG71TwHjXxkagn88O8FI8FuA=
+github.com/kaptinlin/jsonschema v0.5.2 h1:ipUBEv1/RnT+ErwdqXZ3Xtwkwp6uqp/Q9lFILrwhUfc=
+github.com/kaptinlin/jsonschema v0.5.2/go.mod h1:HuWb90460GwFxRe0i9Ni3Z7YXwkjpqjeccWTB9gTZZE=
+github.com/kaptinlin/messageformat-go v0.4.6 h1:57DUC9en40mGZR7MvqOS+5EYogAl465fjo+loAA1KPg=
+github.com/kaptinlin/messageformat-go v0.4.6/go.mod h1:r0PH7FsxJX8jS/n6LAYZon5w3X+yfCLUrquqYd2H7ks=
 github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
 github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
 github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
@@ -234,8 +244,8 @@ github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
 github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
 github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
 github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
-github.com/ncruces/go-sqlite3 v0.29.1 h1:NIi8AISWBToRHyoz01FXiTNvU147Tqdibgj2tFzJCqM=
-github.com/ncruces/go-sqlite3 v0.29.1/go.mod h1:PpccBNNhvjwUOwDQEn2gXQPFPTWdlromj0+fSkd5KSg=
+github.com/ncruces/go-sqlite3 v0.30.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
+github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
 github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
 github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
 github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
@@ -246,6 +256,8 @@ github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
 github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
 github.com/openai/openai-go/v2 v2.7.1 h1:/tfvTJhfv7hTSL8mWwc5VL4WLLSDL5yn9VqVykdu9r8=
 github.com/openai/openai-go/v2 v2.7.1/go.mod h1:jrJs23apqJKKbT+pqtFgNKpRju/KP9zpUTZhz3GElQE=
+github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
+github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
 github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
 github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
@@ -294,8 +306,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
-github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
+github.com/tetratelabs/wazero v1.10.0 h1:CXP3zneLDl6J4Zy8N/J+d5JsWKfrjE6GtvVK1fpnDlk=
+github.com/tetratelabs/wazero v1.10.0/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
 github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
 github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
 github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -344,8 +356,8 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx
 go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
-go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
+go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
+go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
@@ -353,8 +365,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
 golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
-golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
-golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
+golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
+golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
 golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
@@ -375,10 +387,10 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
-golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
-golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
-golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
-golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
+golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
+golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
+golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -386,8 +398,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -403,8 +415,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -416,8 +428,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
-golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
-golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
+golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
+golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -427,8 +439,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
 golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
 golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -437,13 +449,13 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
-golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
-golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
+golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo=
 google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
-google.golang.org/genai v1.33.1-0.20251103191629-d15baab4f79e h1:pGBT6ptC4ENtN9wA4dGhvjwrYpVZ6X9Lnpwu4Y+jozk=
-google.golang.org/genai v1.33.1-0.20251103191629-d15baab4f79e/go.mod h1:7pAilaICJlQBonjKKJNhftDFv3SREhZcTe9F6nRcjbg=
+google.golang.org/genai v1.34.0 h1:lPRJRO+HqRX1SwFo1Xb/22nZ5MBEPUbXDl61OoDxlbY=
+google.golang.org/genai v1.34.0/go.mod h1:7pAilaICJlQBonjKKJNhftDFv3SREhZcTe9F6nRcjbg=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
 google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
@@ -454,8 +466,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20250923044825-7b4892dd3117 h1:fbE/sTnBb9UNfE8cJsOzrYYPqVWVHb7jWH4SI1W//cM=
-gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20250923044825-7b4892dd3117/go.mod h1:YuVT9NPq7t3oT2WpUemB0DbNL7djIjgajZycxoDLnqs=
+gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20251110073552-01de4eb40290 h1:g3ah7zaWmw41EtOgBNXpx8zk4HYuH3OMwB+qh1Dt834=
+gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20251110073552-01de4eb40290/go.mod h1:sbq5oMEcM4PXngbcNbHhzfCP9OdZodLhrbRYoyg09HY=
 gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
 gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=

internal/agent/agent.go πŸ”—

@@ -8,6 +8,7 @@
 package agent
 
 import (
+	"cmp"
 	"context"
 	_ "embed"
 	"errors"
@@ -32,6 +33,7 @@ import (
 	"github.com/charmbracelet/crush/internal/message"
 	"github.com/charmbracelet/crush/internal/permission"
 	"github.com/charmbracelet/crush/internal/session"
+	"github.com/charmbracelet/crush/internal/stringext"
 )
 
 //go:embed templates/title.md
@@ -303,7 +305,7 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy
 			currentAssistant.AddToolCall(toolCall)
 			return a.messages.Update(genCtx, *currentAssistant)
 		},
-		OnRetry: func(err *fantasy.APICallError, delay time.Duration) {
+		OnRetry: func(err *fantasy.ProviderError, delay time.Duration) {
 			// TODO: implement
 		},
 		OnToolCall: func(tc fantasy.ToolCallContent) error {
@@ -459,12 +461,19 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy
 				return nil, createErr
 			}
 		}
+		var fantasyErr *fantasy.Error
+		var providerErr *fantasy.ProviderError
+		const defaultTitle = "Provider Error"
 		if isCancelErr {
 			currentAssistant.AddFinish(message.FinishReasonCanceled, "User canceled request", "")
 		} else if isPermissionErr {
 			currentAssistant.AddFinish(message.FinishReasonPermissionDenied, "User denied permission", "")
+		} else if errors.As(err, &providerErr) {
+			currentAssistant.AddFinish(message.FinishReasonError, cmp.Or(stringext.Capitalize(providerErr.Title), defaultTitle), providerErr.Message)
+		} else if errors.As(err, &fantasyErr) {
+			currentAssistant.AddFinish(message.FinishReasonError, cmp.Or(stringext.Capitalize(fantasyErr.Title), defaultTitle), fantasyErr.Message)
 		} else {
-			currentAssistant.AddFinish(message.FinishReasonError, "API Error", err.Error())
+			currentAssistant.AddFinish(message.FinishReasonError, defaultTitle, err.Error())
 		}
 		// Note: we use the parent context here because the genCtx has been
 		// cancelled.

internal/agent/agent_test.go πŸ”—

@@ -8,12 +8,11 @@ import (
 	"testing"
 
 	"charm.land/fantasy"
+	"charm.land/x/vcr"
 	"github.com/charmbracelet/crush/internal/agent/tools"
 	"github.com/charmbracelet/crush/internal/message"
-	"github.com/charmbracelet/crush/internal/shell"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
-	"gopkg.in/dnaeon/go-vcr.v4/pkg/recorder"
 
 	_ "github.com/joho/godotenv/autoload"
 )
@@ -25,7 +24,7 @@ var modelPairs = []modelPair{
 	{"zai-glm4.6", zAIBuilder("glm-4.6"), zAIBuilder("glm-4.5-air")},
 }
 
-func getModels(t *testing.T, r *recorder.Recorder, pair modelPair) (fantasy.LanguageModel, fantasy.LanguageModel) {
+func getModels(t *testing.T, r *vcr.Recorder, pair modelPair) (fantasy.LanguageModel, fantasy.LanguageModel) {
 	large, err := pair.largeModel(t, r)
 	require.NoError(t, err)
 	small, err := pair.smallModel(t, r)
@@ -34,13 +33,12 @@ func getModels(t *testing.T, r *recorder.Recorder, pair modelPair) (fantasy.Lang
 }
 
 func setupAgent(t *testing.T, pair modelPair) (SessionAgent, fakeEnv) {
-	r := newRecorder(t)
+	r := vcr.NewRecorder(t)
 	large, small := getModels(t, r, pair)
 	env := testEnv(t)
 
 	createSimpleGoProject(t, env.workingDir)
 	agent, err := coderAgent(r, env, large, small)
-	shell.Reset(env.workingDir)
 	require.NoError(t, err)
 	return agent, env
 }

internal/agent/common_test.go πŸ”—

@@ -13,6 +13,7 @@ import (
 	"charm.land/fantasy/providers/openai"
 	"charm.land/fantasy/providers/openaicompat"
 	"charm.land/fantasy/providers/openrouter"
+	"charm.land/x/vcr"
 	"github.com/charmbracelet/catwalk/pkg/catwalk"
 	"github.com/charmbracelet/crush/internal/agent/prompt"
 	"github.com/charmbracelet/crush/internal/agent/tools"
@@ -25,7 +26,6 @@ import (
 	"github.com/charmbracelet/crush/internal/permission"
 	"github.com/charmbracelet/crush/internal/session"
 	"github.com/stretchr/testify/require"
-	"gopkg.in/dnaeon/go-vcr.v4/pkg/recorder"
 
 	_ "github.com/joho/godotenv/autoload"
 )
@@ -40,7 +40,7 @@ type fakeEnv struct {
 	lspClients  *csync.Map[string, *lsp.Client]
 }
 
-type builderFunc func(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error)
+type builderFunc func(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error)
 
 type modelPair struct {
 	name       string
@@ -49,7 +49,7 @@ type modelPair struct {
 }
 
 func anthropicBuilder(model string) builderFunc {
-	return func(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error) {
+	return func(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
 		provider, err := anthropic.New(
 			anthropic.WithAPIKey(os.Getenv("CRUSH_ANTHROPIC_API_KEY")),
 			anthropic.WithHTTPClient(&http.Client{Transport: r}),
@@ -62,7 +62,7 @@ func anthropicBuilder(model string) builderFunc {
 }
 
 func openaiBuilder(model string) builderFunc {
-	return func(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error) {
+	return func(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
 		provider, err := openai.New(
 			openai.WithAPIKey(os.Getenv("CRUSH_OPENAI_API_KEY")),
 			openai.WithHTTPClient(&http.Client{Transport: r}),
@@ -75,7 +75,7 @@ func openaiBuilder(model string) builderFunc {
 }
 
 func openRouterBuilder(model string) builderFunc {
-	return func(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error) {
+	return func(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
 		provider, err := openrouter.New(
 			openrouter.WithAPIKey(os.Getenv("CRUSH_OPENROUTER_API_KEY")),
 			openrouter.WithHTTPClient(&http.Client{Transport: r}),
@@ -88,7 +88,7 @@ func openRouterBuilder(model string) builderFunc {
 }
 
 func zAIBuilder(model string) builderFunc {
-	return func(t *testing.T, r *recorder.Recorder) (fantasy.LanguageModel, error) {
+	return func(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
 		provider, err := openaicompat.New(
 			openaicompat.WithBaseURL("https://api.z.ai/api/coding/paas/v4"),
 			openaicompat.WithAPIKey(os.Getenv("CRUSH_ZAI_API_KEY")),
@@ -153,7 +153,7 @@ func testSessionAgent(env fakeEnv, large, small fantasy.LanguageModel, systemPro
 	return agent
 }
 
-func coderAgent(r *recorder.Recorder, env fakeEnv, large, small fantasy.LanguageModel) (SessionAgent, error) {
+func coderAgent(r *vcr.Recorder, env fakeEnv, large, small fantasy.LanguageModel) (SessionAgent, error) {
 	fixedTime := func() time.Time {
 		t, _ := time.Parse("1/2/2006", "1/1/2025")
 		return t
@@ -171,12 +171,26 @@ func coderAgent(r *recorder.Recorder, env fakeEnv, large, small fantasy.Language
 		return nil, err
 	}
 
+	// NOTE(@andreynering): Set a fixed config to ensure cassettes match
+	// independently of user config on `$HOME/.config/crush/crush.json`.
+	cfg.Options.Attribution = &config.Attribution{
+		TrailerStyle:  "co-authored-by",
+		GeneratedWith: true,
+	}
+
 	systemPrompt, err := prompt.Build(context.TODO(), large.Provider(), large.Model(), *cfg)
 	if err != nil {
 		return nil, err
 	}
+
+	// Get the model name for the bash tool
+	modelName := large.Model() // fallback to ID if Name not available
+	if model := cfg.GetModel(large.Provider(), large.Model()); model != nil {
+		modelName = model.Name
+	}
+
 	allTools := []fantasy.AgentTool{
-		tools.NewBashTool(env.permissions, env.workingDir, cfg.Options.Attribution),
+		tools.NewBashTool(env.permissions, env.workingDir, cfg.Options.Attribution, modelName),
 		tools.NewDownloadTool(env.permissions, env.workingDir, r.GetDefaultClient()),
 		tools.NewEditTool(env.lspClients, env.permissions, env.history, env.workingDir),
 		tools.NewMultiEditTool(env.lspClients, env.permissions, env.history, env.workingDir),

internal/agent/coordinator.go πŸ”—

@@ -327,8 +327,18 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
 		allTools = append(allTools, agenticFetchTool)
 	}
 
+	// Get the model name for the agent
+	modelName := ""
+	if modelCfg, ok := c.cfg.Models[agent.Model]; ok {
+		if model := c.cfg.GetModel(modelCfg.Provider, modelCfg.Model); model != nil {
+			modelName = model.Name
+		}
+	}
+
 	allTools = append(allTools,
-		tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Options.Attribution),
+		tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Options.Attribution, modelName),
+		tools.NewJobOutputTool(),
+		tools.NewJobKillTool(),
 		tools.NewDownloadTool(c.permissions, c.cfg.WorkingDir(), nil),
 		tools.NewEditTool(c.lspClients, c.permissions, c.history, c.cfg.WorkingDir()),
 		tools.NewMultiEditTool(c.lspClients, c.permissions, c.history, c.cfg.WorkingDir()),
@@ -360,7 +370,7 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
 		}
 		if len(agent.AllowedMCP) == 0 {
 			// No MCPs allowed
-			slog.Warn("MCPs not allowed")
+			slog.Debug("no MCPs allowed", "tool", tool.Name(), "agent", agent.Name)
 			break
 		}
 
@@ -372,6 +382,7 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
 				filteredTools = append(filteredTools, tool)
 			}
 		}
+		slog.Debug("MCP not allowed", "tool", tool.Name(), "agent", agent.Name)
 	}
 	slices.SortFunc(filteredTools, func(a, b fantasy.AgentTool) int {
 		return strings.Compare(a.Info().Name, b.Info().Name)

internal/agent/prompts.go πŸ”—

@@ -1,9 +1,11 @@
 package agent
 
 import (
+	"context"
 	_ "embed"
 
 	"github.com/charmbracelet/crush/internal/agent/prompt"
+	"github.com/charmbracelet/crush/internal/config"
 )
 
 //go:embed templates/coder.md.tpl
@@ -12,8 +14,8 @@ var coderPromptTmpl []byte
 //go:embed templates/task.md.tpl
 var taskPromptTmpl []byte
 
-//go:embed templates/initialize.md
-var initializePrompt []byte
+//go:embed templates/initialize.md.tpl
+var initializePromptTmpl []byte
 
 func coderPrompt(opts ...prompt.Option) (*prompt.Prompt, error) {
 	systemPrompt, err := prompt.NewPrompt("coder", string(coderPromptTmpl), opts...)
@@ -31,6 +33,10 @@ func taskPrompt(opts ...prompt.Option) (*prompt.Prompt, error) {
 	return systemPrompt, nil
 }
 
-func InitializePrompt() string {
-	return string(initializePrompt)
+func InitializePrompt(cfg config.Config) (string, error) {
+	systemPrompt, err := prompt.NewPrompt("initialize", string(initializePromptTmpl))
+	if err != nil {
+		return "", err
+	}
+	return systemPrompt.Build(context.Background(), "", "", cfg)
 }

internal/agent/recorder_test.go πŸ”—

@@ -1,116 +0,0 @@
-package agent
-
-import (
-	"bytes"
-	"encoding/json"
-	"io"
-	"net/http"
-	"path/filepath"
-	"reflect"
-	"strings"
-	"testing"
-
-	"go.yaml.in/yaml/v4"
-	"gopkg.in/dnaeon/go-vcr.v4/pkg/cassette"
-	"gopkg.in/dnaeon/go-vcr.v4/pkg/recorder"
-)
-
-func newRecorder(t *testing.T) *recorder.Recorder {
-	cassetteName := filepath.Join("testdata", t.Name())
-
-	r, err := recorder.New(
-		cassetteName,
-		recorder.WithMode(recorder.ModeRecordOnce),
-		recorder.WithMatcher(customMatcher(t)),
-		recorder.WithMarshalFunc(marshalFunc),
-		recorder.WithSkipRequestLatency(true), // disable sleep to simulate response time, makes tests faster
-		recorder.WithHook(hookRemoveHeaders, recorder.AfterCaptureHook),
-	)
-	if err != nil {
-		t.Fatalf("recorder: failed to create recorder: %v", err)
-	}
-
-	t.Cleanup(func() {
-		if err := r.Stop(); err != nil {
-			t.Errorf("recorder: failed to stop recorder: %v", err)
-		}
-	})
-
-	return r
-}
-
-func customMatcher(t *testing.T) recorder.MatcherFunc {
-	return func(r *http.Request, i cassette.Request) bool {
-		if r.Body == nil || r.Body == http.NoBody {
-			return cassette.DefaultMatcher(r, i)
-		}
-		if r.Method != i.Method || r.URL.String() != i.URL {
-			return false
-		}
-
-		reqBody, err := io.ReadAll(r.Body)
-		if err != nil {
-			t.Fatalf("recorder: failed to read request body")
-		}
-		r.Body.Close()
-		r.Body = io.NopCloser(bytes.NewBuffer(reqBody))
-
-		// Some providers can sometimes generate JSON requests with keys in
-		// a different order, which means a direct string comparison will fail.
-		// Falling back to deserializing the content if we don't have a match.
-		requestContent := normalizeLineEndings(reqBody)
-		cassetteContent := normalizeLineEndings(i.Body)
-		if requestContent == cassetteContent {
-			return true
-		}
-		var content1, content2 any
-		if err := json.Unmarshal([]byte(requestContent), &content1); err != nil {
-			return false
-		}
-		if err := json.Unmarshal([]byte(cassetteContent), &content2); err != nil {
-			return false
-		}
-		return reflect.DeepEqual(content1, content2)
-	}
-}
-
-func marshalFunc(in any) ([]byte, error) {
-	var buff bytes.Buffer
-	enc := yaml.NewEncoder(&buff)
-	enc.SetIndent(2)
-	enc.CompactSeqIndent()
-	if err := enc.Encode(in); err != nil {
-		return nil, err
-	}
-	return buff.Bytes(), nil
-}
-
-var headersToKeep = map[string]struct{}{
-	"accept":       {},
-	"content-type": {},
-	"user-agent":   {},
-}
-
-func hookRemoveHeaders(i *cassette.Interaction) error {
-	for k := range i.Request.Headers {
-		if _, ok := headersToKeep[strings.ToLower(k)]; !ok {
-			delete(i.Request.Headers, k)
-		}
-	}
-	for k := range i.Response.Headers {
-		if _, ok := headersToKeep[strings.ToLower(k)]; !ok {
-			delete(i.Response.Headers, k)
-		}
-	}
-	return nil
-}
-
-// normalizeLineEndings does not only replace `\r\n` into `\n`,
-// but also replaces `\\r\\n` into `\\n`. That's because we want the content
-// inside JSON string to be replaces as well.
-func normalizeLineEndings[T string | []byte](s T) string {
-	str := string(s)
-	str = strings.ReplaceAll(str, "\r\n", "\n")
-	str = strings.ReplaceAll(str, `\r\n`, `\n`)
-	return str
-}

internal/agent/templates/coder.md.tpl πŸ”—

@@ -267,6 +267,7 @@ After significant changes:
 - Run tools in parallel when safe (no dependencies)
 - When making multiple independent bash calls, send them in a single message with multiple tool calls for parallel execution
 - Summarize tool output for user (they don't see it)
+- Never use `curl` through the bash tool it is not allowed use the fetch tool instead.
 
 <bash_commands>
 When running non-trivial bash commands (especially those that modify the system):

internal/agent/templates/initialize.md β†’ internal/agent/templates/initialize.md.tpl πŸ”—

@@ -1,6 +1,6 @@
-Analyze this codebase and create/update **CRUSH.md** to help future agents work effectively in this repository.
+Analyze this codebase and create/update **{{.Config.Options.InitializeAs}}** to help future agents work effectively in this repository.
 
-**First**: Check if directory is empty or contains only config files. If so, stop and say "Directory appears empty or only contains config. Add source code first, then run this command to generate CRUSH.md."
+**First**: Check if directory is empty or contains only config files. If so, stop and say "Directory appears empty or only contains config. Add source code first, then run this command to generate {{.Config.Options.InitializeAs}}."
 
 **Goal**: Document what an agent needs to know to work in this codebase - commands, patterns, conventions, gotchas.
 
@@ -11,7 +11,7 @@ Analyze this codebase and create/update **CRUSH.md** to help future agents work
 3. Identify project type from config files and directory structure
 4. Find build/test/lint commands from config files, scripts, Makefiles, or CI configs
 5. Read representative source files to understand code patterns
-6. If CRUSH.md exists, read and improve it
+6. If {{.Config.Options.InitializeAs}} exists, read and improve it
 
 **Content to include**:
 

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/bash_tool.yaml πŸ”—

@@ -25,52 +25,49 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01GbNg6Ry9zebpJZ4SxbcBWA","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":152,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}              }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01Q9dscSikE4wKde4Li2gBwH","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":152,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":3,"service_tier":"standard"}}    }
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}            }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}        }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Creating"}               }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Bash File"}              }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" a bash"}            }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Creation"}  }
 
       event: ping
       data: {"type": "ping"}
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" file"}            }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Comman"}}
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" with hello"}      }
-
-      event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" message"}           }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d"}            }
 
       event: content_block_stop
-      data: {"type":"content_block_stop","index":0        }
+      data: {"type":"content_block_stop","index":0   }
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":152,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":10} }
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":152,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":8}     }
 
       event: message_stop
-      data: {"type":"message_stop"}
+      data: {"type":"message_stop"             }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 1.497832667s
+    duration: 649.141458ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46761
+    content_length: 48499
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/download_tool.yaml πŸ”—

@@ -25,52 +25,49 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01UXxqRskCGYn8pLqqgaFbfK","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":160,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}             }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_011BBMjWTNiVsUoGcaMCabtR","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":160,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}   }
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}            }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}     }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Downloa"}              }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Downloa"} }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d File"}               }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d File"}            }
 
       event: ping
       data: {"type": "ping"}
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" from"}            }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" from"}       }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Example-"}         }
-
-      event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Files Website"}     }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Example URL"}    }
 
       event: content_block_stop
-      data: {"type":"content_block_stop","index":0    }
+      data: {"type":"content_block_stop","index":0         }
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":160,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":10}           }
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":160,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":8}     }
 
       event: message_stop
-      data: {"type":"message_stop"          }
+      data: {"type":"message_stop"  }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 1.152031084s
+    duration: 1.132761291s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46786
+    content_length: 48524
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/fetch_tool.yaml πŸ”—

@@ -25,52 +25,55 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01QBUSTN71eh95F8NGHNVJ7r","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":167,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}        }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_0182u7NoaqXjs73QApLGhQea","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":167,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}}
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}               }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}  }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Web"}       }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Web"}           }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Page Content Search"}         }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Content"}  }
 
       event: ping
       data: {"type": "ping"}
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" for"}           }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Search"}    }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" John"}           }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" for"}               }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Doe"}     }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" John"}               }
+
+      event: content_block_delta
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Doe"}}
 
       event: content_block_stop
-      data: {"type":"content_block_stop","index":0    }
+      data: {"type":"content_block_stop","index":0         }
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":167,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":11}   }
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":167,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":10}        }
 
       event: message_stop
-      data: {"type":"message_stop"            }
+      data: {"type":"message_stop"   }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 840.714166ms
+    duration: 557.634834ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46804
+    content_length: 48542
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/glob_tool.yaml πŸ”—

@@ -25,52 +25,49 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_014RSxCeV1UGnPSQkNpEQTUq","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":142,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}            }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01VUjaaTY5YMVyC4o4S9hYzE","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":142,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}   }
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}              }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}     }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Fin"}}
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Fin"}    }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d Go"}         }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d Go files with"}        }
 
       event: ping
       data: {"type": "ping"}
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Files"}         }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" glob"}    }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" with"}}
-
-      event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Glob"}    }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" pattern"}             }
 
       event: content_block_stop
-      data: {"type":"content_block_stop","index":0}
+      data: {"type":"content_block_stop","index":0   }
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":142,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":9}             }
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":142,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":9}   }
 
       event: message_stop
-      data: {"type":"message_stop"         }
+      data: {"type":"message_stop" }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 546.158083ms
+    duration: 1.197842875s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46722
+    content_length: 48460
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/grep_tool.yaml πŸ”—

@@ -25,52 +25,52 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01MMg192G5uVZkAaHV6NCB7T","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":144,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":2,"service_tier":"standard"}}  }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01EgibS2fAFz3UCbKFNbqmyz","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":144,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":3,"service_tier":"standard"}}           }
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}              }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}        }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Searching"}        }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Searching "}   }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Packages"}   }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'package' in Go"}          }
 
       event: ping
       data: {"type": "ping"}
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" in Go Files"}     }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Files"}           }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" with"}          }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" with"}               }
 
       event: content_block_delta
       data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Grep"}         }
 
       event: content_block_stop
-      data: {"type":"content_block_stop","index":0              }
+      data: {"type":"content_block_stop","index":0  }
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":144,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":13}    }
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":144,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":15}     }
 
       event: message_stop
-      data: {"type":"message_stop"              }
+      data: {"type":"message_stop" }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 781.230459ms
+    duration: 559.233125ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46720
+    content_length: 48458
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/ls_tool.yaml πŸ”—

@@ -25,22 +25,28 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01B7Qa35Q32jAQNEVkyd7y87","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":140,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":3,"service_tier":"standard"}} }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01P8m2YD9BpcZF8ATFTP5UQa","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":140,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":3,"service_tier":"standard"}}  }
 
       event: content_block_start
       data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}  }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Listing Files"}           }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Listing Files"}   }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" in Current Directory"}       }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" in"}       }
+
+      event: ping
+      data: {"type": "ping"}
+
+      event: content_block_delta
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Current Directory"}        }
 
       event: ping
       data: {"type": "ping"}
 
       event: content_block_stop
-      data: {"type":"content_block_stop","index":0         }
+      data: {"type":"content_block_stop","index":0              }
 
       event: ping
       data: {"type": "ping"}
@@ -49,25 +55,25 @@ interactions:
       data: {"type": "ping"}
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":140,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":9}   }
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":140,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":9}        }
 
       event: message_stop
-      data: {"type":"message_stop"            }
+      data: {"type":"message_stop"               }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 750.596875ms
+    duration: 975.215833ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46714
+    content_length: 48452
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/multiedit_tool.yaml πŸ”—

@@ -25,13 +25,13 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_015YzXs6XaPmruFbmBEQmbzN","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":170,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}          }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01YDEhnTbiQwTk2qsjPe6xWX","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":170,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}              }
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}          }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}         }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Edit"}           }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Edit"}   }
 
       event: content_block_delta
       data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Go"}    }
@@ -40,37 +40,34 @@ interactions:
       data: {"type": "ping"}
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Code"}    }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Code"}         }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Greeting"}        }
-
-      event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Modification"}             }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Greeting"}            }
 
       event: content_block_stop
       data: {"type":"content_block_stop","index":0      }
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":170,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":10}               }
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":170,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":8}          }
 
       event: message_stop
-      data: {"type":"message_stop"        }
+      data: {"type":"message_stop"             }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 728.836709ms
+    duration: 717.238667ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46800
+    content_length: 48538
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/parallel_tool_calls.yaml πŸ”—

@@ -25,40 +25,34 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01PPi2LYDWFX14idF4WAuxdH","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":159,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":2,"service_tier":"standard"}}        }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01NjeYn4nRR6THSVmVnvXJFz","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":159,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":2,"service_tier":"standard"}}     }
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}  }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}              }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Parallel"}}
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Parallel"}           }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" File"}            }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Go File"}     }
 
       event: ping
       data: {"type": "ping"}
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Discovery"}  }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Search"}          }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" with"}     }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" and Directory"}      }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Glob"}            }
-
-      event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" and "}    }
-
-      event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"LS"}   }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Listing"}        }
 
       event: content_block_stop
       data: {"type":"content_block_stop","index":0            }
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":159,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":13}       }
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":159,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":12}    }
 
       event: message_stop
       data: {"type":"message_stop"}
@@ -68,15 +62,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 774.758583ms
+    duration: 927.461292ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46811
+    content_length: 48549
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/read_a_file.yaml πŸ”—

@@ -25,49 +25,49 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_013ygFg57WnsbgYEgWKpUHgg","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":134,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}  }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_018k2WQ7ueajsP9D4wfhzsN6","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":134,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}       }
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}      }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}              }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Rea"}               }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Review"} }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d Go"}             }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Go"} }
 
       event: ping
       data: {"type": "ping"}
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Module"}         }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Module"}             }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Details"}        }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Configuration"}          }
 
       event: content_block_stop
-      data: {"type":"content_block_stop","index":0         }
+      data: {"type":"content_block_stop","index":0            }
 
       event: message_delta
       data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":134,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":7}       }
 
       event: message_stop
-      data: {"type":"message_stop"             }
+      data: {"type":"message_stop"          }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 543.64625ms
+    duration: 662.423375ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46684
+    content_length: 48422
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/simple_test.yaml πŸ”—

@@ -25,22 +25,28 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01EmVyk4hid2PC6VniyWihaR","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":131,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}    }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01782JU37x1h65gkxzPb4q2M","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":131,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":2,"service_tier":"standard"}}            }
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}     }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}       }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Quick"}  }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Greeting"} }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Greeting"}            }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Initiate"}         }
+
+      event: ping
+      data: {"type": "ping"}
+
+      event: content_block_delta
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d"}          }
 
       event: ping
       data: {"type": "ping"}
 
       event: content_block_stop
-      data: {"type":"content_block_stop","index":0            }
+      data: {"type":"content_block_stop","index":0     }
 
       event: ping
       data: {"type": "ping"}
@@ -49,25 +55,25 @@ interactions:
       data: {"type": "ping"}
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":131,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":6}          }
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":131,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":7} }
 
       event: message_stop
-      data: {"type":"message_stop"}
+      data: {"type":"message_stop"        }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 1.063515458s
+    duration: 1.209730625s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46674
+    content_length: 48412
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/sourcegraph_tool.yaml πŸ”—

@@ -25,52 +25,46 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01KYmwLRQ7WjQjA2pbUzEhvU","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":145,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":2,"service_tier":"standard"}} }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_018FLb5xaVnuEGuEXuTJj2MB","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":145,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":3,"service_tier":"standard"}}              }
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}  }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}               }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Go"}       }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Searching Go"}       }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Repo"}          }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Repositories for main"}           }
 
       event: ping
       data: {"type": "ping"}
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Main"}}
-
-      event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Function"}      }
-
-      event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Search"}            }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Functions"}    }
 
       event: content_block_stop
-      data: {"type":"content_block_stop","index":0}
+      data: {"type":"content_block_stop","index":0             }
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":145,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":9}}
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":145,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":11}  }
 
       event: message_stop
-      data: {"type":"message_stop"              }
+      data: {"type":"message_stop"               }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 1.002887292s
+    duration: 605.347625ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46734
+    content_length: 48472
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/update_a_file.yaml πŸ”—

@@ -25,16 +25,16 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01RwCAShrU1kRMXy4cvo5e5t","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":145,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}         }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01B6gHqtZy7HBaGQGB4VeEhq","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":145,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}          }
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}  }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}        }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Update"}        }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Update"}     }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" main"}       }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" main"}}
 
       event: ping
       data: {"type": "ping"}
@@ -43,31 +43,31 @@ interactions:
       data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":".go Hello"}             }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Message"}             }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Greeting"}   }
 
       event: content_block_stop
-      data: {"type":"content_block_stop","index":0       }
+      data: {"type":"content_block_stop","index":0         }
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":145,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":9} }
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":145,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":10} }
 
       event: message_stop
-      data: {"type":"message_stop"     }
+      data: {"type":"message_stop"           }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 682.594041ms
+    duration: 651.076375ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46740
+    content_length: 48478
     host: ""

internal/agent/testdata/TestCoderAgent/anthropic-sonnet/write_tool.yaml πŸ”—

@@ -25,52 +25,46 @@ interactions:
     content_length: -1
     body: |+
       event: message_start
-      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01FXMC8aT9YaPtMV15mGZNsp","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":161,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}} }
+      data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01NvPqt2YUw2WJwqS55hJMEW","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":161,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}}          }
 
       event: content_block_start
-      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}              }
+      data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}       }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Creating"}         }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Creating"}      }
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" config"}        }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" config"}      }
 
       event: ping
       data: {"type": "ping"}
 
       event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":".json with"}               }
-
-      event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" JSON"}           }
-
-      event: content_block_delta
-      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" data"}              }
+      data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":".json file"}             }
 
       event: content_block_stop
-      data: {"type":"content_block_stop","index":0               }
+      data: {"type":"content_block_stop","index":0  }
 
       event: message_delta
-      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":161,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":10}  }
+      data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":161,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":8}         }
 
       event: message_stop
-      data: {"type":"message_stop"}
+      data: {"type":"message_stop"    }
 
     headers:
       Content-Type:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 950.954959ms
+    duration: 643.354917ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 46777
+    content_length: 48515
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/bash_tool.yaml πŸ”—

@@ -24,23 +24,23 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhWs20Zop6sbl4OStZokp9a0vd9d","object":"chat.completion.chunk","created":1761909858,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"pttO1OA8dsQGEG"}
+      data: {"id":"chatcmpl-CbUkKJYGuzDVpaD3ZHvlKMt23Dawc","object":"chat.completion.chunk","created":1763052360,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vp9vN4wPzJiQm0"}
 
-      data: {"id":"chatcmpl-CWhWs20Zop6sbl4OStZokp9a0vd9d","object":"chat.completion.chunk","created":1761909858,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Create"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"0RUeMk1U5O"}
+      data: {"id":"chatcmpl-CbUkKJYGuzDVpaD3ZHvlKMt23Dawc","object":"chat.completion.chunk","created":1763052360,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Create"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"AeUYOEW8Ft"}
 
-      data: {"id":"chatcmpl-CWhWs20Zop6sbl4OStZokp9a0vd9d","object":"chat.completion.chunk","created":1761909858,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"wLi9GRw5fqJ"}
+      data: {"id":"chatcmpl-CbUkKJYGuzDVpaD3ZHvlKMt23Dawc","object":"chat.completion.chunk","created":1763052360,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"0qdTPaws0uc"}
 
-      data: {"id":"chatcmpl-CWhWs20Zop6sbl4OStZokp9a0vd9d","object":"chat.completion.chunk","created":1761909858,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"21x6SCO5rY2"}
+      data: {"id":"chatcmpl-CbUkKJYGuzDVpaD3ZHvlKMt23Dawc","object":"chat.completion.chunk","created":1763052360,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"zb56Y0oW0dl"}
 
-      data: {"id":"chatcmpl-CWhWs20Zop6sbl4OStZokp9a0vd9d","object":"chat.completion.chunk","created":1761909858,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Content"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"0nwcSIkE"}
+      data: {"id":"chatcmpl-CbUkKJYGuzDVpaD3ZHvlKMt23Dawc","object":"chat.completion.chunk","created":1763052360,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Content"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"fDzTlp5H"}
 
-      data: {"id":"chatcmpl-CWhWs20Zop6sbl4OStZokp9a0vd9d","object":"chat.completion.chunk","created":1761909858,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"txtgsExxlu5Ep"}
+      data: {"id":"chatcmpl-CbUkKJYGuzDVpaD3ZHvlKMt23Dawc","object":"chat.completion.chunk","created":1763052360,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Using"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Zhj73X8thj"}
 
-      data: {"id":"chatcmpl-CWhWs20Zop6sbl4OStZokp9a0vd9d","object":"chat.completion.chunk","created":1761909858,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Bash"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"SouwjjVL0HG"}
+      data: {"id":"chatcmpl-CbUkKJYGuzDVpaD3ZHvlKMt23Dawc","object":"chat.completion.chunk","created":1763052360,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Bash"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Q5cojZq6cl9"}
 
-      data: {"id":"chatcmpl-CWhWs20Zop6sbl4OStZokp9a0vd9d","object":"chat.completion.chunk","created":1761909858,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"UIjY7l3Fq2"}
+      data: {"id":"chatcmpl-CbUkKJYGuzDVpaD3ZHvlKMt23Dawc","object":"chat.completion.chunk","created":1763052360,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"1reReQTAAX"}
 
-      data: {"id":"chatcmpl-CWhWs20Zop6sbl4OStZokp9a0vd9d","object":"chat.completion.chunk","created":1761909858,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":145,"completion_tokens":6,"total_tokens":151,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"FzDWgx8tnMDVXf"}
+      data: {"id":"chatcmpl-CbUkKJYGuzDVpaD3ZHvlKMt23Dawc","object":"chat.completion.chunk","created":1763052360,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":145,"completion_tokens":6,"total_tokens":151,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"etlTfn3EZP8MT2"}
 
       data: [DONE]
 
@@ -49,15 +49,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 526.4125ms
+    duration: 1.298479333s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45186
+    content_length: 46899
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/download_tool.yaml πŸ”—

@@ -24,23 +24,25 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhX9tjyqID6dcImwmtScsTH2gMCA","object":"chat.completion.chunk","created":1761909875,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"pRl8hi4mKyV7v4"}
+      data: {"id":"chatcmpl-CbUkbu7jI2EzIMSnAGose7GG7z6Dy","object":"chat.completion.chunk","created":1763052377,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"EStgbdvZeBtZum"}
 
-      data: {"id":"chatcmpl-CWhX9tjyqID6dcImwmtScsTH2gMCA","object":"chat.completion.chunk","created":1761909875,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Download"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"6Ty98YpI"}
+      data: {"id":"chatcmpl-CbUkbu7jI2EzIMSnAGose7GG7z6Dy","object":"chat.completion.chunk","created":1763052377,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":"Downloading"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"q8oyR"}
 
-      data: {"id":"chatcmpl-CWhX9tjyqID6dcImwmtScsTH2gMCA","object":"chat.completion.chunk","created":1761909875,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"9QyOAidqj1g1"}
+      data: {"id":"chatcmpl-CbUkbu7jI2EzIMSnAGose7GG7z6Dy","object":"chat.completion.chunk","created":1763052377,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2owvn8JFBE9w"}
 
-      data: {"id":"chatcmpl-CWhX9tjyqID6dcImwmtScsTH2gMCA","object":"chat.completion.chunk","created":1761909875,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Save"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vN0Iq39KAX0"}
+      data: {"id":"chatcmpl-CbUkbu7jI2EzIMSnAGose7GG7z6Dy","object":"chat.completion.chunk","created":1763052377,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" Saving"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"A3w0rmCj2"}
 
-      data: {"id":"chatcmpl-CWhX9tjyqID6dcImwmtScsTH2gMCA","object":"chat.completion.chunk","created":1761909875,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"SUk4BJZ9jFVGsF"}
+      data: {"id":"chatcmpl-CbUkbu7jI2EzIMSnAGose7GG7z6Dy","object":"chat.completion.chunk","created":1763052377,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"NNLouoRfcxUlf9"}
 
-      data: {"id":"chatcmpl-CWhX9tjyqID6dcImwmtScsTH2gMCA","object":"chat.completion.chunk","created":1761909875,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Text"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"9Ce03nGGaDK"}
+      data: {"id":"chatcmpl-CbUkbu7jI2EzIMSnAGose7GG7z6Dy","object":"chat.completion.chunk","created":1763052377,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"XaIPLdgeHiI"}
 
-      data: {"id":"chatcmpl-CWhX9tjyqID6dcImwmtScsTH2gMCA","object":"chat.completion.chunk","created":1761909875,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"GYeSvnujasB"}
+      data: {"id":"chatcmpl-CbUkbu7jI2EzIMSnAGose7GG7z6Dy","object":"chat.completion.chunk","created":1763052377,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" from"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"hx9xrsJiu6x"}
 
-      data: {"id":"chatcmpl-CWhX9tjyqID6dcImwmtScsTH2gMCA","object":"chat.completion.chunk","created":1761909875,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"tW1ZfW5I0o"}
+      data: {"id":"chatcmpl-CbUkbu7jI2EzIMSnAGose7GG7z6Dy","object":"chat.completion.chunk","created":1763052377,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" URL"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"AH5Ug4eV9vAD"}
 
-      data: {"id":"chatcmpl-CWhX9tjyqID6dcImwmtScsTH2gMCA","object":"chat.completion.chunk","created":1761909875,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":148,"completion_tokens":6,"total_tokens":154,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"KCYAxQFjHWFd2L"}
+      data: {"id":"chatcmpl-CbUkbu7jI2EzIMSnAGose7GG7z6Dy","object":"chat.completion.chunk","created":1763052377,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"aQ1TxrNv5B"}
+
+      data: {"id":"chatcmpl-CbUkbu7jI2EzIMSnAGose7GG7z6Dy","object":"chat.completion.chunk","created":1763052377,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[],"usage":{"prompt_tokens":148,"completion_tokens":7,"total_tokens":155,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"fogROHPA9AQhgg"}
 
       data: [DONE]
 
@@ -49,15 +51,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 515.942833ms
+    duration: 2.314738042s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45211
+    content_length: 46924
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/fetch_tool.yaml πŸ”—

@@ -24,25 +24,31 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhXOu4oZ9WdLk0MqlX7ErUyg1LEH","object":"chat.completion.chunk","created":1761909890,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"NmqSJ50vbN4r42"}
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"NZCbEacyPz4pwr"}
 
-      data: {"id":"chatcmpl-CWhXOu4oZ9WdLk0MqlX7ErUyg1LEH","object":"chat.completion.chunk","created":1761909890,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Check"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"sr4OJu3lLyu"}
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":"Check"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"kbBkMxwYVJB"}
 
-      data: {"id":"chatcmpl-CWhXOu4oZ9WdLk0MqlX7ErUyg1LEH","object":"chat.completion.chunk","created":1761909890,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" HTML"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KmaFu3GGouR"}
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" for"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"cwJeadkIQUKl"}
 
-      data: {"id":"chatcmpl-CWhXOu4oZ9WdLk0MqlX7ErUyg1LEH","object":"chat.completion.chunk","created":1761909890,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" for"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"H245qWL5SGiq"}
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"7KS6CI22E2tPaK"}
 
-      data: {"id":"chatcmpl-CWhXOu4oZ9WdLk0MqlX7ErUyg1LEH","object":"chat.completion.chunk","created":1761909890,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" John"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ZUvzYSFf8lB"}
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":"John"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"AfZPoUI8y91v"}
 
-      data: {"id":"chatcmpl-CWhXOu4oZ9WdLk0MqlX7ErUyg1LEH","object":"chat.completion.chunk","created":1761909890,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Doe"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"s8QJh8k4MHNa"}
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" Doe"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"LpbtDnUHQU9A"}
 
-      data: {"id":"chatcmpl-CWhXOu4oZ9WdLk0MqlX7ErUyg1LEH","object":"chat.completion.chunk","created":1761909890,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Occ"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"5isKdqUb0HOD"}
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"qiOyYT2y7EJdq6r"}
 
-      data: {"id":"chatcmpl-CWhXOu4oZ9WdLk0MqlX7ErUyg1LEH","object":"chat.completion.chunk","created":1761909890,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"urrence"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ghVyn4TaC"}
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"qD9sjQcHygh7R"}
 
-      data: {"id":"chatcmpl-CWhXOu4oZ9WdLk0MqlX7ErUyg1LEH","object":"chat.completion.chunk","created":1761909890,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"IQcLjlqYzI"}
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" Example"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"xvzFm7Kl"}
 
-      data: {"id":"chatcmpl-CWhXOu4oZ9WdLk0MqlX7ErUyg1LEH","object":"chat.completion.chunk","created":1761909890,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":153,"completion_tokens":7,"total_tokens":160,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"4FTCmELUOWr1y1"}
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" HTML"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"nSTOMJdQbip"}
+
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" Content"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ez1TEH3o"}
+
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"Kpu1VTQLvM"}
+
+      data: {"id":"chatcmpl-CbUkjR7GpjLIFNyNRNmiL40SSTxxH","object":"chat.completion.chunk","created":1763052385,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[],"usage":{"prompt_tokens":153,"completion_tokens":10,"total_tokens":163,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"5gaF7n1tbADaH"}
 
       data: [DONE]
 
@@ -51,15 +57,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 532.60175ms
+    duration: 715.834959ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45229
+    content_length: 46942
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/glob_tool.yaml πŸ”—

@@ -24,29 +24,25 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"nUGlLYeV4PG3P7"}
+      data: {"id":"chatcmpl-CbUkwKSNynPTu1uyNioPPtws6jh8X","object":"chat.completion.chunk","created":1763052398,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Mh4DoLcVO4xS0h"}
 
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Finding"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"aPwkd1hKI"}
+      data: {"id":"chatcmpl-CbUkwKSNynPTu1uyNioPPtws6jh8X","object":"chat.completion.chunk","created":1763052398,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Finding"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"iwqeMF4Bp"}
 
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" ."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lzKg5YVSFHVwma"}
+      data: {"id":"chatcmpl-CbUkwKSNynPTu1uyNioPPtws6jh8X","object":"chat.completion.chunk","created":1763052398,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" `."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"srie2Bgwul6dv"}
 
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"BEOfuOOPpuDTu1"}
+      data: {"id":"chatcmpl-CbUkwKSNynPTu1uyNioPPtws6jh8X","object":"chat.completion.chunk","created":1763052398,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"rrtxZ5zgqAV6fV"}
 
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"H5gIqlzUGQ"}
+      data: {"id":"chatcmpl-CbUkwKSNynPTu1uyNioPPtws6jh8X","object":"chat.completion.chunk","created":1763052398,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"`"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2ZshZ4RYBtdnQPO"}
 
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Q63P87LfKEJ"}
+      data: {"id":"chatcmpl-CbUkwKSNynPTu1uyNioPPtws6jh8X","object":"chat.completion.chunk","created":1763052398,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Y67zUlqyFS"}
 
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Glob"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"GoNoWnJtfSk"}
+      data: {"id":"chatcmpl-CbUkwKSNynPTu1uyNioPPtws6jh8X","object":"chat.completion.chunk","created":1763052398,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Using"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lTcF8L5RVO"}
 
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"tX6giDfA1VUuB"}
+      data: {"id":"chatcmpl-CbUkwKSNynPTu1uyNioPPtws6jh8X","object":"chat.completion.chunk","created":1763052398,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Glob"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"JLPPu1DpXr2"}
 
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Current"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lpFxnTag"}
+      data: {"id":"chatcmpl-CbUkwKSNynPTu1uyNioPPtws6jh8X","object":"chat.completion.chunk","created":1763052398,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"SMLHukmXMn"}
 
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Directory"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"sFvWcn"}
-
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"3bg73MZKl9"}
-
-      data: {"id":"chatcmpl-CWhXWSMCr6qB3gnWTLgyqU7xhyNfD","object":"chat.completion.chunk","created":1761909898,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":137,"completion_tokens":9,"total_tokens":146,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"pSfi02ffdiVgGo"}
+      data: {"id":"chatcmpl-CbUkwKSNynPTu1uyNioPPtws6jh8X","object":"chat.completion.chunk","created":1763052398,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":137,"completion_tokens":7,"total_tokens":144,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"TOE6jUxH7CfZvb"}
 
       data: [DONE]
 
@@ -55,15 +51,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 420.291ms
+    duration: 588.432875ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45147
+    content_length: 46860
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/grep_tool.yaml πŸ”—

@@ -24,31 +24,33 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"GoaiUY1uzWtKYt"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"iLOZwOwiokrZuz"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Search"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ZAPV7XTqck"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Search"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"DlbjFQxsHr"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" for"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"J8yw8jrWnNUr"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" for"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"i9fGUHvYdwkX"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"XdxqK7R5JFw0F5"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"3I6NnbTIs9oMM6"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"package"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"wkBDR8dQf"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"package"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Tp6w4ElkU"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vXtbve7G3a8maN4"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"DVsWXgHcxXVj7Yj"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"8wf45VJIxwFsZ"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"SqBXBoHdTWde9"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"pRsCVMZRQ070q"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"JarASpDMwg549"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"x7uOQzDFpI"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"xV2sEmRndW"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" using"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2IWrOTMyMS"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"rcbAIhtOq7L"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" grep"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KxgMgUqrUvo"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Gre"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Lm9JTmBYS8Ed"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"7ovkOmaQwj"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"p"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"9sjadzI0rntMOmO"}
 
-      data: {"id":"chatcmpl-CWhXc3eVJlBmjRe4RlplK8Xv5mR1Z","object":"chat.completion.chunk","created":1761909904,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":138,"completion_tokens":10,"total_tokens":148,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"7o8VW7i5JEpVy"}
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"zbh8PF90nZ"}
+
+      data: {"id":"chatcmpl-CbUl2IAjfdVJHW5nokJYLkeYz4GfN","object":"chat.completion.chunk","created":1763052404,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":138,"completion_tokens":11,"total_tokens":149,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"lEU38UWnKoUkj"}
 
       data: [DONE]
 
@@ -57,15 +59,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 1.038688833s
+    duration: 905.066666ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45145
+    content_length: 46858
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/ls_tool.yaml πŸ”—

@@ -24,31 +24,29 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"MCWFcLhdK58FbJ"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"h6r6m9nNnzjc2b"}
 
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Using"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"3EBKxRkyeGg"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Using"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1x1KoK0Hb0x"}
 
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"xIi5UalBhOwMbg"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"aeBru4myZMj8U"}
 
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"ls"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"CAVw4k1WmiwjLa"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"ls"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"AKrfTGwPHPbmHR"}
 
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"F3Bq812chIUIzGX"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"\""},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Uke3XVyrorkLmz"}
 
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"95ULPKgMvp7To"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"hISNaQuZGrnB7"}
 
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" List"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"r5GrJW1wmqP"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" List"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"btTM02jJnwp"}
 
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"MxdLTHikvT"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Current"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"yRseOaxI"}
 
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"QGNfitR1P70YY"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Directory"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"OmgEC9"}
 
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Current"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"sUeNiG4y"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"zo7agHZlcJ"}
 
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Directory"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"J0RDzd"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"9p7oJJimGD"}
 
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"5QOUpMs2Ml"}
-
-      data: {"id":"chatcmpl-CWhXkFTjwl5xUXnehGUg9PaN6qNq8","object":"chat.completion.chunk","created":1761909912,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":135,"completion_tokens":10,"total_tokens":145,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"vFQ6uI3Cg2SNR"}
+      data: {"id":"chatcmpl-CbUl6Eo9LBITv9jqkxRV8ST3YiW8K","object":"chat.completion.chunk","created":1763052408,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":135,"completion_tokens":9,"total_tokens":144,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"agLuSnoPNH0Vcl"}
 
       data: [DONE]
 
@@ -57,15 +55,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 426.932792ms
+    duration: 512.434875ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45139
+    content_length: 46852
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/multiedit_tool.yaml πŸ”—

@@ -24,35 +24,29 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vib8DrjqB0M7hn"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"H3R9eYIF6idNFg"}
 
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Modify"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1k07w3fzQu"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Edit"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"aKyHBfmaTJ0O"}
 
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"GyM9bWqkLw92Vr"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"q1bgNUrXPJlA"}
 
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"CUtYPUorfpS"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Comment"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Romp8onI"}
 
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"qJjRwbZDfVEbChS"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"SRm0wTqnVipZc"}
 
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" World"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"a8xCMyxcxi"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" main"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"CyrbI2x4kis"}
 
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"!'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"kaNX4o0E836bQf"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":".go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"eVWU5HRDDmtMp"}
 
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KZw6b8gqUCMF"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" for"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"oxyQzCXPlPMk"}
 
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Add"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"C7aH71QYiC4X"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Greeting"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"r9iEorH"}
 
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Comment"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"rLwLkf9r"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Update"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"6BCBJuLcl"}
 
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2jC9m9myYQL9E"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"U9YYQSWkgT"}
 
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" main"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"G4ZfwxDqjIx"}
-
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":".go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"zOlVgRrJ3tTdH"}
-
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"E7YvkSyrAi"}
-
-      data: {"id":"chatcmpl-CWhXqpTXLkin7AKNshvJ7Z7SfYWRL","object":"chat.completion.chunk","created":1761909918,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":157,"completion_tokens":12,"total_tokens":169,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"gDJn2sP6sddQe"}
+      data: {"id":"chatcmpl-CbUlCvlCNyoHxyfL6OG4rSI5SfBFJ","object":"chat.completion.chunk","created":1763052414,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":157,"completion_tokens":9,"total_tokens":166,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"nbn5J8AdAuvaXC"}
 
       data: [DONE]
 
@@ -61,15 +55,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 911.194292ms
+    duration: 617.736125ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45225
+    content_length: 46938
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/parallel_tool_calls.yaml πŸ”—

@@ -24,29 +24,25 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"7Cz8ZXHoTLl4gu"}
+      data: {"id":"chatcmpl-CbUmiKV4H97n0B3Ob91SElEDTeo79","object":"chat.completion.chunk","created":1763052508,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"38eUDEFLeE7ZtA"}
 
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Run"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Kk1HPfHLJ3CkO"}
+      data: {"id":"chatcmpl-CbUmiKV4H97n0B3Ob91SElEDTeo79","object":"chat.completion.chunk","created":1763052508,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Parallel"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"sKWfEePK"}
 
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Glob"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"DR9fbWtQCxy"}
+      data: {"id":"chatcmpl-CbUmiKV4H97n0B3Ob91SElEDTeo79","object":"chat.completion.chunk","created":1763052508,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Execution"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"mmGyMl"}
 
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"PftnV31ZNOAo"}
+      data: {"id":"chatcmpl-CbUmiKV4H97n0B3Ob91SElEDTeo79","object":"chat.completion.chunk","created":1763052508,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"fFOWGorFW1Tse"}
 
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" LS"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"NMfENqXfVuow8"}
+      data: {"id":"chatcmpl-CbUmiKV4H97n0B3Ob91SElEDTeo79","object":"chat.completion.chunk","created":1763052508,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Glob"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"fc0w3pKu7eX"}
 
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"i7QWCjqbnVkyH"}
+      data: {"id":"chatcmpl-CbUmiKV4H97n0B3Ob91SElEDTeo79","object":"chat.completion.chunk","created":1763052508,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"fJbKXygv1oCi"}
 
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Parallel"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"V27SfNN"}
+      data: {"id":"chatcmpl-CbUmiKV4H97n0B3Ob91SElEDTeo79","object":"chat.completion.chunk","created":1763052508,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" ls"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"7K9h8d5dcvpla"}
 
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" for"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"OFLkXlJfMYgH"}
+      data: {"id":"chatcmpl-CbUmiKV4H97n0B3Ob91SElEDTeo79","object":"chat.completion.chunk","created":1763052508,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Commands"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"kPVTp92"}
 
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"h0JrPavopghNT"}
+      data: {"id":"chatcmpl-CbUmiKV4H97n0B3Ob91SElEDTeo79","object":"chat.completion.chunk","created":1763052508,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"Jii0PIQji4"}
 
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"UgOZnrSTAV"}
-
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"s9vV5Zt6Af"}
-
-      data: {"id":"chatcmpl-CWhZJC0z3DUeuu1TL3VBI1UmbDPr2","object":"chat.completion.chunk","created":1761910009,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":154,"completion_tokens":9,"total_tokens":163,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"aZRwb8LKGbbiX9"}
+      data: {"id":"chatcmpl-CbUmiKV4H97n0B3Ob91SElEDTeo79","object":"chat.completion.chunk","created":1763052508,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":154,"completion_tokens":7,"total_tokens":161,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"gOTFO7fnS0raPc"}
 
       data: [DONE]
 
@@ -55,15 +51,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 515.978334ms
+    duration: 845.506083ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45236
+    content_length: 46949
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/read_a_file.yaml πŸ”—

@@ -24,17 +24,19 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhWHAhp1KAuQiNQHtVYJLkaqqgii","object":"chat.completion.chunk","created":1761909821,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KwiQzorjHP4yXF"}
+      data: {"id":"chatcmpl-CbUjbk0FbXUMEIhur153OYQjxrcPg","object":"chat.completion.chunk","created":1763052315,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"uT7wUdt4SRHamH"}
 
-      data: {"id":"chatcmpl-CWhWHAhp1KAuQiNQHtVYJLkaqqgii","object":"chat.completion.chunk","created":1761909821,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Understanding"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"6p7"}
+      data: {"id":"chatcmpl-CbUjbk0FbXUMEIhur153OYQjxrcPg","object":"chat.completion.chunk","created":1763052315,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":"Reading"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Bx94I54zj"}
 
-      data: {"id":"chatcmpl-CWhWHAhp1KAuQiNQHtVYJLkaqqgii","object":"chat.completion.chunk","created":1761909821,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"idrszYUBJu5Xh"}
+      data: {"id":"chatcmpl-CbUjbk0FbXUMEIhur153OYQjxrcPg","object":"chat.completion.chunk","created":1763052315,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"IdmbSYj4oqHy1"}
 
-      data: {"id":"chatcmpl-CWhWHAhp1KAuQiNQHtVYJLkaqqgii","object":"chat.completion.chunk","created":1761909821,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Modules"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"yqcihFlL"}
+      data: {"id":"chatcmpl-CbUjbk0FbXUMEIhur153OYQjxrcPg","object":"chat.completion.chunk","created":1763052315,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" Mod"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"JnskSeubME1a"}
 
-      data: {"id":"chatcmpl-CWhWHAhp1KAuQiNQHtVYJLkaqqgii","object":"chat.completion.chunk","created":1761909821,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"932OjQNMI8"}
+      data: {"id":"chatcmpl-CbUjbk0FbXUMEIhur153OYQjxrcPg","object":"chat.completion.chunk","created":1763052315,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2SHtWMenh9E"}
 
-      data: {"id":"chatcmpl-CWhWHAhp1KAuQiNQHtVYJLkaqqgii","object":"chat.completion.chunk","created":1761909821,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":129,"completion_tokens":3,"total_tokens":132,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"u4zstHSs1oMWL5"}
+      data: {"id":"chatcmpl-CbUjbk0FbXUMEIhur153OYQjxrcPg","object":"chat.completion.chunk","created":1763052315,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"ox5d7o7dBk"}
+
+      data: {"id":"chatcmpl-CbUjbk0FbXUMEIhur153OYQjxrcPg","object":"chat.completion.chunk","created":1763052315,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[],"usage":{"prompt_tokens":129,"completion_tokens":4,"total_tokens":133,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"2jPOuQKFjFRVxo"}
 
       data: [DONE]
 
@@ -43,15 +45,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 1.136475792s
+    duration: 1.350092792s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45109
+    content_length: 46822
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/simple_test.yaml πŸ”—

@@ -24,13 +24,15 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhWAKSjJG7aS3VQh2aSTFIcnAoGw","object":"chat.completion.chunk","created":1761909814,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_65564d8ba5","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"kJ82cmYumWTGLC"}
+      data: {"id":"chatcmpl-CbUjYq4LXkJbKgYIRG5hMg4oQOON1","object":"chat.completion.chunk","created":1763052312,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"IxOrABX6yljD2G"}
 
-      data: {"id":"chatcmpl-CWhWAKSjJG7aS3VQh2aSTFIcnAoGw","object":"chat.completion.chunk","created":1761909814,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_65564d8ba5","choices":[{"index":0,"delta":{"content":"Greetings"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"jM0L3i7"}
+      data: {"id":"chatcmpl-CbUjYq4LXkJbKgYIRG5hMg4oQOON1","object":"chat.completion.chunk","created":1763052312,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Greeting"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"xPZrJHtX"}
 
-      data: {"id":"chatcmpl-CWhWAKSjJG7aS3VQh2aSTFIcnAoGw","object":"chat.completion.chunk","created":1761909814,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_65564d8ba5","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"BcuY68lDzu"}
+      data: {"id":"chatcmpl-CbUjYq4LXkJbKgYIRG5hMg4oQOON1","object":"chat.completion.chunk","created":1763052312,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Inquiry"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"HvYXHs27"}
 
-      data: {"id":"chatcmpl-CWhWAKSjJG7aS3VQh2aSTFIcnAoGw","object":"chat.completion.chunk","created":1761909814,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_65564d8ba5","choices":[],"usage":{"prompt_tokens":126,"completion_tokens":1,"total_tokens":127,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"u4EbJXDAHkMaSU"}
+      data: {"id":"chatcmpl-CbUjYq4LXkJbKgYIRG5hMg4oQOON1","object":"chat.completion.chunk","created":1763052312,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"7XLuydsygC"}
+
+      data: {"id":"chatcmpl-CbUjYq4LXkJbKgYIRG5hMg4oQOON1","object":"chat.completion.chunk","created":1763052312,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":126,"completion_tokens":2,"total_tokens":128,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"yi1YkNtf9pGxOj"}
 
       data: [DONE]
 
@@ -39,15 +41,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 1.241931458s
+    duration: 1.689023333s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45099
+    content_length: 46812
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/sourcegraph_tool.yaml πŸ”—

@@ -24,35 +24,35 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KK8aYMSSiDELdH"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"q9ikvfRO9M9ZCD"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Searching"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"mLGYZIS"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Searching"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ZhFnlDX"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2cbsMo2svk1BEP"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"HElY6XnNKJ1BA"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"func"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"fV3z1rB74yfO"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Re"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2zyFERj6QNdmo"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" main"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"kHnjkHWN7YP"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"pos"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Cn4Gpri5nvD77"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vDM2T7mSIPdYRY7"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" for"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"YSHLewPbtDfF"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"4A8UqCAKl3ulb"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"QjGpWTc9855ITW"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"epXxfMkv3UACx"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"func"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1Uvyj8nvbukf"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Re"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"uXHKW1MHl43nQ"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" main"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"IhWK6eQjLLy"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"positories"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"94vXSJ"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"d3p3kVTt9RekBy5"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"iiXaNVpsfub"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"thSYY5CMCD0"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Source"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"8pmzsoxNg"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Source"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"6hcYX3QlM"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"graph"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Zf9Wv78IXa9"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"graph"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"GSae3ccbYPD"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"LzKLlUWW2m"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"l8TqeuFuRK"}
 
-      data: {"id":"chatcmpl-CWhYTIiw1PNfl5a13JwCrw4pIultS","object":"chat.completion.chunk","created":1761909957,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":138,"completion_tokens":12,"total_tokens":150,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"lva2Gdhcco8Ri"}
+      data: {"id":"chatcmpl-CbUm9HzQPFeLZIe0BpSquzUVtbhWJ","object":"chat.completion.chunk","created":1763052473,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":138,"completion_tokens":12,"total_tokens":150,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"VZadSktxeaWcn"}
 
       data: [DONE]
 
@@ -61,15 +61,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 845.432125ms
+    duration: 637.585333ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45159
+    content_length: 46872
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/update_a_file.yaml πŸ”—

@@ -24,31 +24,31 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"PLDLeNZd82YM71"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"GesaPJpzwJRMHM"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Main"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"oNiQ5GCygP3G"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":"Update"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Y9fFfWfnr1"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":".go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"LFVFeDtgmVuLq"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" main"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vR4scZ7Ao7k"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Update"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"t0AlYBr9y"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":".go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vHGU6ynU8b8tx"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":":"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"c9qMSV1Y8ekFTIo"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"gt32CmTR8rqsH"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Print"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"QcJdYyzl3Z"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" Print"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"5y27FvlZ3l"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"js7cOcHrxK7UT"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"noEsu8vW42kmo"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"UOHtjq3TQlC"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"IJlcciGmQfb"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" from"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"36Gr5Flq5LJ"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" from"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ApiILnaGLSk"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Crush"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"fYBXSUWUd9"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":" Crush"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"CGM8fiIg0S"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"\""},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"QV42kvlcA9ibpc"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{"content":"\""},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"9TrOloEfBdeWSw"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"yWaQRolC0O"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"B4ThyV59MI"}
 
-      data: {"id":"chatcmpl-CWhWUw6avqGtWuzPB9lUBctmnnIuj","object":"chat.completion.chunk","created":1761909834,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":139,"completion_tokens":10,"total_tokens":149,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"hw9iNRpmFNweD"}
+      data: {"id":"chatcmpl-CbUjk3PHkBtrSPSn2hCQjnyk4hjCn","object":"chat.completion.chunk","created":1763052324,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_b1442291a8","choices":[],"usage":{"prompt_tokens":139,"completion_tokens":10,"total_tokens":149,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"LNofLrRQBTpT6"}
 
       data: [DONE]
 
@@ -57,15 +57,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 469.220667ms
+    duration: 633.346959ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45165
+    content_length: 46878
     host: ""

internal/agent/testdata/TestCoderAgent/openai-gpt-5/write_tool.yaml πŸ”—

@@ -24,23 +24,23 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"chatcmpl-CWhZ84hPjwruz42MM9mMzNrU0xGRh","object":"chat.completion.chunk","created":1761909998,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"6nknPjuOLCwz2j"}
+      data: {"id":"chatcmpl-CbUmZKAn1GsoqE3PCOdflHQc2MsXM","object":"chat.completion.chunk","created":1763052499,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"TlZ2Zfe1QT8R7J"}
 
-      data: {"id":"chatcmpl-CWhZ84hPjwruz42MM9mMzNrU0xGRh","object":"chat.completion.chunk","created":1761909998,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Create"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"UKbO0tOEx5"}
+      data: {"id":"chatcmpl-CbUmZKAn1GsoqE3PCOdflHQc2MsXM","object":"chat.completion.chunk","created":1763052499,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Create"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"OGuyTScdFB"}
 
-      data: {"id":"chatcmpl-CWhZ84hPjwruz42MM9mMzNrU0xGRh","object":"chat.completion.chunk","created":1761909998,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" JSON"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Zw7na2EwlSz"}
+      data: {"id":"chatcmpl-CbUmZKAn1GsoqE3PCOdflHQc2MsXM","object":"chat.completion.chunk","created":1763052499,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Config"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"H2zjPXZfm"}
 
-      data: {"id":"chatcmpl-CWhZ84hPjwruz42MM9mMzNrU0xGRh","object":"chat.completion.chunk","created":1761909998,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"a1Ti8eDKQqT"}
+      data: {"id":"chatcmpl-CbUmZKAn1GsoqE3PCOdflHQc2MsXM","object":"chat.completion.chunk","created":1763052499,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ZzwG2NWPWFw"}
 
-      data: {"id":"chatcmpl-CWhZ84hPjwruz42MM9mMzNrU0xGRh","object":"chat.completion.chunk","created":1761909998,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"rRhBI7hB4p6"}
+      data: {"id":"chatcmpl-CbUmZKAn1GsoqE3PCOdflHQc2MsXM","object":"chat.completion.chunk","created":1763052499,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"xaJbLeMtyC7"}
 
-      data: {"id":"chatcmpl-CWhZ84hPjwruz42MM9mMzNrU0xGRh","object":"chat.completion.chunk","created":1761909998,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Config"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"VBjObVGZe"}
+      data: {"id":"chatcmpl-CbUmZKAn1GsoqE3PCOdflHQc2MsXM","object":"chat.completion.chunk","created":1763052499,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" JSON"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"75BDNHjPtek"}
 
-      data: {"id":"chatcmpl-CWhZ84hPjwruz42MM9mMzNrU0xGRh","object":"chat.completion.chunk","created":1761909998,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Details"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"sbKPiIhw"}
+      data: {"id":"chatcmpl-CbUmZKAn1GsoqE3PCOdflHQc2MsXM","object":"chat.completion.chunk","created":1763052499,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":" Content"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1D8uJ5sJ"}
 
-      data: {"id":"chatcmpl-CWhZ84hPjwruz42MM9mMzNrU0xGRh","object":"chat.completion.chunk","created":1761909998,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"CgNioou140"}
+      data: {"id":"chatcmpl-CbUmZKAn1GsoqE3PCOdflHQc2MsXM","object":"chat.completion.chunk","created":1763052499,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"TOLCmNVKfG"}
 
-      data: {"id":"chatcmpl-CWhZ84hPjwruz42MM9mMzNrU0xGRh","object":"chat.completion.chunk","created":1761909998,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":153,"completion_tokens":6,"total_tokens":159,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"1GFTG66Q9TDJFr"}
+      data: {"id":"chatcmpl-CbUmZKAn1GsoqE3PCOdflHQc2MsXM","object":"chat.completion.chunk","created":1763052499,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[],"usage":{"prompt_tokens":153,"completion_tokens":6,"total_tokens":159,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}},"obfuscation":"ToMmmKEknxFTKs"}
 
       data: [DONE]
 
@@ -49,15 +49,15 @@ interactions:
       - text/event-stream; charset=utf-8
     status: 200 OK
     code: 200
-    duration: 495.299125ms
+    duration: 1.097819708s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45202
+    content_length: 46915
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/bash_tool.yaml πŸ”—

@@ -24,23 +24,21 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761910038-dOJ1BpSmQV2Bjlc0MYXe","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910038,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763054736-Rp5v2bhZCQHuksIBMnt3","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763054737,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910038-dOJ1BpSmQV2Bjlc0MYXe","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910038,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763054736-Rp5v2bhZCQHuksIBMnt3","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763054737,"choices":[{"index":0,"delta":{"role":"assistant","content":"Create"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910038-dOJ1BpSmQV2Bjlc0MYXe","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910038,"choices":[{"index":0,"delta":{"role":"assistant","content":"Create"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763054736-Rp5v2bhZCQHuksIBMnt3","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763054737,"choices":[{"index":0,"delta":{"role":"assistant","content":" test"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910038-dOJ1BpSmQV2Bjlc0MYXe","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910038,"choices":[{"index":0,"delta":{"role":"assistant","content":" test"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763054736-Rp5v2bhZCQHuksIBMnt3","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763054737,"choices":[{"index":0,"delta":{"role":"assistant","content":".txt with"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910038-dOJ1BpSmQV2Bjlc0MYXe","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910038,"choices":[{"index":0,"delta":{"role":"assistant","content":".txt with"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763054736-Rp5v2bhZCQHuksIBMnt3","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763054737,"choices":[{"index":0,"delta":{"role":"assistant","content":" hello bash using"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910038-dOJ1BpSmQV2Bjlc0MYXe","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910038,"choices":[{"index":0,"delta":{"role":"assistant","content":" hello bash using"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763054736-Rp5v2bhZCQHuksIBMnt3","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763054737,"choices":[{"index":0,"delta":{"role":"assistant","content":" bash"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910038-dOJ1BpSmQV2Bjlc0MYXe","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910038,"choices":[{"index":0,"delta":{"role":"assistant","content":" bash"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763054736-Rp5v2bhZCQHuksIBMnt3","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763054737,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
 
-      data: {"id":"gen-1761910038-dOJ1BpSmQV2Bjlc0MYXe","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910038,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
-
-      data: {"id":"gen-1761910038-dOJ1BpSmQV2Bjlc0MYXe","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910038,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":147,"completion_tokens":9,"total_tokens":156,"cost":0.00003048,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00002058,"upstream_inference_completions_cost":0.0000099},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763054736-Rp5v2bhZCQHuksIBMnt3","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763054737,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":147,"completion_tokens":9,"total_tokens":156,"cost":0.0000468,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.0000441,"upstream_inference_completions_cost":0.0000027},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -49,15 +47,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 305.396125ms
+    duration: 2.193986583s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45298
+    content_length: 47011
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/download_tool.yaml πŸ”—

@@ -24,25 +24,25 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761910040-jpK7NLYwz180tBpsNAIV","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910040,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052563-D9skvXP0T7wXQTjZkAN3","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052563,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910040-jpK7NLYwz180tBpsNAIV","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910040,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052563-D9skvXP0T7wXQTjZkAN3","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052563,"choices":[{"index":0,"delta":{"role":"assistant","content":"Download"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910040-jpK7NLYwz180tBpsNAIV","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910040,"choices":[{"index":0,"delta":{"role":"assistant","content":"Download"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052563-D9skvXP0T7wXQTjZkAN3","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052563,"choices":[{"index":0,"delta":{"role":"assistant","content":" example"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910040-jpK7NLYwz180tBpsNAIV","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910040,"choices":[{"index":0,"delta":{"role":"assistant","content":" and save example.txt"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052563-D9skvXP0T7wXQTjZkAN3","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052563,"choices":[{"index":0,"delta":{"role":"assistant","content":".txt from example"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910040-jpK7NLYwz180tBpsNAIV","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910040,"choices":[{"index":0,"delta":{"role":"assistant","content":" from example-files"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052563-D9skvXP0T7wXQTjZkAN3","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052563,"choices":[{"index":0,"delta":{"role":"assistant","content":"-files"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910040-jpK7NLYwz180tBpsNAIV","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910040,"choices":[{"index":0,"delta":{"role":"assistant","content":".online"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052563-D9skvXP0T7wXQTjZkAN3","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052563,"choices":[{"index":0,"delta":{"role":"assistant","content":".online"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910040-jpK7NLYwz180tBpsNAIV","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910040,"choices":[{"index":0,"delta":{"role":"assistant","content":"-"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052563-D9skvXP0T7wXQTjZkAN3","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052563,"choices":[{"index":0,"delta":{"role":"assistant","content":"-"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910040-jpK7NLYwz180tBpsNAIV","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910040,"choices":[{"index":0,"delta":{"role":"assistant","content":"convert.com"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052563-D9skvXP0T7wXQTjZkAN3","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052563,"choices":[{"index":0,"delta":{"role":"assistant","content":"convert.com"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910040-jpK7NLYwz180tBpsNAIV","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910040,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+      data: {"id":"gen-1763052563-D9skvXP0T7wXQTjZkAN3","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052563,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
 
-      data: {"id":"gen-1761910040-jpK7NLYwz180tBpsNAIV","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910040,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":150,"completion_tokens":13,"total_tokens":163,"cost":0.0000353,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.000021,"upstream_inference_completions_cost":0.0000143},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763052563-D9skvXP0T7wXQTjZkAN3","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052563,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":150,"completion_tokens":11,"total_tokens":161,"cost":0.0000357,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.0000225,"upstream_inference_completions_cost":0.0000132},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -51,15 +51,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 710.793208ms
+    duration: 739.791416ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45323
+    content_length: 47036
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/glob_tool.yaml πŸ”—

@@ -24,21 +24,21 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761910052-uuOETI7IJpIcw17hHFAk","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910052,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052581-GDBNWsvqk1wEnVWEbZPn","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052581,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910052-uuOETI7IJpIcw17hHFAk","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910052,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052581-GDBNWsvqk1wEnVWEbZPn","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052581,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910052-uuOETI7IJpIcw17hHFAk","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910052,"choices":[{"index":0,"delta":{"role":"assistant","content":"Find"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052581-GDBNWsvqk1wEnVWEbZPn","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052581,"choices":[{"index":0,"delta":{"role":"assistant","content":"Find"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910052-uuOETI7IJpIcw17hHFAk","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910052,"choices":[{"index":0,"delta":{"role":"assistant","content":" all .go files"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052581-GDBNWsvqk1wEnVWEbZPn","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052581,"choices":[{"index":0,"delta":{"role":"assistant","content":" all .go files"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910052-uuOETI7IJpIcw17hHFAk","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910052,"choices":[{"index":0,"delta":{"role":"assistant","content":" in current directory using"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052581-GDBNWsvqk1wEnVWEbZPn","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052581,"choices":[{"index":0,"delta":{"role":"assistant","content":" in current directory using"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910052-uuOETI7IJpIcw17hHFAk","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910052,"choices":[{"index":0,"delta":{"role":"assistant","content":" glob"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052581-GDBNWsvqk1wEnVWEbZPn","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052581,"choices":[{"index":0,"delta":{"role":"assistant","content":" glob"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910052-uuOETI7IJpIcw17hHFAk","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910052,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+      data: {"id":"gen-1763052581-GDBNWsvqk1wEnVWEbZPn","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052581,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
 
-      data: {"id":"gen-1761910052-uuOETI7IJpIcw17hHFAk","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910052,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":139,"completion_tokens":11,"total_tokens":150,"cost":0.00003156,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00001946,"upstream_inference_completions_cost":0.0000121},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763052581-GDBNWsvqk1wEnVWEbZPn","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052581,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":139,"completion_tokens":11,"total_tokens":150,"cost":0.00003156,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00001946,"upstream_inference_completions_cost":0.0000121},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -47,15 +47,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 568.273625ms
+    duration: 951.550792ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45259
+    content_length: 46972
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/grep_tool.yaml πŸ”—

@@ -24,17 +24,27 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761911360-yPPMIO7ueEGdqymanmPO","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761911360,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052587-qJxRVUHqJg5Er7XDvwR0","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052587,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761911360-yPPMIO7ueEGdqymanmPO","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761911360,"choices":[{"index":0,"delta":{"role":"assistant","content":"Search"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052587-qJxRVUHqJg5Er7XDvwR0","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052587,"choices":[{"index":0,"delta":{"role":"assistant","content":"Search"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761911360-yPPMIO7ueEGdqymanmPO","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761911360,"choices":[{"index":0,"delta":{"role":"assistant","content":" for package in Go"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052587-qJxRVUHqJg5Er7XDvwR0","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052587,"choices":[{"index":0,"delta":{"role":"assistant","content":" for"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761911360-yPPMIO7ueEGdqymanmPO","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761911360,"choices":[{"index":0,"delta":{"role":"assistant","content":" files using grep"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052587-qJxRVUHqJg5Er7XDvwR0","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052587,"choices":[{"index":0,"delta":{"role":"assistant","content":" package"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761911360-yPPMIO7ueEGdqymanmPO","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761911360,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+      data: {"id":"gen-1763052587-qJxRVUHqJg5Er7XDvwR0","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052587,"choices":[{"index":0,"delta":{"role":"assistant","content":" in"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761911360-yPPMIO7ueEGdqymanmPO","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761911360,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":140,"completion_tokens":9,"total_tokens":149,"cost":0.0000345,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.000021,"upstream_inference_completions_cost":0.0000135},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763052587-qJxRVUHqJg5Er7XDvwR0","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052587,"choices":[{"index":0,"delta":{"role":"assistant","content":" Go"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+
+      data: {"id":"gen-1763052587-qJxRVUHqJg5Er7XDvwR0","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052587,"choices":[{"index":0,"delta":{"role":"assistant","content":" files"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+
+      data: {"id":"gen-1763052587-qJxRVUHqJg5Er7XDvwR0","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052587,"choices":[{"index":0,"delta":{"role":"assistant","content":" using"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+
+      data: {"id":"gen-1763052587-qJxRVUHqJg5Er7XDvwR0","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052587,"choices":[{"index":0,"delta":{"role":"assistant","content":" grep"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+
+      data: {"id":"gen-1763052587-qJxRVUHqJg5Er7XDvwR0","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052587,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+
+      data: {"id":"gen-1763052587-qJxRVUHqJg5Er7XDvwR0","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052587,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":140,"completion_tokens":9,"total_tokens":149,"cost":0.0000239,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.000014,"upstream_inference_completions_cost":0.0000099},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -43,15 +53,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 2.174957041s
+    duration: 723.240875ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45257
+    content_length: 46970
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/ls_tool.yaml πŸ”—

@@ -24,17 +24,23 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761910060-VJbmBjm0lLRDsPUFIR6S","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910060,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052593-uakxMpv6HJqAIbq3mPEq","provider":"AtlasCloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052593,"choices":[{"index":0,"delta":{"role":"assistant","content":"List"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910060-VJbmBjm0lLRDsPUFIR6S","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910060,"choices":[{"index":0,"delta":{"role":"assistant","content":"List"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052593-uakxMpv6HJqAIbq3mPEq","provider":"AtlasCloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052593,"choices":[{"index":0,"delta":{"role":"assistant","content":" files"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910060-VJbmBjm0lLRDsPUFIR6S","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910060,"choices":[{"index":0,"delta":{"role":"assistant","content":" files in current directory"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052593-uakxMpv6HJqAIbq3mPEq","provider":"AtlasCloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052593,"choices":[{"index":0,"delta":{"role":"assistant","content":" in"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910060-VJbmBjm0lLRDsPUFIR6S","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910060,"choices":[{"index":0,"delta":{"role":"assistant","content":" using ls"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052593-uakxMpv6HJqAIbq3mPEq","provider":"AtlasCloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052593,"choices":[{"index":0,"delta":{"role":"assistant","content":" current"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910060-VJbmBjm0lLRDsPUFIR6S","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910060,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+      data: {"id":"gen-1763052593-uakxMpv6HJqAIbq3mPEq","provider":"AtlasCloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052593,"choices":[{"index":0,"delta":{"role":"assistant","content":" directory"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910060-VJbmBjm0lLRDsPUFIR6S","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910060,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":137,"completion_tokens":8,"total_tokens":145,"cost":0.00003015,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00002055,"upstream_inference_completions_cost":0.0000096},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763052593-uakxMpv6HJqAIbq3mPEq","provider":"AtlasCloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052593,"choices":[{"index":0,"delta":{"role":"assistant","content":" using"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+
+      data: {"id":"gen-1763052593-uakxMpv6HJqAIbq3mPEq","provider":"AtlasCloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052593,"choices":[{"index":0,"delta":{"role":"assistant","content":" ls"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+
+      data: {"id":"gen-1763052593-uakxMpv6HJqAIbq3mPEq","provider":"AtlasCloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052593,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+
+      data: {"id":"gen-1763052593-uakxMpv6HJqAIbq3mPEq","provider":"AtlasCloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052593,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":137,"completion_tokens":8,"total_tokens":145,"cost":0.00003255,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00002055,"upstream_inference_completions_cost":0.000012},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -43,15 +49,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 717.847333ms
+    duration: 1.369507708s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45251
+    content_length: 46964
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/multiedit_tool.yaml πŸ”—

@@ -24,17 +24,21 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761910063-ujbWJwTviThqiNBCyUac","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910063,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052597-4YCWGGuImNyofX71dIua","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052597,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910063-ujbWJwTviThqiNBCyUac","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910063,"choices":[{"index":0,"delta":{"role":"assistant","content":"Use multiedit to"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052597-4YCWGGuImNyofX71dIua","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052597,"choices":[{"index":0,"delta":{"role":"assistant","content":"Use"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910063-ujbWJwTviThqiNBCyUac","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910063,"choices":[{"index":0,"delta":{"role":"assistant","content":" update greeting and add comment in"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052597-4YCWGGuImNyofX71dIua","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052597,"choices":[{"index":0,"delta":{"role":"assistant","content":" multiedit to"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910063-ujbWJwTviThqiNBCyUac","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910063,"choices":[{"index":0,"delta":{"role":"assistant","content":" main.go"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052597-4YCWGGuImNyofX71dIua","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052597,"choices":[{"index":0,"delta":{"role":"assistant","content":" update greeting"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910063-ujbWJwTviThqiNBCyUac","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910063,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+      data: {"id":"gen-1763052597-4YCWGGuImNyofX71dIua","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052597,"choices":[{"index":0,"delta":{"role":"assistant","content":" and add comment in"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910063-ujbWJwTviThqiNBCyUac","provider":"Hyperbolic","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910063,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":160,"completion_tokens":14,"total_tokens":174,"cost":0.0000522,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.000048,"upstream_inference_completions_cost":0.0000042},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763052597-4YCWGGuImNyofX71dIua","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052597,"choices":[{"index":0,"delta":{"role":"assistant","content":" main.go"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+
+      data: {"id":"gen-1763052597-4YCWGGuImNyofX71dIua","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052597,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+
+      data: {"id":"gen-1763052597-4YCWGGuImNyofX71dIua","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052597,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":160,"completion_tokens":14,"total_tokens":174,"cost":0.0000408,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.000024,"upstream_inference_completions_cost":0.0000168},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -43,15 +47,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 1.272179209s
+    duration: 1.316448375s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45337
+    content_length: 47050
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/parallel_tool_calls.yaml πŸ”—

@@ -24,21 +24,23 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761910105-3A4csdIPom3cGo1LJez4","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910105,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052638-VGVg47KFz5A3bpjJBMOu","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052638,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
 
-      data: {"id":"gen-1761910105-3A4csdIPom3cGo1LJez4","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910105,"choices":[{"index":0,"delta":{"role":"assistant","content":"Find"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052638-VGVg47KFz5A3bpjJBMOu","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052638,"choices":[{"index":0,"delta":{"role":"assistant","content":"Run"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
 
-      data: {"id":"gen-1761910105-3A4csdIPom3cGo1LJez4","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910105,"choices":[{"index":0,"delta":{"role":"assistant","content":" .go files and"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052638-VGVg47KFz5A3bpjJBMOu","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052638,"choices":[{"index":0,"delta":{"role":"assistant","content":" glob"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
 
-      data: {"id":"gen-1761910105-3A4csdIPom3cGo1LJez4","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910105,"choices":[{"index":0,"delta":{"role":"assistant","content":" list directory"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052638-VGVg47KFz5A3bpjJBMOu","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052638,"choices":[{"index":0,"delta":{"role":"assistant","content":" and ls in"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
 
-      data: {"id":"gen-1761910105-3A4csdIPom3cGo1LJez4","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910105,"choices":[{"index":0,"delta":{"role":"assistant","content":" in"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052638-VGVg47KFz5A3bpjJBMOu","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052638,"choices":[{"index":0,"delta":{"role":"assistant","content":" parallel to find ."},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
 
-      data: {"id":"gen-1761910105-3A4csdIPom3cGo1LJez4","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910105,"choices":[{"index":0,"delta":{"role":"assistant","content":" parallel"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052638-VGVg47KFz5A3bpjJBMOu","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052638,"choices":[{"index":0,"delta":{"role":"assistant","content":"go files and list"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
 
-      data: {"id":"gen-1761910105-3A4csdIPom3cGo1LJez4","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910105,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052638-VGVg47KFz5A3bpjJBMOu","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052638,"choices":[{"index":0,"delta":{"role":"assistant","content":" directory"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
 
-      data: {"id":"gen-1761910105-3A4csdIPom3cGo1LJez4","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910105,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":160,"completion_tokens":9,"total_tokens":169,"cost":0.0000375,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.000024,"upstream_inference_completions_cost":0.0000135},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763052638-VGVg47KFz5A3bpjJBMOu","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052638,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}],"system_fingerprint":null}
+
+      data: {"id":"gen-1763052638-VGVg47KFz5A3bpjJBMOu","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052638,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":160,"completion_tokens":14,"total_tokens":174,"cost":0.0000408,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.000024,"upstream_inference_completions_cost":0.0000168},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -47,15 +49,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 1.345112459s
+    duration: 1.793021083s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45348
+    content_length: 47061
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/read_a_file.yaml πŸ”—

@@ -24,19 +24,15 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761910020-fGmWbz6IBhnFHrolqfCi","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910020,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052523-9JI1sa5za1SM7CeAqvbp","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052523,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910020-fGmWbz6IBhnFHrolqfCi","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910020,"choices":[{"index":0,"delta":{"role":"assistant","content":"Read"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052523-9JI1sa5za1SM7CeAqvbp","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052523,"choices":[{"index":0,"delta":{"role":"assistant","content":"Read"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910020-fGmWbz6IBhnFHrolqfCi","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910020,"choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052523-9JI1sa5za1SM7CeAqvbp","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052523,"choices":[{"index":0,"delta":{"role":"assistant","content":" the go mod"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910020-fGmWbz6IBhnFHrolqfCi","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910020,"choices":[{"index":0,"delta":{"role":"assistant","content":" go"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052523-9JI1sa5za1SM7CeAqvbp","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052523,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
 
-      data: {"id":"gen-1761910020-fGmWbz6IBhnFHrolqfCi","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910020,"choices":[{"index":0,"delta":{"role":"assistant","content":" mod"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
-
-      data: {"id":"gen-1761910020-fGmWbz6IBhnFHrolqfCi","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910020,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
-
-      data: {"id":"gen-1761910020-fGmWbz6IBhnFHrolqfCi","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910020,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":131,"completion_tokens":5,"total_tokens":136,"cost":0.0000186,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.0000131,"upstream_inference_completions_cost":0.0000055},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763052523-9JI1sa5za1SM7CeAqvbp","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052523,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":131,"completion_tokens":5,"total_tokens":136,"cost":0.00002565,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00001965,"upstream_inference_completions_cost":0.000006},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -45,15 +41,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 572.50025ms
+    duration: 1.185187833s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45221
+    content_length: 46934
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/simple_test.yaml πŸ”—

@@ -24,13 +24,13 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761910017-kwKmQaoZRZJprbrCiwzP","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910017,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052519-lSHRC3DhuosBVu60GhSe","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052519,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910017-kwKmQaoZRZJprbrCiwzP","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910017,"choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052519-lSHRC3DhuosBVu60GhSe","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052519,"choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910017-kwKmQaoZRZJprbrCiwzP","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910017,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+      data: {"id":"gen-1763052519-lSHRC3DhuosBVu60GhSe","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052519,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
 
-      data: {"id":"gen-1761910017-kwKmQaoZRZJprbrCiwzP","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910017,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":128,"completion_tokens":2,"total_tokens":130,"cost":0.000015,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.0000128,"upstream_inference_completions_cost":0.0000022},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763052519-lSHRC3DhuosBVu60GhSe","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052519,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":128,"completion_tokens":2,"total_tokens":130,"cost":0.000015,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.0000128,"upstream_inference_completions_cost":0.0000022},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -39,15 +39,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 1.304010709s
+    duration: 1.850255542s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45211
+    content_length: 46924
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/sourcegraph_tool.yaml πŸ”—

@@ -24,31 +24,23 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052617-MVMPtbtGW4vt7na58iPz","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052617,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":"Search"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052617-MVMPtbtGW4vt7na58iPz","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052617,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":" for"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052617-MVMPtbtGW4vt7na58iPz","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052617,"choices":[{"index":0,"delta":{"role":"assistant","content":"Search"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":" func"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052617-MVMPtbtGW4vt7na58iPz","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052617,"choices":[{"index":0,"delta":{"role":"assistant","content":" for func"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":" main"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052617-MVMPtbtGW4vt7na58iPz","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052617,"choices":[{"index":0,"delta":{"role":"assistant","content":" main in Go repositories"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":" in"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052617-MVMPtbtGW4vt7na58iPz","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052617,"choices":[{"index":0,"delta":{"role":"assistant","content":" using Source"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":" Go"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052617-MVMPtbtGW4vt7na58iPz","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052617,"choices":[{"index":0,"delta":{"role":"assistant","content":"graph"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":" repositories"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052617-MVMPtbtGW4vt7na58iPz","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052617,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
 
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":" using"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
-
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":" Source"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
-
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":"graph"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
-
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
-
-      data: {"id":"gen-1761910083-DMwj8ByxTb7hAGnhEawD","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910083,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":140,"completion_tokens":11,"total_tokens":151,"cost":0.0000261,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.000014,"upstream_inference_completions_cost":0.0000121},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763052617-MVMPtbtGW4vt7na58iPz","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052617,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":140,"completion_tokens":11,"total_tokens":151,"cost":0.0000317,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.0000196,"upstream_inference_completions_cost":0.0000121},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -57,15 +49,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 781.411208ms
+    duration: 1.105004083s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45271
+    content_length: 46984
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/update_a_file.yaml πŸ”—

@@ -24,19 +24,27 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761910023-cE8wcf03fPUk6DeRS4aD","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910023,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052541-znjKrucNZrGyRGlj9kgI","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052541,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910023-cE8wcf03fPUk6DeRS4aD","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910023,"choices":[{"index":0,"delta":{"role":"assistant","content":"Update"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052541-znjKrucNZrGyRGlj9kgI","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052541,"choices":[{"index":0,"delta":{"role":"assistant","content":"Update"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910023-cE8wcf03fPUk6DeRS4aD","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910023,"choices":[{"index":0,"delta":{"role":"assistant","content":" main.go to print"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052541-znjKrucNZrGyRGlj9kgI","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052541,"choices":[{"index":0,"delta":{"role":"assistant","content":" main"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910023-cE8wcf03fPUk6DeRS4aD","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910023,"choices":[{"index":0,"delta":{"role":"assistant","content":" hello"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052541-znjKrucNZrGyRGlj9kgI","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052541,"choices":[{"index":0,"delta":{"role":"assistant","content":".go"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910023-cE8wcf03fPUk6DeRS4aD","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910023,"choices":[{"index":0,"delta":{"role":"assistant","content":" from crush"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052541-znjKrucNZrGyRGlj9kgI","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052541,"choices":[{"index":0,"delta":{"role":"assistant","content":" to"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910023-cE8wcf03fPUk6DeRS4aD","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910023,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}],"system_fingerprint":""}
+      data: {"id":"gen-1763052541-znjKrucNZrGyRGlj9kgI","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052541,"choices":[{"index":0,"delta":{"role":"assistant","content":" print"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910023-cE8wcf03fPUk6DeRS4aD","provider":"Novita","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910023,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":145,"completion_tokens":8,"total_tokens":153,"cost":0.00003375,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00002175,"upstream_inference_completions_cost":0.000012},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763052541-znjKrucNZrGyRGlj9kgI","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052541,"choices":[{"index":0,"delta":{"role":"assistant","content":" hello"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+
+      data: {"id":"gen-1763052541-znjKrucNZrGyRGlj9kgI","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052541,"choices":[{"index":0,"delta":{"role":"assistant","content":" from"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+
+      data: {"id":"gen-1763052541-znjKrucNZrGyRGlj9kgI","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052541,"choices":[{"index":0,"delta":{"role":"assistant","content":" crush"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+
+      data: {"id":"gen-1763052541-znjKrucNZrGyRGlj9kgI","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052541,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+
+      data: {"id":"gen-1763052541-znjKrucNZrGyRGlj9kgI","provider":"Parasail","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052541,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":141,"completion_tokens":9,"total_tokens":150,"cost":0.000024,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.0000141,"upstream_inference_completions_cost":0.0000099},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -45,15 +53,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 1.368202375s
+    duration: 769.608625ms
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45277
+    content_length: 46990
     host: ""

internal/agent/testdata/TestCoderAgent/openrouter-kimi-k2/write_tool.yaml πŸ”—

@@ -24,17 +24,17 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"gen-1761910094-GtEfRGOYinkHCLBjmD3s","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910094,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052631-4hRxQBB1LFt5BAllqs8O","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052631,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910094-GtEfRGOYinkHCLBjmD3s","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910094,"choices":[{"index":0,"delta":{"role":"assistant","content":"Create"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052631-4hRxQBB1LFt5BAllqs8O","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052631,"choices":[{"index":0,"delta":{"role":"assistant","content":"Create"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910094-GtEfRGOYinkHCLBjmD3s","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910094,"choices":[{"index":0,"delta":{"role":"assistant","content":" config.json with name"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052631-4hRxQBB1LFt5BAllqs8O","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052631,"choices":[{"index":0,"delta":{"role":"assistant","content":" config.json with test"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910094-GtEfRGOYinkHCLBjmD3s","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910094,"choices":[{"index":0,"delta":{"role":"assistant","content":" and version values"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+      data: {"id":"gen-1763052631-4hRxQBB1LFt5BAllqs8O","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052631,"choices":[{"index":0,"delta":{"role":"assistant","content":" data"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
 
-      data: {"id":"gen-1761910094-GtEfRGOYinkHCLBjmD3s","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910094,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+      data: {"id":"gen-1763052631-4hRxQBB1LFt5BAllqs8O","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052631,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
 
-      data: {"id":"gen-1761910094-GtEfRGOYinkHCLBjmD3s","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1761910094,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":155,"completion_tokens":9,"total_tokens":164,"cost":0.00003675,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00002325,"upstream_inference_completions_cost":0.0000135},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+      data: {"id":"gen-1763052631-4hRxQBB1LFt5BAllqs8O","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1763052631,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":155,"completion_tokens":7,"total_tokens":162,"cost":0.00003165,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00002325,"upstream_inference_completions_cost":0.0000084},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
 
       data: [DONE]
 
@@ -43,15 +43,15 @@ interactions:
       - text/event-stream
     status: 200 OK
     code: 200
-    duration: 1.740511708s
+    duration: 1.065012417s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45314
+    content_length: 47027
     host: ""

internal/agent/testdata/TestCoderAgent/zai-glm4.6/bash_tool.yaml πŸ”—

@@ -24,21 +24,19 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"202510311928475a9131ca38d64af3","created":1761910127,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
+      data: {"id":"202511140051060f0c70f8c5d24ef5","created":1763052666,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
 
-      data: {"id":"202510311928475a9131ca38d64af3","created":1761910127,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"B"}}]}
+      data: {"id":"202511140051060f0c70f8c5d24ef5","created":1763052666,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Create"}}]}
 
-      data: {"id":"202510311928475a9131ca38d64af3","created":1761910127,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"ash"}}]}
+      data: {"id":"202511140051060f0c70f8c5d24ef5","created":1763052666,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" file"}}]}
 
-      data: {"id":"202510311928475a9131ca38d64af3","created":1761910127,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" file"}}]}
+      data: {"id":"202511140051060f0c70f8c5d24ef5","created":1763052666,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" with"}}]}
 
-      data: {"id":"202510311928475a9131ca38d64af3","created":1761910127,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" creation"}}]}
+      data: {"id":"202511140051060f0c70f8c5d24ef5","created":1763052666,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" bash"}}]}
 
-      data: {"id":"202510311928475a9131ca38d64af3","created":1761910127,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" without"}}]}
+      data: {"id":"202511140051060f0c70f8c5d24ef5","created":1763052666,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" content"}}]}
 
-      data: {"id":"202510311928475a9131ca38d64af3","created":1761910127,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" timestamp"}}]}
-
-      data: {"id":"202510311928475a9131ca38d64af3","created":1761910127,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":140,"completion_tokens":10,"total_tokens":150,"prompt_tokens_details":{"cached_tokens":114}}}
+      data: {"id":"202511140051060f0c70f8c5d24ef5","created":1763052666,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":140,"completion_tokens":9,"total_tokens":149,"prompt_tokens_details":{"cached_tokens":4}}}
 
       data: [DONE]
 
@@ -47,15 +45,15 @@ interactions:
       - text/event-stream;charset=UTF-8
     status: 200 OK
     code: 200
-    duration: 721.892875ms
+    duration: 1.21418475s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45175
+    content_length: 46888
     host: ""

internal/agent/testdata/TestCoderAgent/zai-glm4.6/download_tool.yaml πŸ”—

@@ -24,23 +24,17 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"202510311928525f9092beb5134db5","created":1761910133,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
+      data: {"id":"2025111400511397dbc3f3d7714e98","created":1763052673,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
 
-      data: {"id":"202510311928525f9092beb5134db5","created":1761910133,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Download"}}]}
+      data: {"id":"2025111400511397dbc3f3d7714e98","created":1763052673,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Download"}}]}
 
-      data: {"id":"202510311928525f9092beb5134db5","created":1761910133,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" file"}}]}
+      data: {"id":"2025111400511397dbc3f3d7714e98","created":1763052673,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" file"}}]}
 
-      data: {"id":"202510311928525f9092beb5134db5","created":1761910133,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" and"}}]}
+      data: {"id":"2025111400511397dbc3f3d7714e98","created":1763052673,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" from example"}}]}
 
-      data: {"id":"202510311928525f9092beb5134db5","created":1761910133,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" save"}}]}
+      data: {"id":"2025111400511397dbc3f3d7714e98","created":1763052673,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".com"}}]}
 
-      data: {"id":"202510311928525f9092beb5134db5","created":1761910133,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" as"}}]}
-
-      data: {"id":"202510311928525f9092beb5134db5","created":1761910133,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" example"}}]}
-
-      data: {"id":"202510311928525f9092beb5134db5","created":1761910133,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".txt"}}]}
-
-      data: {"id":"202510311928525f9092beb5134db5","created":1761910133,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":143,"completion_tokens":11,"total_tokens":154,"prompt_tokens_details":{"cached_tokens":114}}}
+      data: {"id":"2025111400511397dbc3f3d7714e98","created":1763052673,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":143,"completion_tokens":9,"total_tokens":152,"prompt_tokens_details":{"cached_tokens":114}}}
 
       data: [DONE]
 
@@ -49,15 +43,15 @@ interactions:
       - text/event-stream;charset=UTF-8
     status: 200 OK
     code: 200
-    duration: 985.514791ms
+    duration: 1.41999975s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45200
+    content_length: 46913
     host: ""

internal/agent/testdata/TestCoderAgent/zai-glm4.6/glob_tool.yaml πŸ”—

@@ -6,9 +6,9 @@ interactions:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 718
+    content_length: 46849
     host: ""
-    body: '{"messages":[{"content":"you will generate a short title based on the first message a user begins a conversation with\n\n<rules>\n- ensure it is not more than 50 characters long\n- the title should be a summary of the user''s message\n- it should be one line long\n- do not use quotes or colons\n- the entire text you return will be used as the title\n- never return anything that is more than one sentence (one line) long\n</rules>\n\n /no_think","role":"system"},{"content":"Generate a concise title for the following content:\n\nuse glob to find all .go files in the current directory\n <think>\n\n</think>","role":"user"}],"model":"glm-4.5-air","max_tokens":40,"stream_options":{"include_usage":true},"stream":true}'

internal/agent/testdata/TestCoderAgent/zai-glm4.6/grep_tool.yaml πŸ”—

@@ -24,23 +24,17 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"202510311929138fde4a0b262745c5","created":1761910153,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
+      data: {"id":"2025111400514174a058d8ceb64121","created":1763052701,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
 
-      data: {"id":"202510311929138fde4a0b262745c5","created":1761910153,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"G"}}]}
+      data: {"id":"2025111400514174a058d8ceb64121","created":1763052701,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"grep"}}]}
 
-      data: {"id":"202510311929138fde4a0b262745c5","created":1761910153,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"rep"}}]}
+      data: {"id":"2025111400514174a058d8ceb64121","created":1763052701,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" package"}}]}
 
-      data: {"id":"202510311929138fde4a0b262745c5","created":1761910153,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" for"}}]}
+      data: {"id":"2025111400514174a058d8ceb64121","created":1763052701,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" go"}}]}
 
-      data: {"id":"202510311929138fde4a0b262745c5","created":1761910153,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" package"}}]}
+      data: {"id":"2025111400514174a058d8ceb64121","created":1763052701,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" files"}}]}
 
-      data: {"id":"202510311929138fde4a0b262745c5","created":1761910153,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" in"}}]}
-
-      data: {"id":"202510311929138fde4a0b262745c5","created":1761910153,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Go"}}]}
-
-      data: {"id":"202510311929138fde4a0b262745c5","created":1761910153,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" files"}}]}
-
-      data: {"id":"202510311929138fde4a0b262745c5","created":1761910153,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":133,"completion_tokens":11,"total_tokens":144,"prompt_tokens_details":{"cached_tokens":4}}}
+      data: {"id":"2025111400514174a058d8ceb64121","created":1763052701,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":133,"completion_tokens":8,"total_tokens":141,"prompt_tokens_details":{"cached_tokens":115}}}
 
       data: [DONE]
 
@@ -49,15 +43,15 @@ interactions:
       - text/event-stream;charset=UTF-8
     status: 200 OK
     code: 200
-    duration: 674.16375ms
+    duration: 1.451020458s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45134
+    content_length: 46847
     host: ""

internal/agent/testdata/TestCoderAgent/zai-glm4.6/multiedit_tool.yaml πŸ”—

@@ -6,9 +6,9 @@ interactions:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 791
+    content_length: 46927
     host: ""
-    body: '{"messages":[{"content":"you will generate a short title based on the first message a user begins a conversation with\n\n<rules>\n- ensure it is not more than 50 characters long\n- the title should be a summary of the user''s message\n- it should be one line long\n- do not use quotes or colons\n- the entire text you return will be used as the title\n- never return anything that is more than one sentence (one line) long\n</rules>\n\n /no_think","role":"system"},{"content":"Generate a concise title for the following content:\n\nuse multiedit to change ''Hello, World!'' to ''Hello, Crush!'' and add a comment ''// Greeting'' above the fmt.Println line in main.go\n <think>\n\n</think>","role":"user"}],"model":"glm-4.5-air","max_tokens":40,"stream_options":{"include_usage":true},"stream":true}'

internal/agent/testdata/TestCoderAgent/zai-glm4.6/parallel_tool_calls.yaml πŸ”—

@@ -24,19 +24,19 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"202510311929487b90bdbe3b9e4f2c","created":1761910188,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
+      data: {"id":"202511140052098fbba3c33f2e4a7e","created":1763052729,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
 
-      data: {"id":"202510311929487b90bdbe3b9e4f2c","created":1761910188,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Parallel"}}]}
+      data: {"id":"202511140052098fbba3c33f2e4a7e","created":1763052729,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Parallel"}}]}
 
-      data: {"id":"202510311929487b90bdbe3b9e4f2c","created":1761910188,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" glob"}}]}
+      data: {"id":"202511140052098fbba3c33f2e4a7e","created":1763052729,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" glob"}}]}
 
-      data: {"id":"202510311929487b90bdbe3b9e4f2c","created":1761910188,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" and"}}]}
+      data: {"id":"202511140052098fbba3c33f2e4a7e","created":1763052729,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" and"}}]}
 
-      data: {"id":"202510311929487b90bdbe3b9e4f2c","created":1761910188,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" ls"}}]}
+      data: {"id":"202511140052098fbba3c33f2e4a7e","created":1763052729,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" ls"}}]}
 
-      data: {"id":"202510311929487b90bdbe3b9e4f2c","created":1761910188,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" commands"}}]}
+      data: {"id":"202511140052098fbba3c33f2e4a7e","created":1763052729,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" commands"}}]}
 
-      data: {"id":"202510311929487b90bdbe3b9e4f2c","created":1761910188,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":149,"completion_tokens":9,"total_tokens":158,"prompt_tokens_details":{"cached_tokens":122}}}
+      data: {"id":"202511140052098fbba3c33f2e4a7e","created":1763052729,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":149,"completion_tokens":9,"total_tokens":158,"prompt_tokens_details":{"cached_tokens":122}}}
 
       data: [DONE]
 
@@ -45,15 +45,15 @@ interactions:
       - text/event-stream;charset=UTF-8
     status: 200 OK
     code: 200
-    duration: 729.095834ms
+    duration: 1.064597541s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45225
+    content_length: 46938
     host: ""

internal/agent/testdata/TestCoderAgent/zai-glm4.6/read_a_file.yaml πŸ”—

@@ -24,17 +24,15 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"202510311928360f6fed40ae54414a","created":1761910116,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
+      data: {"id":"2025111400504912adf17fef49444d","created":1763052649,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
 
-      data: {"id":"202510311928360f6fed40ae54414a","created":1761910116,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Read"}}]}
+      data: {"id":"2025111400504912adf17fef49444d","created":1763052649,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Go"}}]}
 
-      data: {"id":"202510311928360f6fed40ae54414a","created":1761910116,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" the"}}]}
+      data: {"id":"2025111400504912adf17fef49444d","created":1763052649,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Module"}}]}
 
-      data: {"id":"202510311928360f6fed40ae54414a","created":1761910116,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" go"}}]}
+      data: {"id":"2025111400504912adf17fef49444d","created":1763052649,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Analysis"}}]}
 
-      data: {"id":"202510311928360f6fed40ae54414a","created":1761910116,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" mod"}}]}
-
-      data: {"id":"202510311928360f6fed40ae54414a","created":1761910116,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":124,"completion_tokens":8,"total_tokens":132,"prompt_tokens_details":{"cached_tokens":4}}}
+      data: {"id":"2025111400504912adf17fef49444d","created":1763052649,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":124,"completion_tokens":7,"total_tokens":131,"prompt_tokens_details":{"cached_tokens":114}}}
 
       data: [DONE]
 
@@ -43,15 +41,15 @@ interactions:
       - text/event-stream;charset=UTF-8
     status: 200 OK
     code: 200
-    duration: 784.494417ms
+    duration: 1.194369166s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45098
+    content_length: 46811
     host: ""

internal/agent/testdata/TestCoderAgent/zai-glm4.6/simple_test.yaml πŸ”—

@@ -24,11 +24,13 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"20251031192833c9eb6e69b4dd4ae5","created":1761910113,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
+      data: {"id":"20251114005046333dd12fd72448dc","created":1763052646,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
 
-      data: {"id":"20251031192833c9eb6e69b4dd4ae5","created":1761910113,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"}}]}
+      data: {"id":"20251114005046333dd12fd72448dc","created":1763052646,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"G"}}]}
 
-      data: {"id":"20251031192833c9eb6e69b4dd4ae5","created":1761910113,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":121,"completion_tokens":5,"total_tokens":126,"prompt_tokens_details":{"cached_tokens":4}}}
+      data: {"id":"20251114005046333dd12fd72448dc","created":1763052646,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"reeting"}}]}
+
+      data: {"id":"20251114005046333dd12fd72448dc","created":1763052646,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":121,"completion_tokens":6,"total_tokens":127,"prompt_tokens_details":{"cached_tokens":114}}}
 
       data: [DONE]
 
@@ -37,15 +39,15 @@ interactions:
       - text/event-stream;charset=UTF-8
     status: 200 OK
     code: 200
-    duration: 2.120641833s
+    duration: 2.311827458s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45088
+    content_length: 46801
     host: ""

internal/agent/testdata/TestCoderAgent/zai-glm4.6/update_a_file.yaml πŸ”—

@@ -24,21 +24,25 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"2025103119284010db94ea823c4a22","created":1761910120,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
+      data: {"id":"2025111400505978547e15a5934733","created":1763052659,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
 
-      data: {"id":"2025103119284010db94ea823c4a22","created":1761910120,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Update"}}]}
+      data: {"id":"2025111400505978547e15a5934733","created":1763052659,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Update"}}]}
 
-      data: {"id":"2025103119284010db94ea823c4a22","created":1761910120,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" main"}}]}
+      data: {"id":"2025111400505978547e15a5934733","created":1763052659,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" main"}}]}
 
-      data: {"id":"2025103119284010db94ea823c4a22","created":1761910120,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".go"}}]}
+      data: {"id":"2025111400505978547e15a5934733","created":1763052659,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".go"}}]}
 
-      data: {"id":"2025103119284010db94ea823c4a22","created":1761910120,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" to"}}]}
+      data: {"id":"2025111400505978547e15a5934733","created":1763052659,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" to"}}]}
 
-      data: {"id":"2025103119284010db94ea823c4a22","created":1761910120,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" print"}}]}
+      data: {"id":"2025111400505978547e15a5934733","created":1763052659,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" print"}}]}
 
-      data: {"id":"2025103119284010db94ea823c4a22","created":1761910120,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" hello"}}]}
+      data: {"id":"2025111400505978547e15a5934733","created":1763052659,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" hello"}}]}
 
-      data: {"id":"2025103119284010db94ea823c4a22","created":1761910120,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":134,"completion_tokens":10,"total_tokens":144,"prompt_tokens_details":{"cached_tokens":4}}}
+      data: {"id":"2025111400505978547e15a5934733","created":1763052659,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" from"}}]}
+
+      data: {"id":"2025111400505978547e15a5934733","created":1763052659,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" crush"}}]}
+
+      data: {"id":"2025111400505978547e15a5934733","created":1763052659,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":134,"completion_tokens":12,"total_tokens":146,"prompt_tokens_details":{"cached_tokens":114}}}
 
       data: [DONE]
 
@@ -47,15 +51,15 @@ interactions:
       - text/event-stream;charset=UTF-8
     status: 200 OK
     code: 200
-    duration: 1.158018209s
+    duration: 1.03368525s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45154
+    content_length: 46867
     host: ""

internal/agent/testdata/TestCoderAgent/zai-glm4.6/write_tool.yaml πŸ”—

@@ -24,19 +24,21 @@ interactions:
     proto_minor: 0
     content_length: -1
     body: |+
-      data: {"id":"2025103119294422b16915b94e40a0","created":1761910184,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
+      data: {"id":"2025111400520566f14c4c42784763","created":1763052725,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}]}
 
-      data: {"id":"2025103119294422b16915b94e40a0","created":1761910184,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Create"}}]}
+      data: {"id":"2025111400520566f14c4c42784763","created":1763052725,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Create"}}]}
 
-      data: {"id":"2025103119294422b16915b94e40a0","created":1761910184,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" config"}}]}
+      data: {"id":"2025111400520566f14c4c42784763","created":1763052725,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" config"}}]}
 
-      data: {"id":"2025103119294422b16915b94e40a0","created":1761910184,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".json"}}]}
+      data: {"id":"2025111400520566f14c4c42784763","created":1763052725,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".json"}}]}
 
-      data: {"id":"2025103119294422b16915b94e40a0","created":1761910184,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" with"}}]}
+      data: {"id":"2025111400520566f14c4c42784763","created":1763052725,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" file"}}]}
 
-      data: {"id":"2025103119294422b16915b94e40a0","created":1761910184,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" content"}}]}
+      data: {"id":"2025111400520566f14c4c42784763","created":1763052725,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" with"}}]}
 
-      data: {"id":"2025103119294422b16915b94e40a0","created":1761910184,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":148,"completion_tokens":9,"total_tokens":157,"prompt_tokens_details":{"cached_tokens":4}}}
+      data: {"id":"2025111400520566f14c4c42784763","created":1763052725,"model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" content"}}]}
+
+      data: {"id":"2025111400520566f14c4c42784763","created":1763052725,"model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":148,"completion_tokens":10,"total_tokens":158,"prompt_tokens_details":{"cached_tokens":115}}}
 
       data: [DONE]
 
@@ -45,15 +47,15 @@ interactions:
       - text/event-stream;charset=UTF-8
     status: 200 OK
     code: 200
-    duration: 781.244542ms
+    duration: 1.054026458s
 - id: 1
   request:
     proto: HTTP/1.1
     proto_major: 1
     proto_minor: 1
-    content_length: 45191
+    content_length: 46904
     host: ""

internal/agent/tools/bash.go πŸ”—

@@ -2,6 +2,7 @@ package tools
 
 import (
 	"bytes"
+	"cmp"
 	"context"
 	_ "embed"
 	"fmt"
@@ -19,15 +20,17 @@ import (
 )
 
 type BashParams struct {
-	Command     string `json:"command" description:"The command to execute"`
-	Description string `json:"description,omitempty" description:"A brief description of what the command does"`
-	Timeout     int    `json:"timeout,omitempty" description:"Optional timeout in milliseconds (max 600000)"`
+	Description     string `json:"description" description:"A brief description of what the command does, try to keep it under 30 characters or so"`
+	Command         string `json:"command" description:"The command to execute"`
+	WorkingDir      string `json:"working_dir,omitempty" description:"The working directory to execute the command in (defaults to current directory)"`
+	RunInBackground bool   `json:"run_in_background,omitempty" description:"Set to true (boolean) to run this command in the background. Use job_output to read the output later."`
 }
 
 type BashPermissionsParams struct {
-	Command     string `json:"command"`
-	Description string `json:"description"`
-	Timeout     int    `json:"timeout"`
+	Description     string `json:"description"`
+	Command         string `json:"command"`
+	WorkingDir      string `json:"working_dir"`
+	RunInBackground bool   `json:"run_in_background"`
 }
 
 type BashResponseMetadata struct {
@@ -36,15 +39,16 @@ type BashResponseMetadata struct {
 	Output           string `json:"output"`
 	Description      string `json:"description"`
 	WorkingDirectory string `json:"working_directory"`
+	Background       bool   `json:"background,omitempty"`
+	ShellID          string `json:"shell_id,omitempty"`
 }
 
 const (
 	BashToolName = "bash"
 
-	DefaultTimeout  = 1 * 60 * 1000  // 1 minutes in milliseconds
-	MaxTimeout      = 10 * 60 * 1000 // 10 minutes in milliseconds
-	MaxOutputLength = 30000
-	BashNoOutput    = "no output"
+	AutoBackgroundThreshold = 1 * time.Minute // Commands taking longer automatically become background jobs
+	MaxOutputLength         = 30000
+	BashNoOutput            = "no output"
 )
 
 //go:embed bash.tpl
@@ -59,6 +63,7 @@ type bashDescriptionData struct {
 	BannedCommands  string
 	MaxOutputLength int
 	Attribution     config.Attribution
+	ModelName       string
 }
 
 var bannedCommands = []string{
@@ -134,13 +139,14 @@ var bannedCommands = []string{
 	"ufw",
 }
 
-func bashDescription(attribution *config.Attribution) string {
+func bashDescription(attribution *config.Attribution, modelName string) string {
 	bannedCommandsStr := strings.Join(bannedCommands, ", ")
 	var out bytes.Buffer
 	if err := bashDescriptionTpl.Execute(&out, bashDescriptionData{
 		BannedCommands:  bannedCommandsStr,
 		MaxOutputLength: MaxOutputLength,
 		Attribution:     *attribution,
+		ModelName:       modelName,
 	}); err != nil {
 		// this should never happen.
 		panic("failed to execute bash description template: " + err.Error())
@@ -180,24 +186,18 @@ func blockFuncs() []shell.BlockFunc {
 	}
 }
 
-func NewBashTool(permissions permission.Service, workingDir string, attribution *config.Attribution) fantasy.AgentTool {
-	// Set up command blocking on the persistent shell
-	persistentShell := shell.GetPersistentShell(workingDir)
-	persistentShell.SetBlockFuncs(blockFuncs())
+func NewBashTool(permissions permission.Service, workingDir string, attribution *config.Attribution, modelName string) fantasy.AgentTool {
 	return fantasy.NewAgentTool(
 		BashToolName,
-		string(bashDescription(attribution)),
+		string(bashDescription(attribution, modelName)),
 		func(ctx context.Context, params BashParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
-			if params.Timeout > MaxTimeout {
-				params.Timeout = MaxTimeout
-			} else if params.Timeout <= 0 {
-				params.Timeout = DefaultTimeout
-			}
-
 			if params.Command == "" {
 				return fantasy.NewTextErrorResponse("missing command"), nil
 			}
 
+			// Determine working directory
+			execWorkingDir := cmp.Or(params.WorkingDir, workingDir)
+
 			isSafeReadOnly := false
 			cmdLower := strings.ToLower(params.Command)
 
@@ -215,88 +215,197 @@ func NewBashTool(permissions permission.Service, workingDir string, attribution
 				return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for executing shell command")
 			}
 			if !isSafeReadOnly {
-				shell := shell.GetPersistentShell(workingDir)
 				p := permissions.Request(
 					permission.CreatePermissionRequest{
 						SessionID:   sessionID,
-						Path:        shell.GetWorkingDir(),
+						Path:        execWorkingDir,
 						ToolCallID:  call.ID,
 						ToolName:    BashToolName,
 						Action:      "execute",
 						Description: fmt.Sprintf("Execute command: %s", params.Command),
-						Params: BashPermissionsParams{
-							Command:     params.Command,
-							Description: params.Description,
-						},
+						Params:      BashPermissionsParams(params),
 					},
 				)
 				if !p {
 					return fantasy.ToolResponse{}, permission.ErrorPermissionDenied
 				}
 			}
-			startTime := time.Now()
-			if params.Timeout > 0 {
-				var cancel context.CancelFunc
-				ctx, cancel = context.WithTimeout(ctx, time.Duration(params.Timeout)*time.Millisecond)
-				defer cancel()
-			}
 
-			persistentShell := shell.GetPersistentShell(workingDir)
-			stdout, stderr, err := persistentShell.Exec(ctx, params.Command)
+			// If explicitly requested as background, start immediately with detached context
+			if params.RunInBackground {
+				startTime := time.Now()
+				bgManager := shell.GetBackgroundShellManager()
+				bgManager.Cleanup()
+				// Use background context so it continues after tool returns
+				bgShell, err := bgManager.Start(context.Background(), execWorkingDir, blockFuncs(), params.Command, params.Description)
+				if err != nil {
+					return fantasy.ToolResponse{}, fmt.Errorf("error starting background shell: %w", err)
+				}
 
-			// Get the current working directory after command execution
-			currentWorkingDir := persistentShell.GetWorkingDir()
-			interrupted := shell.IsInterrupt(err)
-			exitCode := shell.ExitCode(err)
-			if exitCode == 0 && !interrupted && err != nil {
-				return fantasy.ToolResponse{}, fmt.Errorf("error executing command: %w", err)
-			}
+				// Wait a short time to detect fast failures (blocked commands, syntax errors, etc.)
+				time.Sleep(1 * time.Second)
+				stdout, stderr, done, execErr := bgShell.GetOutput()
 
-			stdout = truncateOutput(stdout)
-			stderr = truncateOutput(stderr)
+				if done {
+					// Command failed or completed very quickly
+					bgManager.Remove(bgShell.ID)
 
-			errorMessage := stderr
-			if errorMessage == "" && err != nil {
-				errorMessage = err.Error()
-			}
+					interrupted := shell.IsInterrupt(execErr)
+					exitCode := shell.ExitCode(execErr)
+					if exitCode == 0 && !interrupted && execErr != nil {
+						return fantasy.ToolResponse{}, fmt.Errorf("[Job %s] error executing command: %w", bgShell.ID, execErr)
+					}
+
+					stdout = formatOutput(stdout, stderr, execErr)
 
-			if interrupted {
-				if errorMessage != "" {
-					errorMessage += "\n"
+					metadata := BashResponseMetadata{
+						StartTime:        startTime.UnixMilli(),
+						EndTime:          time.Now().UnixMilli(),
+						Output:           stdout,
+						Description:      params.Description,
+						Background:       params.RunInBackground,
+						WorkingDirectory: bgShell.WorkingDir,
+					}
+					if stdout == "" {
+						return fantasy.WithResponseMetadata(fantasy.NewTextResponse(BashNoOutput), metadata), nil
+					}
+					stdout += fmt.Sprintf("\n\n<cwd>%s</cwd>", normalizeWorkingDir(bgShell.WorkingDir))
+					return fantasy.WithResponseMetadata(fantasy.NewTextResponse(stdout), metadata), nil
 				}
-				errorMessage += "Command was aborted before completion"
-			} else if exitCode != 0 {
-				if errorMessage != "" {
-					errorMessage += "\n"
+
+				// Still running after fast-failure check - return as background job
+				metadata := BashResponseMetadata{
+					StartTime:        startTime.UnixMilli(),
+					EndTime:          time.Now().UnixMilli(),
+					Description:      params.Description,
+					WorkingDirectory: bgShell.WorkingDir,
+					Background:       true,
+					ShellID:          bgShell.ID,
 				}
-				errorMessage += fmt.Sprintf("Exit code %d", exitCode)
+				response := fmt.Sprintf("Background shell started with ID: %s\n\nUse job_output tool to view output or job_kill to terminate.", bgShell.ID)
+				return fantasy.WithResponseMetadata(fantasy.NewTextResponse(response), metadata), nil
 			}
 
-			hasBothOutputs := stdout != "" && stderr != ""
+			// Start synchronous execution with auto-background support
+			startTime := time.Now()
+
+			// Start with detached context so it can survive if moved to background
+			bgManager := shell.GetBackgroundShellManager()
+			bgManager.Cleanup()
+			bgShell, err := bgManager.Start(context.Background(), execWorkingDir, blockFuncs(), params.Command, params.Description)
+			if err != nil {
+				return fantasy.ToolResponse{}, fmt.Errorf("error starting shell: %w", err)
+			}
 
-			if hasBothOutputs {
-				stdout += "\n"
+			// Wait for either completion, auto-background threshold, or context cancellation
+			ticker := time.NewTicker(100 * time.Millisecond)
+			defer ticker.Stop()
+			timeout := time.After(AutoBackgroundThreshold)
+
+			var stdout, stderr string
+			var done bool
+			var execErr error
+
+		waitLoop:
+			for {
+				select {
+				case <-ticker.C:
+					stdout, stderr, done, execErr = bgShell.GetOutput()
+					if done {
+						break waitLoop
+					}
+				case <-timeout:
+					stdout, stderr, done, execErr = bgShell.GetOutput()
+					break waitLoop
+				case <-ctx.Done():
+					// Incoming context was cancelled before we moved to background
+					// Kill the shell and return error
+					bgManager.Kill(bgShell.ID)
+					return fantasy.ToolResponse{}, ctx.Err()
+				}
 			}
 
-			if errorMessage != "" {
-				stdout += "\n" + errorMessage
+			if done {
+				// Command completed within threshold - return synchronously
+				// Remove from background manager since we're returning directly
+				// Don't call Kill() as it cancels the context and corrupts the exit code
+				bgManager.Remove(bgShell.ID)
+
+				interrupted := shell.IsInterrupt(execErr)
+				exitCode := shell.ExitCode(execErr)
+				if exitCode == 0 && !interrupted && execErr != nil {
+					return fantasy.ToolResponse{}, fmt.Errorf("[Job %s] error executing command: %w", bgShell.ID, execErr)
+				}
+
+				stdout = formatOutput(stdout, stderr, execErr)
+
+				metadata := BashResponseMetadata{
+					StartTime:        startTime.UnixMilli(),
+					EndTime:          time.Now().UnixMilli(),
+					Output:           stdout,
+					Description:      params.Description,
+					Background:       params.RunInBackground,
+					WorkingDirectory: bgShell.WorkingDir,
+				}
+				if stdout == "" {
+					return fantasy.WithResponseMetadata(fantasy.NewTextResponse(BashNoOutput), metadata), nil
+				}
+				stdout += fmt.Sprintf("\n\n<cwd>%s</cwd>", normalizeWorkingDir(bgShell.WorkingDir))
+				return fantasy.WithResponseMetadata(fantasy.NewTextResponse(stdout), metadata), nil
 			}
 
+			// Still running - keep as background job
 			metadata := BashResponseMetadata{
 				StartTime:        startTime.UnixMilli(),
 				EndTime:          time.Now().UnixMilli(),
-				Output:           stdout,
 				Description:      params.Description,
-				WorkingDirectory: currentWorkingDir,
-			}
-			if stdout == "" {
-				return fantasy.WithResponseMetadata(fantasy.NewTextResponse(BashNoOutput), metadata), nil
+				WorkingDirectory: bgShell.WorkingDir,
+				Background:       true,
+				ShellID:          bgShell.ID,
 			}
-			stdout += fmt.Sprintf("\n\n<cwd>%s</cwd>", normalizeWorkingDir(currentWorkingDir))
-			return fantasy.WithResponseMetadata(fantasy.NewTextResponse(stdout), metadata), nil
+			response := fmt.Sprintf("Command is taking longer than expected and has been moved to background.\n\nBackground shell ID: %s\n\nUse job_output tool to view output or job_kill to terminate.", bgShell.ID)
+			return fantasy.WithResponseMetadata(fantasy.NewTextResponse(response), metadata), nil
 		})
 }
 
+// formatOutput formats the output of a completed command with error handling
+func formatOutput(stdout, stderr string, execErr error) string {
+	interrupted := shell.IsInterrupt(execErr)
+	exitCode := shell.ExitCode(execErr)
+
+	stdout = truncateOutput(stdout)
+	stderr = truncateOutput(stderr)
+
+	errorMessage := stderr
+	if errorMessage == "" && execErr != nil {
+		errorMessage = execErr.Error()
+	}
+
+	if interrupted {
+		if errorMessage != "" {
+			errorMessage += "\n"
+		}
+		errorMessage += "Command was aborted before completion"
+	} else if exitCode != 0 {
+		if errorMessage != "" {
+			errorMessage += "\n"
+		}
+		errorMessage += fmt.Sprintf("Exit code %d", exitCode)
+	}
+
+	hasBothOutputs := stdout != "" && stderr != ""
+
+	if hasBothOutputs {
+		stdout += "\n"
+	}
+
+	if errorMessage != "" {
+		stdout += "\n" + errorMessage
+	}
+
+	return stdout
+}
+
 func truncateOutput(content string) string {
 	if len(content) <= MaxOutputLength {
 		return content

internal/agent/tools/bash.tpl πŸ”—

@@ -1,4 +1,4 @@
-Executes bash commands in persistent shell session with timeout and security measures.
+Executes bash commands with automatic background conversion for long-running tasks.
 
 <cross_platform>
 Uses mvdan/sh interpreter (Bash-compatible on all platforms including Windows).
@@ -10,18 +10,38 @@ Common shell builtins and core utils available on Windows.
 1. Directory Verification: If creating directories/files, use LS tool to verify parent exists
 2. Security Check: Banned commands ({{ .BannedCommands }}) return error - explain to user. Safe read-only commands execute without prompts
 3. Command Execution: Execute with proper quoting, capture output
-4. Output Processing: Truncate if exceeds {{ .MaxOutputLength }} characters
-5. Return Result: Include errors, metadata with <cwd></cwd> tags
+4. Auto-Background: Commands exceeding 1 minute automatically move to background and return shell ID
+5. Output Processing: Truncate if exceeds {{ .MaxOutputLength }} characters
+6. Return Result: Include errors, metadata with <cwd></cwd> tags
 </execution_steps>
 
 <usage_notes>
-- Command required, timeout optional (max 600000ms/10min, default 30min if unspecified)
+- Command required, working_dir optional (defaults to current directory)
 - IMPORTANT: Use Grep/Glob/Agent tools instead of 'find'/'grep'. Use View/LS tools instead of 'cat'/'head'/'tail'/'ls'
 - Chain with ';' or '&&', avoid newlines except in quoted strings
-- Shell state persists (env vars, virtual envs, cwd, etc.)
+- Each command runs in independent shell (no state persistence between calls)
 - Prefer absolute paths over 'cd' (use 'cd' only if user explicitly requests)
 </usage_notes>
 
+<background_execution>
+- Set run_in_background=true to run commands in a separate background shell
+- Returns a shell ID for managing the background process
+- Use job_output tool to view current output from background shell
+- Use job_kill tool to terminate a background shell
+- IMPORTANT: NEVER use `&` at the end of commands to run in background - use run_in_background parameter instead
+- Commands that should run in background:
+  * Long-running servers (e.g., `npm start`, `python -m http.server`, `node server.js`)
+  * Watch/monitoring tasks (e.g., `npm run watch`, `tail -f logfile`)
+  * Continuous processes that don't exit on their own
+  * Any command expected to run indefinitely
+- Commands that should NOT run in background:
+  * Build commands (e.g., `npm run build`, `go build`)
+  * Test suites (e.g., `npm test`, `pytest`)
+  * Git operations
+  * File operations
+  * Short-lived scripts
+</background_execution>
+
 <git_commits>
 When user asks to create git commit:
 
@@ -40,15 +60,21 @@ When user asks to create git commit:
    - Use clear language, accurate reflection ("add"=new feature, "update"=enhancement, "fix"=bug fix)
    - Avoid generic messages, review draft
 
-4. Create commit with Crush signature using HEREDOC:
+4. Create commit{{ if or (eq .Attribution.TrailerStyle "assisted-by") (eq .Attribution.TrailerStyle "co-authored-by")}} with attribution{{ end }} using HEREDOC:
    git commit -m "$(cat <<'EOF'
    Commit message here.
-{{ if .Attribution.GeneratedWith}}
+
+{{ if .Attribution.GeneratedWith }}
    πŸ’˜ Generated with Crush
-{{ end }}
-{{ if .Attribution.CoAuthoredBy}}
+{{ end}}
+{{if eq .Attribution.TrailerStyle "assisted-by" }}
+
+   Assisted-by: {{ .ModelName }} via Crush <crush@charm.land>
+{{ else if eq .Attribution.TrailerStyle "co-authored-by" }}
+
    Co-Authored-By: Crush <crush@charm.land>
 {{ end }}
+
    EOF
    )"
 

internal/agent/tools/job_kill.go πŸ”—

@@ -0,0 +1,59 @@
+package tools
+
+import (
+	"context"
+	_ "embed"
+	"fmt"
+
+	"charm.land/fantasy"
+	"github.com/charmbracelet/crush/internal/shell"
+)
+
+const (
+	JobKillToolName = "job_kill"
+)
+
+//go:embed job_kill.md
+var jobKillDescription []byte
+
+type JobKillParams struct {
+	ShellID string `json:"shell_id" description:"The ID of the background shell to terminate"`
+}
+
+type JobKillResponseMetadata struct {
+	ShellID     string `json:"shell_id"`
+	Command     string `json:"command"`
+	Description string `json:"description"`
+}
+
+func NewJobKillTool() fantasy.AgentTool {
+	return fantasy.NewAgentTool(
+		JobKillToolName,
+		string(jobKillDescription),
+		func(ctx context.Context, params JobKillParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
+			if params.ShellID == "" {
+				return fantasy.NewTextErrorResponse("missing shell_id"), nil
+			}
+
+			bgManager := shell.GetBackgroundShellManager()
+
+			bgShell, ok := bgManager.Get(params.ShellID)
+			if !ok {
+				return fantasy.NewTextErrorResponse(fmt.Sprintf("background shell not found: %s", params.ShellID)), nil
+			}
+
+			metadata := JobKillResponseMetadata{
+				ShellID:     params.ShellID,
+				Command:     bgShell.Command,
+				Description: bgShell.Description,
+			}
+
+			err := bgManager.Kill(params.ShellID)
+			if err != nil {
+				return fantasy.NewTextErrorResponse(err.Error()), nil
+			}
+
+			result := fmt.Sprintf("Background shell %s terminated successfully", params.ShellID)
+			return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result), metadata), nil
+		})
+}

internal/agent/tools/job_kill.md πŸ”—

@@ -0,0 +1,18 @@
+Terminates a background shell process.
+
+<usage>
+- Provide the shell ID returned from a background bash execution
+- Cancels the running process and cleans up resources
+</usage>
+
+<features>
+- Stop long-running background processes
+- Clean up completed background shells
+- Immediately terminates the process
+</features>
+
+<tips>
+- Use this when you need to stop a background process
+- The process is terminated immediately (similar to SIGTERM)
+- After killing, the shell ID becomes invalid
+</tips>

internal/agent/tools/job_output.go πŸ”—

@@ -0,0 +1,85 @@
+package tools
+
+import (
+	"context"
+	_ "embed"
+	"fmt"
+	"strings"
+
+	"charm.land/fantasy"
+	"github.com/charmbracelet/crush/internal/shell"
+)
+
+const (
+	JobOutputToolName = "job_output"
+)
+
+//go:embed job_output.md
+var jobOutputDescription []byte
+
+type JobOutputParams struct {
+	ShellID string `json:"shell_id" description:"The ID of the background shell to retrieve output from"`
+}
+
+type JobOutputResponseMetadata struct {
+	ShellID          string `json:"shell_id"`
+	Command          string `json:"command"`
+	Description      string `json:"description"`
+	Done             bool   `json:"done"`
+	WorkingDirectory string `json:"working_directory"`
+}
+
+func NewJobOutputTool() fantasy.AgentTool {
+	return fantasy.NewAgentTool(
+		JobOutputToolName,
+		string(jobOutputDescription),
+		func(ctx context.Context, params JobOutputParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
+			if params.ShellID == "" {
+				return fantasy.NewTextErrorResponse("missing shell_id"), nil
+			}
+
+			bgManager := shell.GetBackgroundShellManager()
+			bgShell, ok := bgManager.Get(params.ShellID)
+			if !ok {
+				return fantasy.NewTextErrorResponse(fmt.Sprintf("background shell not found: %s", params.ShellID)), nil
+			}
+
+			stdout, stderr, done, err := bgShell.GetOutput()
+
+			var outputParts []string
+			if stdout != "" {
+				outputParts = append(outputParts, stdout)
+			}
+			if stderr != "" {
+				outputParts = append(outputParts, stderr)
+			}
+
+			status := "running"
+			if done {
+				status = "completed"
+				if err != nil {
+					exitCode := shell.ExitCode(err)
+					if exitCode != 0 {
+						outputParts = append(outputParts, fmt.Sprintf("Exit code %d", exitCode))
+					}
+				}
+			}
+
+			output := strings.Join(outputParts, "\n")
+
+			metadata := JobOutputResponseMetadata{
+				ShellID:          params.ShellID,
+				Command:          bgShell.Command,
+				Description:      bgShell.Description,
+				Done:             done,
+				WorkingDirectory: bgShell.WorkingDir,
+			}
+
+			if output == "" {
+				output = BashNoOutput
+			}
+
+			result := fmt.Sprintf("Status: %s\n\n%s", status, output)
+			return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result), metadata), nil
+		})
+}

internal/agent/tools/job_output.md πŸ”—

@@ -0,0 +1,19 @@
+Retrieves the current output from a background shell.
+
+<usage>
+- Provide the shell ID returned from a background bash execution
+- Returns the current stdout and stderr output
+- Indicates whether the shell has completed execution
+</usage>
+
+<features>
+- View output from running background processes
+- Check if background process has completed
+- Get cumulative output from process start
+</features>
+
+<tips>
+- Use this to monitor long-running processes
+- Check the 'done' status to see if process completed
+- Can be called multiple times to view incremental output
+</tips>

internal/agent/tools/job_test.go πŸ”—

@@ -0,0 +1,329 @@
+package tools
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/charmbracelet/crush/internal/shell"
+	"github.com/stretchr/testify/require"
+)
+
+func TestBackgroundShell_Integration(t *testing.T) {
+	t.Parallel()
+
+	workingDir := t.TempDir()
+	ctx := context.Background()
+
+	// Start a background shell
+	bgManager := shell.GetBackgroundShellManager()
+	bgShell, err := bgManager.Start(ctx, workingDir, nil, "echo 'hello background' && echo 'done'", "")
+	require.NoError(t, err)
+	require.NotEmpty(t, bgShell.ID)
+
+	// Wait for completion
+	bgShell.Wait()
+
+	// Check final output
+	stdout, stderr, done, err := bgShell.GetOutput()
+	require.NoError(t, err)
+	require.Contains(t, stdout, "hello background")
+	require.Contains(t, stdout, "done")
+	require.True(t, done)
+	require.Empty(t, stderr)
+
+	// Clean up
+	bgManager.Kill(bgShell.ID)
+}
+
+func TestBackgroundShell_Kill(t *testing.T) {
+	t.Parallel()
+
+	workingDir := t.TempDir()
+	ctx := context.Background()
+
+	// Start a long-running background shell
+	bgManager := shell.GetBackgroundShellManager()
+	bgShell, err := bgManager.Start(ctx, workingDir, nil, "sleep 100", "")
+	require.NoError(t, err)
+
+	// Kill it
+	err = bgManager.Kill(bgShell.ID)
+	require.NoError(t, err)
+
+	// Verify it's gone
+	_, ok := bgManager.Get(bgShell.ID)
+	require.False(t, ok)
+
+	// Verify the shell is done
+	require.True(t, bgShell.IsDone())
+}
+
+func TestBackgroundShell_MultipleOutputCalls(t *testing.T) {
+	t.Parallel()
+
+	workingDir := t.TempDir()
+	ctx := context.Background()
+
+	// Start a background shell
+	bgManager := shell.GetBackgroundShellManager()
+	bgShell, err := bgManager.Start(ctx, workingDir, nil, "echo 'step 1' && echo 'step 2' && echo 'step 3'", "")
+	require.NoError(t, err)
+	defer bgManager.Kill(bgShell.ID)
+
+	// Check that we can call GetOutput multiple times while running
+	for range 5 {
+		_, _, done, _ := bgShell.GetOutput()
+		if done {
+			break
+		}
+		time.Sleep(10 * time.Millisecond)
+	}
+
+	// Wait for completion
+	bgShell.Wait()
+
+	// Multiple calls after completion should return the same result
+	stdout1, _, done1, _ := bgShell.GetOutput()
+	require.True(t, done1)
+	require.Contains(t, stdout1, "step 1")
+	require.Contains(t, stdout1, "step 2")
+	require.Contains(t, stdout1, "step 3")
+
+	stdout2, _, done2, _ := bgShell.GetOutput()
+	require.True(t, done2)
+	require.Equal(t, stdout1, stdout2, "Multiple GetOutput calls should return same result")
+}
+
+func TestBackgroundShell_EmptyOutput(t *testing.T) {
+	t.Parallel()
+
+	workingDir := t.TempDir()
+	ctx := context.Background()
+
+	// Start a background shell with no output
+	bgManager := shell.GetBackgroundShellManager()
+	bgShell, err := bgManager.Start(ctx, workingDir, nil, "sleep 0.1", "")
+	require.NoError(t, err)
+	defer bgManager.Kill(bgShell.ID)
+
+	// Wait for completion
+	bgShell.Wait()
+
+	stdout, stderr, done, err := bgShell.GetOutput()
+	require.NoError(t, err)
+	require.Empty(t, stdout)
+	require.Empty(t, stderr)
+	require.True(t, done)
+}
+
+func TestBackgroundShell_ExitCode(t *testing.T) {
+	t.Parallel()
+
+	workingDir := t.TempDir()
+	ctx := context.Background()
+
+	// Start a background shell that exits with non-zero code
+	bgManager := shell.GetBackgroundShellManager()
+	bgShell, err := bgManager.Start(ctx, workingDir, nil, "echo 'failing' && exit 42", "")
+	require.NoError(t, err)
+	defer bgManager.Kill(bgShell.ID)
+
+	// Wait for completion
+	bgShell.Wait()
+
+	stdout, _, done, execErr := bgShell.GetOutput()
+	require.True(t, done)
+	require.Contains(t, stdout, "failing")
+	require.Error(t, execErr)
+
+	exitCode := shell.ExitCode(execErr)
+	require.Equal(t, 42, exitCode)
+}
+
+func TestBackgroundShell_WithBlockFuncs(t *testing.T) {
+	t.Parallel()
+
+	workingDir := t.TempDir()
+	ctx := context.Background()
+
+	blockFuncs := []shell.BlockFunc{
+		shell.CommandsBlocker([]string{"curl", "wget"}),
+	}
+
+	// Start a background shell with a blocked command
+	bgManager := shell.GetBackgroundShellManager()
+	bgShell, err := bgManager.Start(ctx, workingDir, blockFuncs, "curl example.com", "")
+	require.NoError(t, err)
+	defer bgManager.Kill(bgShell.ID)
+
+	// Wait for completion
+	bgShell.Wait()
+
+	stdout, stderr, done, execErr := bgShell.GetOutput()
+	require.True(t, done)
+
+	// The command should have been blocked, check stderr or error
+	if execErr != nil {
+		// Error might contain the message
+		require.Contains(t, execErr.Error(), "not allowed")
+	} else {
+		// Or it might be in stderr
+		output := stdout + stderr
+		require.Contains(t, output, "not allowed")
+	}
+}
+
+func TestBackgroundShell_StdoutAndStderr(t *testing.T) {
+	t.Parallel()
+
+	workingDir := t.TempDir()
+	ctx := context.Background()
+
+	// Start a background shell with both stdout and stderr
+	bgManager := shell.GetBackgroundShellManager()
+	bgShell, err := bgManager.Start(ctx, workingDir, nil, "echo 'stdout message' && echo 'stderr message' >&2", "")
+	require.NoError(t, err)
+	defer bgManager.Kill(bgShell.ID)
+
+	// Wait for completion
+	bgShell.Wait()
+
+	stdout, stderr, done, err := bgShell.GetOutput()
+	require.NoError(t, err)
+	require.True(t, done)
+	require.Contains(t, stdout, "stdout message")
+	require.Contains(t, stderr, "stderr message")
+}
+
+func TestBackgroundShell_ConcurrentAccess(t *testing.T) {
+	t.Parallel()
+
+	workingDir := t.TempDir()
+	ctx := context.Background()
+
+	// Start a background shell
+	bgManager := shell.GetBackgroundShellManager()
+	bgShell, err := bgManager.Start(ctx, workingDir, nil, "for i in 1 2 3 4 5; do echo \"line $i\"; sleep 0.05; done", "")
+	require.NoError(t, err)
+	defer bgManager.Kill(bgShell.ID)
+
+	// Access output concurrently from multiple goroutines
+	done := make(chan struct{})
+	errors := make(chan error, 10)
+
+	for range 10 {
+		go func() {
+			for {
+				select {
+				case <-done:
+					return
+				default:
+					_, _, _, err := bgShell.GetOutput()
+					if err != nil {
+						errors <- err
+					}
+					dir := bgShell.WorkingDir
+					if dir == "" {
+						errors <- err
+					}
+					time.Sleep(10 * time.Millisecond)
+				}
+			}
+		}()
+	}
+
+	// Let it run for a bit
+	time.Sleep(300 * time.Millisecond)
+	close(done)
+
+	// Check for any errors
+	select {
+	case err := <-errors:
+		t.Fatalf("Concurrent access caused error: %v", err)
+	case <-time.After(100 * time.Millisecond):
+		// No errors - success
+	}
+}
+
+func TestBackgroundShell_List(t *testing.T) {
+	t.Parallel()
+
+	workingDir := t.TempDir()
+	ctx := context.Background()
+
+	bgManager := shell.GetBackgroundShellManager()
+
+	// Start multiple background shells
+	shells := make([]*shell.BackgroundShell, 3)
+	for i := range 3 {
+		bgShell, err := bgManager.Start(ctx, workingDir, nil, "sleep 1", "")
+		require.NoError(t, err)
+		shells[i] = bgShell
+	}
+
+	// Get the list
+	ids := bgManager.List()
+
+	// Verify all our shells are in the list
+	for _, sh := range shells {
+		require.Contains(t, ids, sh.ID, "Shell %s not found in list", sh.ID)
+	}
+
+	// Clean up
+	for _, sh := range shells {
+		bgManager.Kill(sh.ID)
+	}
+}
+
+func TestBackgroundShell_AutoBackground(t *testing.T) {
+	t.Parallel()
+
+	workingDir := t.TempDir()
+	ctx := context.Background()
+
+	// Test that a quick command completes synchronously
+	t.Run("quick command completes synchronously", func(t *testing.T) {
+		t.Parallel()
+		bgManager := shell.GetBackgroundShellManager()
+		bgShell, err := bgManager.Start(ctx, workingDir, nil, "echo 'quick'", "")
+		require.NoError(t, err)
+
+		// Wait threshold time
+		time.Sleep(5 * time.Second)
+
+		// Should be done by now
+		stdout, stderr, done, err := bgShell.GetOutput()
+		require.NoError(t, err)
+		require.True(t, done, "Quick command should be done")
+		require.Contains(t, stdout, "quick")
+		require.Empty(t, stderr)
+
+		// Clean up
+		bgManager.Kill(bgShell.ID)
+	})
+
+	// Test that a long command stays in background
+	t.Run("long command stays in background", func(t *testing.T) {
+		t.Parallel()
+		bgManager := shell.GetBackgroundShellManager()
+		bgShell, err := bgManager.Start(ctx, workingDir, nil, "sleep 20 && echo '20 seconds completed'", "")
+		require.NoError(t, err)
+		defer bgManager.Kill(bgShell.ID)
+
+		// Wait threshold time
+		time.Sleep(5 * time.Second)
+
+		// Should still be running
+		stdout, stderr, done, err := bgShell.GetOutput()
+		require.NoError(t, err)
+		require.False(t, done, "Long command should still be running")
+		require.Empty(t, stdout, "No output yet from sleep command")
+		require.Empty(t, stderr)
+
+		// Verify we can get the shell from manager
+		retrieved, ok := bgManager.Get(bgShell.ID)
+		require.True(t, ok, "Should be able to retrieve background shell")
+		require.Equal(t, bgShell.ID, retrieved.ID)
+	})
+}

internal/agent/tools/mcp/init.go πŸ”—

@@ -109,8 +109,8 @@ func GetState(name string) (ClientInfo, bool) {
 // Close closes all MCP clients. This should be called during application shutdown.
 func Close() error {
 	var errs []error
-	for name, c := range sessions.Seq2() {
-		if err := c.Close(); err != nil &&
+	for name, session := range sessions.Seq2() {
+		if err := session.Close(); err != nil &&
 			!errors.Is(err, io.EOF) &&
 			!errors.Is(err, context.Canceled) &&
 			err.Error() != "signal: killed" {
@@ -154,9 +154,7 @@ func Initialize(ctx context.Context, permissions permission.Service, cfg *config
 				}
 			}()
 
-			ctx, cancel := context.WithTimeout(ctx, mcpTimeout(m))
-			defer cancel()
-
+			// createSession handles its own timeout internally.
 			session, err := createSession(ctx, name, m, cfg.Resolver())
 			if err != nil {
 				return
@@ -233,7 +231,6 @@ func updateState(name string, state State, err error, client *mcp.ClientSession,
 	case StateConnected:
 		info.ConnectedAt = time.Now()
 	case StateError:
-		updateTools(name, nil)
 		sessions.Del(name)
 	}
 	states.Set(name, info)

internal/agent/tools/mcp/tools.go πŸ”—

@@ -74,9 +74,9 @@ func RefreshTools(ctx context.Context, name string) {
 }
 
 func getTools(ctx context.Context, session *mcp.ClientSession) ([]*Tool, error) {
-	if session.InitializeResult().Capabilities.Tools == nil {
-		return nil, nil
-	}
+	// Always call ListTools to get the actual available tools.
+	// The InitializeResult Capabilities.Tools field may be an empty object {},
+	// which is valid per MCP spec, but we still need to call ListTools to discover tools.
 	result, err := session.ListTools(ctx, &mcp.ListToolsParams{})
 	if err != nil {
 		return nil, err

internal/app/app.go πŸ”—

@@ -29,6 +29,7 @@ import (
 	"github.com/charmbracelet/crush/internal/permission"
 	"github.com/charmbracelet/crush/internal/pubsub"
 	"github.com/charmbracelet/crush/internal/session"
+	"github.com/charmbracelet/crush/internal/shell"
 	"github.com/charmbracelet/crush/internal/term"
 	"github.com/charmbracelet/crush/internal/tui/components/anim"
 	"github.com/charmbracelet/crush/internal/tui/styles"
@@ -368,6 +369,9 @@ func (app *App) Shutdown() {
 		app.AgentCoordinator.CancelAll()
 	}
 
+	// Kill all background shells.
+	shell.GetBackgroundShellManager().KillAll()
+
 	// Shutdown all LSP clients.
 	for name, client := range app.LSPClients.Seq2() {
 		shutdownCtx, cancel := context.WithTimeout(app.globalCtx, 5*time.Second)

internal/config/attribution_migration_test.go πŸ”—

@@ -0,0 +1,95 @@
+package config
+
+import (
+	"io"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestAttributionMigration(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name             string
+		configJSON       string
+		expectedTrailer  TrailerStyle
+		expectedGenerate bool
+	}{
+		{
+			name: "old setting co_authored_by=true migrates to co-authored-by",
+			configJSON: `{
+				"options": {
+					"attribution": {
+						"co_authored_by": true,
+						"generated_with": false
+					}
+				}
+			}`,
+			expectedTrailer:  TrailerStyleCoAuthoredBy,
+			expectedGenerate: false,
+		},
+		{
+			name: "old setting co_authored_by=false migrates to none",
+			configJSON: `{
+				"options": {
+					"attribution": {
+						"co_authored_by": false,
+						"generated_with": true
+					}
+				}
+			}`,
+			expectedTrailer:  TrailerStyleNone,
+			expectedGenerate: true,
+		},
+		{
+			name: "new setting takes precedence over old setting",
+			configJSON: `{
+				"options": {
+					"attribution": {
+						"trailer_style": "assisted-by",
+						"co_authored_by": true,
+						"generated_with": false
+					}
+				}
+			}`,
+			expectedTrailer:  TrailerStyleAssistedBy,
+			expectedGenerate: false,
+		},
+		{
+			name: "default when neither setting present",
+			configJSON: `{
+				"options": {
+					"attribution": {
+						"generated_with": true
+					}
+				}
+			}`,
+			expectedTrailer:  TrailerStyleAssistedBy,
+			expectedGenerate: true,
+		},
+		{
+			name: "default when attribution is null",
+			configJSON: `{
+				"options": {}
+			}`,
+			expectedTrailer:  TrailerStyleAssistedBy,
+			expectedGenerate: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			cfg, err := loadFromReaders([]io.Reader{strings.NewReader(tt.configJSON)})
+			require.NoError(t, err)
+
+			cfg.setDefaults(t.TempDir(), "")
+
+			require.Equal(t, tt.expectedTrailer, cfg.Options.Attribution.TrailerStyle)
+			require.Equal(t, tt.expectedGenerate, cfg.Options.Attribution.GeneratedWith)
+		})
+	}
+}

internal/config/config.go πŸ”—

@@ -14,12 +14,14 @@ import (
 	"github.com/charmbracelet/catwalk/pkg/catwalk"
 	"github.com/charmbracelet/crush/internal/csync"
 	"github.com/charmbracelet/crush/internal/env"
+	"github.com/invopop/jsonschema"
 	"github.com/tidwall/sjson"
 )
 
 const (
 	appName              = "crush"
 	defaultDataDirectory = ".crush"
+	defaultInitializeAs  = "AGENTS.md"
 )
 
 var defaultContextPaths = []string{
@@ -166,9 +168,27 @@ type Permissions struct {
 	SkipRequests bool     `json:"-"`                                                                                                                              // Automatically accept all permissions (YOLO mode)
 }
 
+type TrailerStyle string
+
+const (
+	TrailerStyleNone         TrailerStyle = "none"
+	TrailerStyleCoAuthoredBy TrailerStyle = "co-authored-by"
+	TrailerStyleAssistedBy   TrailerStyle = "assisted-by"
+)
+
 type Attribution struct {
-	CoAuthoredBy  bool `json:"co_authored_by,omitempty" jsonschema:"description=Add Co-Authored-By trailer to commit messages,default=true"`
-	GeneratedWith bool `json:"generated_with,omitempty" jsonschema:"description=Add Generated with Crush line to commit messages and issues and PRs,default=true"`
+	TrailerStyle  TrailerStyle `json:"trailer_style,omitempty" jsonschema:"description=Style of attribution trailer to add to commits,enum=none,enum=co-authored-by,enum=assisted-by,default=assisted-by"`
+	CoAuthoredBy  *bool        `json:"co_authored_by,omitempty" jsonschema:"description=Deprecated: use trailer_style instead"`
+	GeneratedWith bool         `json:"generated_with,omitempty" jsonschema:"description=Add Generated with Crush line to commit messages and issues and PRs,default=true"`
+}
+
+// JSONSchemaExtend marks the co_authored_by field as deprecated in the schema.
+func (Attribution) JSONSchemaExtend(schema *jsonschema.Schema) {
+	if schema.Properties != nil {
+		if prop, ok := schema.Properties.Get("co_authored_by"); ok {
+			prop.Deprecated = true
+		}
+	}
 }
 
 type Options struct {
@@ -182,6 +202,7 @@ type Options struct {
 	DisableProviderAutoUpdate bool         `json:"disable_provider_auto_update,omitempty" jsonschema:"description=Disable providers auto-update,default=false"`
 	Attribution               *Attribution `json:"attribution,omitempty" jsonschema:"description=Attribution settings for generated content"`
 	DisableMetrics            bool         `json:"disable_metrics,omitempty" jsonschema:"description=Disable sending metrics,default=false"`
+	InitializeAs              string       `json:"initialize_as,omitempty" jsonschema:"description=Name of the context file to create/update during project initialization,default=AGENTS.md,example=AGENTS.md,example=CRUSH.md,example=CLAUDE.md,example=docs/LLMs.md"`
 }
 
 type MCPs map[string]MCPConfig
@@ -289,6 +310,8 @@ type Config struct {
 
 	// We currently only support large/small as values here.
 	Models map[SelectedModelType]SelectedModel `json:"models,omitempty" jsonschema:"description=Model configurations for different model types,example={\"large\":{\"model\":\"gpt-4o\",\"provider\":\"openai\"}}"`
+	// Recently used models stored in the data directory config.
+	RecentModels map[SelectedModelType][]SelectedModel `json:"recent_models,omitempty" jsonschema:"description=Recently used models sorted by most recent first"`
 
 	// The providers that are configured
 	Providers *csync.Map[string, ProviderConfig] `json:"providers,omitempty" jsonschema:"description=AI provider configurations"`
@@ -398,6 +421,9 @@ func (c *Config) UpdatePreferredModel(modelType SelectedModelType, model Selecte
 	if err := c.SetConfigField(fmt.Sprintf("models.%s", modelType), model); err != nil {
 		return fmt.Errorf("failed to update preferred model: %w", err)
 	}
+	if err := c.recordRecentModel(modelType, model); err != nil {
+		return err
+	}
 	return nil
 }
 
@@ -465,10 +491,55 @@ func (c *Config) SetProviderAPIKey(providerID, apiKey string) error {
 	return nil
 }
 
+const maxRecentModelsPerType = 5
+
+func (c *Config) recordRecentModel(modelType SelectedModelType, model SelectedModel) error {
+	if model.Provider == "" || model.Model == "" {
+		return nil
+	}
+
+	if c.RecentModels == nil {
+		c.RecentModels = make(map[SelectedModelType][]SelectedModel)
+	}
+
+	eq := func(a, b SelectedModel) bool {
+		return a.Provider == b.Provider && a.Model == b.Model
+	}
+
+	entry := SelectedModel{
+		Provider: model.Provider,
+		Model:    model.Model,
+	}
+
+	current := c.RecentModels[modelType]
+	withoutCurrent := slices.DeleteFunc(slices.Clone(current), func(existing SelectedModel) bool {
+		return eq(existing, entry)
+	})
+
+	updated := append([]SelectedModel{entry}, withoutCurrent...)
+	if len(updated) > maxRecentModelsPerType {
+		updated = updated[:maxRecentModelsPerType]
+	}
+
+	if slices.EqualFunc(current, updated, eq) {
+		return nil
+	}
+
+	c.RecentModels[modelType] = updated
+
+	if err := c.SetConfigField(fmt.Sprintf("recent_models.%s", modelType), updated); err != nil {
+		return fmt.Errorf("failed to persist recent models: %w", err)
+	}
+
+	return nil
+}
+
 func allToolNames() []string {
 	return []string{
 		"agent",
 		"bash",
+		"job_output",
+		"job_kill",
 		"download",
 		"edit",
 		"multiedit",

internal/config/load.go πŸ”—

@@ -329,6 +329,9 @@ func (c *Config) setDefaults(workingDir, dataDir string) {
 	if c.Models == nil {
 		c.Models = make(map[SelectedModelType]SelectedModel)
 	}
+	if c.RecentModels == nil {
+		c.RecentModels = make(map[SelectedModelType][]SelectedModel)
+	}
 	if c.MCP == nil {
 		c.MCP = make(map[string]MCPConfig)
 	}
@@ -350,9 +353,23 @@ func (c *Config) setDefaults(workingDir, dataDir string) {
 
 	if c.Options.Attribution == nil {
 		c.Options.Attribution = &Attribution{
-			CoAuthoredBy:  true,
+			TrailerStyle:  TrailerStyleAssistedBy,
 			GeneratedWith: true,
 		}
+	} else if c.Options.Attribution.TrailerStyle == "" {
+		// Migrate deprecated co_authored_by or apply default
+		if c.Options.Attribution.CoAuthoredBy != nil {
+			if *c.Options.Attribution.CoAuthoredBy {
+				c.Options.Attribution.TrailerStyle = TrailerStyleCoAuthoredBy
+			} else {
+				c.Options.Attribution.TrailerStyle = TrailerStyleNone
+			}
+		} else {
+			c.Options.Attribution.TrailerStyle = TrailerStyleAssistedBy
+		}
+	}
+	if c.Options.InitializeAs == "" {
+		c.Options.InitializeAs = defaultInitializeAs
 	}
 }
 

internal/config/load_test.go πŸ”—

@@ -50,6 +50,7 @@ func TestConfig_setDefaults(t *testing.T) {
 	require.NotNil(t, cfg.LSP)
 	require.NotNil(t, cfg.MCP)
 	require.Equal(t, filepath.Join("/tmp", ".crush"), cfg.Options.DataDirectory)
+	require.Equal(t, "AGENTS.md", cfg.Options.InitializeAs)
 	for _, path := range defaultContextPaths {
 		require.Contains(t, cfg.Options.ContextPaths, path)
 	}
@@ -485,7 +486,8 @@ func TestConfig_setupAgentsWithDisabledTools(t *testing.T) {
 	cfg.SetupAgents()
 	coderAgent, ok := cfg.Agents[AgentCoder]
 	require.True(t, ok)
-	assert.Equal(t, []string{"agent", "bash", "multiedit", "lsp_diagnostics", "lsp_references", "fetch", "agentic_fetch", "glob", "ls", "sourcegraph", "view", "write"}, coderAgent.AllowedTools)
+
+	assert.Equal(t, []string{"agent", "bash", "job_output", "job_kill", "multiedit", "lsp_diagnostics", "lsp_references", "fetch", "agentic_fetch", "glob", "ls", "sourcegraph", "view", "write"}, coderAgent.AllowedTools)
 
 	taskAgent, ok := cfg.Agents[AgentTask]
 	require.True(t, ok)
@@ -508,7 +510,7 @@ func TestConfig_setupAgentsWithEveryReadOnlyToolDisabled(t *testing.T) {
 	cfg.SetupAgents()
 	coderAgent, ok := cfg.Agents[AgentCoder]
 	require.True(t, ok)
-	assert.Equal(t, []string{"agent", "bash", "download", "edit", "multiedit", "lsp_diagnostics", "lsp_references", "fetch", "agentic_fetch", "write"}, coderAgent.AllowedTools)
+	assert.Equal(t, []string{"agent", "bash", "job_output", "job_kill", "download", "edit", "multiedit", "lsp_diagnostics", "lsp_references", "fetch", "agentic_fetch", "write"}, coderAgent.AllowedTools)
 
 	taskAgent, ok := cfg.Agents[AgentTask]
 	require.True(t, ok)

internal/config/recent_models_test.go πŸ”—

@@ -0,0 +1,253 @@
+package config
+
+import (
+	"encoding/json"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+// readConfigJSON reads and unmarshals the JSON config file at path.
+func readConfigJSON(t *testing.T, path string) map[string]any {
+	t.Helper()
+	baseDir := filepath.Dir(path)
+	fileName := filepath.Base(path)
+	b, err := fs.ReadFile(os.DirFS(baseDir), fileName)
+	require.NoError(t, err)
+	var out map[string]any
+	require.NoError(t, json.Unmarshal(b, &out))
+	return out
+}
+
+// readRecentModels reads the recent_models section from the config file.
+func readRecentModels(t *testing.T, path string) map[string]any {
+	t.Helper()
+	out := readConfigJSON(t, path)
+	rm, ok := out["recent_models"].(map[string]any)
+	require.True(t, ok)
+	return rm
+}
+
+func TestRecordRecentModel_AddsAndPersists(t *testing.T) {
+	t.Parallel()
+
+	dir := t.TempDir()
+	cfg := &Config{}
+	cfg.setDefaults(dir, "")
+	cfg.dataConfigDir = filepath.Join(dir, "config.json")
+
+	err := cfg.recordRecentModel(SelectedModelTypeLarge, SelectedModel{Provider: "openai", Model: "gpt-4o"})
+	require.NoError(t, err)
+
+	// in-memory state
+	require.Len(t, cfg.RecentModels[SelectedModelTypeLarge], 1)
+	require.Equal(t, "openai", cfg.RecentModels[SelectedModelTypeLarge][0].Provider)
+	require.Equal(t, "gpt-4o", cfg.RecentModels[SelectedModelTypeLarge][0].Model)
+
+	// persisted state
+	rm := readRecentModels(t, cfg.dataConfigDir)
+	large, ok := rm[string(SelectedModelTypeLarge)].([]any)
+	require.True(t, ok)
+	require.Len(t, large, 1)
+	item, ok := large[0].(map[string]any)
+	require.True(t, ok)
+	require.Equal(t, "openai", item["provider"])
+	require.Equal(t, "gpt-4o", item["model"])
+}
+
+func TestRecordRecentModel_DedupeAndMoveToFront(t *testing.T) {
+	t.Parallel()
+
+	dir := t.TempDir()
+	cfg := &Config{}
+	cfg.setDefaults(dir, "")
+	cfg.dataConfigDir = filepath.Join(dir, "config.json")
+
+	// Add two entries
+	require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, SelectedModel{Provider: "openai", Model: "gpt-4o"}))
+	require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, SelectedModel{Provider: "anthropic", Model: "claude"}))
+	// Re-add first; should move to front and not duplicate
+	require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, SelectedModel{Provider: "openai", Model: "gpt-4o"}))
+
+	got := cfg.RecentModels[SelectedModelTypeLarge]
+	require.Len(t, got, 2)
+	require.Equal(t, SelectedModel{Provider: "openai", Model: "gpt-4o"}, got[0])
+	require.Equal(t, SelectedModel{Provider: "anthropic", Model: "claude"}, got[1])
+}
+
+func TestRecordRecentModel_TrimsToMax(t *testing.T) {
+	t.Parallel()
+
+	dir := t.TempDir()
+	cfg := &Config{}
+	cfg.setDefaults(dir, "")
+	cfg.dataConfigDir = filepath.Join(dir, "config.json")
+
+	// Insert 6 unique models; max is 5
+	entries := []SelectedModel{
+		{Provider: "p1", Model: "m1"},
+		{Provider: "p2", Model: "m2"},
+		{Provider: "p3", Model: "m3"},
+		{Provider: "p4", Model: "m4"},
+		{Provider: "p5", Model: "m5"},
+		{Provider: "p6", Model: "m6"},
+	}
+	for _, e := range entries {
+		require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, e))
+	}
+
+	// in-memory state
+	got := cfg.RecentModels[SelectedModelTypeLarge]
+	require.Len(t, got, 5)
+	// Newest first, capped at 5: p6..p2
+	require.Equal(t, SelectedModel{Provider: "p6", Model: "m6"}, got[0])
+	require.Equal(t, SelectedModel{Provider: "p5", Model: "m5"}, got[1])
+	require.Equal(t, SelectedModel{Provider: "p4", Model: "m4"}, got[2])
+	require.Equal(t, SelectedModel{Provider: "p3", Model: "m3"}, got[3])
+	require.Equal(t, SelectedModel{Provider: "p2", Model: "m2"}, got[4])
+
+	// persisted state: verify trimmed to 5 and newest-first order
+	rm := readRecentModels(t, cfg.dataConfigDir)
+	large, ok := rm[string(SelectedModelTypeLarge)].([]any)
+	require.True(t, ok)
+	require.Len(t, large, 5)
+	// Build provider:model IDs and verify order
+	var ids []string
+	for _, v := range large {
+		m := v.(map[string]any)
+		ids = append(ids, m["provider"].(string)+":"+m["model"].(string))
+	}
+	require.Equal(t, []string{"p6:m6", "p5:m5", "p4:m4", "p3:m3", "p2:m2"}, ids)
+}
+
+func TestRecordRecentModel_SkipsEmptyValues(t *testing.T) {
+	t.Parallel()
+
+	dir := t.TempDir()
+	cfg := &Config{}
+	cfg.setDefaults(dir, "")
+	cfg.dataConfigDir = filepath.Join(dir, "config.json")
+
+	// Missing provider
+	require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, SelectedModel{Provider: "", Model: "m"}))
+	// Missing model
+	require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, SelectedModel{Provider: "p", Model: ""}))
+
+	_, ok := cfg.RecentModels[SelectedModelTypeLarge]
+	// Map may be initialized, but should have no entries
+	if ok {
+		require.Len(t, cfg.RecentModels[SelectedModelTypeLarge], 0)
+	}
+	// No file should be written (stat via fs.FS)
+	baseDir := filepath.Dir(cfg.dataConfigDir)
+	fileName := filepath.Base(cfg.dataConfigDir)
+	_, err := fs.Stat(os.DirFS(baseDir), fileName)
+	require.True(t, os.IsNotExist(err))
+}
+
+func TestRecordRecentModel_NoPersistOnNoop(t *testing.T) {
+	t.Parallel()
+
+	dir := t.TempDir()
+	cfg := &Config{}
+	cfg.setDefaults(dir, "")
+	cfg.dataConfigDir = filepath.Join(dir, "config.json")
+
+	entry := SelectedModel{Provider: "openai", Model: "gpt-4o"}
+	require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, entry))
+
+	baseDir := filepath.Dir(cfg.dataConfigDir)
+	fileName := filepath.Base(cfg.dataConfigDir)
+	before, err := fs.ReadFile(os.DirFS(baseDir), fileName)
+	require.NoError(t, err)
+
+	// Get file ModTime to verify no write occurs
+	stBefore, err := fs.Stat(os.DirFS(baseDir), fileName)
+	require.NoError(t, err)
+	beforeMod := stBefore.ModTime()
+
+	// Re-record same entry should be a no-op (no write)
+	require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, entry))
+
+	after, err := fs.ReadFile(os.DirFS(baseDir), fileName)
+	require.NoError(t, err)
+	require.Equal(t, string(before), string(after))
+
+	// Verify ModTime unchanged to ensure truly no write occurred
+	stAfter, err := fs.Stat(os.DirFS(baseDir), fileName)
+	require.NoError(t, err)
+	require.True(t, stAfter.ModTime().Equal(beforeMod), "file ModTime should not change on noop")
+}
+
+func TestUpdatePreferredModel_UpdatesRecents(t *testing.T) {
+	t.Parallel()
+
+	dir := t.TempDir()
+	cfg := &Config{}
+	cfg.setDefaults(dir, "")
+	cfg.dataConfigDir = filepath.Join(dir, "config.json")
+
+	sel := SelectedModel{Provider: "openai", Model: "gpt-4o"}
+	require.NoError(t, cfg.UpdatePreferredModel(SelectedModelTypeSmall, sel))
+
+	// in-memory
+	require.Equal(t, sel, cfg.Models[SelectedModelTypeSmall])
+	require.Len(t, cfg.RecentModels[SelectedModelTypeSmall], 1)
+
+	// persisted (read via fs.FS)
+	rm := readRecentModels(t, cfg.dataConfigDir)
+	small, ok := rm[string(SelectedModelTypeSmall)].([]any)
+	require.True(t, ok)
+	require.Len(t, small, 1)
+}
+
+func TestRecordRecentModel_TypeIsolation(t *testing.T) {
+	t.Parallel()
+
+	dir := t.TempDir()
+	cfg := &Config{}
+	cfg.setDefaults(dir, "")
+	cfg.dataConfigDir = filepath.Join(dir, "config.json")
+
+	// Add models to both large and small types
+	largeModel := SelectedModel{Provider: "openai", Model: "gpt-4o"}
+	smallModel := SelectedModel{Provider: "anthropic", Model: "claude"}
+
+	require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, largeModel))
+	require.NoError(t, cfg.recordRecentModel(SelectedModelTypeSmall, smallModel))
+
+	// in-memory: verify types maintain separate histories
+	require.Len(t, cfg.RecentModels[SelectedModelTypeLarge], 1)
+	require.Len(t, cfg.RecentModels[SelectedModelTypeSmall], 1)
+	require.Equal(t, largeModel, cfg.RecentModels[SelectedModelTypeLarge][0])
+	require.Equal(t, smallModel, cfg.RecentModels[SelectedModelTypeSmall][0])
+
+	// Add another to large, verify small unchanged
+	anotherLarge := SelectedModel{Provider: "google", Model: "gemini"}
+	require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, anotherLarge))
+
+	require.Len(t, cfg.RecentModels[SelectedModelTypeLarge], 2)
+	require.Len(t, cfg.RecentModels[SelectedModelTypeSmall], 1)
+	require.Equal(t, smallModel, cfg.RecentModels[SelectedModelTypeSmall][0])
+
+	// persisted state: verify both types exist with correct lengths and contents
+	rm := readRecentModels(t, cfg.dataConfigDir)
+
+	large, ok := rm[string(SelectedModelTypeLarge)].([]any)
+	require.True(t, ok)
+	require.Len(t, large, 2)
+	// Verify newest first for large type
+	require.Equal(t, "google", large[0].(map[string]any)["provider"])
+	require.Equal(t, "gemini", large[0].(map[string]any)["model"])
+	require.Equal(t, "openai", large[1].(map[string]any)["provider"])
+	require.Equal(t, "gpt-4o", large[1].(map[string]any)["model"])
+
+	small, ok := rm[string(SelectedModelTypeSmall)].([]any)
+	require.True(t, ok)
+	require.Len(t, small, 1)
+	require.Equal(t, "anthropic", small[0].(map[string]any)["provider"])
+	require.Equal(t, "claude", small[0].(map[string]any)["model"])
+}

internal/db/connect.go πŸ”—

@@ -23,7 +23,7 @@ func Connect(ctx context.Context, dataDir string) (*sql.DB, error) {
 	// Set pragmas for better performance
 	pragmas := []string{
 		"PRAGMA foreign_keys = ON;",
-		"PRAGMA journal_mode = DELETE;",
+		"PRAGMA journal_mode = WAL;",
 		"PRAGMA page_size = 4096;",
 		"PRAGMA cache_size = -8000;",
 		"PRAGMA synchronous = NORMAL;",

internal/message/message.go πŸ”—

@@ -256,6 +256,7 @@ func unmarshallParts(data []byte) ([]ContentPart, error) {
 			if err := json.Unmarshal(wrapper.Data, &part); err != nil {
 				return nil, err
 			}
+			parts = append(parts, part)
 		case binaryType:
 			part := BinaryContent{}
 			if err := json.Unmarshal(wrapper.Data, &part); err != nil {

internal/shell/background.go πŸ”—

@@ -0,0 +1,201 @@
+package shell
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"github.com/charmbracelet/crush/internal/csync"
+)
+
+const (
+	// MaxBackgroundJobs is the maximum number of concurrent background jobs allowed
+	MaxBackgroundJobs = 50
+	// CompletedJobRetentionMinutes is how long to keep completed jobs before auto-cleanup (8 hours)
+	CompletedJobRetentionMinutes = 8 * 60
+)
+
+// BackgroundShell represents a shell running in the background.
+type BackgroundShell struct {
+	ID          string
+	Command     string
+	Description string
+	Shell       *Shell
+	WorkingDir  string
+	ctx         context.Context
+	cancel      context.CancelFunc
+	stdout      *bytes.Buffer
+	stderr      *bytes.Buffer
+	done        chan struct{}
+	exitErr     error
+	completedAt int64 // Unix timestamp when job completed (0 if still running)
+}
+
+// BackgroundShellManager manages background shell instances.
+type BackgroundShellManager struct {
+	shells *csync.Map[string, *BackgroundShell]
+}
+
+var (
+	backgroundManager     *BackgroundShellManager
+	backgroundManagerOnce sync.Once
+	idCounter             atomic.Uint64
+)
+
+// GetBackgroundShellManager returns the singleton background shell manager.
+func GetBackgroundShellManager() *BackgroundShellManager {
+	backgroundManagerOnce.Do(func() {
+		backgroundManager = &BackgroundShellManager{
+			shells: csync.NewMap[string, *BackgroundShell](),
+		}
+	})
+	return backgroundManager
+}
+
+// Start creates and starts a new background shell with the given command.
+func (m *BackgroundShellManager) Start(ctx context.Context, workingDir string, blockFuncs []BlockFunc, command string, description string) (*BackgroundShell, error) {
+	// Check job limit
+	if m.shells.Len() >= MaxBackgroundJobs {
+		return nil, fmt.Errorf("maximum number of background jobs (%d) reached. Please terminate or wait for some jobs to complete", MaxBackgroundJobs)
+	}
+
+	id := fmt.Sprintf("%03X", idCounter.Add(1))
+
+	shell := NewShell(&Options{
+		WorkingDir: workingDir,
+		BlockFuncs: blockFuncs,
+	})
+
+	shellCtx, cancel := context.WithCancel(ctx)
+
+	bgShell := &BackgroundShell{
+		ID:          id,
+		Command:     command,
+		Description: description,
+		WorkingDir:  workingDir,
+		Shell:       shell,
+		ctx:         shellCtx,
+		cancel:      cancel,
+		stdout:      &bytes.Buffer{},
+		stderr:      &bytes.Buffer{},
+		done:        make(chan struct{}),
+	}
+
+	m.shells.Set(id, bgShell)
+
+	go func() {
+		defer close(bgShell.done)
+
+		err := shell.ExecStream(shellCtx, command, bgShell.stdout, bgShell.stderr)
+
+		bgShell.exitErr = err
+		atomic.StoreInt64(&bgShell.completedAt, time.Now().Unix())
+	}()
+
+	return bgShell, nil
+}
+
+// Get retrieves a background shell by ID.
+func (m *BackgroundShellManager) Get(id string) (*BackgroundShell, bool) {
+	return m.shells.Get(id)
+}
+
+// Remove removes a background shell from the manager without terminating it.
+// This is useful when a shell has already completed and you just want to clean up tracking.
+func (m *BackgroundShellManager) Remove(id string) error {
+	_, ok := m.shells.Take(id)
+	if !ok {
+		return fmt.Errorf("background shell not found: %s", id)
+	}
+	return nil
+}
+
+// Kill terminates a background shell by ID.
+func (m *BackgroundShellManager) Kill(id string) error {
+	shell, ok := m.shells.Take(id)
+	if !ok {
+		return fmt.Errorf("background shell not found: %s", id)
+	}
+
+	shell.cancel()
+	<-shell.done
+	return nil
+}
+
+// BackgroundShellInfo contains information about a background shell.
+type BackgroundShellInfo struct {
+	ID          string
+	Command     string
+	Description string
+}
+
+// List returns all background shell IDs.
+func (m *BackgroundShellManager) List() []string {
+	ids := make([]string, 0, m.shells.Len())
+	for id := range m.shells.Seq2() {
+		ids = append(ids, id)
+	}
+	return ids
+}
+
+// Cleanup removes completed jobs that have been finished for more than the retention period
+func (m *BackgroundShellManager) Cleanup() int {
+	now := time.Now().Unix()
+	retentionSeconds := int64(CompletedJobRetentionMinutes * 60)
+
+	var toRemove []string
+	for shell := range m.shells.Seq() {
+		completedAt := atomic.LoadInt64(&shell.completedAt)
+		if completedAt > 0 && now-completedAt > retentionSeconds {
+			toRemove = append(toRemove, shell.ID)
+		}
+	}
+
+	for _, id := range toRemove {
+		m.Remove(id)
+	}
+
+	return len(toRemove)
+}
+
+// KillAll terminates all background shells.
+func (m *BackgroundShellManager) KillAll() {
+	shells := make([]*BackgroundShell, 0, m.shells.Len())
+	for shell := range m.shells.Seq() {
+		shells = append(shells, shell)
+	}
+	m.shells.Reset(map[string]*BackgroundShell{})
+
+	for _, shell := range shells {
+		shell.cancel()
+		<-shell.done
+	}
+}
+
+// GetOutput returns the current output of a background shell.
+func (bs *BackgroundShell) GetOutput() (stdout string, stderr string, done bool, err error) {
+	select {
+	case <-bs.done:
+		return bs.stdout.String(), bs.stderr.String(), true, bs.exitErr
+	default:
+		return bs.stdout.String(), bs.stderr.String(), false, nil
+	}
+}
+
+// IsDone checks if the background shell has finished execution.
+func (bs *BackgroundShell) IsDone() bool {
+	select {
+	case <-bs.done:
+		return true
+	default:
+		return false
+	}
+}
+
+// Wait blocks until the background shell completes.
+func (bs *BackgroundShell) Wait() {
+	<-bs.done
+}

internal/shell/background_test.go πŸ”—

@@ -0,0 +1,276 @@
+package shell
+
+import (
+	"context"
+	"strings"
+	"testing"
+	"time"
+)
+
+func TestBackgroundShellManager_Start(t *testing.T) {
+	t.Parallel()
+
+	ctx := context.Background()
+	workingDir := t.TempDir()
+	manager := GetBackgroundShellManager()
+
+	bgShell, err := manager.Start(ctx, workingDir, nil, "echo 'hello world'", "")
+	if err != nil {
+		t.Fatalf("failed to start background shell: %v", err)
+	}
+
+	if bgShell.ID == "" {
+		t.Error("expected shell ID to be non-empty")
+	}
+
+	// Wait for the command to complete
+	bgShell.Wait()
+
+	stdout, stderr, done, err := bgShell.GetOutput()
+	if !done {
+		t.Error("expected shell to be done")
+	}
+
+	if err != nil {
+		t.Errorf("expected no error, got: %v", err)
+	}
+
+	if !strings.Contains(stdout, "hello world") {
+		t.Errorf("expected stdout to contain 'hello world', got: %s", stdout)
+	}
+
+	if stderr != "" {
+		t.Errorf("expected empty stderr, got: %s", stderr)
+	}
+}
+
+func TestBackgroundShellManager_Get(t *testing.T) {
+	t.Parallel()
+
+	ctx := context.Background()
+	workingDir := t.TempDir()
+	manager := GetBackgroundShellManager()
+
+	bgShell, err := manager.Start(ctx, workingDir, nil, "echo 'test'", "")
+	if err != nil {
+		t.Fatalf("failed to start background shell: %v", err)
+	}
+
+	// Retrieve the shell
+	retrieved, ok := manager.Get(bgShell.ID)
+	if !ok {
+		t.Error("expected to find the background shell")
+	}
+
+	if retrieved.ID != bgShell.ID {
+		t.Errorf("expected shell ID %s, got %s", bgShell.ID, retrieved.ID)
+	}
+
+	// Clean up
+	manager.Kill(bgShell.ID)
+}
+
+func TestBackgroundShellManager_Kill(t *testing.T) {
+	t.Parallel()
+
+	ctx := context.Background()
+	workingDir := t.TempDir()
+	manager := GetBackgroundShellManager()
+
+	// Start a long-running command
+	bgShell, err := manager.Start(ctx, workingDir, nil, "sleep 10", "")
+	if err != nil {
+		t.Fatalf("failed to start background shell: %v", err)
+	}
+
+	// Kill it
+	err = manager.Kill(bgShell.ID)
+	if err != nil {
+		t.Errorf("failed to kill background shell: %v", err)
+	}
+
+	// Verify it's no longer in the manager
+	_, ok := manager.Get(bgShell.ID)
+	if ok {
+		t.Error("expected shell to be removed after kill")
+	}
+
+	// Verify the shell is done
+	if !bgShell.IsDone() {
+		t.Error("expected shell to be done after kill")
+	}
+}
+
+func TestBackgroundShellManager_KillNonExistent(t *testing.T) {
+	t.Parallel()
+
+	manager := GetBackgroundShellManager()
+
+	err := manager.Kill("non-existent-id")
+	if err == nil {
+		t.Error("expected error when killing non-existent shell")
+	}
+}
+
+func TestBackgroundShell_IsDone(t *testing.T) {
+	t.Parallel()
+
+	ctx := context.Background()
+	workingDir := t.TempDir()
+	manager := GetBackgroundShellManager()
+
+	bgShell, err := manager.Start(ctx, workingDir, nil, "echo 'quick'", "")
+	if err != nil {
+		t.Fatalf("failed to start background shell: %v", err)
+	}
+
+	// Wait a bit for the command to complete
+	time.Sleep(100 * time.Millisecond)
+
+	if !bgShell.IsDone() {
+		t.Error("expected shell to be done")
+	}
+
+	// Clean up
+	manager.Kill(bgShell.ID)
+}
+
+func TestBackgroundShell_WithBlockFuncs(t *testing.T) {
+	t.Parallel()
+
+	ctx := context.Background()
+	workingDir := t.TempDir()
+	manager := GetBackgroundShellManager()
+
+	blockFuncs := []BlockFunc{
+		CommandsBlocker([]string{"curl", "wget"}),
+	}
+
+	bgShell, err := manager.Start(ctx, workingDir, blockFuncs, "curl example.com", "")
+	if err != nil {
+		t.Fatalf("failed to start background shell: %v", err)
+	}
+
+	// Wait for the command to complete
+	bgShell.Wait()
+
+	stdout, stderr, done, execErr := bgShell.GetOutput()
+	if !done {
+		t.Error("expected shell to be done")
+	}
+
+	// The command should have been blocked
+	output := stdout + stderr
+	if !strings.Contains(output, "not allowed") && execErr == nil {
+		t.Errorf("expected command to be blocked, got stdout: %s, stderr: %s, err: %v", stdout, stderr, execErr)
+	}
+
+	// Clean up
+	manager.Kill(bgShell.ID)
+}
+
+func TestBackgroundShellManager_List(t *testing.T) {
+	t.Parallel()
+
+	ctx := context.Background()
+	workingDir := t.TempDir()
+	manager := GetBackgroundShellManager()
+
+	// Start two shells
+	bgShell1, err := manager.Start(ctx, workingDir, nil, "sleep 1", "")
+	if err != nil {
+		t.Fatalf("failed to start first background shell: %v", err)
+	}
+
+	bgShell2, err := manager.Start(ctx, workingDir, nil, "sleep 1", "")
+	if err != nil {
+		t.Fatalf("failed to start second background shell: %v", err)
+	}
+
+	ids := manager.List()
+
+	// Check that both shells are in the list
+	found1 := false
+	found2 := false
+	for _, id := range ids {
+		if id == bgShell1.ID {
+			found1 = true
+		}
+		if id == bgShell2.ID {
+			found2 = true
+		}
+	}
+
+	if !found1 {
+		t.Errorf("expected to find shell %s in list", bgShell1.ID)
+	}
+	if !found2 {
+		t.Errorf("expected to find shell %s in list", bgShell2.ID)
+	}
+
+	// Clean up
+	manager.Kill(bgShell1.ID)
+	manager.Kill(bgShell2.ID)
+}
+
+func TestBackgroundShellManager_KillAll(t *testing.T) {
+	t.Parallel()
+
+	ctx := context.Background()
+	workingDir := t.TempDir()
+	manager := GetBackgroundShellManager()
+
+	// Start multiple long-running shells
+	shell1, err := manager.Start(ctx, workingDir, nil, "sleep 10", "")
+	if err != nil {
+		t.Fatalf("failed to start shell 1: %v", err)
+	}
+
+	shell2, err := manager.Start(ctx, workingDir, nil, "sleep 10", "")
+	if err != nil {
+		t.Fatalf("failed to start shell 2: %v", err)
+	}
+
+	shell3, err := manager.Start(ctx, workingDir, nil, "sleep 10", "")
+	if err != nil {
+		t.Fatalf("failed to start shell 3: %v", err)
+	}
+
+	// Verify shells are running
+	if shell1.IsDone() || shell2.IsDone() || shell3.IsDone() {
+		t.Error("shells should not be done yet")
+	}
+
+	// Kill all shells
+	manager.KillAll()
+
+	// Verify all shells are done
+	if !shell1.IsDone() {
+		t.Error("shell1 should be done after KillAll")
+	}
+	if !shell2.IsDone() {
+		t.Error("shell2 should be done after KillAll")
+	}
+	if !shell3.IsDone() {
+		t.Error("shell3 should be done after KillAll")
+	}
+
+	// Verify they're removed from the manager
+	if _, ok := manager.Get(shell1.ID); ok {
+		t.Error("shell1 should be removed from manager")
+	}
+	if _, ok := manager.Get(shell2.ID); ok {
+		t.Error("shell2 should be removed from manager")
+	}
+	if _, ok := manager.Get(shell3.ID); ok {
+		t.Error("shell3 should be removed from manager")
+	}
+
+	// Verify list is empty (or doesn't contain our shells)
+	ids := manager.List()
+	for _, id := range ids {
+		if id == shell1.ID || id == shell2.ID || id == shell3.ID {
+			t.Errorf("shell %s should not be in list after KillAll", id)
+		}
+	}
+}

internal/shell/doc.go πŸ”—

@@ -16,12 +16,7 @@ package shell
 //	shell.Exec(ctx, "export FOO=bar")
 //	shell.Exec(ctx, "echo $FOO")  // Will print "bar"
 //
-// 3. For the singleton persistent shell (used by tools):
-//
-//	shell := shell.GetPersistentShell("/path/to/cwd")
-//	stdout, stderr, err := shell.Exec(ctx, "ls -la")
-//
-// 4. Managing environment and working directory:
+// 3. Managing environment and working directory:
 //
 //	shell := shell.NewShell(nil)
 //	shell.SetEnv("MY_VAR", "value")

internal/shell/persistent.go πŸ”—

@@ -1,43 +0,0 @@
-package shell
-
-import (
-	"log/slog"
-	"sync"
-)
-
-// PersistentShell is a singleton shell instance that maintains state across the application
-type PersistentShell struct {
-	*Shell
-}
-
-var (
-	once          sync.Once
-	shellInstance *PersistentShell
-)
-
-// GetPersistentShell returns the singleton persistent shell instance
-// This maintains backward compatibility with the existing API
-func GetPersistentShell(cwd string) *PersistentShell {
-	once.Do(func() {
-		shellInstance = &PersistentShell{
-			Shell: NewShell(&Options{
-				WorkingDir: cwd,
-				Logger:     &loggingAdapter{},
-			}),
-		}
-	})
-	return shellInstance
-}
-
-// INFO: only used for tests
-func Reset(cwd string) {
-	once = sync.Once{}
-	_ = GetPersistentShell(cwd)
-}
-
-// slog.dapter adapts the internal slog.package to the Logger interface
-type loggingAdapter struct{}
-
-func (l *loggingAdapter) InfoPersist(msg string, keysAndValues ...any) {
-	slog.Info(msg, keysAndValues...)
-}

internal/shell/shell.go πŸ”—

@@ -1,13 +1,12 @@
 // Package shell provides cross-platform shell execution capabilities.
 //
-// This package offers two main types:
-// - Shell: A general-purpose shell executor for one-off or managed commands
-// - PersistentShell: A singleton shell that maintains state across the application
+// This package provides Shell instances for executing commands with their own
+// working directory and environment. Each shell execution is independent.
 //
 // WINDOWS COMPATIBILITY:
-// This implementation provides both POSIX shell emulation (mvdan.cc/sh/v3),
-// even on Windows. Some caution has to be taken: commands should have forward
-// slashes (/) as path separators to work, even on Windows.
+// This implementation provides POSIX shell emulation (mvdan.cc/sh/v3) even on
+// Windows. Commands should use forward slashes (/) as path separators to work
+// correctly on all platforms.
 package shell
 
 import (
@@ -15,6 +14,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"io"
 	"os"
 	"slices"
 	"strings"
@@ -103,6 +103,14 @@ func (s *Shell) Exec(ctx context.Context, command string) (string, string, error
 	return s.exec(ctx, command)
 }
 
+// ExecStream executes a command in the shell with streaming output to provided writers
+func (s *Shell) ExecStream(ctx context.Context, command string, stdout, stderr io.Writer) error {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	return s.execStream(ctx, command, stdout, stderr)
+}
+
 // GetWorkingDir returns the current working directory
 func (s *Shell) GetWorkingDir() string {
 	s.mu.Lock()
@@ -228,34 +236,56 @@ func (s *Shell) blockHandler() func(next interp.ExecHandlerFunc) interp.ExecHand
 	}
 }
 
-// exec executes commands using a cross-platform shell interpreter.
-func (s *Shell) exec(ctx context.Context, command string) (string, string, error) {
-	line, err := syntax.NewParser().Parse(strings.NewReader(command), "")
-	if err != nil {
-		return "", "", fmt.Errorf("could not parse command: %w", err)
-	}
-
-	var stdout, stderr bytes.Buffer
-	runner, err := interp.New(
-		interp.StdIO(nil, &stdout, &stderr),
+// newInterp creates a new interpreter with the current shell state
+func (s *Shell) newInterp(stdout, stderr io.Writer) (*interp.Runner, error) {
+	return interp.New(
+		interp.StdIO(nil, stdout, stderr),
 		interp.Interactive(false),
 		interp.Env(expand.ListEnviron(s.env...)),
 		interp.Dir(s.cwd),
 		interp.ExecHandlers(s.execHandlers()...),
 	)
-	if err != nil {
-		return "", "", fmt.Errorf("could not run command: %w", err)
-	}
+}
 
-	err = runner.Run(ctx, line)
+// updateShellFromRunner updates the shell from the interpreter after execution
+func (s *Shell) updateShellFromRunner(runner *interp.Runner) {
 	s.cwd = runner.Dir
+	s.env = nil
 	for name, vr := range runner.Vars {
 		s.env = append(s.env, fmt.Sprintf("%s=%s", name, vr.Str))
 	}
+}
+
+// execCommon is the shared implementation for executing commands
+func (s *Shell) execCommon(ctx context.Context, command string, stdout, stderr io.Writer) error {
+	line, err := syntax.NewParser().Parse(strings.NewReader(command), "")
+	if err != nil {
+		return fmt.Errorf("could not parse command: %w", err)
+	}
+
+	runner, err := s.newInterp(stdout, stderr)
+	if err != nil {
+		return fmt.Errorf("could not run command: %w", err)
+	}
+
+	err = runner.Run(ctx, line)
+	s.updateShellFromRunner(runner)
 	s.logger.InfoPersist("command finished", "command", command, "err", err)
+	return err
+}
+
+// exec executes commands using a cross-platform shell interpreter.
+func (s *Shell) exec(ctx context.Context, command string) (string, string, error) {
+	var stdout, stderr bytes.Buffer
+	err := s.execCommon(ctx, command, &stdout, &stderr)
 	return stdout.String(), stderr.String(), err
 }
 
+// execStream executes commands using POSIX shell emulation with streaming output
+func (s *Shell) execStream(ctx context.Context, command string, stdout, stderr io.Writer) error {
+	return s.execCommon(ctx, command, stdout, stderr)
+}
+
 func (s *Shell) execHandlers() []func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
 	handlers := []func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc{
 		s.blockHandler(),

internal/stringext/string.go πŸ”—

@@ -0,0 +1,10 @@
+package stringext
+
+import (
+	"golang.org/x/text/cases"
+	"golang.org/x/text/language"
+)
+
+func Capitalize(text string) string {
+	return cases.Title(language.English, cases.Compact).String(text)
+}

internal/tui/components/chat/messages/renderer.go πŸ”—

@@ -1,6 +1,7 @@
 package messages
 
 import (
+	"cmp"
 	"encoding/json"
 	"fmt"
 	"strings"
@@ -163,6 +164,8 @@ func (br baseRenderer) renderError(v *toolCallCmp, message string) string {
 // Register tool renderers
 func init() {
 	registry.register(tools.BashToolName, func() renderer { return bashRenderer{} })
+	registry.register(tools.JobOutputToolName, func() renderer { return bashOutputRenderer{} })
+	registry.register(tools.JobKillToolName, func() renderer { return bashKillRenderer{} })
 	registry.register(tools.DownloadToolName, func() renderer { return downloadRenderer{} })
 	registry.register(tools.ViewToolName, func() renderer { return viewRenderer{} })
 	registry.register(tools.EditToolName, func() renderer { return editRenderer{} })
@@ -213,7 +216,31 @@ func (br bashRenderer) Render(v *toolCallCmp) string {
 
 	cmd := strings.ReplaceAll(params.Command, "\n", " ")
 	cmd = strings.ReplaceAll(cmd, "\t", "    ")
-	args := newParamBuilder().addMain(cmd).build()
+	args := newParamBuilder().
+		addMain(cmd).
+		addFlag("background", params.RunInBackground).
+		build()
+	if v.call.Finished {
+		var meta tools.BashResponseMetadata
+		_ = br.unmarshalParams(v.result.Metadata, &meta)
+		if meta.Background {
+			description := cmp.Or(meta.Description, params.Command)
+			width := v.textWidth()
+			if v.isNested {
+				width -= 4 // Adjust for nested tool call indentation
+			}
+			header := makeJobHeader(v, "Start", fmt.Sprintf("PID %s", meta.ShellID), description, width)
+			if v.isNested {
+				return v.style().Render(header)
+			}
+			if res, done := earlyState(header, v); done {
+				return res
+			}
+			content := "Command: " + params.Command + "\n" + v.result.Content
+			body := renderPlainContent(v, content)
+			return joinHeaderBody(header, body)
+		}
+	}
 
 	return br.renderWithParams(v, "Bash", args, func() string {
 		var meta tools.BashResponseMetadata
@@ -232,6 +259,131 @@ func (br bashRenderer) Render(v *toolCallCmp) string {
 	})
 }
 
+// -----------------------------------------------------------------------------
+//  Bash Output renderer
+// -----------------------------------------------------------------------------
+
+func makeJobHeader(v *toolCallCmp, subcommand, pid, description string, width int) string {
+	t := styles.CurrentTheme()
+	icon := t.S().Base.Foreground(t.GreenDark).Render(styles.ToolPending)
+	if v.result.ToolCallID != "" {
+		if v.result.IsError {
+			icon = t.S().Base.Foreground(t.RedDark).Render(styles.ToolError)
+		} else {
+			icon = t.S().Base.Foreground(t.Green).Render(styles.ToolSuccess)
+		}
+	} else if v.cancelled {
+		icon = t.S().Muted.Render(styles.ToolPending)
+	}
+
+	jobPart := t.S().Base.Foreground(t.Blue).Render("Job")
+	subcommandPart := t.S().Base.Foreground(t.BlueDark).Render("(" + subcommand + ")")
+	pidPart := t.S().Muted.Render(pid)
+	descPart := ""
+	if description != "" {
+		descPart = " " + t.S().Subtle.Render(description)
+	}
+
+	// Build the complete header
+	prefix := fmt.Sprintf("%s %s %s %s", icon, jobPart, subcommandPart, pidPart)
+	fullHeader := prefix + descPart
+
+	// Truncate if needed
+	if lipgloss.Width(fullHeader) > width {
+		availableWidth := width - lipgloss.Width(prefix) - 1 // -1 for space
+		if availableWidth < 10 {
+			// Not enough space for description, just show prefix
+			return prefix
+		}
+		descPart = " " + t.S().Subtle.Render(ansi.Truncate(description, availableWidth, "…"))
+		fullHeader = prefix + descPart
+	}
+
+	return fullHeader
+}
+
+// bashOutputRenderer handles bash output retrieval display
+type bashOutputRenderer struct {
+	baseRenderer
+}
+
+// Render displays the shell ID and output from a background shell
+func (bor bashOutputRenderer) Render(v *toolCallCmp) string {
+	var params tools.JobOutputParams
+	if err := bor.unmarshalParams(v.call.Input, &params); err != nil {
+		return bor.renderError(v, "Invalid job_output parameters")
+	}
+
+	var meta tools.JobOutputResponseMetadata
+	var description string
+	if v.result.Metadata != "" {
+		if err := bor.unmarshalParams(v.result.Metadata, &meta); err == nil {
+			if meta.Description != "" {
+				description = meta.Description
+			} else {
+				description = meta.Command
+			}
+		}
+	}
+
+	width := v.textWidth()
+	if v.isNested {
+		width -= 4 // Adjust for nested tool call indentation
+	}
+	header := makeJobHeader(v, "Output", fmt.Sprintf("PID %s", params.ShellID), description, width)
+	if v.isNested {
+		return v.style().Render(header)
+	}
+	if res, done := earlyState(header, v); done {
+		return res
+	}
+	body := renderPlainContent(v, v.result.Content)
+	return joinHeaderBody(header, body)
+}
+
+// -----------------------------------------------------------------------------
+//  Bash Kill renderer
+// -----------------------------------------------------------------------------
+
+// bashKillRenderer handles bash process termination display
+type bashKillRenderer struct {
+	baseRenderer
+}
+
+// Render displays the shell ID being terminated
+func (bkr bashKillRenderer) Render(v *toolCallCmp) string {
+	var params tools.JobKillParams
+	if err := bkr.unmarshalParams(v.call.Input, &params); err != nil {
+		return bkr.renderError(v, "Invalid job_kill parameters")
+	}
+
+	var meta tools.JobKillResponseMetadata
+	var description string
+	if v.result.Metadata != "" {
+		if err := bkr.unmarshalParams(v.result.Metadata, &meta); err == nil {
+			if meta.Description != "" {
+				description = meta.Description
+			} else {
+				description = meta.Command
+			}
+		}
+	}
+
+	width := v.textWidth()
+	if v.isNested {
+		width -= 4 // Adjust for nested tool call indentation
+	}
+	header := makeJobHeader(v, "Kill", fmt.Sprintf("PID %s", params.ShellID), description, width)
+	if v.isNested {
+		return v.style().Render(header)
+	}
+	if res, done := earlyState(header, v); done {
+		return res
+	}
+	body := renderPlainContent(v, v.result.Content)
+	return joinHeaderBody(header, body)
+}
+
 // -----------------------------------------------------------------------------
 //  View renderer
 // -----------------------------------------------------------------------------
@@ -1013,6 +1165,10 @@ func prettifyToolName(name string) string {
 		return "Agent"
 	case tools.BashToolName:
 		return "Bash"
+	case tools.JobOutputToolName:
+		return "Job: Output"
+	case tools.JobKillToolName:
+		return "Job: Kill"
 	case tools.DownloadToolName:
 		return "Download"
 	case tools.EditToolName:

internal/tui/components/chat/splash/splash.go πŸ”—

@@ -331,10 +331,14 @@ func (s *splashCmp) initializeProject() tea.Cmd {
 
 	cmds = append(cmds, util.CmdHandler(OnboardingCompleteMsg{}))
 	if !s.selectedNo {
+		initPrompt, err := agent.InitializePrompt(*config.Get())
+		if err != nil {
+			return util.ReportError(err)
+		}
 		cmds = append(cmds,
 			util.CmdHandler(chat.SessionClearedMsg{}),
 			util.CmdHandler(chat.SendMsg{
-				Text: agent.InitializePrompt(),
+				Text: initPrompt,
 			}),
 		)
 	}
@@ -442,7 +446,7 @@ func (s *splashCmp) View() string {
 		modelSelector := t.S().Base.AlignVertical(lipgloss.Bottom).Height(remainingHeight).Render(
 			lipgloss.JoinVertical(
 				lipgloss.Left,
-				t.S().Base.PaddingLeft(1).Foreground(t.Primary).Render("Choose a Model"),
+				t.S().Base.PaddingLeft(1).Foreground(t.Primary).Render("To start, let’s choose a provider and model."),
 				"",
 				modelListView,
 			),
@@ -458,6 +462,7 @@ func (s *splashCmp) View() string {
 		bodyStyle := t.S().Base.Foreground(t.FgMuted)
 		shortcutStyle := t.S().Base.Foreground(t.Success)
 
+		initFile := config.Get().Options.InitializeAs
 		initText := lipgloss.JoinVertical(
 			lipgloss.Left,
 			titleStyle.Render("Would you like to initialize this project?"),
@@ -465,7 +470,7 @@ func (s *splashCmp) View() string {
 			pathStyle.Render(s.cwd()),
 			"",
 			bodyStyle.Render("When I initialize your codebase I examine the project and put the"),
-			bodyStyle.Render("result into a CRUSH.md file which serves as general context."),
+			bodyStyle.Render(fmt.Sprintf("result into an %s file which serves as general context.", initFile)),
 			"",
 			bodyStyle.Render("You can also initialize anytime via ")+shortcutStyle.Render("ctrl+p")+bodyStyle.Render("."),
 			"",

internal/tui/components/dialogs/commands/commands.go πŸ”—

@@ -1,6 +1,7 @@
 package commands
 
 import (
+	"fmt"
 	"os"
 	"slices"
 	"strings"
@@ -335,6 +336,7 @@ func (c *commandDialogCmp) defaultCommands() []Command {
 			ID:          "switch_model",
 			Title:       "Switch Model",
 			Description: "Switch to a different model",
+			Shortcut:    "ctrl+l",
 			Handler: func(cmd Command) tea.Cmd {
 				return util.CmdHandler(SwitchModelMsg{})
 			},
@@ -453,10 +455,14 @@ func (c *commandDialogCmp) defaultCommands() []Command {
 		{
 			ID:          "init",
 			Title:       "Initialize Project",
-			Description: "Create/Update the CRUSH.md memory file",
+			Description: fmt.Sprintf("Create/Update the %s memory file", config.Get().Options.InitializeAs),
 			Handler: func(cmd Command) tea.Cmd {
+				initPrompt, err := agent.InitializePrompt(*config.Get())
+				if err != nil {
+					return util.ReportError(err)
+				}
 				return util.CmdHandler(chat.SendMsg{
-					Text: agent.InitializePrompt(),
+					Text: initPrompt,
 				})
 			},
 		},

internal/tui/components/dialogs/models/list.go πŸ”—

@@ -22,6 +22,13 @@ type ModelListComponent struct {
 	providers []catwalk.Provider
 }
 
+func modelKey(providerID, modelID string) string {
+	if providerID == "" || modelID == "" {
+		return ""
+	}
+	return providerID + ":" + modelID
+}
+
 func NewModelListComponent(keyMap list.KeyMap, inputPlaceholder string, shouldResize bool) *ModelListComponent {
 	t := styles.CurrentTheme()
 	inputStyle := t.S().Base.PaddingLeft(1).PaddingBottom(1)
@@ -104,14 +111,19 @@ func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
 	var groups []list.Group[list.CompletionItem[ModelOption]]
 	// first none section
 	selectedItemID := ""
+	itemsByKey := make(map[string]list.CompletionItem[ModelOption])
 
 	cfg := config.Get()
 	var currentModel config.SelectedModel
+	selectedType := config.SelectedModelTypeLarge
 	if m.modelType == LargeModelType {
 		currentModel = cfg.Models[config.SelectedModelTypeLarge]
+		selectedType = config.SelectedModelTypeLarge
 	} else {
 		currentModel = cfg.Models[config.SelectedModelTypeSmall]
+		selectedType = config.SelectedModelTypeSmall
 	}
+	recentItems := cfg.RecentModels[selectedType]
 
 	configuredIcon := t.S().Base.Foreground(t.Success).Render(styles.CheckIcon)
 	configured := fmt.Sprintf("%s %s", configuredIcon, t.S().Subtle.Render("Configured"))
@@ -169,14 +181,17 @@ func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
 				Section: section,
 			}
 			for _, model := range configProvider.Models {
-				item := list.NewCompletionItem(model.Name, ModelOption{
+				modelOption := ModelOption{
 					Provider: configProvider,
 					Model:    model,
-				},
-					list.WithCompletionID(
-						fmt.Sprintf("%s:%s", providerConfig.ID, model.ID),
-					),
+				}
+				key := modelKey(string(configProvider.ID), model.ID)
+				item := list.NewCompletionItem(
+					model.Name,
+					modelOption,
+					list.WithCompletionID(key),
 				)
+				itemsByKey[key] = item
 
 				group.Items = append(group.Items, item)
 				if model.ID == currentModel.Model && string(configProvider.ID) == currentModel.Provider {
@@ -239,14 +254,17 @@ func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
 			Section: section,
 		}
 		for _, model := range displayProvider.Models {
-			item := list.NewCompletionItem(model.Name, ModelOption{
+			modelOption := ModelOption{
 				Provider: displayProvider,
 				Model:    model,
-			},
-				list.WithCompletionID(
-					fmt.Sprintf("%s:%s", displayProvider.ID, model.ID),
-				),
+			}
+			key := modelKey(string(displayProvider.ID), model.ID)
+			item := list.NewCompletionItem(
+				model.Name,
+				modelOption,
+				list.WithCompletionID(key),
 			)
+			itemsByKey[key] = item
 			group.Items = append(group.Items, item)
 			if model.ID == currentModel.Model && string(displayProvider.ID) == currentModel.Provider {
 				selectedItemID = item.ID()
@@ -255,6 +273,48 @@ func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
 		groups = append(groups, group)
 	}
 
+	if len(recentItems) > 0 {
+		recentSection := list.NewItemSection("Recently used")
+		recentGroup := list.Group[list.CompletionItem[ModelOption]]{
+			Section: recentSection,
+		}
+		var validRecentItems []config.SelectedModel
+		for _, recent := range recentItems {
+			key := modelKey(recent.Provider, recent.Model)
+			option, ok := itemsByKey[key]
+			if !ok {
+				continue
+			}
+			validRecentItems = append(validRecentItems, recent)
+			recentID := fmt.Sprintf("recent::%s", key)
+			modelOption := option.Value()
+			providerName := modelOption.Provider.Name
+			if providerName == "" {
+				providerName = string(modelOption.Provider.ID)
+			}
+			item := list.NewCompletionItem(
+				modelOption.Model.Name,
+				option.Value(),
+				list.WithCompletionID(recentID),
+				list.WithCompletionShortcut(providerName),
+			)
+			recentGroup.Items = append(recentGroup.Items, item)
+			if recent.Model == currentModel.Model && recent.Provider == currentModel.Provider {
+				selectedItemID = recentID
+			}
+		}
+
+		if len(validRecentItems) != len(recentItems) {
+			if err := cfg.SetConfigField(fmt.Sprintf("recent_models.%s", selectedType), validRecentItems); err != nil {
+				return util.ReportError(err)
+			}
+		}
+
+		if len(recentGroup.Items) > 0 {
+			groups = append([]list.Group[list.CompletionItem[ModelOption]]{recentGroup}, groups...)
+		}
+	}
+
 	var cmds []tea.Cmd
 
 	cmd := m.list.SetGroups(groups)

internal/tui/components/dialogs/models/list_recent_test.go πŸ”—

@@ -0,0 +1,369 @@
+package models
+
+import (
+	"encoding/json"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	tea "charm.land/bubbletea/v2"
+	"github.com/charmbracelet/catwalk/pkg/catwalk"
+	"github.com/charmbracelet/crush/internal/config"
+	"github.com/charmbracelet/crush/internal/log"
+	"github.com/charmbracelet/crush/internal/tui/exp/list"
+	"github.com/stretchr/testify/require"
+)
+
+// execCmdML runs a tea.Cmd through the ModelListComponent's Update loop.
+func execCmdML(t *testing.T, m *ModelListComponent, cmd tea.Cmd) {
+	t.Helper()
+	for cmd != nil {
+		msg := cmd()
+		var next tea.Cmd
+		_, next = m.Update(msg)
+		cmd = next
+	}
+}
+
+// readConfigJSON reads and unmarshals the JSON config file at path.
+func readConfigJSON(t *testing.T, path string) map[string]any {
+	t.Helper()
+	baseDir := filepath.Dir(path)
+	fileName := filepath.Base(path)
+	b, err := fs.ReadFile(os.DirFS(baseDir), fileName)
+	require.NoError(t, err)
+	var out map[string]any
+	require.NoError(t, json.Unmarshal(b, &out))
+	return out
+}
+
+// readRecentModels reads the recent_models section from the config file.
+func readRecentModels(t *testing.T, path string) map[string]any {
+	t.Helper()
+	out := readConfigJSON(t, path)
+	rm, ok := out["recent_models"].(map[string]any)
+	require.True(t, ok)
+	return rm
+}
+
+func TestModelList_RecentlyUsedSectionAndPrunesInvalid(t *testing.T) {
+	// Pre-initialize logger to os.DevNull to prevent file lock on Windows.
+	log.Setup(os.DevNull, false)
+
+	// Isolate config/data paths
+	cfgDir := t.TempDir()
+	dataDir := t.TempDir()
+	t.Setenv("XDG_CONFIG_HOME", cfgDir)
+	t.Setenv("XDG_DATA_HOME", dataDir)
+
+	// Pre-seed config so provider auto-update is disabled and we have recents
+	confPath := filepath.Join(cfgDir, "crush", "crush.json")
+	require.NoError(t, os.MkdirAll(filepath.Dir(confPath), 0o755))
+	initial := map[string]any{
+		"options": map[string]any{
+			"disable_provider_auto_update": true,
+		},
+		"models": map[string]any{
+			"large": map[string]any{
+				"model":    "m1",
+				"provider": "p1",
+			},
+		},
+		"recent_models": map[string]any{
+			"large": []any{
+				map[string]any{"model": "m2", "provider": "p1"},              // valid
+				map[string]any{"model": "x", "provider": "unknown-provider"}, // invalid -> pruned
+			},
+		},
+	}
+	bts, err := json.Marshal(initial)
+	require.NoError(t, err)
+	require.NoError(t, os.WriteFile(confPath, bts, 0o644))
+
+	// Also create empty providers.json to prevent loading real providers
+	dataConfDir := filepath.Join(dataDir, "crush")
+	require.NoError(t, os.MkdirAll(dataConfDir, 0o755))
+	emptyProviders := []byte("[]")
+	require.NoError(t, os.WriteFile(filepath.Join(dataConfDir, "providers.json"), emptyProviders, 0o644))
+
+	// Initialize global config instance (no network due to auto-update disabled)
+	_, err = config.Init(cfgDir, dataDir, false)
+	require.NoError(t, err)
+
+	// Build a small provider set for the list component
+	provider := catwalk.Provider{
+		ID:   catwalk.InferenceProvider("p1"),
+		Name: "Provider One",
+		Models: []catwalk.Model{
+			{ID: "m1", Name: "Model One", DefaultMaxTokens: 100},
+			{ID: "m2", Name: "Model Two", DefaultMaxTokens: 100}, // recent
+		},
+	}
+
+	// Create and initialize the component with our provider set
+	listKeyMap := list.DefaultKeyMap()
+	cmp := NewModelListComponent(listKeyMap, "Find your fave", false)
+	cmp.providers = []catwalk.Provider{provider}
+	execCmdML(t, cmp, cmp.Init())
+
+	// Find all recent items (IDs prefixed with "recent::") and verify pruning
+	groups := cmp.list.Groups()
+	require.NotEmpty(t, groups)
+	var recentItems []list.CompletionItem[ModelOption]
+	for _, g := range groups {
+		for _, it := range g.Items {
+			if strings.HasPrefix(it.ID(), "recent::") {
+				recentItems = append(recentItems, it)
+			}
+		}
+	}
+	require.NotEmpty(t, recentItems, "no recent items found")
+	// Ensure the valid recent (p1:m2) is present and the invalid one is not
+	foundValid := false
+	for _, it := range recentItems {
+		if it.ID() == "recent::p1:m2" {
+			foundValid = true
+		}
+		require.NotEqual(t, "recent::unknown-provider:x", it.ID(), "invalid recent should be pruned")
+	}
+	require.True(t, foundValid, "expected valid recent not found")
+
+	// Verify original config in cfgDir remains unchanged
+	origConfPath := filepath.Join(cfgDir, "crush", "crush.json")
+	afterOrig, err := fs.ReadFile(os.DirFS(filepath.Dir(origConfPath)), filepath.Base(origConfPath))
+	require.NoError(t, err)
+	var origParsed map[string]any
+	require.NoError(t, json.Unmarshal(afterOrig, &origParsed))
+	origRM := origParsed["recent_models"].(map[string]any)
+	origLarge := origRM["large"].([]any)
+	require.Len(t, origLarge, 2, "original config should be unchanged")
+
+	// Config should be rewritten with pruned recents in dataDir
+	dataConf := filepath.Join(dataDir, "crush", "crush.json")
+	rm := readRecentModels(t, dataConf)
+	largeAny, ok := rm["large"].([]any)
+	require.True(t, ok)
+	// Ensure that only valid recent(s) remain and the invalid one is removed
+	found := false
+	for _, v := range largeAny {
+		m := v.(map[string]any)
+		require.NotEqual(t, "unknown-provider", m["provider"], "invalid provider should be pruned")
+		if m["provider"] == "p1" && m["model"] == "m2" {
+			found = true
+		}
+	}
+	require.True(t, found, "persisted recents should include p1:m2")
+}
+
+func TestModelList_PrunesInvalidModelWithinValidProvider(t *testing.T) {
+	// Pre-initialize logger to os.DevNull to prevent file lock on Windows.
+	log.Setup(os.DevNull, false)
+
+	// Isolate config/data paths
+	cfgDir := t.TempDir()
+	dataDir := t.TempDir()
+	t.Setenv("XDG_CONFIG_HOME", cfgDir)
+	t.Setenv("XDG_DATA_HOME", dataDir)
+
+	// Pre-seed config with valid provider but one invalid model
+	confPath := filepath.Join(cfgDir, "crush", "crush.json")
+	require.NoError(t, os.MkdirAll(filepath.Dir(confPath), 0o755))
+	initial := map[string]any{
+		"options": map[string]any{
+			"disable_provider_auto_update": true,
+		},
+		"models": map[string]any{
+			"large": map[string]any{
+				"model":    "m1",
+				"provider": "p1",
+			},
+		},
+		"recent_models": map[string]any{
+			"large": []any{
+				map[string]any{"model": "m1", "provider": "p1"},      // valid
+				map[string]any{"model": "missing", "provider": "p1"}, // invalid model
+			},
+		},
+	}
+	bts, err := json.Marshal(initial)
+	require.NoError(t, err)
+	require.NoError(t, os.WriteFile(confPath, bts, 0o644))
+
+	// Create empty providers.json
+	dataConfDir := filepath.Join(dataDir, "crush")
+	require.NoError(t, os.MkdirAll(dataConfDir, 0o755))
+	emptyProviders := []byte("[]")
+	require.NoError(t, os.WriteFile(filepath.Join(dataConfDir, "providers.json"), emptyProviders, 0o644))
+
+	// Initialize global config instance
+	_, err = config.Init(cfgDir, dataDir, false)
+	require.NoError(t, err)
+
+	// Build provider set that only includes m1, not "missing"
+	provider := catwalk.Provider{
+		ID:   catwalk.InferenceProvider("p1"),
+		Name: "Provider One",
+		Models: []catwalk.Model{
+			{ID: "m1", Name: "Model One", DefaultMaxTokens: 100},
+		},
+	}
+
+	// Create and initialize component
+	listKeyMap := list.DefaultKeyMap()
+	cmp := NewModelListComponent(listKeyMap, "Find your fave", false)
+	cmp.providers = []catwalk.Provider{provider}
+	execCmdML(t, cmp, cmp.Init())
+
+	// Find all recent items
+	groups := cmp.list.Groups()
+	require.NotEmpty(t, groups)
+	var recentItems []list.CompletionItem[ModelOption]
+	for _, g := range groups {
+		for _, it := range g.Items {
+			if strings.HasPrefix(it.ID(), "recent::") {
+				recentItems = append(recentItems, it)
+			}
+		}
+	}
+	require.NotEmpty(t, recentItems, "valid recent should exist")
+
+	// Verify the valid recent is present and invalid model is not
+	foundValid := false
+	for _, it := range recentItems {
+		if it.ID() == "recent::p1:m1" {
+			foundValid = true
+		}
+		require.NotEqual(t, "recent::p1:missing", it.ID(), "invalid model should be pruned")
+	}
+	require.True(t, foundValid, "valid recent p1:m1 should be present")
+
+	// Verify original config in cfgDir remains unchanged
+	origConfPath := filepath.Join(cfgDir, "crush", "crush.json")
+	afterOrig, err := fs.ReadFile(os.DirFS(filepath.Dir(origConfPath)), filepath.Base(origConfPath))
+	require.NoError(t, err)
+	var origParsed map[string]any
+	require.NoError(t, json.Unmarshal(afterOrig, &origParsed))
+	origRM := origParsed["recent_models"].(map[string]any)
+	origLarge := origRM["large"].([]any)
+	require.Len(t, origLarge, 2, "original config should be unchanged")
+
+	// Config should be rewritten with pruned recents in dataDir
+	dataConf := filepath.Join(dataDir, "crush", "crush.json")
+	rm := readRecentModels(t, dataConf)
+	largeAny, ok := rm["large"].([]any)
+	require.True(t, ok)
+	require.Len(t, largeAny, 1, "should only have one valid model")
+	// Verify only p1:m1 remains
+	m := largeAny[0].(map[string]any)
+	require.Equal(t, "p1", m["provider"])
+	require.Equal(t, "m1", m["model"])
+}
+
+func TestModelKey_EmptyInputs(t *testing.T) {
+	// Empty provider
+	require.Equal(t, "", modelKey("", "model"))
+	// Empty model
+	require.Equal(t, "", modelKey("provider", ""))
+	// Both empty
+	require.Equal(t, "", modelKey("", ""))
+	// Valid inputs
+	require.Equal(t, "p:m", modelKey("p", "m"))
+}
+
+func TestModelList_AllRecentsInvalid(t *testing.T) {
+	// Pre-initialize logger to os.DevNull to prevent file lock on Windows.
+	log.Setup(os.DevNull, false)
+
+	// Isolate config/data paths
+	cfgDir := t.TempDir()
+	dataDir := t.TempDir()
+	t.Setenv("XDG_CONFIG_HOME", cfgDir)
+	t.Setenv("XDG_DATA_HOME", dataDir)
+
+	// Pre-seed config with only invalid recents
+	confPath := filepath.Join(cfgDir, "crush", "crush.json")
+	require.NoError(t, os.MkdirAll(filepath.Dir(confPath), 0o755))
+	initial := map[string]any{
+		"options": map[string]any{
+			"disable_provider_auto_update": true,
+		},
+		"models": map[string]any{
+			"large": map[string]any{
+				"model":    "m1",
+				"provider": "p1",
+			},
+		},
+		"recent_models": map[string]any{
+			"large": []any{
+				map[string]any{"model": "x", "provider": "unknown1"},
+				map[string]any{"model": "y", "provider": "unknown2"},
+			},
+		},
+	}
+	bts, err := json.Marshal(initial)
+	require.NoError(t, err)
+	require.NoError(t, os.WriteFile(confPath, bts, 0o644))
+
+	// Also create empty providers.json and data config
+	dataConfDir := filepath.Join(dataDir, "crush")
+	require.NoError(t, os.MkdirAll(dataConfDir, 0o755))
+	emptyProviders := []byte("[]")
+	require.NoError(t, os.WriteFile(filepath.Join(dataConfDir, "providers.json"), emptyProviders, 0o644))
+
+	// Initialize global config instance with isolated dataDir
+	_, err = config.Init(cfgDir, dataDir, false)
+	require.NoError(t, err)
+
+	// Build provider set (doesn't include unknown1 or unknown2)
+	provider := catwalk.Provider{
+		ID:   catwalk.InferenceProvider("p1"),
+		Name: "Provider One",
+		Models: []catwalk.Model{
+			{ID: "m1", Name: "Model One", DefaultMaxTokens: 100},
+		},
+	}
+
+	// Create and initialize component
+	listKeyMap := list.DefaultKeyMap()
+	cmp := NewModelListComponent(listKeyMap, "Find your fave", false)
+	cmp.providers = []catwalk.Provider{provider}
+	execCmdML(t, cmp, cmp.Init())
+
+	// Verify no recent items exist in UI
+	groups := cmp.list.Groups()
+	require.NotEmpty(t, groups)
+	var recentItems []list.CompletionItem[ModelOption]
+	for _, g := range groups {
+		for _, it := range g.Items {
+			if strings.HasPrefix(it.ID(), "recent::") {
+				recentItems = append(recentItems, it)
+			}
+		}
+	}
+	require.Empty(t, recentItems, "all invalid recents should be pruned, resulting in no recent section")
+
+	// Verify original config in cfgDir remains unchanged
+	origConfPath := filepath.Join(cfgDir, "crush", "crush.json")
+	afterOrig, err := fs.ReadFile(os.DirFS(filepath.Dir(origConfPath)), filepath.Base(origConfPath))
+	require.NoError(t, err)
+	var origParsed map[string]any
+	require.NoError(t, json.Unmarshal(afterOrig, &origParsed))
+	origRM := origParsed["recent_models"].(map[string]any)
+	origLarge := origRM["large"].([]any)
+	require.Len(t, origLarge, 2, "original config should be unchanged")
+
+	// Config should be rewritten with empty recents in dataDir
+	dataConf := filepath.Join(dataDir, "crush", "crush.json")
+	rm := readRecentModels(t, dataConf)
+	// When all recents are pruned, the value may be nil or an empty array
+	largeVal := rm["large"]
+	if largeVal == nil {
+		// nil is acceptable - means empty
+		return
+	}
+	largeAny, ok := largeVal.([]any)
+	require.True(t, ok, "large key should be nil or array")
+	require.Empty(t, largeAny, "persisted recents should be empty after pruning all invalid entries")
+}

internal/tui/components/dialogs/permissions/permissions.go πŸ”—

@@ -291,19 +291,30 @@ func (p *permissionDialogCmp) renderHeader() string {
 			toolKey,
 			toolValue,
 		),
-		baseStyle.Render(strings.Repeat(" ", p.width)),
 		lipgloss.JoinHorizontal(
 			lipgloss.Left,
 			pathKey,
 			pathValue,
 		),
-		baseStyle.Render(strings.Repeat(" ", p.width)),
 	}
 
 	// Add tool-specific header information
 	switch p.permission.ToolName {
 	case tools.BashToolName:
-		headerParts = append(headerParts, t.S().Muted.Width(p.width).Render("Command"))
+		params := p.permission.Params.(tools.BashPermissionsParams)
+		descKey := t.S().Muted.Render("Desc")
+		descValue := t.S().Text.
+			Width(p.width - lipgloss.Width(descKey)).
+			Render(fmt.Sprintf(" %s", params.Description))
+		headerParts = append(headerParts,
+			lipgloss.JoinHorizontal(
+				lipgloss.Left,
+				descKey,
+				descValue,
+			),
+			baseStyle.Render(strings.Repeat(" ", p.width)),
+			t.S().Muted.Width(p.width).Render("Command"),
+		)
 	case tools.DownloadToolName:
 		params := p.permission.Params.(tools.DownloadPermissionsParams)
 		urlKey := t.S().Muted.Render("URL")
@@ -320,7 +331,6 @@ func (p *permissionDialogCmp) renderHeader() string {
 				urlKey,
 				urlValue,
 			),
-			baseStyle.Render(strings.Repeat(" ", p.width)),
 			lipgloss.JoinHorizontal(
 				lipgloss.Left,
 				fileKey,
@@ -372,9 +382,15 @@ func (p *permissionDialogCmp) renderHeader() string {
 			baseStyle.Render(strings.Repeat(" ", p.width)),
 		)
 	case tools.FetchToolName:
-		headerParts = append(headerParts, t.S().Muted.Width(p.width).Bold(true).Render("URL"))
+		headerParts = append(headerParts,
+			baseStyle.Render(strings.Repeat(" ", p.width)),
+			t.S().Muted.Width(p.width).Bold(true).Render("URL"),
+		)
 	case tools.AgenticFetchToolName:
-		headerParts = append(headerParts, t.S().Muted.Width(p.width).Bold(true).Render("URL"))
+		headerParts = append(headerParts,
+			baseStyle.Render(strings.Repeat(" ", p.width)),
+			t.S().Muted.Width(p.width).Bold(true).Render("URL"),
+		)
 	case tools.ViewToolName:
 		params := p.permission.Params.(tools.ViewPermissionsParams)
 		fileKey := t.S().Muted.Render("File")
@@ -466,6 +482,17 @@ func (p *permissionDialogCmp) generateBashContent() string {
 				Render(ln))
 		}
 
+		// Ensure minimum of 7 lines for command display
+		minLines := 7
+		for len(out) < minLines {
+			out = append(out, t.S().Muted.
+				Width(width).
+				Padding(0, 3).
+				Foreground(t.FgBase).
+				Background(t.BgSubtle).
+				Render(""))
+		}
+
 		// Use the cache for markdown rendering
 		renderedContent := strings.Join(out, "\n")
 		finalContent := baseStyle.
@@ -741,6 +768,7 @@ func (p *permissionDialogCmp) render() string {
 		title,
 		"",
 		headerContent,
+		"",
 		p.styleViewport(),
 		"",
 		buttons,

internal/tui/exp/list/list.go πŸ”—

@@ -475,7 +475,8 @@ func (l *list[T]) selectionView(view string, textOnly bool) string {
 				ts := t.TextSelection
 
 				cell = cell.Clone()
-				cell.Style = cell.Style.Background(ts.GetBackground()).Foreground(ts.GetForeground())
+				cell.Style.Bg = ts.GetBackground()
+				cell.Style.Fg = ts.GetForeground()
 				scr.SetCell(x, y, cell)
 			}
 		}

internal/tui/keys.go πŸ”—

@@ -9,6 +9,7 @@ type KeyMap struct {
 	Help     key.Binding
 	Commands key.Binding
 	Suspend  key.Binding
+	Models   key.Binding
 	Sessions key.Binding
 
 	pageBindings []key.Binding
@@ -32,6 +33,10 @@ func DefaultKeyMap() KeyMap {
 			key.WithKeys("ctrl+z"),
 			key.WithHelp("ctrl+z", "suspend"),
 		),
+		Models: key.NewBinding(
+			key.WithKeys("ctrl+l", "ctrl+m"),
+			key.WithHelp("ctrl+l", "models"),
+		),
 		Sessions: key.NewBinding(
 			key.WithKeys("ctrl+s"),
 			key.WithHelp("ctrl+s", "sessions"),

internal/tui/page/chat/chat.go πŸ”—

@@ -144,7 +144,7 @@ func (p *chatPage) Init() tea.Cmd {
 		p.isOnboarding = true
 		p.splashFullScreen = true
 	} else if b, _ := config.ProjectNeedsInitialization(); b {
-		// Project needs CRUSH.md initialization
+		// Project needs context initialization
 		p.splash.SetProjectInit(true)
 		p.isProjectInit = true
 		p.splashFullScreen = true
@@ -942,11 +942,19 @@ func (p *chatPage) Help() help.KeyMap {
 			key.WithKeys("ctrl+p"),
 			key.WithHelp("ctrl+p", "commands"),
 		)
+		modelsBinding := key.NewBinding(
+			key.WithKeys("ctrl+m", "ctrl+l"),
+			key.WithHelp("ctrl+l", "models"),
+		)
+		if p.keyboardEnhancements.Flags > 0 {
+			// non-zero flags mean we have at least key disambiguation
+			modelsBinding.SetHelp("ctrl+m", "models")
+		}
 		helpBinding := key.NewBinding(
 			key.WithKeys("ctrl+g"),
 			key.WithHelp("ctrl+g", "more"),
 		)
-		globalBindings = append(globalBindings, commandsBinding)
+		globalBindings = append(globalBindings, commandsBinding, modelsBinding)
 		globalBindings = append(globalBindings,
 			key.NewBinding(
 				key.WithKeys("ctrl+s"),
@@ -963,6 +971,7 @@ func (p *chatPage) Help() help.KeyMap {
 		shortList = append(shortList,
 			// Commands
 			commandsBinding,
+			modelsBinding,
 		)
 		fullList = append(fullList, globalBindings)
 

internal/tui/styles/charmtone.go πŸ”—

@@ -42,6 +42,7 @@ func NewCharmtoneTheme() *Theme {
 		White: charmtone.Butter,
 
 		BlueLight: charmtone.Sardine,
+		BlueDark:  charmtone.Damson,
 		Blue:      charmtone.Malibu,
 
 		Yellow: charmtone.Mustard,

internal/tui/tui.go πŸ”—

@@ -127,6 +127,10 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		}
 		return a, nil
 	case tea.KeyboardEnhancementsMsg:
+		// A non-zero value means we have key disambiguation support.
+		if msg.Flags > 0 {
+			a.keyMap.Models.SetHelp("ctrl+m", "models")
+		}
 		for id, page := range a.pages {
 			m, pageCmd := page.Update(msg)
 			a.pages[id] = m
@@ -263,7 +267,10 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			return a, util.ReportWarn("Agent is busy, please wait...")
 		}
 
-		config.Get().UpdatePreferredModel(msg.ModelType, msg.Model)
+		cfg := config.Get()
+		if err := cfg.UpdatePreferredModel(msg.ModelType, msg.Model); err != nil {
+			return a, util.ReportError(err)
+		}
 
 		go a.app.UpdateAgentModel(context.TODO())
 
@@ -477,6 +484,20 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
 		return util.CmdHandler(dialogs.OpenDialogMsg{
 			Model: commands.NewCommandDialog(a.selectedSessionID),
 		})
+	case key.Matches(msg, a.keyMap.Models):
+		// if the app is not configured show no models
+		if !a.isConfigured {
+			return nil
+		}
+		if a.dialog.ActiveDialogID() == models.ModelsDialogID {
+			return util.CmdHandler(dialogs.CloseDialogMsg{})
+		}
+		if a.dialog.HasDialogs() {
+			return nil
+		}
+		return util.CmdHandler(dialogs.OpenDialogMsg{
+			Model: models.NewModelDialogCmp(),
+		})
 	case key.Matches(msg, a.keyMap.Sessions):
 		// if the app is not configured show no sessions
 		if !a.isConfigured {
@@ -489,10 +510,6 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
 			return nil
 		}
 		var cmds []tea.Cmd
-		if a.dialog.ActiveDialogID() == commands.CommandsDialogID {
-			// If the commands dialog is open, close it first
-			cmds = append(cmds, util.CmdHandler(dialogs.CloseDialogMsg{}))
-		}
 		cmds = append(cmds,
 			func() tea.Msg {
 				allSessions, _ := a.app.Sessions.List(context.Background())
@@ -545,22 +562,25 @@ func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd {
 // View renders the complete application interface including pages, dialogs, and overlays.
 func (a *appModel) View() tea.View {
 	var view tea.View
-	view.AltScreen = true
 	t := styles.CurrentTheme()
+	view.AltScreen = true
+	view.MouseMode = tea.MouseModeCellMotion
 	view.BackgroundColor = t.BgBase
 	if a.wWidth < 25 || a.wHeight < 15 {
-		view.Layer = lipgloss.NewCanvas(
-			lipgloss.NewLayer(
-				t.S().Base.Width(a.wWidth).Height(a.wHeight).
-					Align(lipgloss.Center, lipgloss.Center).
-					Render(
-						t.S().Base.
-							Padding(1, 4).
-							Foreground(t.White).
-							BorderStyle(lipgloss.RoundedBorder()).
-							BorderForeground(t.Primary).
-							Render("Window too small!"),
-					),
+		view.SetContent(
+			lipgloss.NewCanvas(
+				lipgloss.NewLayer(
+					t.S().Base.Width(a.wWidth).Height(a.wHeight).
+						Align(lipgloss.Center, lipgloss.Center).
+						Render(
+							t.S().Base.
+								Padding(1, 4).
+								Foreground(t.White).
+								BorderStyle(lipgloss.RoundedBorder()).
+								BorderForeground(t.Primary).
+								Render("Window too small!"),
+						),
+				),
 			),
 		)
 		return view
@@ -617,9 +637,8 @@ func (a *appModel) View() tea.View {
 		layers...,
 	)
 
-	view.Layer = canvas
+	view.Content = canvas
 	view.Cursor = cursor
-	view.MouseMode = tea.MouseModeCellMotion
 
 	if a.sendProgressBar && a.app != nil && a.app.AgentCoordinator != nil && a.app.AgentCoordinator.IsBusy() {
 		// HACK: use a random percentage to prevent ghostty from hiding it

schema.json πŸ”—

@@ -5,10 +5,20 @@
   "$defs": {
     "Attribution": {
       "properties": {
+        "trailer_style": {
+          "type": "string",
+          "enum": [
+            "none",
+            "co-authored-by",
+            "assisted-by"
+          ],
+          "description": "Style of attribution trailer to add to commits",
+          "default": "assisted-by"
+        },
         "co_authored_by": {
           "type": "boolean",
-          "description": "Add Co-Authored-By trailer to commit messages",
-          "default": true
+          "description": "Deprecated: use trailer_style instead",
+          "deprecated": true
         },
         "generated_with": {
           "type": "boolean",
@@ -53,6 +63,16 @@
           "type": "object",
           "description": "Model configurations for different model types"
         },
+        "recent_models": {
+          "additionalProperties": {
+            "items": {
+              "$ref": "#/$defs/SelectedModel"
+            },
+            "type": "array"
+          },
+          "type": "object",
+          "description": "Recently used models sorted by most recent first"
+        },
         "providers": {
           "additionalProperties": {
             "$ref": "#/$defs/ProviderConfig"
@@ -384,6 +404,17 @@
           "type": "boolean",
           "description": "Disable sending metrics",
           "default": false
+        },
+        "initialize_as": {
+          "type": "string",
+          "description": "Name of the context file to create/update during project initialization",
+          "default": "AGENTS.md",
+          "examples": [
+            "AGENTS.md",
+            "CRUSH.md",
+            "CLAUDE.md",
+            "docs/LLMs.md"
+          ]
         }
       },
       "additionalProperties": false,