Detailed changes
@@ -1327,6 +1327,22 @@
"created_at": "2026-03-01T17:51:04Z",
"repoId": 987670088,
"pullRequestNo": 2337
+ },
+ {
+ "name": "ZeitbyteRepo",
+ "id": 187110998,
+ "comment_id": 4025287397,
+ "created_at": "2026-03-09T17:00:06Z",
+ "repoId": 987670088,
+ "pullRequestNo": 2390
+ },
+ {
+ "name": "seroperson",
+ "id": 3791221,
+ "comment_id": 4047392328,
+ "created_at": "2026-03-12T14:53:20Z",
+ "repoId": 987670088,
+ "pullRequestNo": 2401
}
]
}
@@ -67,7 +67,7 @@ jobs:
persist-credentials: false
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
- go-version: 1.26.0
+ go-version: 1.26.1
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run govulncheck
@@ -1,39 +1,130 @@
# Crush Development Guide
+## Project Overview
+
+Crush is a terminal-based AI coding assistant built in Go by
+[Charm](https://charm.land). It connects to LLMs and gives them tools to read,
+write, and execute code. It supports multiple providers (Anthropic, OpenAI,
+Gemini, Bedrock, Copilot, Hyper, MiniMax, Vercel, and more), integrates with
+LSPs for code intelligence, and supports extensibility via MCP servers and
+agent skills.
+
+The module path is `github.com/charmbracelet/crush`.
+
+## Architecture
+
+```
+main.go CLI entry point (cobra via internal/cmd)
+internal/
+ app/app.go Top-level wiring: DB, config, agents, LSP, MCP, events
+ cmd/ CLI commands (root, run, login, models, stats, sessions)
+ config/
+ config.go Config struct, context file paths, agent definitions
+ load.go crush.json loading and validation
+ provider.go Provider configuration and model resolution
+ agent/
+ agent.go SessionAgent: runs LLM conversations per session
+ coordinator.go Coordinator: manages named agents ("coder", "task")
+ prompts.go Loads Go-template system prompts
+ templates/ System prompt templates (coder.md.tpl, task.md.tpl, etc.)
+ tools/ All built-in tools (bash, edit, view, grep, glob, etc.)
+ mcp/ MCP client integration
+ session/session.go Session CRUD backed by SQLite
+ message/ Message model and content types
+ db/ SQLite via sqlc, with migrations
+ sql/ Raw SQL queries (consumed by sqlc)
+ migrations/ Schema migrations
+ lsp/ LSP client manager, auto-discovery, on-demand startup
+ ui/ Bubble Tea v2 TUI (see internal/ui/AGENTS.md)
+ permission/ Tool permission checking and allow-lists
+ skills/ Skill file discovery and loading
+ shell/ Bash command execution with background job support
+ event/ Telemetry (PostHog)
+ pubsub/ Internal pub/sub for cross-component messaging
+ filetracker/ Tracks files touched per session
+ history/ Prompt history
+```
+
+### Key Dependency Roles
+
+- **`charm.land/fantasy`**: LLM provider abstraction layer. Handles protocol
+ differences between Anthropic, OpenAI, Gemini, etc. Used in `internal/app`
+ and `internal/agent`.
+- **`charm.land/bubbletea/v2`**: TUI framework powering the interactive UI.
+- **`charm.land/lipgloss/v2`**: Terminal styling.
+- **`charm.land/glamour/v2`**: Markdown rendering in the terminal.
+- **`charm.land/catwalk`**: Snapshot/golden-file testing for TUI components.
+- **`sqlc`**: Generates Go code from SQL queries in `internal/db/sql/`.
+
+### Key Patterns
+
+- **Config is a Service**: accessed via `config.Service`, not global state.
+- **Tools are self-documenting**: each tool has a `.go` implementation and a
+ `.md` description file in `internal/agent/tools/`.
+- **System prompts are Go templates**: `internal/agent/templates/*.md.tpl`
+ with runtime data injected.
+- **Context files**: Crush reads AGENTS.md, CRUSH.md, CLAUDE.md, GEMINI.md
+ (and `.local` variants) from the working directory for project-specific
+ instructions.
+- **Persistence**: SQLite + sqlc. All queries live in `internal/db/sql/`,
+ generated code in `internal/db/`. Migrations in `internal/db/migrations/`.
+- **Pub/sub**: `internal/pubsub` for decoupled communication between agent,
+ UI, and services.
+- **CGO disabled**: builds with `CGO_ENABLED=0` and
+ `GOEXPERIMENT=greenteagc`.
+
## Build/Test/Lint Commands
- **Build**: `go build .` or `go run .`
-- **Test**: `task test` or `go test ./...` (run single test: `go test ./internal/llm/prompt -run TestGetContextFromPaths`)
-- **Update Golden Files**: `go test ./... -update` (regenerates .golden files when test output changes)
- - Update specific package: `go test ./internal/tui/components/core -update` (in this case, we're updating "core")
+- **Test**: `task test` or `go test ./...` (run single test:
+ `go test ./internal/llm/prompt -run TestGetContextFromPaths`)
+- **Update Golden Files**: `go test ./... -update` (regenerates `.golden`
+ files when test output changes)
+ - Update specific package:
+ `go test ./internal/tui/components/core -update` (in this case,
+ we're updating "core")
- **Lint**: `task lint:fix`
- **Format**: `task fmt` (`gofumpt -w .`)
-- **Modernize**: `task modernize` (runs `modernize` which make code simplifications)
+- **Modernize**: `task modernize` (runs `modernize` which makes code
+ simplifications)
- **Dev**: `task dev` (runs with profiling enabled)
## Code Style Guidelines
-- **Imports**: Use `goimports` formatting, group stdlib, external, internal packages
-- **Formatting**: Use gofumpt (stricter than gofmt), enabled in golangci-lint
-- **Naming**: Standard Go conventions - PascalCase for exported, camelCase for unexported
-- **Types**: Prefer explicit types, use type aliases for clarity (e.g., `type AgentName string`)
-- **Error handling**: Return errors explicitly, use `fmt.Errorf` for wrapping
-- **Context**: Always pass `context.Context` as first parameter for operations
-- **Interfaces**: Define interfaces in consuming packages, keep them small and focused
-- **Structs**: Use struct embedding for composition, group related fields
-- **Constants**: Use typed constants with iota for enums, group in const blocks
-- **Testing**: Use testify's `require` package, parallel tests with `t.Parallel()`,
- `t.SetEnv()` to set environment variables. Always use `t.Tempdir()` when in
- need of a temporary directory. This directory does not need to be removed.
-- **JSON tags**: Use snake_case for JSON field names
-- **File permissions**: Use octal notation (0o755, 0o644) for file permissions
-- **Log messages**: Log messages must start with a capital letter (e.g., "Failed to save session" not "failed to save session")
- - This is enforced by `task lint:log` which runs as part of `task lint`
-- **Comments**: End comments in periods unless comments are at the end of the line.
+- **Imports**: Use `goimports` formatting, group stdlib, external, internal
+ packages.
+- **Formatting**: Use gofumpt (stricter than gofmt), enabled in
+ golangci-lint.
+- **Naming**: Standard Go conventions — PascalCase for exported, camelCase
+ for unexported.
+- **Types**: Prefer explicit types, use type aliases for clarity (e.g.,
+ `type AgentName string`).
+- **Error handling**: Return errors explicitly, use `fmt.Errorf` for
+ wrapping.
+- **Context**: Always pass `context.Context` as first parameter for
+ operations.
+- **Interfaces**: Define interfaces in consuming packages, keep them small
+ and focused.
+- **Structs**: Use struct embedding for composition, group related fields.
+- **Constants**: Use typed constants with iota for enums, group in const
+ blocks.
+- **Testing**: Use testify's `require` package, parallel tests with
+ `t.Parallel()`, `t.SetEnv()` to set environment variables. Always use
+ `t.Tempdir()` when in need of a temporary directory. This directory does
+ not need to be removed.
+- **JSON tags**: Use snake_case for JSON field names.
+- **File permissions**: Use octal notation (0o755, 0o644) for file
+ permissions.
+- **Log messages**: Log messages must start with a capital letter (e.g.,
+ "Failed to save session" not "failed to save session").
+ - This is enforced by `task lint:log` which runs as part of `task lint`.
+- **Comments**: End comments in periods unless comments are at the end of the
+ line.
## Testing with Mock Providers
-When writing tests that involve provider configurations, use the mock providers to avoid API calls:
+When writing tests that involve provider configurations, use the mock
+providers to avoid API calls:
```go
func TestYourFunction(t *testing.T) {
@@ -70,9 +161,12 @@ func TestYourFunction(t *testing.T) {
## Committing
-- ALWAYS use semantic commits (`fix:`, `feat:`, `chore:`, `refactor:`, `docs:`, `sec:`, etc).
+- ALWAYS use semantic commits (`fix:`, `feat:`, `chore:`, `refactor:`,
+ `docs:`, `sec:`, etc).
- Try to keep commits to one line, not including your attribution. Only use
multi-line commits when additional context is truly necessary.
## Working on the TUI (UI)
-Anytime you need to work on the tui before starting work read the internal/ui/AGENTS.md file
+
+Anytime you need to work on the TUI, read `internal/ui/AGENTS.md` before
+starting work.
@@ -424,6 +424,25 @@ git clone https://github.com/anthropics/skills.git _temp
mv _temp/skills/* . ; rm -r -force _temp
```
+### Desktop notifications
+
+Crush sends desktop notifications when a tool call requires permission and when
+the agent finishes its turn. They're only sent when the terminal window isn't
+focused _and_ your terminal supports reporting the focus state.
+
+```jsonc
+{
+ "$schema": "https://charm.land/crush.json",
+ "options": {
+ "disable_notifications": false // default
+ }
+}
+```
+
+To disable desktop notifications, set `disable_notifications` to `true` in your
+configuration. On macOS, notifications currently lack icons due to platform
+limitations.
+
### Initialization
When you initialize a project, Crush analyzes your codebase and creates
@@ -1,15 +1,16 @@
module github.com/charmbracelet/crush
-go 1.26.0
+go 1.26.1
require (
charm.land/bubbles/v2 v2.0.0
- charm.land/bubbletea/v2 v2.0.0
- charm.land/catwalk v0.25.3
- charm.land/fantasy v0.11.0
- charm.land/glamour/v2 v2.0.0-20260123212943-6014aa153a9b
- charm.land/lipgloss/v2 v2.0.0
- charm.land/log/v2 v2.0.0-20251110204020-529bb77f35da
+ charm.land/bubbletea/v2 v2.0.2
+ charm.land/catwalk v0.29.1
+ charm.land/fang/v2 v2.0.1
+ charm.land/fantasy v0.12.2
+ charm.land/glamour/v2 v2.0.0
+ charm.land/lipgloss/v2 v2.0.1
+ charm.land/log/v2 v2.0.0
charm.land/x/vcr v0.1.1
github.com/JohannesKaufmann/html-to-markdown v1.6.0
github.com/MakeNowJust/heredoc v1.0.0
@@ -17,11 +18,10 @@ require (
github.com/alecthomas/chroma/v2 v2.23.1
github.com/atotto/clipboard v0.1.4
github.com/aymanbagabas/go-nativeclipboard v0.1.3
- github.com/aymanbagabas/go-udiff v0.4.0
+ github.com/aymanbagabas/go-udiff v0.4.1
github.com/bmatcuk/doublestar/v4 v4.10.0
github.com/charlievieth/fastwalk v1.0.14
- github.com/charmbracelet/colorprofile v0.4.2
- github.com/charmbracelet/fang v0.4.4
+ github.com/charmbracelet/colorprofile v0.4.3
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8
github.com/charmbracelet/x/ansi v0.11.6
github.com/charmbracelet/x/editor v0.2.0
@@ -38,6 +38,7 @@ require (
github.com/denisbrodbeck/machineid v1.0.1
github.com/disintegration/imaging v1.6.2
github.com/dustin/go-humanize v1.0.1
+ github.com/gen2brain/beeep v0.11.2
github.com/go-git/go-git/v5 v5.17.0
github.com/google/uuid v1.6.0
github.com/invopop/jsonschema v0.13.0
@@ -46,7 +47,7 @@ require (
github.com/lucasb-eyer/go-colorful v1.3.0
github.com/mattn/go-isatty v0.0.20
github.com/modelcontextprotocol/go-sdk v1.4.0
- github.com/ncruces/go-sqlite3 v0.30.5
+ github.com/ncruces/go-sqlite3 v0.31.1
github.com/nxadm/tail v1.4.11
github.com/openai/openai-go/v2 v2.7.1
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
@@ -63,13 +64,13 @@ require (
github.com/zeebo/xxh3 v1.1.0
go.uber.org/goleak v1.3.0
golang.org/x/net v0.51.0
- golang.org/x/sync v0.19.0
+ golang.org/x/sync v0.20.0
golang.org/x/text v0.34.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.46.1
mvdan.cc/sh/moreinterp v0.0.0-20250902163504-3cf4fd5717a5
- mvdan.cc/sh/v3 v3.12.1-0.20250902163504-3cf4fd5717a5
+ mvdan.cc/sh/v3 v3.13.0
)
require (
@@ -77,24 +78,25 @@ require (
cloud.google.com/go/auth v0.18.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
+ git.sr.ht/~jackmordaunt/go-toast v1.1.2 // 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/andybalholm/cascadia v1.3.3 // indirect
- github.com/aws/aws-sdk-go-v2 v1.41.2 // indirect
- github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
- github.com/aws/aws-sdk-go-v2/config v1.32.10 // indirect
- github.com/aws/aws-sdk-go-v2/credentials v1.19.10 // indirect
- github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
- github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect
- github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect
- github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect
- github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
- github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
- github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
- github.com/aws/smithy-go v1.24.1 // indirect
+ github.com/aws/aws-sdk-go-v2 v1.41.3 // indirect
+ github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
+ github.com/aws/aws-sdk-go-v2/config v1.32.11 // indirect
+ github.com/aws/aws-sdk-go-v2/credentials v1.19.11 // indirect
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 // indirect
+ github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sts v1.41.8 // indirect
+ github.com/aws/smithy-go v1.24.2 // 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
@@ -105,18 +107,21 @@ require (
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
- github.com/ebitengine/purego v0.10.0-alpha.5 // indirect
+ github.com/ebitengine/purego v0.10.0 // indirect
+ github.com/esiqveland/notify v0.13.3 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.8.0 // indirect
- github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e // indirect
+ github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 // 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-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
+ github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/jsonschema-go v0.4.2 // indirect
@@ -127,10 +132,11 @@ 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/jackmordaunt/icns/v3 v3.0.1 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
- github.com/kaptinlin/go-i18n v0.2.11 // indirect
- github.com/kaptinlin/jsonpointer v0.4.16 // indirect
- github.com/kaptinlin/jsonschema v0.7.3 // indirect
+ github.com/kaptinlin/go-i18n v0.2.12 // indirect
+ github.com/kaptinlin/jsonpointer v0.4.17 // indirect
+ github.com/kaptinlin/jsonschema v0.7.5 // indirect
github.com/kaptinlin/messageformat-go v0.4.18 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
@@ -147,13 +153,17 @@ require (
github.com/muesli/roff v0.1.0 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
+ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/pierrec/lz4/v4 v4.1.25 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/segmentio/encoding v0.5.3 // indirect
+ github.com/sergeymakinen/go-bmp v1.0.0 // indirect
+ github.com/sergeymakinen/go-ico v1.0.0-beta.0 // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
+ github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
github.com/tetratelabs/wazero v1.11.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
@@ -175,13 +185,13 @@ require (
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/image v0.36.0 // indirect
- golang.org/x/oauth2 v0.35.0 // indirect
- golang.org/x/sys v0.41.0 // indirect
+ golang.org/x/oauth2 v0.36.0 // indirect
+ golang.org/x/sys v0.42.0 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/time v0.14.0 // indirect
- google.golang.org/api v0.267.0 // indirect
- google.golang.org/genai v1.48.0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
+ google.golang.org/api v0.269.0 // indirect
+ google.golang.org/genai v1.49.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20251110073552-01de4eb40290 // indirect
@@ -1,17 +1,19 @@
charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
-charm.land/bubbletea/v2 v2.0.0 h1:p0d6CtWyJXJ9GfzMpUUqbP/XUUhhlk06+vCKWmox1wQ=
-charm.land/bubbletea/v2 v2.0.0/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
-charm.land/catwalk v0.25.3 h1:mkeICGwUPR9ZeOKNaeRmUrTyDJazTFYiNFWSeyjhM1A=
-charm.land/catwalk v0.25.3/go.mod h1:rFC/V96rIHX7VES215c/qzI1EW/Moo1ggs1Q6seTy5s=
-charm.land/fantasy v0.11.0 h1:KrYa7B3JMCViXsbDyho9vLdzoml9Id8OgyytowrmkNY=
-charm.land/fantasy v0.11.0/go.mod h1:NtQpqji9blpicYopEzcbgj8mIR4fOMjwK0wyr/D9D5M=
-charm.land/glamour/v2 v2.0.0-20260123212943-6014aa153a9b h1:A6IUUyChZDWP16RUdRJCfmYISAKWQGyIcfhZJUCViQ0=
-charm.land/glamour/v2 v2.0.0-20260123212943-6014aa153a9b/go.mod h1:J3kVhY6oHXZq5f+8vC3hmDO95fEvbqj3z7xDwxrfzU8=
-charm.land/lipgloss/v2 v2.0.0 h1:sd8N/B3x892oiOjFfBQdXBQp3cAkvjGaU5TvVZC3ivo=
-charm.land/lipgloss/v2 v2.0.0/go.mod h1:w6SnmsBFBmEFBodiEDurGS/sdUY/u1+v72DqUzc6J14=
-charm.land/log/v2 v2.0.0-20251110204020-529bb77f35da h1:vZa/Ow0uLclpfaDY0ubjzE+B0eLQqi2zanmpeALanow=
-charm.land/log/v2 v2.0.0-20251110204020-529bb77f35da/go.mod h1:Tj12StbPc4GwksDF6XwhC9wdXouinIVxRGKKmmmzdSU=
+charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
+charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
+charm.land/catwalk v0.29.1 h1:DomSgVkMpnEKJH1GzgNfwNG4tikhFXpD8m6HIJ/wbRY=
+charm.land/catwalk v0.29.1/go.mod h1:+fqw/6YGNtvapvPy9vhwA/fAMxVjD2K8hVIKYov8Vhg=
+charm.land/fang/v2 v2.0.1 h1:zQCM8JQJ1JnQX/66B5jlCYBUxL2as5JXQZ2KJ6EL0mY=
+charm.land/fang/v2 v2.0.1/go.mod h1:S1GmkpcvK+OB5w9caywUnJcsMew45Ot8FXqoz8ALrII=
+charm.land/fantasy v0.12.2 h1:9GR3lQ1agK3t0Y9T/b5Mn4P0ExC+5uOqUlk85y4P8vQ=
+charm.land/fantasy v0.12.2/go.mod h1:QeRVUeG1XNTWBszRAbhUtPyX1VWs6zjkCxwfcwnICdc=
+charm.land/glamour/v2 v2.0.0 h1:IDBoqLEy7Hdpb9VOXN+khLP/XSxtJy1VsHuW/yF87+U=
+charm.land/glamour/v2 v2.0.0/go.mod h1:kjq9WB0s8vuUYZNYey2jp4Lgd9f4cKdzAw88FZtpj/w=
+charm.land/lipgloss/v2 v2.0.1 h1:6Xzrn49+Py1Um5q/wZG1gWgER2+7dUyZ9XMEufqPSys=
+charm.land/lipgloss/v2 v2.0.1/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
+charm.land/log/v2 v2.0.0 h1:SY3Cey7ipx86/MBXQHwsguOT6X1exT94mmJRdzTNs+s=
+charm.land/log/v2 v2.0.0/go.mod h1:c3cZSRqm20qUVVAR1WmS/7ab8bgha3C6G7DjPcaVZz0=
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.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
@@ -22,6 +24,8 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
+git.sr.ht/~jackmordaunt/go-toast v1.1.2 h1:/yrfI55LRt1M7H1vkaw+NaH1+L1CDxrqDltwm5euVuE=
+git.sr.ht/~jackmordaunt/go-toast v1.1.2/go.mod h1:jA4OqHKTQ4AFBdwrSnwnskUIIS3HYzlJSgdzCKqfavo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
@@ -48,40 +52,40 @@ 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.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
-github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
-github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
-github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
-github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
-github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
-github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
-github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
-github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
-github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
-github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
-github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
-github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=
-github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
+github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
+github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
+github.com/aws/aws-sdk-go-v2/config v1.32.11 h1:ftxI5sgz8jZkckuUHXfC/wMUc8u3fG1vQS0plr2F2Zs=
+github.com/aws/aws-sdk-go-v2/config v1.32.11/go.mod h1:twF11+6ps9aNRKEDimksp923o44w/Thk9+8YIlzWMmo=
+github.com/aws/aws-sdk-go-v2/credentials v1.19.11 h1:NdV8cwCcAXrCWyxArt58BrvZJ9pZ9Fhf9w6Uh5W3Uyc=
+github.com/aws/aws-sdk-go-v2/credentials v1.19.11/go.mod h1:30yY2zqkMPdrvxBqzI9xQCM+WrlrZKSOpSJEsylVU+8=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 h1:INUvJxmhdEbVulJYHI061k4TVuS3jzzthNvjqvVvTKM=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19/go.mod h1:FpZN2QISLdEBWkayloda+sZjVJL+e9Gl0k1SyTgcswU=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 h1:/sECfyq2JTifMI2JPyZ4bdRN77zJmr6SrS1eL3augIA=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19/go.mod h1:dMf8A5oAqr9/oxOfLkC/c2LU/uMcALP0Rgn2BD5LWn0=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 h1:AWeJMk33GTBf6J20XJe6qZoRSJo0WfUhsMdUKhoODXE=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 h1:X1Tow7suZk9UCJHE1Iw9GMZJJl0dAnKXXP1NaSDHwmw=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA=
+github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row=
+github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
+github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias=
+github.com/aws/aws-sdk-go-v2/service/sso v1.30.12/go.mod h1:fEWYKTRGoZNl8tZ77i61/ccwOMJdGxwOhWCkp6TXAr0=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16 h1:EnUdUqRP1CNzt2DkV67tJx6XDN4xlfBFm+bzeNOQVb0=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16/go.mod h1:Jic/xv0Rq/pFNCh3WwpH4BEqdbSAl+IyHro8LbibHD8=
+github.com/aws/aws-sdk-go-v2/service/sts v1.41.8 h1:XQTQTF75vnug2TXS8m7CVJfC2nniYPZnO1D4Np761Oo=
+github.com/aws/aws-sdk-go-v2/service/sts v1.41.8/go.mod h1:Xgx+PR1NUOjNmQY+tRMnouRp83JRM8pRMw/vCaVhPkI=
+github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
+github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aymanbagabas/go-nativeclipboard v0.1.3 h1:FmAWHPTwneAixu7uGDn3cL42xPlUCdNp2J8egMn3P1k=
github.com/aymanbagabas/go-nativeclipboard v0.1.3/go.mod h1:2o7MyZwwi4pmXXpOpvOS5FwaHyoCIUks0ktjUvB0EoE=
-github.com/aymanbagabas/go-udiff v0.4.0 h1:TKnLPh7IbnizJIBKFWa9mKayRUBQ9Kh1BPCk6w2PnYM=
-github.com/aymanbagabas/go-udiff v0.4.0/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
+github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
+github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
@@ -96,10 +100,8 @@ github.com/charlievieth/fastwalk v1.0.14 h1:3Eh5uaFGwHZd8EGwTjJnSpBkfwfsak9h6ICg
github.com/charlievieth/fastwalk v1.0.14/go.mod h1:diVcUreiU1aQ4/Wu3NbxxH4/KYdKpLDojrQ1Bb2KgNY=
github.com/charmbracelet/anthropic-sdk-go v0.0.0-20260223140439-63879b0b8dab h1:J7XQLgl9sefgTnTGrmX3xqvp5o6MCiBzEjGv5igAlc4=
github.com/charmbracelet/anthropic-sdk-go v0.0.0-20260223140439-63879b0b8dab/go.mod h1:hqlYqR7uPKOKfnNeicUbZp0Ps0GeYFlKYtwh5HGDCx8=
-github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
-github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
-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/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
+github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
@@ -151,26 +153,30 @@ github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
-github.com/ebitengine/purego v0.10.0-alpha.5 h1:IUIZ1pu0wnpxrn7o6utj8AeoZBS2upI11kLcddBF414=
-github.com/ebitengine/purego v0.10.0-alpha.5/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
+github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
+github.com/esiqveland/notify v0.13.3 h1:QCMw6o1n+6rl+oLUfg8P1IIDSFsDEb2WlXvVvIJbI/o=
+github.com/esiqveland/notify v0.13.3/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
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/gen2brain/beeep v0.11.2 h1:+KfiKQBbQCuhfJFPANZuJ+oxsSKAYNe88hIpJuyKWDA=
+github.com/gen2brain/beeep v0.11.2/go.mod h1:jQVvuwnLuwOcdctHn/uyh8horSBNJ8uGb9Cn2W4tvoc=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM=
github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI=
-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-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=
+github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
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=
@@ -178,6 +184,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
@@ -186,6 +194,9 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
+github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
+github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
@@ -220,6 +231,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
+github.com/jackmordaunt/icns/v3 v3.0.1 h1:xxot6aNuGrU+lNgxz5I5H0qSeCjNKp8uTXB1j8D4S3o=
+github.com/jackmordaunt/icns/v3 v3.0.1/go.mod h1:5sHL59nqTd2ynTnowxB/MDQFhKNqkK8X687uKNygaSQ=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
@@ -227,12 +240,12 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
github.com/jordanella/go-ansi-paintbrush v0.0.0-20240728195301-b7ad996ecf3d h1:on25kP+Sx7sxUMRQiA8gdcToAGet4DK/EIA30mXre+4=
github.com/jordanella/go-ansi-paintbrush v0.0.0-20240728195301-b7ad996ecf3d/go.mod h1:SV0W0APWP9MZ1/gfDQ/NzzTlWdIgYZ/ZbpN4d/UXRYw=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
-github.com/kaptinlin/go-i18n v0.2.11 h1:OayNt8mWt8nDaqAOp09/C1VG9Y5u8LpQnnxbyGARDV4=
-github.com/kaptinlin/go-i18n v0.2.11/go.mod h1:pVcu9qsW5pOIOoZFJXesRYmLos1vMQrby70JPAoWmJU=
-github.com/kaptinlin/jsonpointer v0.4.16 h1:Ux4w4FY+uLv+K+TxaCJtM/TpPv+1+eS6gH4Z9/uhOuA=
-github.com/kaptinlin/jsonpointer v0.4.16/go.mod h1:SsfsjqnHG5zuKo1DTBzk1VknaHlL4osHw+X9kZKukpU=
-github.com/kaptinlin/jsonschema v0.7.3 h1:kyIydij76ORiSxmfy0xFYy0cOx8MwG6pyyaSoQshsK4=
-github.com/kaptinlin/jsonschema v0.7.3/go.mod h1:Ys6zr+W6/1330FzZEouFrAYImK+AmYt5HQVTHQQXQo8=
+github.com/kaptinlin/go-i18n v0.2.12 h1:ywDsvb4KDFddMC2dpI/rrIzGU2mWUSvHmWUm9BMsdl4=
+github.com/kaptinlin/go-i18n v0.2.12/go.mod h1:pVcu9qsW5pOIOoZFJXesRYmLos1vMQrby70JPAoWmJU=
+github.com/kaptinlin/jsonpointer v0.4.17 h1:mY9k8ciWncxbsECyaxKnR0MdmxamNdp2tLQkAKVrtSk=
+github.com/kaptinlin/jsonpointer v0.4.17/go.mod h1:SsfsjqnHG5zuKo1DTBzk1VknaHlL4osHw+X9kZKukpU=
+github.com/kaptinlin/jsonschema v0.7.5 h1:jkK4a3NyzNoGlvu12CsL3IcqNMVa5sL51HPVa0nWcPY=
+github.com/kaptinlin/jsonschema v0.7.5/go.mod h1:3gIWnptl+SWMyfMR2r4TXXd0xsQZ1m50AKrwmcUONSg=
github.com/kaptinlin/messageformat-go v0.4.18 h1:RBlHVWgZyoxTcUgGWBsl2AcyScq/urqbLZvzgryTmSI=
github.com/kaptinlin/messageformat-go v0.4.18/go.mod h1:ntI3154RnqJgr7GaC+vZBnIExl2V3sv9selvRNNEM24=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
@@ -276,12 +289,14 @@ github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe
github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
-github.com/ncruces/go-sqlite3 v0.30.5 h1:6usmTQ6khriL8oWilkAZSJM/AIpAlVL2zFrlcpDldCE=
-github.com/ncruces/go-sqlite3 v0.30.5/go.mod h1:0I0JFflTKzfs3Ogfv8erP7CCoV/Z8uxigVDNOR0AQ5E=
+github.com/ncruces/go-sqlite3 v0.31.1 h1:F76NF4NTLNOabLUKuEb2xqjBW+/Ub+MR59/Q7dACRo8=
+github.com/ncruces/go-sqlite3 v0.31.1/go.mod h1:L9OWFjYG/+4dq9O6bFCYoWLG0a7LmtgR6v26TvABmwg=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
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=
@@ -321,6 +336,10 @@ github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w=
github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
+github.com/sergeymakinen/go-bmp v1.0.0 h1:SdGTzp9WvCV0A1V0mBeaS7kQAwNLdVJbmHlqNWq0R+M=
+github.com/sergeymakinen/go-bmp v1.0.0/go.mod h1:/mxlAQZRLxSvJFNIEGGLBE/m40f3ZnUifpgVDlcUIEY=
+github.com/sergeymakinen/go-ico v1.0.0-beta.0 h1:m5qKH7uPKLdrygMWxbamVn+tl2HfiA3K6MFJw4GfZvQ=
+github.com/sergeymakinen/go-ico v1.0.0-beta.0/go.mod h1:wQ47mTczswBO5F0NoDt7O0IXgnV4Xy3ojrroMQzyhUk=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
@@ -334,10 +353,17 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
+github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -427,8 +453,8 @@ 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.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
-golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
-golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
+golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
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=
@@ -436,8 +462,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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
-golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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=
@@ -454,8 +480,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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
-golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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=
@@ -493,12 +519,12 @@ golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE=
-google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
-google.golang.org/genai v1.48.0 h1:1vb15G291wAjJJueisMDpUhssljhEdJU2t5qTidrVPs=
-google.golang.org/genai v1.48.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/api v0.269.0 h1:qDrTOxKUQ/P0MveH6a7vZ+DNHxJQjtGm/uvdbdGXCQg=
+google.golang.org/api v0.269.0/go.mod h1:N8Wpcu23Tlccl0zSHEkcAZQKDLdquxK+l9r2LkwAauE=
+google.golang.org/genai v1.49.0 h1:Se+QJaH2GYK1aaR1o5S38mlU2GD5FnVvP76nfkV7LH0=
+google.golang.org/genai v1.49.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
@@ -518,6 +544,7 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
@@ -550,5 +577,5 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
mvdan.cc/sh/moreinterp v0.0.0-20250902163504-3cf4fd5717a5 h1:mO2lyKtGwu4mGQ+Qqjx0+fd5UU5BXhX/rslFmxd5aco=
mvdan.cc/sh/moreinterp v0.0.0-20250902163504-3cf4fd5717a5/go.mod h1:Of9PCedbLDYT8b3EyiYG64rNnx5nOp27OLCVdDrjJyo=
-mvdan.cc/sh/v3 v3.12.1-0.20250902163504-3cf4fd5717a5 h1:e7Z/Lgw/zMijvQBVrfh/vUDZ+9FpuSLrJDVGBuoJtuo=
-mvdan.cc/sh/v3 v3.12.1-0.20250902163504-3cf4fd5717a5/go.mod h1:P21wo2gLLe3426sP+CmANLBaixSEbRtPl35w3YlM6dg=
+mvdan.cc/sh/v3 v3.13.0 h1:dSfq/MVsY4w0Vsi6Lbs0IcQquMVqLdKLESAOZjuHdLg=
+mvdan.cc/sh/v3 v3.13.0/go.mod h1:KV1GByGPc/Ho0X1E6Uz9euhsIQEj4hwyKnodLlFLoDM=
@@ -32,19 +32,22 @@ import (
"charm.land/fantasy/providers/vercel"
"charm.land/lipgloss/v2"
"github.com/charmbracelet/crush/internal/agent/hyper"
+ "github.com/charmbracelet/crush/internal/agent/notify"
"github.com/charmbracelet/crush/internal/agent/tools"
"github.com/charmbracelet/crush/internal/agent/tools/mcp"
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/csync"
"github.com/charmbracelet/crush/internal/message"
"github.com/charmbracelet/crush/internal/permission"
+ "github.com/charmbracelet/crush/internal/pubsub"
"github.com/charmbracelet/crush/internal/session"
"github.com/charmbracelet/crush/internal/stringext"
+ "github.com/charmbracelet/crush/internal/version"
"github.com/charmbracelet/x/exp/charmtone"
)
const (
- defaultSessionName = "Untitled Session"
+ DefaultSessionName = "Untitled Session"
// Constants for auto-summarization thresholds
largeContextWindowThreshold = 200_000
@@ -52,6 +55,8 @@ const (
smallContextWindowRatio = 0.2
)
+var userAgent = fmt.Sprintf("Charm-Crush/%s (https://charm.land/crush)", version.Version)
+
//go:embed templates/title.md
var titlePrompt []byte
@@ -72,6 +77,7 @@ type SessionAgentCall struct {
TopK *int64
FrequencyPenalty *float64
PresencePenalty *float64
+ NonInteractive bool
}
type SessionAgent interface {
@@ -108,6 +114,7 @@ type sessionAgent struct {
messages message.Service
disableAutoSummarize bool
isYolo bool
+ notify pubsub.Publisher[notify.Notification]
messageQueue *csync.Map[string, []SessionAgentCall]
activeRequests *csync.Map[string, context.CancelFunc]
@@ -124,6 +131,7 @@ type SessionAgentOptions struct {
Sessions session.Service
Messages message.Service
Tools []fantasy.AgentTool
+ Notify pubsub.Publisher[notify.Notification]
}
func NewSessionAgent(
@@ -140,6 +148,7 @@ func NewSessionAgent(
disableAutoSummarize: opts.DisableAutoSummarize,
tools: csync.NewSliceFrom(opts.Tools),
isYolo: opts.IsYolo,
+ notify: opts.Notify,
messageQueue: csync.NewMap[string, []SessionAgentCall](),
activeRequests: csync.NewMap[string, context.CancelFunc](),
}
@@ -194,6 +203,7 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy
largeModel.Model,
fantasy.WithSystemPrompt(systemPrompt),
fantasy.WithTools(agentTools...),
+ fantasy.WithUserAgent(userAgent),
)
sessionLock := sync.Mutex{}
@@ -533,6 +543,16 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy
return nil, err
}
+ // Send notification that agent has finished its turn (skip for
+ // nested/non-interactive sessions).
+ if !call.NonInteractive && a.notify != nil {
+ a.notify.Publish(pubsub.CreatedEvent, notify.Notification{
+ SessionID: call.SessionID,
+ SessionTitle: currentSession.Title,
+ Type: notify.TypeAgentFinished,
+ })
+ }
+
if shouldSummarize {
a.activeRequests.Del(call.SessionID)
if summarizeErr := a.Summarize(genCtx, call.SessionID, call.ProviderOptions); summarizeErr != nil {
@@ -595,6 +615,7 @@ func (a *sessionAgent) Summarize(ctx context.Context, sessionID string, opts fan
agent := fantasy.NewAgent(largeModel.Model,
fantasy.WithSystemPrompt(string(summaryPrompt)),
+ fantasy.WithUserAgent(userAgent),
)
summaryMessage, err := a.messages.Create(ctx, sessionID, message.CreateMessageParams{
Role: message.Assistant,
@@ -790,6 +811,7 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
return fantasy.NewAgent(m,
fantasy.WithSystemPrompt(string(p)+"\n /no_think"),
fantasy.WithMaxOutputTokens(tok),
+ fantasy.WithUserAgent(userAgent),
)
}
@@ -825,9 +847,9 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
// Welp, the large model didn't work either. Use the default
// session name and return.
slog.Error("Error generating title with large model", "err", err)
- saveErr := a.sessions.UpdateTitleAndUsage(ctx, sessionID, defaultSessionName, 0, 0, 0)
+ saveErr := a.sessions.Rename(ctx, sessionID, DefaultSessionName)
if saveErr != nil {
- slog.Error("Failed to save session title and usage", "error", saveErr)
+ slog.Error("Failed to save session title", "error", saveErr)
}
return
}
@@ -837,9 +859,9 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
// Actually, we didn't get a response so we can't. Use the default
// session name and return.
slog.Error("Response is nil; can't generate title")
- saveErr := a.sessions.UpdateTitleAndUsage(ctx, sessionID, defaultSessionName, 0, 0, 0)
+ saveErr := a.sessions.Rename(ctx, sessionID, DefaultSessionName)
if saveErr != nil {
- slog.Error("Failed to save session title and usage", "error", saveErr)
+ slog.Error("Failed to save session title", "error", saveErr)
}
return
}
@@ -852,7 +874,7 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
title = thinkTagRegex.ReplaceAllString(title, "")
title = strings.TrimSpace(title)
- title = cmp.Or(title, defaultSessionName)
+ title = cmp.Or(title, DefaultSessionName)
// Calculate usage and cost.
var openrouterCost *float64
@@ -20,7 +20,7 @@ import (
)
var modelPairs = []modelPair{
- {"anthropic-sonnet", anthropicBuilder("claude-sonnet-4-5-20250929"), anthropicBuilder("claude-3-5-haiku-20241022")},
+ {"anthropic-sonnet", anthropicBuilder("claude-sonnet-4-6"), anthropicBuilder("claude-haiku-4-5-20251001")},
{"openai-gpt-5", openaiBuilder("gpt-5"), openaiBuilder("gpt-4o")},
{"openrouter-kimi-k2", openRouterBuilder("moonshotai/kimi-k2-0905"), openRouterBuilder("qwen/qwen3-next-80b-a3b-instruct")},
{"zai-glm4.6", zAIBuilder("glm-4.6"), zAIBuilder("glm-4.5-air")},
@@ -24,7 +24,7 @@ const (
)
func (c *coordinator) agentTool(ctx context.Context) (fantasy.AgentTool, error) {
- agentCfg, ok := c.cfg.Agents[config.AgentTask]
+ agentCfg, ok := c.cfg.Config().Agents[config.AgentTask]
if !ok {
return nil, errors.New("task agent not configured")
}
@@ -98,7 +98,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
return fantasy.ToolResponse{}, permission.ErrorPermissionDenied
}
- tmpDir, err := os.MkdirTemp(c.cfg.Options.DataDirectory, "crush-fetch-*")
+ tmpDir, err := os.MkdirTemp(c.cfg.Config().Options.DataDirectory, "crush-fetch-*")
if err != nil {
return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to create temporary directory: %s", err)), nil
}
@@ -151,12 +151,12 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
return fantasy.ToolResponse{}, fmt.Errorf("error building models: %s", err)
}
- systemPrompt, err := promptTemplate.Build(ctx, small.Model.Provider(), small.Model.Model(), *c.cfg)
+ systemPrompt, err := promptTemplate.Build(ctx, small.Model.Provider(), small.Model.Model(), c.cfg)
if err != nil {
return fantasy.ToolResponse{}, fmt.Errorf("error building system prompt: %s", err)
}
- smallProviderCfg, ok := c.cfg.Providers.Get(small.ModelCfg.Provider)
+ smallProviderCfg, ok := c.cfg.Config().Providers.Get(small.ModelCfg.Provider)
if !ok {
return fantasy.ToolResponse{}, errors.New("small model provider not configured")
}
@@ -167,7 +167,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
webFetchTool,
webSearchTool,
tools.NewGlobTool(tmpDir),
- tools.NewGrepTool(tmpDir, c.cfg.Tools.Grep),
+ tools.NewGrepTool(tmpDir, c.cfg.Config().Tools.Grep),
tools.NewSourcegraphTool(client),
tools.NewViewTool(c.lspManager, c.permissions, c.filetracker, tmpDir),
}
@@ -177,7 +177,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
SmallModel: small,
SystemPromptPrefix: smallProviderCfg.SystemPromptPrefix,
SystemPrompt: systemPrompt,
- DisableAutoSummarize: c.cfg.Options.DisableAutoSummarize,
+ DisableAutoSummarize: c.cfg.Config().Options.DisableAutoSummarize,
IsYolo: c.permissions.SkipRequests(),
Sessions: c.sessions,
Messages: c.messages,
@@ -153,7 +153,15 @@ func testSessionAgent(env fakeEnv, large, small fantasy.LanguageModel, systemPro
DefaultMaxTokens: 10000,
},
}
- agent := NewSessionAgent(SessionAgentOptions{largeModel, smallModel, "", systemPrompt, false, false, true, env.sessions, env.messages, tools})
+ agent := NewSessionAgent(SessionAgentOptions{
+ LargeModel: largeModel,
+ SmallModel: smallModel,
+ SystemPrompt: systemPrompt,
+ IsYolo: true,
+ Sessions: env.sessions,
+ Messages: env.messages,
+ Tools: tools,
+ })
return agent
}
@@ -177,36 +185,36 @@ func coderAgent(r *vcr.Recorder, env fakeEnv, large, small fantasy.LanguageModel
// 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{
+ cfg.Config().Options.Attribution = &config.Attribution{
TrailerStyle: "co-authored-by",
GeneratedWith: true,
}
// Clear some fields to avoid issues with VCR cassette matching.
- cfg.Options.SkillsPaths = nil
- cfg.Options.ContextPaths = nil
- cfg.LSP = nil
+ cfg.Config().Options.SkillsPaths = nil
+ cfg.Config().Options.ContextPaths = nil
+ cfg.Config().LSP = nil
- systemPrompt, err := prompt.Build(context.TODO(), large.Provider(), large.Model(), *cfg)
+ 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 {
+ if model := cfg.Config().GetModel(large.Provider(), large.Model()); model != nil {
modelName = model.Name
}
allTools := []fantasy.AgentTool{
- tools.NewBashTool(env.permissions, env.workingDir, cfg.Options.Attribution, modelName),
+ tools.NewBashTool(env.permissions, env.workingDir, cfg.Config().Options.Attribution, modelName),
tools.NewDownloadTool(env.permissions, env.workingDir, r.GetDefaultClient()),
tools.NewEditTool(nil, env.permissions, env.history, *env.filetracker, env.workingDir),
tools.NewMultiEditTool(nil, env.permissions, env.history, *env.filetracker, env.workingDir),
tools.NewFetchTool(env.permissions, env.workingDir, r.GetDefaultClient()),
tools.NewGlobTool(env.workingDir),
- tools.NewGrepTool(env.workingDir, cfg.Tools.Grep),
- tools.NewLsTool(env.permissions, env.workingDir, cfg.Tools.Ls),
+ tools.NewGrepTool(env.workingDir, cfg.Config().Tools.Grep),
+ tools.NewLsTool(env.permissions, env.workingDir, cfg.Config().Tools.Ls),
tools.NewSourcegraphTool(r.GetDefaultClient()),
tools.NewViewTool(nil, env.permissions, *env.filetracker, env.workingDir),
tools.NewWriteTool(nil, env.permissions, env.history, *env.filetracker, env.workingDir),
@@ -18,6 +18,7 @@ import (
"charm.land/catwalk/pkg/catwalk"
"charm.land/fantasy"
"github.com/charmbracelet/crush/internal/agent/hyper"
+ "github.com/charmbracelet/crush/internal/agent/notify"
"github.com/charmbracelet/crush/internal/agent/prompt"
"github.com/charmbracelet/crush/internal/agent/tools"
"github.com/charmbracelet/crush/internal/config"
@@ -28,6 +29,7 @@ import (
"github.com/charmbracelet/crush/internal/message"
"github.com/charmbracelet/crush/internal/oauth/copilot"
"github.com/charmbracelet/crush/internal/permission"
+ "github.com/charmbracelet/crush/internal/pubsub"
"github.com/charmbracelet/crush/internal/session"
"golang.org/x/sync/errgroup"
@@ -43,6 +45,18 @@ import (
"github.com/qjebbs/go-jsons"
)
+// Coordinator errors.
+var (
+ errCoderAgentNotConfigured = errors.New("coder agent not configured")
+ errModelProviderNotConfigured = errors.New("model provider not configured")
+ errLargeModelNotSelected = errors.New("large model not selected")
+ errSmallModelNotSelected = errors.New("small model not selected")
+ errLargeModelProviderNotConfigured = errors.New("large model provider not configured")
+ errSmallModelProviderNotConfigured = errors.New("small model provider not configured")
+ errLargeModelNotFound = errors.New("large model not found in provider config")
+ errSmallModelNotFound = errors.New("small model not found in provider config")
+)
+
type Coordinator interface {
// INFO: (kujtim) this is not used yet we will use this when we have multiple agents
// SetMainAgent(string)
@@ -61,13 +75,14 @@ type Coordinator interface {
}
type coordinator struct {
- cfg *config.Config
+ cfg *config.ConfigStore
sessions session.Service
messages message.Service
permissions permission.Service
history history.Service
filetracker filetracker.Service
lspManager *lsp.Manager
+ notify pubsub.Publisher[notify.Notification]
currentAgent SessionAgent
agents map[string]SessionAgent
@@ -77,13 +92,14 @@ type coordinator struct {
func NewCoordinator(
ctx context.Context,
- cfg *config.Config,
+ cfg *config.ConfigStore,
sessions session.Service,
messages message.Service,
permissions permission.Service,
history history.Service,
filetracker filetracker.Service,
lspManager *lsp.Manager,
+ notify pubsub.Publisher[notify.Notification],
) (Coordinator, error) {
c := &coordinator{
cfg: cfg,
@@ -93,12 +109,13 @@ func NewCoordinator(
history: history,
filetracker: filetracker,
lspManager: lspManager,
+ notify: notify,
agents: make(map[string]SessionAgent),
}
- agentCfg, ok := cfg.Agents[config.AgentCoder]
+ agentCfg, ok := cfg.Config().Agents[config.AgentCoder]
if !ok {
- return nil, errors.New("coder agent not configured")
+ return nil, errCoderAgentNotConfigured
}
// TODO: make this dynamic when we support multiple agents
@@ -144,9 +161,9 @@ func (c *coordinator) Run(ctx context.Context, sessionID string, prompt string,
attachments = filteredAttachments
}
- providerCfg, ok := c.cfg.Providers.Get(model.ModelCfg.Provider)
+ providerCfg, ok := c.cfg.Config().Providers.Get(model.ModelCfg.Provider)
if !ok {
- return nil, errors.New("model provider not configured")
+ return nil, errModelProviderNotConfigured
}
mergedOptions, temp, topP, topK, freqPenalty, presPenalty := mergeCallOptions(model, providerCfg)
@@ -367,22 +384,23 @@ func (c *coordinator) buildAgent(ctx context.Context, prompt *prompt.Prompt, age
return nil, err
}
- largeProviderCfg, _ := c.cfg.Providers.Get(large.ModelCfg.Provider)
+ largeProviderCfg, _ := c.cfg.Config().Providers.Get(large.ModelCfg.Provider)
result := NewSessionAgent(SessionAgentOptions{
- large,
- small,
- largeProviderCfg.SystemPromptPrefix,
- "",
- isSubAgent,
- c.cfg.Options.DisableAutoSummarize,
- c.permissions.SkipRequests(),
- c.sessions,
- c.messages,
- nil,
+ LargeModel: large,
+ SmallModel: small,
+ SystemPromptPrefix: largeProviderCfg.SystemPromptPrefix,
+ SystemPrompt: "",
+ IsSubAgent: isSubAgent,
+ DisableAutoSummarize: c.cfg.Config().Options.DisableAutoSummarize,
+ IsYolo: c.permissions.SkipRequests(),
+ Sessions: c.sessions,
+ Messages: c.messages,
+ Tools: nil,
+ Notify: c.notify,
})
c.readyWg.Go(func() error {
- systemPrompt, err := prompt.Build(ctx, large.Model.Provider(), large.Model.Model(), *c.cfg)
+ systemPrompt, err := prompt.Build(ctx, large.Model.Provider(), large.Model.Model(), c.cfg)
if err != nil {
return err
}
@@ -422,14 +440,14 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
// 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 {
+ if modelCfg, ok := c.cfg.Config().Models[agent.Model]; ok {
+ if model := c.cfg.Config().GetModel(modelCfg.Provider, modelCfg.Model); model != nil {
modelName = model.Name
}
}
allTools = append(allTools,
- tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Options.Attribution, modelName),
+ tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Config().Options.Attribution, modelName),
tools.NewJobOutputTool(),
tools.NewJobKillTool(),
tools.NewDownloadTool(c.permissions, c.cfg.WorkingDir(), nil),
@@ -437,20 +455,20 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
tools.NewMultiEditTool(c.lspManager, c.permissions, c.history, c.filetracker, c.cfg.WorkingDir()),
tools.NewFetchTool(c.permissions, c.cfg.WorkingDir(), nil),
tools.NewGlobTool(c.cfg.WorkingDir()),
- tools.NewGrepTool(c.cfg.WorkingDir(), c.cfg.Tools.Grep),
- tools.NewLsTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Tools.Ls),
+ tools.NewGrepTool(c.cfg.WorkingDir(), c.cfg.Config().Tools.Grep),
+ tools.NewLsTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Config().Tools.Ls),
tools.NewSourcegraphTool(nil),
tools.NewTodosTool(c.sessions),
- tools.NewViewTool(c.lspManager, c.permissions, c.filetracker, c.cfg.WorkingDir(), c.cfg.Options.SkillsPaths...),
+ tools.NewViewTool(c.lspManager, c.permissions, c.filetracker, c.cfg.WorkingDir(), c.cfg.Config().Options.SkillsPaths...),
tools.NewWriteTool(c.lspManager, c.permissions, c.history, c.filetracker, c.cfg.WorkingDir()),
)
// Add LSP tools if user has configured LSPs or auto_lsp is enabled (nil or true).
- if len(c.cfg.LSP) > 0 || c.cfg.Options.AutoLSP == nil || *c.cfg.Options.AutoLSP {
+ if len(c.cfg.Config().LSP) > 0 || c.cfg.Config().Options.AutoLSP == nil || *c.cfg.Config().Options.AutoLSP {
allTools = append(allTools, tools.NewDiagnosticsTool(c.lspManager), tools.NewReferencesTool(c.lspManager), tools.NewLSPRestartTool(c.lspManager))
}
- if len(c.cfg.MCP) > 0 {
+ if len(c.cfg.Config().MCP) > 0 {
allTools = append(
allTools,
tools.NewListMCPResourcesTool(c.cfg, c.permissions),
@@ -496,18 +514,18 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
// TODO: when we support multiple agents we need to change this so that we pass in the agent specific model config
func (c *coordinator) buildAgentModels(ctx context.Context, isSubAgent bool) (Model, Model, error) {
- largeModelCfg, ok := c.cfg.Models[config.SelectedModelTypeLarge]
+ largeModelCfg, ok := c.cfg.Config().Models[config.SelectedModelTypeLarge]
if !ok {
- return Model{}, Model{}, errors.New("large model not selected")
+ return Model{}, Model{}, errLargeModelNotSelected
}
- smallModelCfg, ok := c.cfg.Models[config.SelectedModelTypeSmall]
+ smallModelCfg, ok := c.cfg.Config().Models[config.SelectedModelTypeSmall]
if !ok {
- return Model{}, Model{}, errors.New("small model not selected")
+ return Model{}, Model{}, errSmallModelNotSelected
}
- largeProviderCfg, ok := c.cfg.Providers.Get(largeModelCfg.Provider)
+ largeProviderCfg, ok := c.cfg.Config().Providers.Get(largeModelCfg.Provider)
if !ok {
- return Model{}, Model{}, errors.New("large model provider not configured")
+ return Model{}, Model{}, errLargeModelProviderNotConfigured
}
largeProvider, err := c.buildProvider(largeProviderCfg, largeModelCfg, isSubAgent)
@@ -515,9 +533,9 @@ func (c *coordinator) buildAgentModels(ctx context.Context, isSubAgent bool) (Mo
return Model{}, Model{}, err
}
- smallProviderCfg, ok := c.cfg.Providers.Get(smallModelCfg.Provider)
+ smallProviderCfg, ok := c.cfg.Config().Providers.Get(smallModelCfg.Provider)
if !ok {
- return Model{}, Model{}, errors.New("small model provider not configured")
+ return Model{}, Model{}, errSmallModelProviderNotConfigured
}
smallProvider, err := c.buildProvider(smallProviderCfg, smallModelCfg, true)
@@ -540,11 +558,11 @@ func (c *coordinator) buildAgentModels(ctx context.Context, isSubAgent bool) (Mo
}
if largeCatwalkModel == nil {
- return Model{}, Model{}, errors.New("large model not found in provider config")
+ return Model{}, Model{}, errLargeModelNotFound
}
if smallCatwalkModel == nil {
- return Model{}, Model{}, errors.New("small model not found in provider config")
+ return Model{}, Model{}, errSmallModelNotFound
}
largeModelID := largeModelCfg.Model
@@ -603,7 +621,7 @@ func (c *coordinator) buildAnthropicProvider(baseURL, apiKey string, headers map
opts = append(opts, anthropic.WithBaseURL(baseURL))
}
- if c.cfg.Options.Debug {
+ if c.cfg.Config().Options.Debug {
httpClient := log.NewHTTPClient()
opts = append(opts, anthropic.WithHTTPClient(httpClient))
}
@@ -615,7 +633,7 @@ func (c *coordinator) buildOpenaiProvider(baseURL, apiKey string, headers map[st
openai.WithAPIKey(apiKey),
openai.WithUseResponsesAPI(),
}
- if c.cfg.Options.Debug {
+ if c.cfg.Config().Options.Debug {
httpClient := log.NewHTTPClient()
opts = append(opts, openai.WithHTTPClient(httpClient))
}
@@ -632,7 +650,7 @@ func (c *coordinator) buildOpenrouterProvider(_, apiKey string, headers map[stri
opts := []openrouter.Option{
openrouter.WithAPIKey(apiKey),
}
- if c.cfg.Options.Debug {
+ if c.cfg.Config().Options.Debug {
httpClient := log.NewHTTPClient()
opts = append(opts, openrouter.WithHTTPClient(httpClient))
}
@@ -646,7 +664,7 @@ func (c *coordinator) buildVercelProvider(_, apiKey string, headers map[string]s
opts := []vercel.Option{
vercel.WithAPIKey(apiKey),
}
- if c.cfg.Options.Debug {
+ if c.cfg.Config().Options.Debug {
httpClient := log.NewHTTPClient()
opts = append(opts, vercel.WithHTTPClient(httpClient))
}
@@ -666,8 +684,8 @@ func (c *coordinator) buildOpenaiCompatProvider(baseURL, apiKey string, headers
var httpClient *http.Client
if providerID == string(catwalk.InferenceProviderCopilot) {
opts = append(opts, openaicompat.WithUseResponsesAPI())
- httpClient = copilot.NewClient(isSubAgent, c.cfg.Options.Debug)
- } else if c.cfg.Options.Debug {
+ httpClient = copilot.NewClient(isSubAgent, c.cfg.Config().Options.Debug)
+ } else if c.cfg.Config().Options.Debug {
httpClient = log.NewHTTPClient()
}
if httpClient != nil {
@@ -691,7 +709,7 @@ func (c *coordinator) buildAzureProvider(baseURL, apiKey string, headers map[str
azure.WithAPIKey(apiKey),
azure.WithUseResponsesAPI(),
}
- if c.cfg.Options.Debug {
+ if c.cfg.Config().Options.Debug {
httpClient := log.NewHTTPClient()
opts = append(opts, azure.WithHTTPClient(httpClient))
}
@@ -710,7 +728,7 @@ func (c *coordinator) buildAzureProvider(baseURL, apiKey string, headers map[str
func (c *coordinator) buildBedrockProvider(headers map[string]string) (fantasy.Provider, error) {
var opts []bedrock.Option
- if c.cfg.Options.Debug {
+ if c.cfg.Config().Options.Debug {
httpClient := log.NewHTTPClient()
opts = append(opts, bedrock.WithHTTPClient(httpClient))
}
@@ -729,7 +747,7 @@ func (c *coordinator) buildGoogleProvider(baseURL, apiKey string, headers map[st
google.WithBaseURL(baseURL),
google.WithGeminiAPIKey(apiKey),
}
- if c.cfg.Options.Debug {
+ if c.cfg.Config().Options.Debug {
httpClient := log.NewHTTPClient()
opts = append(opts, google.WithHTTPClient(httpClient))
}
@@ -741,7 +759,7 @@ func (c *coordinator) buildGoogleProvider(baseURL, apiKey string, headers map[st
func (c *coordinator) buildGoogleVertexProvider(headers map[string]string, options map[string]string) (fantasy.Provider, error) {
opts := []google.Option{}
- if c.cfg.Options.Debug {
+ if c.cfg.Config().Options.Debug {
httpClient := log.NewHTTPClient()
opts = append(opts, google.WithHTTPClient(httpClient))
}
@@ -762,7 +780,7 @@ func (c *coordinator) buildHyperProvider(baseURL, apiKey string) (fantasy.Provid
hyper.WithBaseURL(baseURL),
hyper.WithAPIKey(apiKey),
}
- if c.cfg.Options.Debug {
+ if c.cfg.Config().Options.Debug {
httpClient := log.NewHTTPClient()
opts = append(opts, hyper.WithHTTPClient(httpClient))
}
@@ -773,19 +791,8 @@ func (c *coordinator) isAnthropicThinking(model config.SelectedModel) bool {
if model.Think {
return true
}
-
- if model.ProviderOptions == nil {
- return false
- }
-
opts, err := anthropic.ParseOptions(model.ProviderOptions)
- if err != nil {
- return false
- }
- if opts.Thinking != nil {
- return true
- }
- return false
+ return err == nil && opts.Thinking != nil
}
func (c *coordinator) buildProvider(providerCfg config.ProviderConfig, model config.SelectedModel, isSubAgent bool) (fantasy.Provider, error) {
@@ -881,9 +888,9 @@ func (c *coordinator) UpdateModels(ctx context.Context) error {
}
c.currentAgent.SetModels(large, small)
- agentCfg, ok := c.cfg.Agents[config.AgentCoder]
+ agentCfg, ok := c.cfg.Config().Agents[config.AgentCoder]
if !ok {
- return errors.New("coder agent not configured")
+ return errCoderAgentNotConfigured
}
tools, err := c.buildTools(ctx, agentCfg)
@@ -895,7 +902,7 @@ func (c *coordinator) UpdateModels(ctx context.Context) error {
}
func (c *coordinator) RefreshTools(ctx context.Context) error {
- agentCfg, ok := c.cfg.Agents[config.AgentCoder]
+ agentCfg, ok := c.cfg.Config().Agents[config.AgentCoder]
if !ok {
return errors.New("coder agent not configured")
}
@@ -918,9 +925,9 @@ func (c *coordinator) QueuedPromptsList(sessionID string) []string {
}
func (c *coordinator) Summarize(ctx context.Context, sessionID string) error {
- providerCfg, ok := c.cfg.Providers.Get(c.currentAgent.Model().ModelCfg.Provider)
+ providerCfg, ok := c.cfg.Config().Providers.Get(c.currentAgent.Model().ModelCfg.Provider)
if !ok {
- return errors.New("model provider not configured")
+ return errModelProviderNotConfigured
}
return c.currentAgent.Summarize(ctx, sessionID, getProviderOptions(c.currentAgent.Model(), providerCfg))
}
@@ -931,7 +938,7 @@ func (c *coordinator) isUnauthorized(err error) bool {
}
func (c *coordinator) refreshOAuth2Token(ctx context.Context, providerCfg config.ProviderConfig) error {
- if err := c.cfg.RefreshOAuthToken(ctx, providerCfg.ID); err != nil {
+ if err := c.cfg.RefreshOAuthToken(ctx, config.ScopeGlobal, providerCfg.ID); err != nil {
slog.Error("Failed to refresh OAuth token after 401 error", "provider", providerCfg.ID, "error", err)
return err
}
@@ -949,7 +956,7 @@ func (c *coordinator) refreshApiKeyTemplate(ctx context.Context, providerCfg con
}
providerCfg.APIKey = newAPIKey
- c.cfg.Providers.Set(providerCfg.ID, providerCfg)
+ c.cfg.Config().Providers.Set(providerCfg.ID, providerCfg)
if err := c.UpdateModels(ctx); err != nil {
return err
@@ -993,9 +1000,9 @@ func (c *coordinator) runSubAgent(ctx context.Context, params subAgentParams) (f
maxTokens = model.ModelCfg.MaxTokens
}
- providerCfg, ok := c.cfg.Providers.Get(model.ModelCfg.Provider)
+ providerCfg, ok := c.cfg.Config().Providers.Get(model.ModelCfg.Provider)
if !ok {
- return fantasy.ToolResponse{}, errors.New("model provider not configured")
+ return fantasy.ToolResponse{}, errModelProviderNotConfigured
}
// Run the agent
@@ -1009,6 +1016,7 @@ func (c *coordinator) runSubAgent(ctx context.Context, params subAgentParams) (f
TopK: model.ModelCfg.TopK,
FrequencyPenalty: model.ModelCfg.FrequencyPenalty,
PresencePenalty: model.ModelCfg.PresencePenalty,
+ NonInteractive: true,
})
if err != nil {
return fantasy.NewTextErrorResponse("error generating response"), nil
@@ -44,7 +44,7 @@ func (m *mockSessionAgent) Summarize(context.Context, string, fantasy.ProviderOp
func newTestCoordinator(t *testing.T, env fakeEnv, providerID string, providerCfg config.ProviderConfig) *coordinator {
cfg, err := config.Init(env.workingDir, "", false)
require.NoError(t, err)
- cfg.Providers.Set(providerID, providerCfg)
+ cfg.Config().Providers.Set(providerID, providerCfg)
return &coordinator{
cfg: cfg,
sessions: env.sessions,
@@ -0,0 +1,19 @@
+// Package notify defines domain notification types for agent events.
+// These types are decoupled from UI concerns so the agent can publish
+// events without importing UI packages.
+package notify
+
+// Type identifies the kind of agent notification.
+type Type string
+
+const (
+ // TypeAgentFinished indicates the agent has completed its turn.
+ TypeAgentFinished Type = "agent_finished"
+)
+
+// Notification represents a domain event published by the agent.
+type Notification struct {
+ SessionID string
+ SessionTitle string
+ Type Type
+}
@@ -76,13 +76,13 @@ func NewPrompt(name, promptTemplate string, opts ...Option) (*Prompt, error) {
return p, nil
}
-func (p *Prompt) Build(ctx context.Context, provider, model string, cfg config.Config) (string, error) {
+func (p *Prompt) Build(ctx context.Context, provider, model string, store *config.ConfigStore) (string, error) {
t, err := template.New(p.name).Parse(p.template)
if err != nil {
return "", fmt.Errorf("parsing template: %w", err)
}
var sb strings.Builder
- d, err := p.promptData(ctx, provider, model, cfg)
+ d, err := p.promptData(ctx, provider, model, store)
if err != nil {
return "", err
}
@@ -104,11 +104,11 @@ func processFile(filePath string) *ContextFile {
}
}
-func processContextPath(p string, cfg config.Config) []ContextFile {
+func processContextPath(p string, store *config.ConfigStore) []ContextFile {
var contexts []ContextFile
fullPath := p
if !filepath.IsAbs(p) {
- fullPath = filepath.Join(cfg.WorkingDir(), p)
+ fullPath = filepath.Join(store.WorkingDir(), p)
}
info, err := os.Stat(fullPath)
if err != nil {
@@ -136,11 +136,11 @@ func processContextPath(p string, cfg config.Config) []ContextFile {
}
// expandPath expands ~ and environment variables in file paths
-func expandPath(path string, cfg config.Config) string {
+func expandPath(path string, store *config.ConfigStore) string {
path = home.Long(path)
// Handle environment variable expansion using the same pattern as config
if strings.HasPrefix(path, "$") {
- if expanded, err := cfg.Resolver().ResolveValue(path); err == nil {
+ if expanded, err := store.Resolver().ResolveValue(path); err == nil {
path = expanded
}
}
@@ -148,19 +148,20 @@ func expandPath(path string, cfg config.Config) string {
return path
}
-func (p *Prompt) promptData(ctx context.Context, provider, model string, cfg config.Config) (PromptDat, error) {
- workingDir := cmp.Or(p.workingDir, cfg.WorkingDir())
+func (p *Prompt) promptData(ctx context.Context, provider, model string, store *config.ConfigStore) (PromptDat, error) {
+ workingDir := cmp.Or(p.workingDir, store.WorkingDir())
platform := cmp.Or(p.platform, runtime.GOOS)
files := map[string][]ContextFile{}
+ cfg := store.Config()
for _, pth := range cfg.Options.ContextPaths {
- expanded := expandPath(pth, cfg)
+ expanded := expandPath(pth, store)
pathKey := strings.ToLower(expanded)
if _, ok := files[pathKey]; ok {
continue
}
- content := processContextPath(expanded, cfg)
+ content := processContextPath(expanded, store)
files[pathKey] = content
}
@@ -169,18 +170,18 @@ func (p *Prompt) promptData(ctx context.Context, provider, model string, cfg con
if len(cfg.Options.SkillsPaths) > 0 {
expandedPaths := make([]string, 0, len(cfg.Options.SkillsPaths))
for _, pth := range cfg.Options.SkillsPaths {
- expandedPaths = append(expandedPaths, expandPath(pth, cfg))
+ expandedPaths = append(expandedPaths, expandPath(pth, store))
}
if discoveredSkills := skills.Discover(expandedPaths); len(discoveredSkills) > 0 {
availSkillXML = skills.ToPromptXML(discoveredSkills)
}
}
- isGit := isGitRepo(cfg.WorkingDir())
+ isGit := isGitRepo(store.WorkingDir())
data := PromptDat{
Provider: provider,
Model: model,
- Config: cfg,
+ Config: *cfg,
WorkingDir: filepath.ToSlash(workingDir),
IsGitRepo: isGit,
Platform: platform,
@@ -189,7 +190,7 @@ func (p *Prompt) promptData(ctx context.Context, provider, model string, cfg con
}
if isGit {
var err error
- data.GitStatus, err = getGitStatus(ctx, cfg.WorkingDir())
+ data.GitStatus, err = getGitStatus(ctx, store.WorkingDir())
if err != nil {
return PromptDat{}, err
}
@@ -33,7 +33,7 @@ func taskPrompt(opts ...prompt.Option) (*prompt.Prompt, error) {
return systemPrompt, nil
}
-func InitializePrompt(cfg config.Config) (string, error) {
+func InitializePrompt(cfg *config.ConfigStore) (string, error) {
systemPrompt, err := prompt.NewPrompt("initialize", string(initializePromptTmpl))
if err != nil {
return "", err
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 802
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse bash to create a file named test.txt with content ''hello bash''. do not print its timestamp\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse bash to create a file named test.txt with content ''hello bash''. do not print its timestamp\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,61 +23,62 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01Lij7hAdrqXtCCiddzXdoGK","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":2,"service_tier":"standard"}}}
+ data: {"type":"message_start","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01NCBUPGM1ZzefNsxK3oAZsz","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","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Bash"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Create"} }
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":" test"} }
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Creation"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":".txt file"} }
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Comman"} }
+ 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":"d"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" bash content"} }
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":8} }
+ 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":12} }
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: 811.339375ms
+ duration: 580.853041ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52393
+ content_length: 52585
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 823
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\ndownload the file from https://example-files.online-convert.com/document/txt/example.txt and save it as example.txt\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\ndownload the file from https://example-files.online-convert.com/document/txt/example.txt and save it as example.txt\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,58 +23,59 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_011YAinXEYrTqbpsq4sBCjxA","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-haiku-4-5-20251001","id":"msg_01HcFVhDJ3LAx7YJAek2tnEX","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","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
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":"Download"} }
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":" and"} }
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" from Example"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" save example"} }
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" URL"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":".txt 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":160,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":8} }
+ 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}}
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: 640.901ms
+ duration: 458.520167ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52418
+ content_length: 52610
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 844
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nfetch the content from https://example-files.online-convert.com/website/html/example.html and tell me if it contains the word ''John Doe''\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nfetch the content from https://example-files.online-convert.com/website/html/example.html and tell me if it contains the word ''John Doe''\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,64 +23,59 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01QHs42eeZPJbxjrFBiaSQu3","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-haiku-4-5-20251001","id":"msg_019yRu75N7cyTTHykxDghwJc","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","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Website"} }
-
- event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Content"} }
-
- event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Check"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Check"} }
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":" if"} }
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" 'John Doe"} }
+ 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":"'"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":".html contains \"John 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":13} }
+ 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":14} }
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: 601.885125ms
+ duration: 446.485541ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52436
+ content_length: 52628
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 763
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse glob to find all .go files in the current directory\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse glob to find all .go files in the current directory\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,61 +23,59 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_016WcJGfCcpgVqfScPMNdmBp","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-haiku-4-5-20251001","id":"msg_01HC4LX9pv7pQ8XXNZwBNJho","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","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
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":"Finding"} }
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: 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":" files with"} }
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":" glob"} }
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":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: 683.862917ms
+ duration: 858.447625ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52354
+ content_length: 52546
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 761
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse grep to search for the word ''package'' in go files\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse grep to search for the word ''package'' in go files\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,61 +23,56 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_0181LpjAHqcVep2LP8kMttkT","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":1,"service_tier":"standard"}} }
+ data: {"type":"message_start","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01HQQmnr4yHdSBMG5kdExyF2","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":1,"service_tier":"standard","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
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":"grep"} }
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":" search"} }
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Package"} }
-
- event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Search with"} }
-
- event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Grep"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" for 'package' in go files"} }
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":10} }
+ 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} }
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: 647.254041ms
+ duration: 451.462041ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52352
+ content_length: 52544
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 757
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse ls to list the files in the current directory\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse ls to list the files in the current directory\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,55 +23,59 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01MFyJCeDt5XVr7qQrdbfQ3Z","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":1,"service_tier":"standard"}} }
+ data: {"type":"message_start","message":{"model":"claude-haiku-4-5-20251001","id":"msg_016ZF7o5tSUmiNsMcT2yvErw","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":1,"service_tier":"standard","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"List"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Using"} }
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":" ls"} }
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":" command"} }
+
+ event: content_block_delta
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" to list files"}}
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":140,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":8} }
+ 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: 539.973208ms
+ duration: 434.346709ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52346
+ content_length: 52538
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 836
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"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 \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"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 \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,61 +23,62 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01MWKFC4YJpdfdLd6h1kfcfd","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":2,"service_tier":"standard"}} }
+ data: {"type":"message_start","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01DEiHkcPX9joxDtDrsGCY3S","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":2,"service_tier":"standard","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Modify"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Mult"}}
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":"iedit Hello"} }
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Hello"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" World"} }
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Worl"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" to Hello Crush in"} }
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d Greeting"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" main.go"} }
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":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":17} }
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: 664.226709ms
+ duration: 469.534792ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52432
+ content_length: 52624
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 842
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse glob to find all .go files and use ls to list the current directory, it is very important that you run both tool calls in parallel\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse glob to find all .go files and use ls to list the current directory, it is very important that you run both tool calls in parallel\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,58 +23,68 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_011AxTk6fRGrP3MaJPbdM1kh","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-haiku-4-5-20251001","id":"msg_013JcAgtbnTMEL4aFdyD4MMK","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":1,"service_tier":"standard","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
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":"Go"} }
+
+ event: content_block_delta
+ 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":" 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":" Go File"} }
+ 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":" an"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" ls directory"} }
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d Directory Listing"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" listing"} }
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":159,"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: 636.447167ms
+ duration: 638.720958ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52443
+ content_length: 52635
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 723
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nRead the go mod\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nRead the go mod\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,58 +23,59 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01BS8RX55UAXGbe4XdL5VxK5","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-haiku-4-5-20251001","id":"msg_01XmjM7aL79Ah9inAJ6FKFSr","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","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
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":"Go"} }
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":" Module"} }
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":" File"} }
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":" Review"} }
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} }
+ 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: 589.305792ms
+ duration: 614.432792ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52316
+ content_length: 52508
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 713
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nHello\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nHello\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,55 +23,50 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01RhwrPwqgU78kv4XsoesFPF","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-haiku-4-5-20251001","id":"msg_01UUWs1TkN4VRwXg5qqAYi4x","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","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Quick"} }
-
- event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Chat"} }
-
- event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Opener"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}
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":131,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":7}}
+ 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":4} }
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: 705.524375ms
+ duration: 464.945084ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52306
+ content_length: 52498
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 768
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse sourcegraph to search for ''func main'' in Go repositories\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse sourcegraph to search for ''func main'' in Go repositories\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,58 +23,59 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_01FkLqqw1crC8WC9dAUtwxqu","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"}} }
+ data: {"type":"message_start","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01Ndi9GYfffhiodY599f7iFx","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","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Searching Go"} }
+ 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":" Repos"}}
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" for func"}}
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" for Main"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" main in Go repositories with"} }
event: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Function"} }
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Sourcegraph"}}
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":11} }
+ 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":16} }
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: 640.628291ms
+ duration: 1.13498575s
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52366
+ content_length: 52558
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 777
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nupdate the main.go file by changing the print to say hello from crush\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nupdate the main.go file by changing the print to say hello from crush\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,58 +23,59 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_0198CQ2JnP8dYvrKr27saGVX","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-haiku-4-5-20251001","id":"msg_01DR6im8rp6CvrbFpFMDz9g7","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","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
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: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":".go print"} }
+ 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":" from crush"} }
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: 557.108833ms
+ duration: 470.455ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52372
+ content_length: 52564
host: ""
@@ -8,14 +8,14 @@ interactions:
proto_minor: 1
content_length: 817
host: ""
- body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse write to create a new file called config.json with content ''{\"name\": \"test\", \"version\": \"1.0.0\"}''\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-3-5-haiku-20241022","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
+ body: '{"max_tokens":40,"messages":[{"content":[{"text":"Generate a concise title for the following content:\n\nuse write to create a new file called config.json with content ''{\"name\": \"test\", \"version\": \"1.0.0\"}''\n \u003cthink\u003e\n\n\u003c/think\u003e","type":"text"}],"role":"user"}],"model":"claude-haiku-4-5-20251001","system":[{"text":"you will generate a short title based on the first message a user begins a conversation with\n\n\u003crules\u003e\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\u003c/rules\u003e\n\n /no_think","type":"text"}],"stream":true}'
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- - Anthropic/Go 1.14.0
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.anthropic.com/v1/messages
method: POST
response:
@@ -23,58 +23,62 @@ interactions:
proto_major: 2
proto_minor: 0
content_length: -1
+ uncompressed: true
body: |+
event: message_start
- data: {"type":"message_start","message":{"model":"claude-3-5-haiku-20241022","id":"msg_016VUcmtZyoNJiJysc1oxRtZ","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-haiku-4-5-20251001","id":"msg_01TYYgbCvYvFDfxiDyMKbZuC","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","inference_geo":"not_available"}} }
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: ping
data: {"type": "ping"}
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":"Create"} }
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: content_block_delta
- data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":".json with JSON"}}
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":".json file"} }
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":" with test"}}
+
+ event: content_block_delta
+ data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" data"} }
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":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: 555.094125ms
+ duration: 807.968541ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 52409
+ content_length: 52601
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,23 +24,25 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-Czjp9999VWZzcDOifgxBIBqcSm4HT","object":"chat.completion.chunk","created":1768830191,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"bUV63yG95BqGXw"}
+ data: {"id":"chatcmpl-DIgbv0pTSe1Khfah2QQT8m4hsLZHS","object":"chat.completion.chunk","created":1773346071,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"XNeud2NVXIfn5k"}
- data: {"id":"chatcmpl-Czjp9999VWZzcDOifgxBIBqcSm4HT","object":"chat.completion.chunk","created":1768830191,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Create"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1z0Fv3Tkd6"}
+ data: {"id":"chatcmpl-DIgbv0pTSe1Khfah2QQT8m4hsLZHS","object":"chat.completion.chunk","created":1773346071,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"Create"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"asUFZQqxyC"}
- data: {"id":"chatcmpl-Czjp9999VWZzcDOifgxBIBqcSm4HT","object":"chat.completion.chunk","created":1768830191,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"C426rRcRvG6"}
+ data: {"id":"chatcmpl-DIgbv0pTSe1Khfah2QQT8m4hsLZHS","object":"chat.completion.chunk","created":1773346071,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"R9PoRhaiu1f2AX"}
- data: {"id":"chatcmpl-Czjp9999VWZzcDOifgxBIBqcSm4HT","object":"chat.completion.chunk","created":1768830191,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"FngAUBvZG3n71"}
+ data: {"id":"chatcmpl-DIgbv0pTSe1Khfah2QQT8m4hsLZHS","object":"chat.completion.chunk","created":1773346071,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"W1xBbRdHXUF"}
- data: {"id":"chatcmpl-Czjp9999VWZzcDOifgxBIBqcSm4HT","object":"chat.completion.chunk","created":1768830191,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Bash"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"k82T3NyIeZn"}
+ data: {"id":"chatcmpl-DIgbv0pTSe1Khfah2QQT8m4hsLZHS","object":"chat.completion.chunk","created":1773346071,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"RkrqMVn7kRO"}
- data: {"id":"chatcmpl-Czjp9999VWZzcDOifgxBIBqcSm4HT","object":"chat.completion.chunk","created":1768830191,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Without"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Vbe4EU54"}
+ data: {"id":"chatcmpl-DIgbv0pTSe1Khfah2QQT8m4hsLZHS","object":"chat.completion.chunk","created":1773346071,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Content"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"zE7JYVBh"}
- data: {"id":"chatcmpl-Czjp9999VWZzcDOifgxBIBqcSm4HT","object":"chat.completion.chunk","created":1768830191,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Timestamp"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"yGQZi2"}
+ data: {"id":"chatcmpl-DIgbv0pTSe1Khfah2QQT8m4hsLZHS","object":"chat.completion.chunk","created":1773346071,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ZQgSormHvSpRP"}
- data: {"id":"chatcmpl-Czjp9999VWZzcDOifgxBIBqcSm4HT","object":"chat.completion.chunk","created":1768830191,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"fC0Xm5I8zq"}
+ data: {"id":"chatcmpl-DIgbv0pTSe1Khfah2QQT8m4hsLZHS","object":"chat.completion.chunk","created":1773346071,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Bash"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"k3Y3oANLYNy"}
- data: {"id":"chatcmpl-Czjp9999VWZzcDOifgxBIBqcSm4HT","object":"chat.completion.chunk","created":1768830191,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","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":"PJLKHqx144Bd0A"}
+ data: {"id":"chatcmpl-DIgbv0pTSe1Khfah2QQT8m4hsLZHS","object":"chat.completion.chunk","created":1773346071,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"Wx5pDuKbv6"}
+
+ data: {"id":"chatcmpl-DIgbv0pTSe1Khfah2QQT8m4hsLZHS","object":"chat.completion.chunk","created":1773346071,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[],"usage":{"prompt_tokens":145,"completion_tokens":7,"total_tokens":152,"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":"GMNsrdtfCMVZvP"}
data: [DONE]
@@ -49,22 +51,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 462.517083ms
+ duration: 590.488833ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50734
+ content_length: 50935
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,23 +24,23 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-CzjpMASHGa0KE7gogMQsoEw5kBkKm","object":"chat.completion.chunk","created":1768830204,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"tc5dsBZDiWj1KC"}
+ data: {"id":"chatcmpl-DIgcBCEsQeF7sIYGZFHfvKWv36Jv4","object":"chat.completion.chunk","created":1773346087,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7c4b976237","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"L6S2S5FbyKuB2Q"}
- data: {"id":"chatcmpl-CzjpMASHGa0KE7gogMQsoEw5kBkKm","object":"chat.completion.chunk","created":1768830204,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":"Download"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"5EIK1K0G"}
+ data: {"id":"chatcmpl-DIgcBCEsQeF7sIYGZFHfvKWv36Jv4","object":"chat.completion.chunk","created":1773346087,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7c4b976237","choices":[{"index":0,"delta":{"content":"Downloading"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"OkXl8"}
- data: {"id":"chatcmpl-CzjpMASHGa0KE7gogMQsoEw5kBkKm","object":"chat.completion.chunk","created":1768830204,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"iZvzUc95rMPu"}
+ data: {"id":"chatcmpl-DIgcBCEsQeF7sIYGZFHfvKWv36Jv4","object":"chat.completion.chunk","created":1773346087,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7c4b976237","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KMyiVw3C3PUl"}
- data: {"id":"chatcmpl-CzjpMASHGa0KE7gogMQsoEw5kBkKm","object":"chat.completion.chunk","created":1768830204,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":" Save"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"c4VqjRgTgUo"}
+ data: {"id":"chatcmpl-DIgcBCEsQeF7sIYGZFHfvKWv36Jv4","object":"chat.completion.chunk","created":1773346087,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7c4b976237","choices":[{"index":0,"delta":{"content":" Saving"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"LmMpnjEv2"}
- data: {"id":"chatcmpl-CzjpMASHGa0KE7gogMQsoEw5kBkKm","object":"chat.completion.chunk","created":1768830204,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":" Example"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"90hZHZYg"}
+ data: {"id":"chatcmpl-DIgcBCEsQeF7sIYGZFHfvKWv36Jv4","object":"chat.completion.chunk","created":1773346087,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7c4b976237","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"dJNPJ5bRg2cKOx"}
- data: {"id":"chatcmpl-CzjpMASHGa0KE7gogMQsoEw5kBkKm","object":"chat.completion.chunk","created":1768830204,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":".txt"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"99FcHSsVLzva"}
+ data: {"id":"chatcmpl-DIgcBCEsQeF7sIYGZFHfvKWv36Jv4","object":"chat.completion.chunk","created":1773346087,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7c4b976237","choices":[{"index":0,"delta":{"content":" TXT"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"QrEQMWUTcVaA"}
- data: {"id":"chatcmpl-CzjpMASHGa0KE7gogMQsoEw5kBkKm","object":"chat.completion.chunk","created":1768830204,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"cdp2a07T0OB"}
+ data: {"id":"chatcmpl-DIgcBCEsQeF7sIYGZFHfvKWv36Jv4","object":"chat.completion.chunk","created":1773346087,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7c4b976237","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Y9rCwhO6q6p"}
- data: {"id":"chatcmpl-CzjpMASHGa0KE7gogMQsoEw5kBkKm","object":"chat.completion.chunk","created":1768830204,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"ijDn66iVAI"}
+ data: {"id":"chatcmpl-DIgcBCEsQeF7sIYGZFHfvKWv36Jv4","object":"chat.completion.chunk","created":1773346087,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7c4b976237","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"ktdWarf7iR"}
- data: {"id":"chatcmpl-CzjpMASHGa0KE7gogMQsoEw5kBkKm","object":"chat.completion.chunk","created":1768830204,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","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":"C9d9Vz5rJwg0pZ"}
+ data: {"id":"chatcmpl-DIgcBCEsQeF7sIYGZFHfvKWv36Jv4","object":"chat.completion.chunk","created":1773346087,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7c4b976237","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":"lw2dl4V3D9MVgE"}
data: [DONE]
@@ -49,22 +49,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 1.09768275s
+ duration: 427.27125ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50759
+ content_length: 50960
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,29 +24,31 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"VhKLdJMzPv4zTF"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"q95A3H5JOC2nwc"}
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Check"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"hrg10QzQcmP"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":"Checking"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"D0lLDihL"}
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" HTML"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"NAFAT7HZQQx"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" for"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"JRHjAu65Ko0t"}
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" for"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"9R8aoN7LrXit"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"SgnzoZUn6ipAdA"}
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"mDowc6tWWiEJXY"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":"John"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"J1k2pGqzS0OB"}
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"John"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"wabcvWDKuvBw"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" Doe"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"LYbaXQklKnYy"}
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Doe"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"p6l0NKowhtjv"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Ar2v6w44ZhjQoYc"}
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"kDCOIpSDBsg27ap"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"a6qkfWzXtBcKA"}
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Occ"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"hlKaj6olmcVp"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" Online"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"s6ocv3ua2"}
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"urrence"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"bK6IboJu4"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" HTML"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"x2qzNmJwyZN"}
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"jz37ZuJ2Cs"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" Content"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"6tqBHzn7"}
- data: {"id":"chatcmpl-CzjpXY0NnY3HE3lqVJsX1E0QZnClg","object":"chat.completion.chunk","created":1768830215,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[],"usage":{"prompt_tokens":153,"completion_tokens":9,"total_tokens":162,"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":"oGOHC8f5F7ED98"}
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"XtbymLtf5u"}
+
+ data: {"id":"chatcmpl-DIgcTmwGKAdkXj8gSUv69bn6kRuIv","object":"chat.completion.chunk","created":1773346105,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","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":"Nts4cEvzbv33W"}
data: [DONE]
@@ -55,22 +57,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 485.428208ms
+ duration: 1.14852275s
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50777
+ content_length: 50978
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,29 +24,29 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"AN8kcBopW7413w"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"cfoaVgskwEMm1A"}
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Finding"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"s9ELbRDAV"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"Finding"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"w4aoChnGJ"}
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" ."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"O9h76mLSPhdkPa"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" ."},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KhMLocySx1RQuA"}
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"uzz6sXLNppHb1H"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"7vdXUA1mdqHC4p"}
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"clQKAC0BtS"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vhERhjUtDP"}
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"iMDIUEkMuf4"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Using"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"IZdmx5EFhP"}
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Glob"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"N6NRkYKNrMR"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Glob"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"XRGDA3U054d"}
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"phy8zvhGTaAdn"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"L6KrfgAcH62KR"}
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Current"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"M4ITICZy"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Current"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"xuOafesK"}
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Directory"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1tNMzp"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Directory"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vZGKrG"}
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"bPVsxaKeA3"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"XFRL0WkCPj"}
- data: {"id":"chatcmpl-Czjpfn0duo3n07ZV2M1UoetlL84LL","object":"chat.completion.chunk","created":1768830223,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","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":"pCeZlHbKDEb9MQ"}
+ data: {"id":"chatcmpl-DIgclnQPkNxEljF48SWE8JwL4QqK4","object":"chat.completion.chunk","created":1773346123,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","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":"EDRwFgK6z3hAbw"}
data: [DONE]
@@ -55,22 +55,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 565.294416ms
+ duration: 519.51625ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50695
+ content_length: 50896
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,33 +24,29 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"TvvAs6UXCKtJpO"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"iSldZE4sXLjbM2"}
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Using"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"8mJifko0V1D"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":"Searching"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"NEct4Kc"}
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Gre"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"7Gbs6dE1n9QQ"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"5H5Dj14eWE7hTh"}
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"p"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"gsKuzPEBBzSDIgF"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":"package"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"9Ctm5HZkk"}
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Jd1M7N6pYuirn"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1NroVJmpjmd6qdP"}
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Search"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"WPPnJsnrD"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"bzjPiNS24nrkg"}
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"3FD4N3lDA8dIIq"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"UqTnKJM4YMTa5"}
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Package"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"sYRitOpVY"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"8bDYz2kt8R"}
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Psh8uHyftKu9OaD"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"54eqdXTY72H"}
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"uB9fseVHm8Qq9"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{"content":" grep"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"wMlECx61bJP"}
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"toOBGh8cblQKy"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"8ZsVcpfC6F"}
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Px8UAjDQ1Y"}
-
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"WEiddfZXsi"}
-
- data: {"id":"chatcmpl-CzjpkEXSYoFaA6TqesglhFgtKBe5E","object":"chat.completion.chunk","created":1768830228,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","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":"fyOv9URDB7WPO"}
+ data: {"id":"chatcmpl-DIgcuUeVNoSMAoX47EemdZSCJTZzY","object":"chat.completion.chunk","created":1773346132,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_22cd3c63e9","choices":[],"usage":{"prompt_tokens":138,"completion_tokens":9,"total_tokens":147,"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":"5c9sIP95JwyNsv"}
data: [DONE]
@@ -59,22 +55,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 529.456125ms
+ duration: 517.527708ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50693
+ content_length: 50894
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,23 +24,29 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-Czjpp21JrAlDsrAmzS6iFO2WfXAXE","object":"chat.completion.chunk","created":1768830233,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"n0nG28t5ZBF6ER"}
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"T0PHu5of1dr9ez"}
- data: {"id":"chatcmpl-Czjpp21JrAlDsrAmzS6iFO2WfXAXE","object":"chat.completion.chunk","created":1768830233,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Listing"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Qy6L2dEVd"}
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"Using"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"kaV6ojPBQK2"}
- data: {"id":"chatcmpl-Czjpp21JrAlDsrAmzS6iFO2WfXAXE","object":"chat.completion.chunk","created":1768830233,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"OGWA86hnC9"}
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2eg2ZonmjCC37E"}
- data: {"id":"chatcmpl-Czjpp21JrAlDsrAmzS6iFO2WfXAXE","object":"chat.completion.chunk","created":1768830233,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"d0UrCtvqjuH"}
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"ls"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"M6sxFQJakJm3dd"}
- data: {"id":"chatcmpl-Czjpp21JrAlDsrAmzS6iFO2WfXAXE","object":"chat.completion.chunk","created":1768830233,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" the"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KJia9s0n7rDV"}
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"OeQHO5aYO56WDMz"}
- data: {"id":"chatcmpl-Czjpp21JrAlDsrAmzS6iFO2WfXAXE","object":"chat.completion.chunk","created":1768830233,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" ls"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"3rzKom80l93ug"}
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"b89MsTugihzdX"}
- data: {"id":"chatcmpl-Czjpp21JrAlDsrAmzS6iFO2WfXAXE","object":"chat.completion.chunk","created":1768830233,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Command"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lyiPHQGe"}
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" List"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"7MEjHpNVcS0"}
- data: {"id":"chatcmpl-Czjpp21JrAlDsrAmzS6iFO2WfXAXE","object":"chat.completion.chunk","created":1768830233,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"JDQYzoRO2P"}
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Files"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"caJJIZ56Ne"}
- data: {"id":"chatcmpl-Czjpp21JrAlDsrAmzS6iFO2WfXAXE","object":"chat.completion.chunk","created":1768830233,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[],"usage":{"prompt_tokens":135,"completion_tokens":6,"total_tokens":141,"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":"EPoJDe4VnXLh1e"}
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"YiJDyQc9vBKA7"}
+
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Directory"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"1R47Xd"}
+
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"DT1EYEwtdR"}
+
+ data: {"id":"chatcmpl-DIgd3usqn3rK2LulE9sOc8Ys9KVyh","object":"chat.completion.chunk","created":1773346141,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","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":"vNwglUmg6ThoSE"}
data: [DONE]
@@ -49,22 +55,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 411.950625ms
+ duration: 630.309125ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50687
+ content_length: 50888
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,41 +24,27 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"w8sqWhzpcnHs6W"}
+ data: {"id":"chatcmpl-DIgdEo8vwexsZno9n14uW5RGLu5Ix","object":"chat.completion.chunk","created":1773346152,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"uHtKH1XdOv3YNP"}
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Edit"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ctpbhoqwzTZz"}
+ data: {"id":"chatcmpl-DIgdEo8vwexsZno9n14uW5RGLu5Ix","object":"chat.completion.chunk","created":1773346152,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"Edit"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"GfD4NZFunoaR"}
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"TGykGuKGPdZeN0"}
+ data: {"id":"chatcmpl-DIgdEo8vwexsZno9n14uW5RGLu5Ix","object":"chat.completion.chunk","created":1773346152,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Code"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"P53I5Ex575l"}
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"61NaUxH95hU"}
+ data: {"id":"chatcmpl-DIgdEo8vwexsZno9n14uW5RGLu5Ix","object":"chat.completion.chunk","created":1773346152,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"0KfR1AumNQnz8"}
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"eDLlGtoT0ySeGqD"}
+ data: {"id":"chatcmpl-DIgdEo8vwexsZno9n14uW5RGLu5Ix","object":"chat.completion.chunk","created":1773346152,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Update"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"BWVmTrFNp"}
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" World"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"8EscbuFp3X"}
+ data: {"id":"chatcmpl-DIgdEo8vwexsZno9n14uW5RGLu5Ix","object":"chat.completion.chunk","created":1773346152,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Message"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"0rXfqgvI"}
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"!'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"FDQCMsLNraJW6l"}
+ data: {"id":"chatcmpl-DIgdEo8vwexsZno9n14uW5RGLu5Ix","object":"chat.completion.chunk","created":1773346152,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"8d0rNQbcKmXp"}
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"PZtbSLH8ktyFq"}
+ data: {"id":"chatcmpl-DIgdEo8vwexsZno9n14uW5RGLu5Ix","object":"chat.completion.chunk","created":1773346152,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Add"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"v7IvPTxoRTRT"}
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"31dms5V3FvO5X1"}
+ data: {"id":"chatcmpl-DIgdEo8vwexsZno9n14uW5RGLu5Ix","object":"chat.completion.chunk","created":1773346152,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Comment"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Tyf06vBC"}
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"oLxfEtBAmAi"}
+ data: {"id":"chatcmpl-DIgdEo8vwexsZno9n14uW5RGLu5Ix","object":"chat.completion.chunk","created":1773346152,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"SvGC99IZy2"}
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"YCAS81ZDASz7hmB"}
-
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Crush"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ZBh8BjoPYS"}
-
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"!'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"LCOdBS6YPqpvye"}
-
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"woB21MizPF1qF"}
-
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" main"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"3vlYGdke04Q"}
-
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":".go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"n2Zprwwja22n4"}
-
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"syai28mVpc"}
-
- data: {"id":"chatcmpl-CzjptZarMAkGsZTFKUM0EMMzEmTkf","object":"chat.completion.chunk","created":1768830237,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[],"usage":{"prompt_tokens":157,"completion_tokens":15,"total_tokens":172,"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":"52ea3nLBvH3wB"}
+ data: {"id":"chatcmpl-DIgdEo8vwexsZno9n14uW5RGLu5Ix","object":"chat.completion.chunk","created":1773346152,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[],"usage":{"prompt_tokens":157,"completion_tokens":8,"total_tokens":165,"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":"FIcmzhdGIYNUGK"}
data: [DONE]
@@ -67,22 +53,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 469.62075ms
+ duration: 620.533292ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50773
+ content_length: 50974
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,27 +24,25 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-CzjqSIDKk0IIoCdj8CTB9GrShYjL0","object":"chat.completion.chunk","created":1768830272,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Thp3GWPBtcfpFl"}
+ data: {"id":"chatcmpl-DIgfXYcSQXhQvAsfquHHsAqUpAkO2","object":"chat.completion.chunk","created":1773346295,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vve0SiQeoAve0J"}
- data: {"id":"chatcmpl-CzjqSIDKk0IIoCdj8CTB9GrShYjL0","object":"chat.completion.chunk","created":1768830272,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Parallel"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"WE3oG6Dc"}
+ data: {"id":"chatcmpl-DIgfXYcSQXhQvAsfquHHsAqUpAkO2","object":"chat.completion.chunk","created":1773346295,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"Parallel"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"LhhJhYlW"}
- data: {"id":"chatcmpl-CzjqSIDKk0IIoCdj8CTB9GrShYjL0","object":"chat.completion.chunk","created":1768830272,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Execution"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"BhxBUy"}
+ data: {"id":"chatcmpl-DIgfXYcSQXhQvAsfquHHsAqUpAkO2","object":"chat.completion.chunk","created":1773346295,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Execution"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"rPvfeJ"}
- data: {"id":"chatcmpl-CzjqSIDKk0IIoCdj8CTB9GrShYjL0","object":"chat.completion.chunk","created":1768830272,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"CerEoaZkPYIr3"}
+ data: {"id":"chatcmpl-DIgfXYcSQXhQvAsfquHHsAqUpAkO2","object":"chat.completion.chunk","created":1773346295,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" of"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"3LZmYKhy5uD0N"}
- data: {"id":"chatcmpl-CzjqSIDKk0IIoCdj8CTB9GrShYjL0","object":"chat.completion.chunk","created":1768830272,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"njaky8RPXtu"}
+ data: {"id":"chatcmpl-DIgfXYcSQXhQvAsfquHHsAqUpAkO2","object":"chat.completion.chunk","created":1773346295,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Glob"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"fa2BY7pEqlg"}
- data: {"id":"chatcmpl-CzjqSIDKk0IIoCdj8CTB9GrShYjL0","object":"chat.completion.chunk","created":1768830272,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Search"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ShEOqgaKt"}
+ data: {"id":"chatcmpl-DIgfXYcSQXhQvAsfquHHsAqUpAkO2","object":"chat.completion.chunk","created":1773346295,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"4hWSdHnJE1dL"}
- data: {"id":"chatcmpl-CzjqSIDKk0IIoCdj8CTB9GrShYjL0","object":"chat.completion.chunk","created":1768830272,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" and"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"yRmX9ZfGFg03"}
+ data: {"id":"chatcmpl-DIgfXYcSQXhQvAsfquHHsAqUpAkO2","object":"chat.completion.chunk","created":1773346295,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" LS"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lD3W51GQd7Fd5"}
- data: {"id":"chatcmpl-CzjqSIDKk0IIoCdj8CTB9GrShYjL0","object":"chat.completion.chunk","created":1768830272,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Directory"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"R5Yuho"}
+ data: {"id":"chatcmpl-DIgfXYcSQXhQvAsfquHHsAqUpAkO2","object":"chat.completion.chunk","created":1773346295,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Commands"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"9dhzkf7"}
- data: {"id":"chatcmpl-CzjqSIDKk0IIoCdj8CTB9GrShYjL0","object":"chat.completion.chunk","created":1768830272,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Listing"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"XV9SCefj"}
+ data: {"id":"chatcmpl-DIgfXYcSQXhQvAsfquHHsAqUpAkO2","object":"chat.completion.chunk","created":1773346295,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"yuDuN4bAk4"}
- data: {"id":"chatcmpl-CzjqSIDKk0IIoCdj8CTB9GrShYjL0","object":"chat.completion.chunk","created":1768830272,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"ChtzpEnrlI"}
-
- data: {"id":"chatcmpl-CzjqSIDKk0IIoCdj8CTB9GrShYjL0","object":"chat.completion.chunk","created":1768830272,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[],"usage":{"prompt_tokens":154,"completion_tokens":8,"total_tokens":162,"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":"Cnz16IJuNPSTX6"}
+ data: {"id":"chatcmpl-DIgfXYcSQXhQvAsfquHHsAqUpAkO2","object":"chat.completion.chunk","created":1773346295,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","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":"5jJIZmrchS4X0N"}
data: [DONE]
@@ -53,22 +51,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 743.282416ms
+ duration: 541.726917ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50784
+ content_length: 50985
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,19 +24,17 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-CzjouBic6rLxQKHupRoN2PEBP7e5B","object":"chat.completion.chunk","created":1768830176,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"FDQDoWDckcMAqF"}
+ data: {"id":"chatcmpl-DIgbNR7z9AwDFGGArsrnbo5IHDoFO","object":"chat.completion.chunk","created":1773346037,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7cdbc83d36","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"h3aEUDsuEOcdM2"}
- data: {"id":"chatcmpl-CzjouBic6rLxQKHupRoN2PEBP7e5B","object":"chat.completion.chunk","created":1768830176,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":"Reading"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"SU89d8sKD"}
+ data: {"id":"chatcmpl-DIgbNR7z9AwDFGGArsrnbo5IHDoFO","object":"chat.completion.chunk","created":1773346037,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7cdbc83d36","choices":[{"index":0,"delta":{"content":"Understanding"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lI8"}
- data: {"id":"chatcmpl-CzjouBic6rLxQKHupRoN2PEBP7e5B","object":"chat.completion.chunk","created":1768830176,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"W3MgsDujc3Eaul"}
+ data: {"id":"chatcmpl-DIgbNR7z9AwDFGGArsrnbo5IHDoFO","object":"chat.completion.chunk","created":1773346037,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7cdbc83d36","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"BlCB1J7w94tsJ"}
- data: {"id":"chatcmpl-CzjouBic6rLxQKHupRoN2PEBP7e5B","object":"chat.completion.chunk","created":1768830176,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"fgaFCwoo9EflX"}
+ data: {"id":"chatcmpl-DIgbNR7z9AwDFGGArsrnbo5IHDoFO","object":"chat.completion.chunk","created":1773346037,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7cdbc83d36","choices":[{"index":0,"delta":{"content":" Modules"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"SSUmRbVb"}
- data: {"id":"chatcmpl-CzjouBic6rLxQKHupRoN2PEBP7e5B","object":"chat.completion.chunk","created":1768830176,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{"content":" Module"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"OembTm90U"}
+ data: {"id":"chatcmpl-DIgbNR7z9AwDFGGArsrnbo5IHDoFO","object":"chat.completion.chunk","created":1773346037,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7cdbc83d36","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"Lh8q0ncyK0"}
- data: {"id":"chatcmpl-CzjouBic6rLxQKHupRoN2PEBP7e5B","object":"chat.completion.chunk","created":1768830176,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"fz77ZNNj1l"}
-
- data: {"id":"chatcmpl-CzjouBic6rLxQKHupRoN2PEBP7e5B","object":"chat.completion.chunk","created":1768830176,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_deacdd5f6f","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":"cXX7n4KtoXfrel"}
+ data: {"id":"chatcmpl-DIgbNR7z9AwDFGGArsrnbo5IHDoFO","object":"chat.completion.chunk","created":1773346037,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_7cdbc83d36","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":"eGJiZzX7fhB3DQ"}
data: [DONE]
@@ -45,22 +43,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 2.249190917s
+ duration: 706.98175ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50657
+ content_length: 50858
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,15 +24,15 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-Czjoqy3qhdl7QlaVUezlnqVq4Su8C","object":"chat.completion.chunk","created":1768830172,"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":"TDnVpvMW88Xmde"}
+ data: {"id":"chatcmpl-DIgbK6R5yUtBIGQDJgY0TPRYQOi6y","object":"chat.completion.chunk","created":1773346034,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_2a3fb89a78","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"abFmE7omz4JrEU"}
- data: {"id":"chatcmpl-Czjoqy3qhdl7QlaVUezlnqVq4Su8C","object":"chat.completion.chunk","created":1768830172,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_cbf1785567","choices":[{"index":0,"delta":{"content":"Friendly"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"xQaCvs2o"}
+ data: {"id":"chatcmpl-DIgbK6R5yUtBIGQDJgY0TPRYQOi6y","object":"chat.completion.chunk","created":1773346034,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_2a3fb89a78","choices":[{"index":0,"delta":{"content":"Greeting"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"N38b9dFW"}
- data: {"id":"chatcmpl-Czjoqy3qhdl7QlaVUezlnqVq4Su8C","object":"chat.completion.chunk","created":1768830172,"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":"k1WWCN6"}
+ data: {"id":"chatcmpl-DIgbK6R5yUtBIGQDJgY0TPRYQOi6y","object":"chat.completion.chunk","created":1773346034,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_2a3fb89a78","choices":[{"index":0,"delta":{"content":" Message"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"WJYwnM0H"}
- data: {"id":"chatcmpl-Czjoqy3qhdl7QlaVUezlnqVq4Su8C","object":"chat.completion.chunk","created":1768830172,"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":"mnZ0FAHYdF"}
+ data: {"id":"chatcmpl-DIgbK6R5yUtBIGQDJgY0TPRYQOi6y","object":"chat.completion.chunk","created":1773346034,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_2a3fb89a78","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"HwCdXBSzEC"}
- data: {"id":"chatcmpl-Czjoqy3qhdl7QlaVUezlnqVq4Su8C","object":"chat.completion.chunk","created":1768830172,"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":"DmwwbNsKumqAUt"}
+ data: {"id":"chatcmpl-DIgbK6R5yUtBIGQDJgY0TPRYQOi6y","object":"chat.completion.chunk","created":1773346034,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_2a3fb89a78","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":"VcX20EEMDDv4zM"}
data: [DONE]
@@ -41,22 +41,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 1.949141s
+ duration: 919.449334ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50647
+ content_length: 50848
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,35 +24,35 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"R95a3iTSrhETUb"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"zkyoMu4xoVx6cx"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Search"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"5SjkmG65LN"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"Searching"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"nDxsCPu"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"PuilvpfYYLgM5x"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" '"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"BbicZpCukzDvXw"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"func"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KvyZFscXzPeq"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"func"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"YRjaWaC6KPev"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" main"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"8eYsEmg7Ldr"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" main"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"P3Wom0W0U5n"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"8kZnxLjyyHcmhAJ"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"'"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"GwooI4y6nUPZXFk"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"iNM0tkifwexEs"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"EE6vq4kWKW1ls"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"CVXDb6ymhmDeW"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"4jDIiRb3w0zWk"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Re"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"CQ8kNt0Llm7Uy"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Re"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"zZ5LRyqjNOOf4"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"positories"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"KbLwwJ"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"positories"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"WIPhQT"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Using"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"L75wF53MuD"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"I51EX5NB2TH"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Source"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"MKSonEgLp"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Source"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"rD2GUHQ4C"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"graph"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"w04Ibu9PdcW"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"graph"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"Dg3TMLF9Ros"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"ukXsXPKqhG"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"AZaU2kawGZ"}
- data: {"id":"chatcmpl-Czk1rASc3CYebBgESFcb9JMKFkFn1","object":"chat.completion.chunk","created":1768830979,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","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":"hPBvtCMxVf9cE"}
+ data: {"id":"chatcmpl-DIgea9hwz4OpfP3VrP7BqtsKtzHy8","object":"chat.completion.chunk","created":1773346236,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","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":"DRfU65h3iz1GX"}
data: [DONE]
@@ -61,22 +61,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 552.732542ms
+ duration: 578.111292ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50707
+ content_length: 50908
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,25 +24,31 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-Czjp135HaPU3V24nyjS3BuWy7rZ18","object":"chat.completion.chunk","created":1768830183,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lyN8ETBdlgol0g"}
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"omtKv086CvMki9"}
- data: {"id":"chatcmpl-Czjp135HaPU3V24nyjS3BuWy7rZ18","object":"chat.completion.chunk","created":1768830183,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Update"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"slpbVLVP2H"}
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{"content":"Update"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"ZEXCH8fpHw"}
- data: {"id":"chatcmpl-Czjp135HaPU3V24nyjS3BuWy7rZ18","object":"chat.completion.chunk","created":1768830183,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Print"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"csnlJXHkWq"}
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{"content":" main"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"aJATAdu3YXf"}
- data: {"id":"chatcmpl-Czjp135HaPU3V24nyjS3BuWy7rZ18","object":"chat.completion.chunk","created":1768830183,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Message"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vgiX0tTd"}
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{"content":".go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"65HJPfi0HP3YE"}
- data: {"id":"chatcmpl-Czjp135HaPU3V24nyjS3BuWy7rZ18","object":"chat.completion.chunk","created":1768830183,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"yL0ebVNjRLVaR"}
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"vnkod9UqKg6kp"}
- data: {"id":"chatcmpl-Czjp135HaPU3V24nyjS3BuWy7rZ18","object":"chat.completion.chunk","created":1768830183,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" main"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"tnpOnQL3w8K"}
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{"content":" Print"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"yxbUPhFzxi"}
- data: {"id":"chatcmpl-Czjp135HaPU3V24nyjS3BuWy7rZ18","object":"chat.completion.chunk","created":1768830183,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":".go"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"UYuaUVZqGFdSu"}
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"q1dEe2zLZzA1S"}
- data: {"id":"chatcmpl-Czjp135HaPU3V24nyjS3BuWy7rZ18","object":"chat.completion.chunk","created":1768830183,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" File"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"2sEh73qqzkE"}
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"p2SCTWanJnk"}
- data: {"id":"chatcmpl-Czjp135HaPU3V24nyjS3BuWy7rZ18","object":"chat.completion.chunk","created":1768830183,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"gf6rrszRnY"}
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{"content":" from"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"qy9fD0ToahO"}
- data: {"id":"chatcmpl-Czjp135HaPU3V24nyjS3BuWy7rZ18","object":"chat.completion.chunk","created":1768830183,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[],"usage":{"prompt_tokens":139,"completion_tokens":7,"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":"42QprxWa0oBMFL"}
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{"content":" Crush"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"13k3IIX4dD"}
+
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{"content":"\""},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"22eb1C3HamxEaF"}
+
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"NpdtMX0n82"}
+
+ data: {"id":"chatcmpl-DIgbUxWHI144OwWvmvV6m9MU7HQM2","object":"chat.completion.chunk","created":1773346044,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_44968754fe","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":"AiiC5wZHOeur4"}
data: [DONE]
@@ -51,22 +57,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 1.424788125s
+ duration: 523.732ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50713
+ content_length: 50914
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.openai.com/v1/chat/completions
method: POST
response:
@@ -24,23 +24,23 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"chatcmpl-CzjqJgtYTNQ1LzNr2Vy8XQCD5uR5p","object":"chat.completion.chunk","created":1768830263,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lbWZeXAjCgr2xM"}
+ data: {"id":"chatcmpl-DIgfCpDj2oYbQgEPcRwZLZ7AyDMyL","object":"chat.completion.chunk","created":1773346274,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"lElIIxTR6SfSJe"}
- data: {"id":"chatcmpl-CzjqJgtYTNQ1LzNr2Vy8XQCD5uR5p","object":"chat.completion.chunk","created":1768830263,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":"Create"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"g2vTNiK29d"}
+ data: {"id":"chatcmpl-DIgfCpDj2oYbQgEPcRwZLZ7AyDMyL","object":"chat.completion.chunk","created":1773346274,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":"Creating"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"aJcyTcJt"}
- data: {"id":"chatcmpl-CzjqJgtYTNQ1LzNr2Vy8XQCD5uR5p","object":"chat.completion.chunk","created":1768830263,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" config"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"NSDOMK424"}
+ data: {"id":"chatcmpl-DIgfCpDj2oYbQgEPcRwZLZ7AyDMyL","object":"chat.completion.chunk","created":1773346274,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" config"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"5h6rbfdKc"}
- data: {"id":"chatcmpl-CzjqJgtYTNQ1LzNr2Vy8XQCD5uR5p","object":"chat.completion.chunk","created":1768830263,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":".json"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"dfB4le0vurg"}
+ data: {"id":"chatcmpl-DIgfCpDj2oYbQgEPcRwZLZ7AyDMyL","object":"chat.completion.chunk","created":1773346274,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":".json"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"U0XSGt1c8eN"}
- data: {"id":"chatcmpl-CzjqJgtYTNQ1LzNr2Vy8XQCD5uR5p","object":"chat.completion.chunk","created":1768830263,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"FxUaaSwfBEd"}
+ data: {"id":"chatcmpl-DIgfCpDj2oYbQgEPcRwZLZ7AyDMyL","object":"chat.completion.chunk","created":1773346274,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" with"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"LyOc5aUnXjO"}
- data: {"id":"chatcmpl-CzjqJgtYTNQ1LzNr2Vy8XQCD5uR5p","object":"chat.completion.chunk","created":1768830263,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" JSON"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"pg0cGCkRGcE"}
+ data: {"id":"chatcmpl-DIgfCpDj2oYbQgEPcRwZLZ7AyDMyL","object":"chat.completion.chunk","created":1773346274,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Initial"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"xERgMAB3"}
- data: {"id":"chatcmpl-CzjqJgtYTNQ1LzNr2Vy8XQCD5uR5p","object":"chat.completion.chunk","created":1768830263,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{"content":" Content"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"FYNYBrua"}
+ data: {"id":"chatcmpl-DIgfCpDj2oYbQgEPcRwZLZ7AyDMyL","object":"chat.completion.chunk","created":1773346274,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{"content":" Content"},"logprobs":null,"finish_reason":null}],"usage":null,"obfuscation":"x7cKTSzp"}
- data: {"id":"chatcmpl-CzjqJgtYTNQ1LzNr2Vy8XQCD5uR5p","object":"chat.completion.chunk","created":1768830263,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"VsEMoKTn2b"}
+ data: {"id":"chatcmpl-DIgfCpDj2oYbQgEPcRwZLZ7AyDMyL","object":"chat.completion.chunk","created":1773346274,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null,"obfuscation":"xt5qjxN5v0"}
- data: {"id":"chatcmpl-CzjqJgtYTNQ1LzNr2Vy8XQCD5uR5p","object":"chat.completion.chunk","created":1768830263,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_a0e9480a2f","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":"SeLpiA4w7bzNvd"}
+ data: {"id":"chatcmpl-DIgfCpDj2oYbQgEPcRwZLZ7AyDMyL","object":"chat.completion.chunk","created":1773346274,"model":"gpt-4o-2024-08-06","service_tier":"default","system_fingerprint":"fp_18c1fee47e","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":"R31AJ3GYVMTuit"}
data: [DONE]
@@ -49,22 +49,22 @@ interactions:
- text/event-stream; charset=utf-8
status: 200 OK
code: 200
- duration: 481.66725ms
+ duration: 518.668542ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50750
+ content_length: 50951
host: ""
@@ -24,21 +24,19 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"gen-1768830633-Osz1qPrYiTrZY6O7qshP","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830633,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346329-EWUWqwmb1NUxgPTCQmMN","object":"chat.completion.chunk","created":1773346329,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"Create","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830633-Osz1qPrYiTrZY6O7qshP","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830633,"choices":[{"index":0,"delta":{"role":"assistant","content":"Create"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346329-EWUWqwmb1NUxgPTCQmMN","object":"chat.completion.chunk","created":1773346329,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" test","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830633-Osz1qPrYiTrZY6O7qshP","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830633,"choices":[{"index":0,"delta":{"role":"assistant","content":" test"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346329-EWUWqwmb1NUxgPTCQmMN","object":"chat.completion.chunk","created":1773346329,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":".txt with","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830633-Osz1qPrYiTrZY6O7qshP","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830633,"choices":[{"index":0,"delta":{"role":"assistant","content":".txt with"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346329-EWUWqwmb1NUxgPTCQmMN","object":"chat.completion.chunk","created":1773346329,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" hello bash using","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830633-Osz1qPrYiTrZY6O7qshP","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830633,"choices":[{"index":0,"delta":{"role":"assistant","content":" hello bash using"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346329-EWUWqwmb1NUxgPTCQmMN","object":"chat.completion.chunk","created":1773346329,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" bash","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830633-Osz1qPrYiTrZY6O7qshP","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830633,"choices":[{"index":0,"delta":{"role":"assistant","content":" bash"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346329-EWUWqwmb1NUxgPTCQmMN","object":"chat.completion.chunk","created":1773346329,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]}
- data: {"id":"gen-1768830633-Osz1qPrYiTrZY6O7qshP","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830633,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
-
- data: {"id":"gen-1768830633-Osz1qPrYiTrZY6O7qshP","provider":"Google","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830633,"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.00003285,"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.00002205,"upstream_inference_completions_cost":0.0000108},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+ data: {"id":"gen-1773346329-EWUWqwmb1NUxgPTCQmMN","object":"chat.completion.chunk","created":1773346329,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[],"usage":{"prompt_tokens":151,"completion_tokens":8,"total_tokens":159,"cost":0.0000209625,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.00003225,"upstream_inference_prompt_cost":0.00002265,"upstream_inference_completions_cost":0.0000096},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}}
data: [DONE]
@@ -47,15 +45,15 @@ interactions:
- text/event-stream
status: 200 OK
code: 200
- duration: 627.379083ms
+ duration: 711.943708ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50883
+ content_length: 51084
host: ""
@@ -24,19 +24,23 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"gen-1768830324-DpEBUpsxBtavj73Ij4Xl","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830324,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346336-TVqiuHC80xFmriSa1GnB","object":"chat.completion.chunk","created":1773346336,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Google","choices":[{"index":0,"delta":{"content":"Download","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830324-DpEBUpsxBtavj73Ij4Xl","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830324,"choices":[{"index":0,"delta":{"role":"assistant","content":"Download"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346336-TVqiuHC80xFmriSa1GnB","object":"chat.completion.chunk","created":1773346336,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Google","choices":[{"index":0,"delta":{"content":" example","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830324-DpEBUpsxBtavj73Ij4Xl","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830324,"choices":[{"index":0,"delta":{"role":"assistant","content":" and save example.txt"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346336-TVqiuHC80xFmriSa1GnB","object":"chat.completion.chunk","created":1773346336,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Google","choices":[{"index":0,"delta":{"content":".txt from example","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830324-DpEBUpsxBtavj73Ij4Xl","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830324,"choices":[{"index":0,"delta":{"role":"assistant","content":" from provided"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346336-TVqiuHC80xFmriSa1GnB","object":"chat.completion.chunk","created":1773346336,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Google","choices":[{"index":0,"delta":{"content":"-files","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830324-DpEBUpsxBtavj73Ij4Xl","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830324,"choices":[{"index":0,"delta":{"role":"assistant","content":" link"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346336-TVqiuHC80xFmriSa1GnB","object":"chat.completion.chunk","created":1773346336,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Google","choices":[{"index":0,"delta":{"content":".online","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830324-DpEBUpsxBtavj73Ij4Xl","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830324,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+ data: {"id":"gen-1773346336-TVqiuHC80xFmriSa1GnB","object":"chat.completion.chunk","created":1773346336,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Google","choices":[{"index":0,"delta":{"content":"-","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830324-DpEBUpsxBtavj73Ij4Xl","provider":"GMICloud","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830324,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":150,"completion_tokens":9,"total_tokens":159,"cost":0.000036,"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.0000135},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+ data: {"id":"gen-1773346336-TVqiuHC80xFmriSa1GnB","object":"chat.completion.chunk","created":1773346336,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Google","choices":[{"index":0,"delta":{"content":"convert.com","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
+
+ data: {"id":"gen-1773346336-TVqiuHC80xFmriSa1GnB","object":"chat.completion.chunk","created":1773346336,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Google","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]}
+
+ data: {"id":"gen-1773346336-TVqiuHC80xFmriSa1GnB","object":"chat.completion.chunk","created":1773346336,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Google","choices":[],"usage":{"prompt_tokens":150,"completion_tokens":11,"total_tokens":161,"cost":0.0000357,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.0000357,"upstream_inference_prompt_cost":0.0000225,"upstream_inference_completions_cost":0.0000132},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}}
data: [DONE]
@@ -45,15 +49,15 @@ interactions:
- text/event-stream
status: 200 OK
code: 200
- duration: 1.180815667s
+ duration: 729.655584ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50908
+ content_length: 51109
host: ""
@@ -24,19 +24,17 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"gen-1768830332-uymAi6cUGQh14tZVvvWc","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830332,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346345-gWD8OXAwYWldd57opVOW","object":"chat.completion.chunk","created":1773346345,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"Check","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830332-uymAi6cUGQh14tZVvvWc","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830332,"choices":[{"index":0,"delta":{"role":"assistant","content":"Check"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346345-gWD8OXAwYWldd57opVOW","object":"chat.completion.chunk","created":1773346345,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" if example","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830332-uymAi6cUGQh14tZVvvWc","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830332,"choices":[{"index":0,"delta":{"role":"assistant","content":" if example"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346345-gWD8OXAwYWldd57opVOW","object":"chat.completion.chunk","created":1773346345,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":".html contains John","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830332-uymAi6cUGQh14tZVvvWc","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830332,"choices":[{"index":0,"delta":{"role":"assistant","content":".html contains John"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346345-gWD8OXAwYWldd57opVOW","object":"chat.completion.chunk","created":1773346345,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" Doe","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830332-uymAi6cUGQh14tZVvvWc","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830332,"choices":[{"index":0,"delta":{"role":"assistant","content":" Doe"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346345-gWD8OXAwYWldd57opVOW","object":"chat.completion.chunk","created":1773346345,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]}
- data: {"id":"gen-1768830332-uymAi6cUGQh14tZVvvWc","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830332,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}],"system_fingerprint":null}
-
- data: {"id":"gen-1768830332-uymAi6cUGQh14tZVvvWc","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830332,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":159,"completion_tokens":7,"total_tokens":166,"cost":0.00003225,"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.00002385,"upstream_inference_completions_cost":0.0000084},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+ data: {"id":"gen-1773346345-gWD8OXAwYWldd57opVOW","object":"chat.completion.chunk","created":1773346345,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[],"usage":{"prompt_tokens":159,"completion_tokens":7,"total_tokens":166,"cost":0.0000209625,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.00003225,"upstream_inference_prompt_cost":0.00002385,"upstream_inference_completions_cost":0.0000084},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}}
data: [DONE]
@@ -45,15 +43,15 @@ interactions:
- text/event-stream
status: 200 OK
code: 200
- duration: 1.455522416s
+ duration: 706.07425ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50926
+ content_length: 51127
host: ""
@@ -24,21 +24,29 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"gen-1768830601-JFP8REyTpY04VjYTZEmH","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830601,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":"Find","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830601-JFP8REyTpY04VjYTZEmH","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830601,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" all","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830601-JFP8REyTpY04VjYTZEmH","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830601,"choices":[{"index":0,"delta":{"role":"assistant","content":"Find"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" .","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830601-JFP8REyTpY04VjYTZEmH","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830601,"choices":[{"index":0,"delta":{"role":"assistant","content":" all .go files"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":"go","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830601-JFP8REyTpY04VjYTZEmH","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830601,"choices":[{"index":0,"delta":{"role":"assistant","content":" in current directory using"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" files","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830601-JFP8REyTpY04VjYTZEmH","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830601,"choices":[{"index":0,"delta":{"role":"assistant","content":" glob"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" in","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830601-JFP8REyTpY04VjYTZEmH","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830601,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" current","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830601-JFP8REyTpY04VjYTZEmH","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830601,"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.00002461,"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.00001251,"upstream_inference_completions_cost":0.0000121},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" directory","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
+
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" using","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
+
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" glob","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
+
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]}
+
+ data: {"id":"gen-1773346355-xVkkhvwNqJJ5XlXkXyr1","object":"chat.completion.chunk","created":1773346355,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[],"usage":{"prompt_tokens":139,"completion_tokens":11,"total_tokens":150,"cost":0.000026,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.000026,"upstream_inference_prompt_cost":0.0000139,"upstream_inference_completions_cost":0.0000121},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}}
data: [DONE]
@@ -47,15 +55,15 @@ interactions:
- text/event-stream
status: 200 OK
code: 200
- duration: 730.054792ms
+ duration: 828.859041ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50844
+ content_length: 51045
host: ""
@@ -6,9 +6,9 @@ interactions:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50842
+ content_length: 762
host: ""
@@ -6,9 +6,9 @@ interactions:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50836
+ content_length: 758
host: ""
@@ -24,23 +24,19 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"gen-1768830361-KnDZJcRrhDeKwzt90zgG","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830361,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346378-hfheSaAb9O98TBQ0ELoK","object":"chat.completion.chunk","created":1773346378,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Novita","choices":[{"index":0,"delta":{"content":"Use","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830361-KnDZJcRrhDeKwzt90zgG","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830361,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346378-hfheSaAb9O98TBQ0ELoK","object":"chat.completion.chunk","created":1773346378,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Novita","choices":[{"index":0,"delta":{"content":" multiedit to","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830361-KnDZJcRrhDeKwzt90zgG","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830361,"choices":[{"index":0,"delta":{"role":"assistant","content":"Use"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346378-hfheSaAb9O98TBQ0ELoK","object":"chat.completion.chunk","created":1773346378,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Novita","choices":[{"index":0,"delta":{"content":" update greeting","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830361-KnDZJcRrhDeKwzt90zgG","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830361,"choices":[{"index":0,"delta":{"role":"assistant","content":" multiedit to"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346378-hfheSaAb9O98TBQ0ELoK","object":"chat.completion.chunk","created":1773346378,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Novita","choices":[{"index":0,"delta":{"content":" and add comment in","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830361-KnDZJcRrhDeKwzt90zgG","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830361,"choices":[{"index":0,"delta":{"role":"assistant","content":" update greeting"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346378-hfheSaAb9O98TBQ0ELoK","object":"chat.completion.chunk","created":1773346378,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Novita","choices":[{"index":0,"delta":{"content":" main.go","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830361-KnDZJcRrhDeKwzt90zgG","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830361,"choices":[{"index":0,"delta":{"role":"assistant","content":" and add comment in"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346378-hfheSaAb9O98TBQ0ELoK","object":"chat.completion.chunk","created":1773346378,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Novita","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]}
- data: {"id":"gen-1768830361-KnDZJcRrhDeKwzt90zgG","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830361,"choices":[{"index":0,"delta":{"role":"assistant","content":" main.go"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
-
- data: {"id":"gen-1768830361-KnDZJcRrhDeKwzt90zgG","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830361,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
-
- data: {"id":"gen-1768830361-KnDZJcRrhDeKwzt90zgG","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830361,"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.0000298,"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.0000144,"upstream_inference_completions_cost":0.0000154},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+ data: {"id":"gen-1773346378-hfheSaAb9O98TBQ0ELoK","object":"chat.completion.chunk","created":1773346378,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Novita","choices":[],"usage":{"prompt_tokens":164,"completion_tokens":13,"total_tokens":177,"cost":0.0000441,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.0000441,"upstream_inference_prompt_cost":0.0000246,"upstream_inference_completions_cost":0.0000195},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}}
data: [DONE]
@@ -49,15 +45,15 @@ interactions:
- text/event-stream
status: 200 OK
code: 200
- duration: 239.99125ms
+ duration: 890.058625ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50922
+ content_length: 51123
host: ""
@@ -24,21 +24,27 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"gen-1768830385-x9fwT8MATFhFKpxBh31a","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830385,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346444-K8iqCl6Tq9o7IKocQEz7","object":"chat.completion.chunk","created":1773346444,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":"Find","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830385-x9fwT8MATFhFKpxBh31a","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830385,"choices":[{"index":0,"delta":{"role":"assistant","content":"Find"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346444-K8iqCl6Tq9o7IKocQEz7","object":"chat.completion.chunk","created":1773346444,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" .","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830385-x9fwT8MATFhFKpxBh31a","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830385,"choices":[{"index":0,"delta":{"role":"assistant","content":" .go files and"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346444-K8iqCl6Tq9o7IKocQEz7","object":"chat.completion.chunk","created":1773346444,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":"go","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830385-x9fwT8MATFhFKpxBh31a","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830385,"choices":[{"index":0,"delta":{"role":"assistant","content":" list directory"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346444-K8iqCl6Tq9o7IKocQEz7","object":"chat.completion.chunk","created":1773346444,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" files","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830385-x9fwT8MATFhFKpxBh31a","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830385,"choices":[{"index":0,"delta":{"role":"assistant","content":" in"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346444-K8iqCl6Tq9o7IKocQEz7","object":"chat.completion.chunk","created":1773346444,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" and","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830385-x9fwT8MATFhFKpxBh31a","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830385,"choices":[{"index":0,"delta":{"role":"assistant","content":" parallel"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346444-K8iqCl6Tq9o7IKocQEz7","object":"chat.completion.chunk","created":1773346444,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" list","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830385-x9fwT8MATFhFKpxBh31a","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830385,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346444-K8iqCl6Tq9o7IKocQEz7","object":"chat.completion.chunk","created":1773346444,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" directory","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830385-x9fwT8MATFhFKpxBh31a","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830385,"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.0000348,"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.0000108},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+ data: {"id":"gen-1773346444-K8iqCl6Tq9o7IKocQEz7","object":"chat.completion.chunk","created":1773346444,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" in","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
+
+ data: {"id":"gen-1773346444-K8iqCl6Tq9o7IKocQEz7","object":"chat.completion.chunk","created":1773346444,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" parallel","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
+
+ data: {"id":"gen-1773346444-K8iqCl6Tq9o7IKocQEz7","object":"chat.completion.chunk","created":1773346444,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]}
+
+ data: {"id":"gen-1773346444-K8iqCl6Tq9o7IKocQEz7","object":"chat.completion.chunk","created":1773346444,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[],"usage":{"prompt_tokens":156,"completion_tokens":10,"total_tokens":166,"cost":0.0000266,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.0000266,"upstream_inference_prompt_cost":0.0000156,"upstream_inference_completions_cost":0.000011},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}}
data: [DONE]
@@ -47,15 +53,15 @@ interactions:
- text/event-stream
status: 200 OK
code: 200
- duration: 832.842584ms
+ duration: 785.290041ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50933
+ content_length: 51134
host: ""
@@ -24,19 +24,17 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"gen-1768830283-jqcQ1UYtDnQXJSsQIqAv","provider":"Chutes","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830283,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346308-4TglX5eq0OVD4GrTmKz4","object":"chat.completion.chunk","created":1773346308,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":"Read","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830283-jqcQ1UYtDnQXJSsQIqAv","provider":"Chutes","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830283,"choices":[{"index":0,"delta":{"role":"assistant","content":"Read"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346308-4TglX5eq0OVD4GrTmKz4","object":"chat.completion.chunk","created":1773346308,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" the","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830283-jqcQ1UYtDnQXJSsQIqAv","provider":"Chutes","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830283,"choices":[{"index":0,"delta":{"role":"assistant","content":" the"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346308-4TglX5eq0OVD4GrTmKz4","object":"chat.completion.chunk","created":1773346308,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" go","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830283-jqcQ1UYtDnQXJSsQIqAv","provider":"Chutes","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830283,"choices":[{"index":0,"delta":{"role":"assistant","content":" go"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346308-4TglX5eq0OVD4GrTmKz4","object":"chat.completion.chunk","created":1773346308,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":" mod","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830283-jqcQ1UYtDnQXJSsQIqAv","provider":"Chutes","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830283,"choices":[{"index":0,"delta":{"role":"assistant","content":" mod"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346308-4TglX5eq0OVD4GrTmKz4","object":"chat.completion.chunk","created":1773346308,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]}
- data: {"id":"gen-1768830283-jqcQ1UYtDnQXJSsQIqAv","provider":"Chutes","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830283,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
-
- data: {"id":"gen-1768830283-jqcQ1UYtDnQXJSsQIqAv","provider":"Chutes","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830283,"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.0000171,"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.0000131,"upstream_inference_completions_cost":0.000004},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+ data: {"id":"gen-1773346308-4TglX5eq0OVD4GrTmKz4","object":"chat.completion.chunk","created":1773346308,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Parasail","choices":[],"usage":{"prompt_tokens":131,"completion_tokens":5,"total_tokens":136,"cost":0.0000186,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.0000186,"upstream_inference_prompt_cost":0.0000131,"upstream_inference_completions_cost":0.0000055},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}}
data: [DONE]
@@ -45,15 +43,15 @@ interactions:
- text/event-stream
status: 200 OK
code: 200
- duration: 1.759914166s
+ duration: 1.141124667s
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50806
+ content_length: 51007
host: ""
@@ -24,13 +24,11 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"gen-1768830280-4PCDv2pnxJ4GGNJaNBDx","provider":"Chutes","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830280,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346305-EPRD1xczhbGL5Lx61Bxi","object":"chat.completion.chunk","created":1773346305,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"Hello","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830280-4PCDv2pnxJ4GGNJaNBDx","provider":"Chutes","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830280,"choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346305-EPRD1xczhbGL5Lx61Bxi","object":"chat.completion.chunk","created":1773346305,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"DeepInfra","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]}
- data: {"id":"gen-1768830280-4PCDv2pnxJ4GGNJaNBDx","provider":"Chutes","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830280,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
-
- data: {"id":"gen-1768830280-4PCDv2pnxJ4GGNJaNBDx","provider":"Chutes","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830280,"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.0000144,"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.0000016},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+ data: {"id":"gen-1773346305-EPRD1xczhbGL5Lx61Bxi","object":"chat.completion.chunk","created":1773346305,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"DeepInfra","choices":[],"usage":{"prompt_tokens":128,"completion_tokens":2,"total_tokens":130,"cost":0.00001372,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.00001372,"upstream_inference_prompt_cost":0.00001152,"upstream_inference_completions_cost":0.0000022},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}}
data: [DONE]
@@ -39,15 +37,15 @@ interactions:
- text/event-stream
status: 200 OK
code: 200
- duration: 1.783372292s
+ duration: 1.422850834s
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50796
+ content_length: 50997
host: ""
@@ -6,9 +6,9 @@ interactions:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50856
+ content_length: 769
host: ""
@@ -24,21 +24,17 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"gen-1768830290-g9cszM7AWBXcykR0NTwS","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830290,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346314-pTxjL4NOw61KwDwlBC4C","object":"chat.completion.chunk","created":1773346314,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"Update","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830290-g9cszM7AWBXcykR0NTwS","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830290,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346314-pTxjL4NOw61KwDwlBC4C","object":"chat.completion.chunk","created":1773346314,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" main.go to print","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830290-g9cszM7AWBXcykR0NTwS","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830290,"choices":[{"index":0,"delta":{"role":"assistant","content":"Update"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346314-pTxjL4NOw61KwDwlBC4C","object":"chat.completion.chunk","created":1773346314,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" hello","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830290-g9cszM7AWBXcykR0NTwS","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830290,"choices":[{"index":0,"delta":{"role":"assistant","content":" main.go to print"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346314-pTxjL4NOw61KwDwlBC4C","object":"chat.completion.chunk","created":1773346314,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" from crush","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830290-g9cszM7AWBXcykR0NTwS","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830290,"choices":[{"index":0,"delta":{"role":"assistant","content":" hello"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
+ data: {"id":"gen-1773346314-pTxjL4NOw61KwDwlBC4C","object":"chat.completion.chunk","created":1773346314,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]}
- data: {"id":"gen-1768830290-g9cszM7AWBXcykR0NTwS","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830290,"choices":[{"index":0,"delta":{"role":"assistant","content":" from crush"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}]}
-
- data: {"id":"gen-1768830290-g9cszM7AWBXcykR0NTwS","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830290,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}]}
-
- data: {"id":"gen-1768830290-g9cszM7AWBXcykR0NTwS","provider":"DeepInfra","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830290,"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.00002259,"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.00001269,"upstream_inference_completions_cost":0.0000099},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+ data: {"id":"gen-1773346314-pTxjL4NOw61KwDwlBC4C","object":"chat.completion.chunk","created":1773346314,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[],"usage":{"prompt_tokens":145,"completion_tokens":8,"total_tokens":153,"cost":0.0000203775,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.00003135,"upstream_inference_prompt_cost":0.00002175,"upstream_inference_completions_cost":0.0000096},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}}
data: [DONE]
@@ -47,15 +43,15 @@ interactions:
- text/event-stream
status: 200 OK
code: 200
- duration: 982.306417ms
+ duration: 667.790583ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50862
+ content_length: 51063
host: ""
@@ -24,17 +24,17 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"gen-1768830376-47Lo1VDfcxY5VPrlmIsL","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830376,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346434-55tqPNTfjytUMCK8C2DN","object":"chat.completion.chunk","created":1773346434,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"Create","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830376-47Lo1VDfcxY5VPrlmIsL","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830376,"choices":[{"index":0,"delta":{"role":"assistant","content":"Create"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346434-55tqPNTfjytUMCK8C2DN","object":"chat.completion.chunk","created":1773346434,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" config.json with sample","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830376-47Lo1VDfcxY5VPrlmIsL","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830376,"choices":[{"index":0,"delta":{"role":"assistant","content":" config.json with test"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346434-55tqPNTfjytUMCK8C2DN","object":"chat.completion.chunk","created":1773346434,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" JSON","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830376-47Lo1VDfcxY5VPrlmIsL","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830376,"choices":[{"index":0,"delta":{"role":"assistant","content":" data"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346434-55tqPNTfjytUMCK8C2DN","object":"chat.completion.chunk","created":1773346434,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":" content","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}
- data: {"id":"gen-1768830376-47Lo1VDfcxY5VPrlmIsL","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830376,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}],"system_fingerprint":null}
+ data: {"id":"gen-1773346434-55tqPNTfjytUMCK8C2DN","object":"chat.completion.chunk","created":1773346434,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]}
- data: {"id":"gen-1768830376-47Lo1VDfcxY5VPrlmIsL","provider":"Alibaba","model":"qwen/qwen3-next-80b-a3b-instruct","object":"chat.completion.chunk","created":1768830376,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":159,"completion_tokens":6,"total_tokens":165,"cost":0.00003105,"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.00002385,"upstream_inference_completions_cost":0.0000072},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}
+ data: {"id":"gen-1773346434-55tqPNTfjytUMCK8C2DN","object":"chat.completion.chunk","created":1773346434,"model":"qwen/qwen3-next-80b-a3b-instruct-2509","provider":"Alibaba","choices":[],"usage":{"prompt_tokens":159,"completion_tokens":7,"total_tokens":166,"cost":0.0000209625,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.00003225,"upstream_inference_prompt_cost":0.00002385,"upstream_inference_completions_cost":0.0000084},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}}
data: [DONE]
@@ -43,15 +43,15 @@ interactions:
- text/event-stream
status: 200 OK
code: 200
- duration: 401.234708ms
+ duration: 1.003629375s
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50899
+ content_length: 51100
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,21 +24,21 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"20260119214720f5bafbb053254ba2","created":1768830440,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"2026031304143176ff481eb8d34725","created":1773346471,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"20260119214720f5bafbb053254ba2","created":1768830440,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"B"}}]}
+ data: {"id":"2026031304143176ff481eb8d34725","created":1773346471,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Creating"}}]}
- data: {"id":"20260119214720f5bafbb053254ba2","created":1768830440,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"ash"}}]}
+ data: {"id":"2026031304143176ff481eb8d34725","created":1773346471,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" test"}}]}
- data: {"id":"20260119214720f5bafbb053254ba2","created":1768830440,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" file"}}]}
+ data: {"id":"2026031304143176ff481eb8d34725","created":1773346471,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".txt"}}]}
- data: {"id":"20260119214720f5bafbb053254ba2","created":1768830440,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" creation"}}]}
+ data: {"id":"2026031304143176ff481eb8d34725","created":1773346471,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" with"}}]}
- data: {"id":"20260119214720f5bafbb053254ba2","created":1768830440,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" with"}}]}
+ data: {"id":"2026031304143176ff481eb8d34725","created":1773346471,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" bash"}}]}
- data: {"id":"20260119214720f5bafbb053254ba2","created":1768830440,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" content"}}]}
+ data: {"id":"2026031304143176ff481eb8d34725","created":1773346471,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" content"}}]}
- data: {"id":"20260119214720f5bafbb053254ba2","created":1768830440,"object":"chat.completion.chunk","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":"2026031304143176ff481eb8d34725","created":1773346471,"object":"chat.completion.chunk","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: [DONE]
@@ -47,22 +47,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 1.716247583s
+ duration: 627.333917ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50723
+ content_length: 50924
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,19 +24,19 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"20260119214746ff0f57f2dd6f4a3c","created":1768830466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"20260313041436a1f83a7f81b64392","created":1773346476,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"20260119214746ff0f57f2dd6f4a3c","created":1768830466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Download"}}]}
+ data: {"id":"20260313041436a1f83a7f81b64392","created":1773346476,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Download"}}]}
- data: {"id":"20260119214746ff0f57f2dd6f4a3c","created":1768830466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" file"}}]}
+ data: {"id":"20260313041436a1f83a7f81b64392","created":1773346476,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" example"}}]}
- data: {"id":"20260119214746ff0f57f2dd6f4a3c","created":1768830466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" as"}}]}
+ data: {"id":"20260313041436a1f83a7f81b64392","created":1773346476,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".txt"}}]}
- data: {"id":"20260119214746ff0f57f2dd6f4a3c","created":1768830466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" example"}}]}
+ data: {"id":"20260313041436a1f83a7f81b64392","created":1773346476,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" from"}}]}
- data: {"id":"20260119214746ff0f57f2dd6f4a3c","created":1768830466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".txt"}}]}
+ data: {"id":"20260313041436a1f83a7f81b64392","created":1773346476,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" URL"}}]}
- data: {"id":"20260119214746ff0f57f2dd6f4a3c","created":1768830466,"object":"chat.completion.chunk","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: {"id":"20260313041436a1f83a7f81b64392","created":1773346476,"object":"chat.completion.chunk","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]
@@ -45,22 +45,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 1.298214334s
+ duration: 687.490875ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50748
+ content_length: 50949
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,19 +24,23 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"2026011921475024193c5bdb7a439a","created":1768830470,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"20260313041457b8e4f6c46e7c468d","created":1773346497,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"2026011921475024193c5bdb7a439a","created":1768830470,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Check"}}]}
+ data: {"id":"20260313041457b8e4f6c46e7c468d","created":1773346497,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Check"}}]}
- data: {"id":"2026011921475024193c5bdb7a439a","created":1768830470,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" HTML"}}]}
+ data: {"id":"20260313041457b8e4f6c46e7c468d","created":1773346497,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" for"}}]}
- data: {"id":"2026011921475024193c5bdb7a439a","created":1768830470,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" for"}}]}
+ data: {"id":"20260313041457b8e4f6c46e7c468d","created":1773346497,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" John"}}]}
- data: {"id":"2026011921475024193c5bdb7a439a","created":1768830470,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" John"}}]}
+ data: {"id":"20260313041457b8e4f6c46e7c468d","created":1773346497,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Doe"}}]}
- data: {"id":"2026011921475024193c5bdb7a439a","created":1768830470,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Doe"}}]}
+ data: {"id":"20260313041457b8e4f6c46e7c468d","created":1773346497,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" in"}}]}
- data: {"id":"2026011921475024193c5bdb7a439a","created":1768830470,"object":"chat.completion.chunk","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":114}}}
+ data: {"id":"20260313041457b8e4f6c46e7c468d","created":1773346497,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" HTML"}}]}
+
+ data: {"id":"20260313041457b8e4f6c46e7c468d","created":1773346497,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" content"}}]}
+
+ data: {"id":"20260313041457b8e4f6c46e7c468d","created":1773346497,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":148,"completion_tokens":11,"total_tokens":159,"prompt_tokens_details":{"cached_tokens":114}}}
data: [DONE]
@@ -45,22 +49,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 2.103121375s
+ duration: 653.304417ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50766
+ content_length: 50967
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,17 +24,21 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"2026011921480674cd894f317040ff","created":1768830486,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"202603130415039728beb3b5dc461d","created":1773346503,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"2026011921480674cd894f317040ff","created":1768830486,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"glob"}}]}
+ data: {"id":"202603130415039728beb3b5dc461d","created":1773346503,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Find"}}]}
- data: {"id":"2026011921480674cd894f317040ff","created":1768830486,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" find"}}]}
+ data: {"id":"202603130415039728beb3b5dc461d","created":1773346503,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" all"}}]}
- data: {"id":"2026011921480674cd894f317040ff","created":1768830486,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" go"}}]}
+ data: {"id":"202603130415039728beb3b5dc461d","created":1773346503,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Go"}}]}
- data: {"id":"2026011921480674cd894f317040ff","created":1768830486,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" files"}}]}
+ data: {"id":"202603130415039728beb3b5dc461d","created":1773346503,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" files"}}]}
- data: {"id":"2026011921480674cd894f317040ff","created":1768830486,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":132,"completion_tokens":8,"total_tokens":140,"prompt_tokens_details":{"cached_tokens":4}}}
+ data: {"id":"202603130415039728beb3b5dc461d","created":1773346503,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" with"}}]}
+
+ data: {"id":"202603130415039728beb3b5dc461d","created":1773346503,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" glob"}}]}
+
+ data: {"id":"202603130415039728beb3b5dc461d","created":1773346503,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":132,"completion_tokens":10,"total_tokens":142,"prompt_tokens_details":{"cached_tokens":115}}}
data: [DONE]
@@ -43,22 +47,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 1.183180875s
+ duration: 692.47975ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50684
+ content_length: 50885
host: ""
@@ -6,16 +6,16 @@ interactions:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50682
+ content_length: 716
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,19 +24,17 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"20260119214825c9d8845a1ce84ef4","created":1768830505,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"20260313041513cafe5646e30a4b09","created":1773346513,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"20260119214825c9d8845a1ce84ef4","created":1768830505,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"List"}}]}
+ data: {"id":"20260313041513cafe5646e30a4b09","created":1773346513,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"List"}}]}
- data: {"id":"20260119214825c9d8845a1ce84ef4","created":1768830505,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" files"}}]}
+ data: {"id":"20260313041513cafe5646e30a4b09","created":1773346513,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Files"}}]}
- data: {"id":"20260119214825c9d8845a1ce84ef4","created":1768830505,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" with"}}]}
+ data: {"id":"20260313041513cafe5646e30a4b09","created":1773346513,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" with"}}]}
- data: {"id":"20260119214825c9d8845a1ce84ef4","created":1768830505,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" ls"}}]}
+ data: {"id":"20260313041513cafe5646e30a4b09","created":1773346513,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" ls"}}]}
- data: {"id":"20260119214825c9d8845a1ce84ef4","created":1768830505,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" command"}}]}
-
- data: {"id":"20260119214825c9d8845a1ce84ef4","created":1768830505,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":130,"completion_tokens":9,"total_tokens":139,"prompt_tokens_details":{"cached_tokens":4}}}
+ data: {"id":"20260313041513cafe5646e30a4b09","created":1773346513,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":130,"completion_tokens":8,"total_tokens":138,"prompt_tokens_details":{"cached_tokens":115}}}
data: [DONE]
@@ -45,22 +43,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 1.619916917s
+ duration: 650.921958ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50676
+ content_length: 50877
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,25 +24,21 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"20260119214830f5b9b304dd864346","created":1768830510,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"2026031304151715bb72c64ce040ce","created":1773346517,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"20260119214830f5b9b304dd864346","created":1768830510,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Mult"}}]}
+ data: {"id":"2026031304151715bb72c64ce040ce","created":1773346517,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Mult"}}]}
- data: {"id":"20260119214830f5b9b304dd864346","created":1768830510,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"ied"}}]}
+ data: {"id":"2026031304151715bb72c64ce040ce","created":1773346517,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"ied"}}]}
- data: {"id":"20260119214830f5b9b304dd864346","created":1768830510,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"it"}}]}
+ data: {"id":"2026031304151715bb72c64ce040ce","created":1773346517,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"it"}}]}
- data: {"id":"20260119214830f5b9b304dd864346","created":1768830510,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" in"}}]}
+ data: {"id":"2026031304151715bb72c64ce040ce","created":1773346517,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Go"}}]}
- data: {"id":"20260119214830f5b9b304dd864346","created":1768830510,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" main"}}]}
+ data: {"id":"2026031304151715bb72c64ce040ce","created":1773346517,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Code"}}]}
- data: {"id":"20260119214830f5b9b304dd864346","created":1768830510,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".go"}}]}
+ data: {"id":"2026031304151715bb72c64ce040ce","created":1773346517,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Modification"}}]}
- data: {"id":"20260119214830f5b9b304dd864346","created":1768830510,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Title"}}]}
-
- data: {"id":"20260119214830f5b9b304dd864346","created":1768830510,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Change"}}]}
-
- data: {"id":"20260119214830f5b9b304dd864346","created":1768830510,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":153,"completion_tokens":12,"total_tokens":165,"prompt_tokens_details":{"cached_tokens":115}}}
+ data: {"id":"2026031304151715bb72c64ce040ce","created":1773346517,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":153,"completion_tokens":10,"total_tokens":163,"prompt_tokens_details":{"cached_tokens":115}}}
data: [DONE]
@@ -51,22 +47,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 1.904839708s
+ duration: 617.22425ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50762
+ content_length: 50963
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,23 +24,25 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"202601192148452cbea4d5a4814811","created":1768830525,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"20260313041539bcf7d231fb614122","created":1773346539,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"202601192148452cbea4d5a4814811","created":1768830525,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Parallel"}}]}
+ data: {"id":"20260313041539bcf7d231fb614122","created":1773346539,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Parallel"}}]}
- data: {"id":"202601192148452cbea4d5a4814811","created":1768830525,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" glob"}}]}
+ data: {"id":"20260313041539bcf7d231fb614122","created":1773346539,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" glob"}}]}
- data: {"id":"202601192148452cbea4d5a4814811","created":1768830525,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" and"}}]}
+ data: {"id":"20260313041539bcf7d231fb614122","created":1773346539,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" and"}}]}
- data: {"id":"202601192148452cbea4d5a4814811","created":1768830525,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" ls"}}]}
+ data: {"id":"20260313041539bcf7d231fb614122","created":1773346539,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" ls"}}]}
- data: {"id":"202601192148452cbea4d5a4814811","created":1768830525,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" for"}}]}
+ data: {"id":"20260313041539bcf7d231fb614122","created":1773346539,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" commands"}}]}
- data: {"id":"202601192148452cbea4d5a4814811","created":1768830525,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Go"}}]}
+ data: {"id":"20260313041539bcf7d231fb614122","created":1773346539,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" for"}}]}
- data: {"id":"202601192148452cbea4d5a4814811","created":1768830525,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" files"}}]}
+ data: {"id":"20260313041539bcf7d231fb614122","created":1773346539,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Go"}}]}
- data: {"id":"202601192148452cbea4d5a4814811","created":1768830525,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":149,"completion_tokens":11,"total_tokens":160,"prompt_tokens_details":{"cached_tokens":4}}}
+ data: {"id":"20260313041539bcf7d231fb614122","created":1773346539,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" files"}}]}
+
+ data: {"id":"20260313041539bcf7d231fb614122","created":1773346539,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":149,"completion_tokens":12,"total_tokens":161,"prompt_tokens_details":{"cached_tokens":122}}}
data: [DONE]
@@ -49,22 +51,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 1.2044225s
+ duration: 698.619667ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50773
+ content_length: 50974
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,15 +24,15 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"20260119214656028e1a27796245c0","created":1768830416,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"20260313041419382e7728255d4003","created":1773346459,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"20260119214656028e1a27796245c0","created":1768830416,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Go"}}]}
+ data: {"id":"20260313041419382e7728255d4003","created":1773346459,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Go"}}]}
- data: {"id":"20260119214656028e1a27796245c0","created":1768830416,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Module"}}]}
+ data: {"id":"20260313041419382e7728255d4003","created":1773346459,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" mod"}}]}
- data: {"id":"20260119214656028e1a27796245c0","created":1768830416,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Summary"}}]}
+ data: {"id":"20260313041419382e7728255d4003","created":1773346459,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" reading"}}]}
- data: {"id":"20260119214656028e1a27796245c0","created":1768830416,"object":"chat.completion.chunk","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: {"id":"20260313041419382e7728255d4003","created":1773346459,"object":"chat.completion.chunk","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]
@@ -41,22 +41,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 1.35162425s
+ duration: 751.460042ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50646
+ content_length: 50847
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,11 +24,13 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"2026011921463481193bf2b83c45e5","created":1768830394,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"202603130414168ff3a068e783494c","created":1773346456,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"2026011921463481193bf2b83c45e5","created":1768830394,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"}}]}
+ data: {"id":"202603130414168ff3a068e783494c","created":1773346456,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"G"}}]}
- data: {"id":"2026011921463481193bf2b83c45e5","created":1768830394,"object":"chat.completion.chunk","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":"202603130414168ff3a068e783494c","created":1773346456,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"reeting"}}]}
+
+ data: {"id":"202603130414168ff3a068e783494c","created":1773346456,"object":"chat.completion.chunk","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":4}}}
data: [DONE]
@@ -37,22 +39,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 1.262129958s
+ duration: 1.762131625s
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50636
+ content_length: 50837
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,19 +24,17 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"20260119215657a528cbba8dc945c7","created":1768831017,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"202603130415283ca31ec31524434b","created":1773346528,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"20260119215657a528cbba8dc945c7","created":1768831017,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Source"}}]}
+ data: {"id":"202603130415283ca31ec31524434b","created":1773346528,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Go"}}]}
- data: {"id":"20260119215657a528cbba8dc945c7","created":1768831017,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"graph"}}]}
+ data: {"id":"202603130415283ca31ec31524434b","created":1773346528,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" main"}}]}
- data: {"id":"20260119215657a528cbba8dc945c7","created":1768831017,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Go"}}]}
+ data: {"id":"202603130415283ca31ec31524434b","created":1773346528,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" function"}}]}
- data: {"id":"20260119215657a528cbba8dc945c7","created":1768831017,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Main"}}]}
+ data: {"id":"202603130415283ca31ec31524434b","created":1773346528,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" search"}}]}
- data: {"id":"20260119215657a528cbba8dc945c7","created":1768831017,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" Search"}}]}
-
- data: {"id":"20260119215657a528cbba8dc945c7","created":1768831017,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":133,"completion_tokens":9,"total_tokens":142,"prompt_tokens_details":{"cached_tokens":4}}}
+ data: {"id":"202603130415283ca31ec31524434b","created":1773346528,"object":"chat.completion.chunk","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]
@@ -45,22 +43,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 1.964927833s
+ duration: 676.245916ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50696
+ content_length: 50897
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,17 +24,21 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"2026011921470892df6a623ba442e8","created":1768830428,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"2026031304142637b25add063545df","created":1773346466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"2026011921470892df6a623ba442e8","created":1768830428,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Update"}}]}
+ data: {"id":"2026031304142637b25add063545df","created":1773346466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Update"}}]}
- data: {"id":"2026011921470892df6a623ba442e8","created":1768830428,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" main"}}]}
+ data: {"id":"2026031304142637b25add063545df","created":1773346466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" main"}}]}
- data: {"id":"2026011921470892df6a623ba442e8","created":1768830428,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".go"}}]}
+ data: {"id":"2026031304142637b25add063545df","created":1773346466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".go"}}]}
- data: {"id":"2026011921470892df6a623ba442e8","created":1768830428,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" greeting"}}]}
+ data: {"id":"2026031304142637b25add063545df","created":1773346466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" to"}}]}
- data: {"id":"2026011921470892df6a623ba442e8","created":1768830428,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":134,"completion_tokens":8,"total_tokens":142,"prompt_tokens_details":{"cached_tokens":114}}}
+ data: {"id":"2026031304142637b25add063545df","created":1773346466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" print"}}]}
+
+ data: {"id":"2026031304142637b25add063545df","created":1773346466,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" hello"}}]}
+
+ data: {"id":"2026031304142637b25add063545df","created":1773346466,"object":"chat.completion.chunk","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":114}}}
data: [DONE]
@@ -43,22 +47,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 2.524076791s
+ duration: 1.264043875s
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50702
+ content_length: 50903
host: ""
@@ -15,7 +15,7 @@ interactions:
Content-Type:
- application/json
User-Agent:
- - OpenAI/Go 2.7.1
+ - Charm-Crush/devel (https://charm.land/crush)
url: https://api.z.ai/api/coding/paas/v4/chat/completions
method: POST
response:
@@ -24,19 +24,23 @@ interactions:
proto_minor: 0
content_length: -1
body: |+
- data: {"id":"20260119214841d5e404cbb41f4b39","created":1768830521,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
+ data: {"id":"202603130415354342f3e7fce041ac","created":1773346535,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"\n"}}]}
- data: {"id":"20260119214841d5e404cbb41f4b39","created":1768830521,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Creating"}}]}
+ data: {"id":"202603130415354342f3e7fce041ac","created":1773346535,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":"Creating"}}]}
- data: {"id":"20260119214841d5e404cbb41f4b39","created":1768830521,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" config"}}]}
+ data: {"id":"202603130415354342f3e7fce041ac","created":1773346535,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" config"}}]}
- data: {"id":"20260119214841d5e404cbb41f4b39","created":1768830521,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".json"}}]}
+ data: {"id":"202603130415354342f3e7fce041ac","created":1773346535,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":".json"}}]}
- data: {"id":"20260119214841d5e404cbb41f4b39","created":1768830521,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" with"}}]}
+ data: {"id":"202603130415354342f3e7fce041ac","created":1773346535,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" file"}}]}
- data: {"id":"20260119214841d5e404cbb41f4b39","created":1768830521,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" content"}}]}
+ data: {"id":"202603130415354342f3e7fce041ac","created":1773346535,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" with"}}]}
- data: {"id":"20260119214841d5e404cbb41f4b39","created":1768830521,"object":"chat.completion.chunk","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":115}}}
+ data: {"id":"202603130415354342f3e7fce041ac","created":1773346535,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" write"}}]}
+
+ data: {"id":"202603130415354342f3e7fce041ac","created":1773346535,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"delta":{"role":"assistant","content":" command"}}]}
+
+ data: {"id":"202603130415354342f3e7fce041ac","created":1773346535,"object":"chat.completion.chunk","model":"glm-4.5-air","choices":[{"index":0,"finish_reason":"stop","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":148,"completion_tokens":11,"total_tokens":159,"prompt_tokens_details":{"cached_tokens":115}}}
data: [DONE]
@@ -45,22 +49,22 @@ interactions:
- text/event-stream;charset=UTF-8
status: 200 OK
code: 200
- duration: 990.550333ms
+ duration: 626.617ms
- id: 1
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
- content_length: 50739
+ content_length: 50940
host: ""
@@ -20,17 +20,19 @@ import (
)
type BashParams struct {
- 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."`
+ 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."`
+ AutoBackgroundAfter int `json:"auto_background_after,omitempty" description:"Seconds to wait before automatically moving the command to a background job (default: 60)"`
}
type BashPermissionsParams struct {
- Description string `json:"description"`
- Command string `json:"command"`
- WorkingDir string `json:"working_dir"`
- RunInBackground bool `json:"run_in_background"`
+ Description string `json:"description"`
+ Command string `json:"command"`
+ WorkingDir string `json:"working_dir"`
+ RunInBackground bool `json:"run_in_background"`
+ AutoBackgroundAfter int `json:"auto_background_after"`
}
type BashResponseMetadata struct {
@@ -46,9 +48,9 @@ type BashResponseMetadata struct {
const (
BashToolName = "bash"
- AutoBackgroundThreshold = 1 * time.Minute // Commands taking longer automatically become background jobs
- MaxOutputLength = 30000
- BashNoOutput = "no output"
+ DefaultAutoBackgroundAfter = 60 // Commands taking longer automatically become background jobs
+ MaxOutputLength = 30000
+ BashNoOutput = "no output"
)
//go:embed bash.tpl
@@ -303,7 +305,10 @@ func NewBashTool(permissions permission.Service, workingDir string, attribution
// Wait for either completion, auto-background threshold, or context cancellation
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
- timeout := time.After(AutoBackgroundThreshold)
+
+ autoBackgroundAfter := cmp.Or(params.AutoBackgroundAfter, DefaultAutoBackgroundAfter)
+ autoBackgroundThreshold := time.Duration(autoBackgroundAfter) * time.Second
+ timeout := time.After(autoBackgroundThreshold)
var stdout, stderr string
var done bool
@@ -10,7 +10,7 @@ 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. Auto-Background: Commands exceeding 1 minute automatically move to background and return shell ID
+4. Auto-Background: Commands exceeding 1 minute (default, configurable via `auto_background_after`) 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>
@@ -0,0 +1,103 @@
+package tools
+
+import (
+ "context"
+ "encoding/json"
+ "testing"
+
+ "charm.land/fantasy"
+ "github.com/charmbracelet/crush/internal/config"
+ "github.com/charmbracelet/crush/internal/permission"
+ "github.com/charmbracelet/crush/internal/pubsub"
+ "github.com/charmbracelet/crush/internal/shell"
+ "github.com/stretchr/testify/require"
+)
+
+type mockBashPermissionService struct {
+ *pubsub.Broker[permission.PermissionRequest]
+}
+
+func (m *mockBashPermissionService) Request(ctx context.Context, req permission.CreatePermissionRequest) (bool, error) {
+ return true, nil
+}
+
+func (m *mockBashPermissionService) Grant(req permission.PermissionRequest) {}
+
+func (m *mockBashPermissionService) Deny(req permission.PermissionRequest) {}
+
+func (m *mockBashPermissionService) GrantPersistent(req permission.PermissionRequest) {}
+
+func (m *mockBashPermissionService) AutoApproveSession(sessionID string) {}
+
+func (m *mockBashPermissionService) SetSkipRequests(skip bool) {}
+
+func (m *mockBashPermissionService) SkipRequests() bool {
+ return false
+}
+
+func (m *mockBashPermissionService) SubscribeNotifications(ctx context.Context) <-chan pubsub.Event[permission.PermissionNotification] {
+ return make(<-chan pubsub.Event[permission.PermissionNotification])
+}
+
+func TestBashTool_DefaultAutoBackgroundThreshold(t *testing.T) {
+ workingDir := t.TempDir()
+ tool := newBashToolForTest(workingDir)
+ ctx := context.WithValue(context.Background(), SessionIDContextKey, "test-session")
+
+ resp := runBashTool(t, tool, ctx, BashParams{
+ Description: "default threshold",
+ Command: "echo done",
+ })
+
+ require.False(t, resp.IsError)
+ var meta BashResponseMetadata
+ require.NoError(t, json.Unmarshal([]byte(resp.Metadata), &meta))
+ require.False(t, meta.Background)
+ require.Empty(t, meta.ShellID)
+ require.Contains(t, meta.Output, "done")
+}
+
+func TestBashTool_CustomAutoBackgroundThreshold(t *testing.T) {
+ workingDir := t.TempDir()
+ tool := newBashToolForTest(workingDir)
+ ctx := context.WithValue(context.Background(), SessionIDContextKey, "test-session")
+
+ resp := runBashTool(t, tool, ctx, BashParams{
+ Description: "custom threshold",
+ Command: "sleep 1.5 && echo done",
+ AutoBackgroundAfter: 1,
+ })
+
+ require.False(t, resp.IsError)
+ var meta BashResponseMetadata
+ require.NoError(t, json.Unmarshal([]byte(resp.Metadata), &meta))
+ require.True(t, meta.Background)
+ require.NotEmpty(t, meta.ShellID)
+ require.Contains(t, resp.Content, "moved to background")
+
+ bgManager := shell.GetBackgroundShellManager()
+ require.NoError(t, bgManager.Kill(meta.ShellID))
+}
+
+func newBashToolForTest(workingDir string) fantasy.AgentTool {
+ permissions := &mockBashPermissionService{Broker: pubsub.NewBroker[permission.PermissionRequest]()}
+ attribution := &config.Attribution{TrailerStyle: config.TrailerStyleNone}
+ return NewBashTool(permissions, workingDir, attribution, "test-model")
+}
+
+func runBashTool(t *testing.T, tool fantasy.AgentTool, ctx context.Context, params BashParams) fantasy.ToolResponse {
+ t.Helper()
+
+ input, err := json.Marshal(params)
+ require.NoError(t, err)
+
+ call := fantasy.ToolCall{
+ ID: "test-call",
+ Name: BashToolName,
+ Input: string(input),
+ }
+
+ resp, err := tool.Run(ctx, call)
+ require.NoError(t, err)
+ return resp
+}
@@ -28,7 +28,7 @@ const ListMCPResourcesToolName = "list_mcp_resources"
//go:embed list_mcp_resources.md
var listMCPResourcesDescription []byte
-func NewListMCPResourcesTool(cfg *config.Config, permissions permission.Service) fantasy.AgentTool {
+func NewListMCPResourcesTool(cfg *config.ConfigStore, permissions permission.Service) fantasy.AgentTool {
return fantasy.NewParallelAgentTool(
ListMCPResourcesToolName,
string(listMCPResourcesDescription),
@@ -21,7 +21,7 @@ var whitelistDockerTools = []string{
}
// GetMCPTools gets all the currently available MCP tools.
-func GetMCPTools(permissions permission.Service, cfg *config.Config, wd string) []*Tool {
+func GetMCPTools(permissions permission.Service, cfg *config.ConfigStore, wd string) []*Tool {
var result []*Tool
for mcpName, tools := range mcp.Tools() {
for _, tool := range tools {
@@ -41,7 +41,7 @@ func GetMCPTools(permissions permission.Service, cfg *config.Config, wd string)
type Tool struct {
mcpName string
tool *mcp.Tool
- cfg *config.Config
+ cfg *config.ConfigStore
permissions permission.Service
workingDir string
providerOptions fantasy.ProviderOptions
@@ -163,11 +163,11 @@ func Close(ctx context.Context) error {
}
// Initialize initializes MCP clients based on the provided configuration.
-func Initialize(ctx context.Context, permissions permission.Service, cfg *config.Config) {
+func Initialize(ctx context.Context, permissions permission.Service, cfg *config.ConfigStore) {
slog.Info("Initializing MCP clients")
var wg sync.WaitGroup
// Initialize states for all configured MCPs
- for name, m := range cfg.MCP {
+ for name, m := range cfg.Config().MCP {
if m.Disabled {
updateState(name, StateDisabled, nil, nil, Counts{})
slog.Debug("Skipping disabled MCP", "name", name)
@@ -215,8 +215,8 @@ func WaitForInit(ctx context.Context) error {
}
// InitializeSingle initializes a single MCP client by name.
-func InitializeSingle(ctx context.Context, name string, cfg *config.Config) error {
- m, exists := cfg.MCP[name]
+func InitializeSingle(ctx context.Context, name string, cfg *config.ConfigStore) error {
+ m, exists := cfg.Config().MCP[name]
if !exists {
return fmt.Errorf("mcp '%s' not found in configuration", name)
}
@@ -231,7 +231,7 @@ func InitializeSingle(ctx context.Context, name string, cfg *config.Config) erro
}
// initClient initializes a single MCP client with the given configuration.
-func initClient(ctx context.Context, cfg *config.Config, name string, m config.MCPConfig, resolver config.VariableResolver) error {
+func initClient(ctx context.Context, cfg *config.ConfigStore, name string, m config.MCPConfig, resolver config.VariableResolver) error {
// Set initial starting state.
updateState(name, StateStarting, nil, nil, Counts{})
@@ -270,7 +270,7 @@ func initClient(ctx context.Context, cfg *config.Config, name string, m config.M
}
// DisableSingle disables and closes a single MCP client by name.
-func DisableSingle(cfg *config.Config, name string) error {
+func DisableSingle(cfg *config.ConfigStore, name string) error {
session, ok := sessions.Get(name)
if ok {
if err := session.Close(); err != nil &&
@@ -293,13 +293,13 @@ func DisableSingle(cfg *config.Config, name string) error {
return nil
}
-func getOrRenewClient(ctx context.Context, cfg *config.Config, name string) (*ClientSession, error) {
+func getOrRenewClient(ctx context.Context, cfg *config.ConfigStore, name string) (*ClientSession, error) {
sess, ok := sessions.Get(name)
if !ok {
return nil, fmt.Errorf("mcp '%s' not available", name)
}
- m := cfg.MCP[name]
+ m := cfg.Config().MCP[name]
state, _ := states.Get(name)
timeout := mcpTimeout(m)
@@ -20,7 +20,7 @@ func Prompts() iter.Seq2[string, []*Prompt] {
}
// GetPromptMessages retrieves the content of an MCP prompt with the given arguments.
-func GetPromptMessages(ctx context.Context, cfg *config.Config, clientName, promptName string, args map[string]string) ([]string, error) {
+func GetPromptMessages(ctx context.Context, cfg *config.ConfigStore, clientName, promptName string, args map[string]string) ([]string, error) {
c, err := getOrRenewClient(ctx, cfg, clientName)
if err != nil {
return nil, err
@@ -24,7 +24,7 @@ func Resources() iter.Seq2[string, []*Resource] {
}
// ListResources returns the current resources for an MCP server.
-func ListResources(ctx context.Context, cfg *config.Config, name string) ([]*Resource, error) {
+func ListResources(ctx context.Context, cfg *config.ConfigStore, name string) ([]*Resource, error) {
session, err := getOrRenewClient(ctx, cfg, name)
if err != nil {
return nil, err
@@ -43,7 +43,7 @@ func ListResources(ctx context.Context, cfg *config.Config, name string) ([]*Res
}
// ReadResource reads the contents of a resource from an MCP server.
-func ReadResource(ctx context.Context, cfg *config.Config, name, uri string) ([]*ResourceContents, error) {
+func ReadResource(ctx context.Context, cfg *config.ConfigStore, name, uri string) ([]*ResourceContents, error) {
session, err := getOrRenewClient(ctx, cfg, name)
if err != nil {
return nil, err
@@ -33,7 +33,7 @@ func Tools() iter.Seq2[string, []*Tool] {
}
// RunTool runs an MCP tool with the given input parameters.
-func RunTool(ctx context.Context, cfg *config.Config, name, toolName string, input string) (ToolResult, error) {
+func RunTool(ctx context.Context, cfg *config.ConfigStore, name, toolName string, input string) (ToolResult, error) {
var args map[string]any
if err := json.Unmarshal([]byte(input), &args); err != nil {
return ToolResult{}, fmt.Errorf("error parsing parameters: %s", err)
@@ -110,7 +110,7 @@ func RunTool(ctx context.Context, cfg *config.Config, name, toolName string, inp
// RefreshTools gets the updated list of tools from the MCP and updates the
// global state.
-func RefreshTools(ctx context.Context, cfg *config.Config, name string) {
+func RefreshTools(ctx context.Context, cfg *config.ConfigStore, name string) {
session, ok := sessions.Get(name)
if !ok {
slog.Warn("Refresh tools: no session", "name", name)
@@ -141,7 +141,7 @@ func getTools(ctx context.Context, session *ClientSession) ([]*Tool, error) {
return result.Tools, nil
}
-func updateTools(cfg *config.Config, name string, tools []*Tool) int {
+func updateTools(cfg *config.ConfigStore, name string, tools []*Tool) int {
tools = filterDisabledTools(cfg, name, tools)
if len(tools) == 0 {
allTools.Del(name)
@@ -152,8 +152,8 @@ func updateTools(cfg *config.Config, name string, tools []*Tool) int {
}
// filterDisabledTools removes tools that are disabled via config.
-func filterDisabledTools(cfg *config.Config, mcpName string, tools []*Tool) []*Tool {
- mcpCfg, ok := cfg.MCP[mcpName]
+func filterDisabledTools(cfg *config.ConfigStore, mcpName string, tools []*Tool) []*Tool {
+ mcpCfg, ok := cfg.Config().MCP[mcpName]
if !ok || len(mcpCfg.DisabledTools) == 0 {
return tools
}
@@ -30,7 +30,7 @@ const ReadMCPResourceToolName = "read_mcp_resource"
//go:embed read_mcp_resource.md
var readMCPResourceDescription []byte
-func NewReadMCPResourceTool(cfg *config.Config, permissions permission.Service) fantasy.AgentTool {
+func NewReadMCPResourceTool(cfg *config.ConfigStore, permissions permission.Service) fantasy.AgentTool {
return fantasy.NewParallelAgentTool(
ReadMCPResourceToolName,
string(readMCPResourceDescription),
@@ -19,6 +19,7 @@ import (
"charm.land/fantasy"
"charm.land/lipgloss/v2"
"github.com/charmbracelet/crush/internal/agent"
+ "github.com/charmbracelet/crush/internal/agent/notify"
"github.com/charmbracelet/crush/internal/agent/tools/mcp"
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/db"
@@ -60,7 +61,7 @@ type App struct {
LSPManager *lsp.Manager
- config *config.Config
+ config *config.ConfigStore
serviceEventsWG *sync.WaitGroup
eventsCtx context.Context
@@ -68,16 +69,18 @@ type App struct {
tuiWG *sync.WaitGroup
// global context and cleanup functions
- globalCtx context.Context
- cleanupFuncs []func(context.Context) error
+ globalCtx context.Context
+ cleanupFuncs []func(context.Context) error
+ agentNotifications *pubsub.Broker[notify.Notification]
}
// New initializes a new application instance.
-func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
+func New(ctx context.Context, conn *sql.DB, store *config.ConfigStore) (*App, error) {
q := db.New(conn)
sessions := session.NewService(q, conn)
messages := message.NewService(q)
files := history.NewService(q, conn)
+ cfg := store.Config()
skipPermissionsRequests := cfg.Permissions != nil && cfg.Permissions.SkipRequests
var allowedTools []string
if cfg.Permissions != nil && cfg.Permissions.AllowedTools != nil {
@@ -88,17 +91,18 @@ func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
Sessions: sessions,
Messages: messages,
History: files,
- Permissions: permission.NewPermissionService(cfg.WorkingDir(), skipPermissionsRequests, allowedTools),
+ Permissions: permission.NewPermissionService(store.WorkingDir(), skipPermissionsRequests, allowedTools),
FileTracker: filetracker.NewService(q),
- LSPManager: lsp.NewManager(cfg),
+ LSPManager: lsp.NewManager(store),
globalCtx: ctx,
- config: cfg,
+ config: store,
- events: make(chan tea.Msg, 100),
- serviceEventsWG: &sync.WaitGroup{},
- tuiWG: &sync.WaitGroup{},
+ events: make(chan tea.Msg, 100),
+ serviceEventsWG: &sync.WaitGroup{},
+ tuiWG: &sync.WaitGroup{},
+ agentNotifications: pubsub.NewBroker[notify.Notification](),
}
app.setupEvents()
@@ -106,13 +110,13 @@ func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
// Check for updates in the background.
go app.checkForUpdates(ctx)
- go mcp.Initialize(ctx, app.Permissions, cfg)
+ go mcp.Initialize(ctx, app.Permissions, store)
// cleanup database upon app shutdown
app.cleanupFuncs = append(
app.cleanupFuncs,
func(context.Context) error { return conn.Close() },
- mcp.Close,
+ func(ctx context.Context) error { return mcp.Close(ctx) },
)
// TODO: remove the concept of agent config, most likely.
@@ -138,11 +142,21 @@ func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
return app, nil
}
-// Config returns the application configuration.
+// Config returns the pure-data configuration.
func (app *App) Config() *config.Config {
+ return app.config.Config()
+}
+
+// Store returns the config store.
+func (app *App) Store() *config.ConfigStore {
return app.config
}
+// AgentNotifications returns the broker for agent notification events.
+func (app *App) AgentNotifications() *pubsub.Broker[notify.Notification] {
+ return app.agentNotifications
+}
+
// RunNonInteractive runs the application in non-interactive mode with the
// given prompt, printing to stdout.
func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt, largeModel, smallModel string, hideSpinner bool) error {
@@ -170,7 +184,7 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt,
}
stderrTTY = term.IsTerminal(os.Stderr.Fd())
stdinTTY = term.IsTerminal(os.Stdin.Fd())
- progress = app.config.Options.Progress == nil || *app.config.Options.Progress
+ progress = app.config.Config().Options.Progress == nil || *app.config.Config().Options.Progress
if !hideSpinner && stderrTTY {
t := styles.DefaultStyles()
@@ -213,18 +227,7 @@ func (app *App) RunNonInteractive(ctx context.Context, output io.Writer, prompt,
defer stopSpinner()
- const maxPromptLengthForTitle = 100
- const titlePrefix = "Non-interactive: "
- var titleSuffix string
-
- if len(prompt) > maxPromptLengthForTitle {
- titleSuffix = prompt[:maxPromptLengthForTitle] + "..."
- } else {
- titleSuffix = prompt
- }
- title := titlePrefix + titleSuffix
-
- sess, err := app.Sessions.Create(ctx, title)
+ sess, err := app.Sessions.Create(ctx, agent.DefaultSessionName)
if err != nil {
return fmt.Errorf("failed to create session for non-interactive mode: %w", err)
}
@@ -334,7 +337,7 @@ func (app *App) UpdateAgentModel(ctx context.Context) error {
// If largeModel is provided but smallModel is not, the small model defaults to
// the provider's default small model.
func (app *App) overrideModelsForNonInteractive(ctx context.Context, largeModel, smallModel string) error {
- providers := app.config.Providers.Copy()
+ providers := app.config.Config().Providers.Copy()
largeMatches, smallMatches, err := findModels(providers, largeModel, smallModel)
if err != nil {
@@ -351,7 +354,7 @@ func (app *App) overrideModelsForNonInteractive(ctx context.Context, largeModel,
}
largeProviderID = found.provider
slog.Info("Overriding large model for non-interactive run", "provider", found.provider, "model", found.modelID)
- app.config.Models[config.SelectedModelTypeLarge] = config.SelectedModel{
+ app.config.Config().Models[config.SelectedModelTypeLarge] = config.SelectedModel{
Provider: found.provider,
Model: found.modelID,
}
@@ -365,7 +368,7 @@ func (app *App) overrideModelsForNonInteractive(ctx context.Context, largeModel,
return err
}
slog.Info("Overriding small model for non-interactive run", "provider", found.provider, "model", found.modelID)
- app.config.Models[config.SelectedModelTypeSmall] = config.SelectedModel{
+ app.config.Config().Models[config.SelectedModelTypeSmall] = config.SelectedModel{
Provider: found.provider,
Model: found.modelID,
}
@@ -373,7 +376,7 @@ func (app *App) overrideModelsForNonInteractive(ctx context.Context, largeModel,
case largeModel != "":
// No small model specified, but large model was - use provider's default.
smallCfg := app.GetDefaultSmallModel(largeProviderID)
- app.config.Models[config.SelectedModelTypeSmall] = smallCfg
+ app.config.Config().Models[config.SelectedModelTypeSmall] = smallCfg
}
return app.AgentCoordinator.UpdateModels(ctx)
@@ -382,7 +385,7 @@ func (app *App) overrideModelsForNonInteractive(ctx context.Context, largeModel,
// GetDefaultSmallModel returns the default small model for the given
// provider. Falls back to the large model if no default is found.
func (app *App) GetDefaultSmallModel(providerID string) config.SelectedModel {
- cfg := app.config
+ cfg := app.config.Config()
largeModelCfg := cfg.Models[config.SelectedModelTypeLarge]
// Find the provider in the known providers list to get its default small model.
@@ -425,6 +428,7 @@ func (app *App) setupEvents() {
setupSubscriber(ctx, app.serviceEventsWG, "permissions", app.Permissions.Subscribe, app.events)
setupSubscriber(ctx, app.serviceEventsWG, "permissions-notifications", app.Permissions.SubscribeNotifications, app.events)
setupSubscriber(ctx, app.serviceEventsWG, "history", app.History.Subscribe, app.events)
+ setupSubscriber(ctx, app.serviceEventsWG, "agent-notifications", app.agentNotifications.Subscribe, app.events)
setupSubscriber(ctx, app.serviceEventsWG, "mcp", mcp.SubscribeEvents, app.events)
setupSubscriber(ctx, app.serviceEventsWG, "lsp", SubscribeLSPEvents, app.events)
cleanupFunc := func(context.Context) error {
@@ -483,7 +487,7 @@ func setupSubscriber[T any](
}
func (app *App) InitCoderAgent(ctx context.Context) error {
- coderAgentCfg := app.config.Agents[config.AgentCoder]
+ coderAgentCfg := app.config.Config().Agents[config.AgentCoder]
if coderAgentCfg.ID == "" {
return fmt.Errorf("coder agent configuration is missing")
}
@@ -497,6 +501,7 @@ func (app *App) InitCoderAgent(ctx context.Context) error {
app.History,
app.FileTracker,
app.LSPManager,
+ app.agentNotifications,
)
if err != nil {
slog.Error("Failed to create coder agent", "err", err)
@@ -52,16 +52,16 @@ crush login copilot
}
switch provider {
case "hyper":
- return loginHyper(app.Config())
+ return loginHyper(app.Store())
case "copilot", "github", "github-copilot":
- return loginCopilot(app.Config())
+ return loginCopilot(app.Store())
default:
return fmt.Errorf("unknown platform: %s", args[0])
}
},
}
-func loginHyper(cfg *config.Config) error {
+func loginHyper(cfg *config.ConfigStore) error {
if !hyperp.Enabled() {
return fmt.Errorf("hyper not enabled")
}
@@ -112,8 +112,8 @@ func loginHyper(cfg *config.Config) error {
}
if err := cmp.Or(
- cfg.SetConfigField("providers.hyper.api_key", token.AccessToken),
- cfg.SetConfigField("providers.hyper.oauth", token),
+ cfg.SetConfigField(config.ScopeGlobal, "providers.hyper.api_key", token.AccessToken),
+ cfg.SetConfigField(config.ScopeGlobal, "providers.hyper.oauth", token),
); err != nil {
return err
}
@@ -123,10 +123,10 @@ func loginHyper(cfg *config.Config) error {
return nil
}
-func loginCopilot(cfg *config.Config) error {
+func loginCopilot(cfg *config.ConfigStore) error {
ctx := getLoginContext()
- if cfg.HasConfigField("providers.copilot.oauth") {
+ if cfg.HasConfigField(config.ScopeGlobal, "providers.copilot.oauth") {
fmt.Println("You are already logged in to GitHub Copilot.")
return nil
}
@@ -177,8 +177,8 @@ func loginCopilot(cfg *config.Config) error {
}
if err := cmp.Or(
- cfg.SetConfigField("providers.copilot.api_key", token.AccessToken),
- cfg.SetConfigField("providers.copilot.oauth", token),
+ cfg.SetConfigField(config.ScopeGlobal, "providers.copilot.api_key", token.AccessToken),
+ cfg.SetConfigField(config.ScopeGlobal, "providers.copilot.oauth", token),
); err != nil {
return err
}
@@ -55,7 +55,7 @@ var logsCmd = &cobra.Command{
if err != nil {
return fmt.Errorf("failed to load configuration: %v", err)
}
- logsFile := filepath.Join(cfg.Options.DataDirectory, "logs", "crush.log")
+ logsFile := filepath.Join(cfg.Config().Options.DataDirectory, "logs", "crush.log")
_, err = os.Stat(logsFile)
if os.IsNotExist(err) {
log.Warn("Looks like you are not in a crush project. No logs found.")
@@ -38,7 +38,7 @@ crush models gpt5`,
return err
}
- if !cfg.IsConfigured() {
+ if !cfg.Config().IsConfigured() {
return fmt.Errorf("no providers configured - please run 'crush' to set up a provider interactively")
}
@@ -55,7 +55,7 @@ crush models gpt5`,
var providerIDs []string
providerModels := make(map[string][]string)
- for providerID, provider := range cfg.Providers.Seq2() {
+ for providerID, provider := range cfg.Config().Providers.Seq2() {
if provider.Disable {
continue
}
@@ -13,6 +13,7 @@ import (
"strings"
tea "charm.land/bubbletea/v2"
+ "charm.land/fang/v2"
"charm.land/lipgloss/v2"
"github.com/charmbracelet/colorprofile"
"github.com/charmbracelet/crush/internal/app"
@@ -23,7 +24,6 @@ import (
"github.com/charmbracelet/crush/internal/ui/common"
ui "github.com/charmbracelet/crush/internal/ui/model"
"github.com/charmbracelet/crush/internal/version"
- "github.com/charmbracelet/fang"
uv "github.com/charmbracelet/ultraviolet"
"github.com/charmbracelet/x/ansi"
"github.com/charmbracelet/x/exp/charmtone"
@@ -47,34 +47,32 @@ func init() {
schemaCmd,
loginCmd,
statsCmd,
+ sessionCmd,
)
}
var rootCmd = &cobra.Command{
Use: "crush",
- Short: "An AI assistant for software development",
- Long: "An AI assistant for software development and similar tasks with direct access to the terminal",
+ Short: "A terminal-first AI assistant for software development",
+ Long: "A glamorous, terminal-first AI assistant for software development and adjacent tasks",
Example: `
# Run in interactive mode
crush
-# Run with debug logging
-crush -d
+# Run non-interactively
+crush run "Guess my 5 favorite Pokémon"
-# Run with debug logging in a specific directory
-crush -d -c /path/to/project
-
-# Run with custom data directory
-crush -D /path/to/custom/.crush
+# Run a non-interactively with pipes and redirection
+cat README.md | crush run "make this more glamorous" > GLAMOROUS_README.md
-# Print version
-crush -v
+# Run with debug logging in a specific directory
+crush --debug --cwd /path/to/project
-# Run a single non-interactive prompt
-crush run "Explain the use of context in Go"
+# Run in yolo mode (auto-accept all permissions; use with care)
+crush --yolo
-# Run in dangerous mode (auto-accept all permissions)
-crush -y
+# Run with custom data directory
+crush --data-dir /path/to/custom/.crush
`,
RunE: func(cmd *cobra.Command, args []string) error {
app, err := setupAppWithProgressBar(cmd)
@@ -192,11 +190,12 @@ func setupApp(cmd *cobra.Command) (*app.App, error) {
return nil, err
}
- cfg, err := config.Init(cwd, dataDir, debug)
+ store, err := config.Init(cwd, dataDir, debug)
if err != nil {
return nil, err
}
+ cfg := store.Config()
if cfg.Permissions == nil {
cfg.Permissions = &config.Permissions{}
}
@@ -218,7 +217,7 @@ func setupApp(cmd *cobra.Command) (*app.App, error) {
return nil, err
}
- appInstance, err := app.New(ctx, conn, cfg)
+ appInstance, err := app.New(ctx, conn, store)
if err != nil {
slog.Error("Failed to create app instance", "error", err)
return nil, err
@@ -20,7 +20,7 @@ var runCmd = &cobra.Command{
The prompt can be provided as arguments or piped from stdin.`,
Example: `
# Run a simple prompt
-crush run Explain the use of context in Go
+crush run "Guess my 5 favorite Pokémon"
# Pipe input from stdin
curl https://charm.land | crush run "Summarize this website"
@@ -28,10 +28,13 @@ curl https://charm.land | crush run "Summarize this website"
# Read from a file
crush run "What is this code doing?" <<< prrr.go
+# Redirect output to a file
+crush run "Generate a hot README for this project" > MY_HOT_README.md
+
# Run in quiet mode (hide the spinner)
crush run --quiet "Generate a README for this project"
-# Run in verbose mode
+# Run in verbose mode (show logs)
crush run --verbose "Generate a README for this project"
`,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -75,9 +78,6 @@ crush run --verbose "Generate a README for this project"
return app.RunNonInteractive(ctx, os.Stdout, prompt, largeModel, smallModel, quiet || verbose)
},
- PostRun: func(cmd *cobra.Command, args []string) {
- event.AppExited()
- },
}
func init() {
@@ -0,0 +1,641 @@
+package cmd
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "runtime"
+ "strings"
+ "syscall"
+ "time"
+
+ "charm.land/lipgloss/v2"
+ "github.com/charmbracelet/colorprofile"
+ "github.com/charmbracelet/crush/internal/config"
+ "github.com/charmbracelet/crush/internal/db"
+ "github.com/charmbracelet/crush/internal/message"
+ "github.com/charmbracelet/crush/internal/session"
+ "github.com/charmbracelet/crush/internal/ui/chat"
+ "github.com/charmbracelet/crush/internal/ui/styles"
+ "github.com/charmbracelet/x/ansi"
+ "github.com/charmbracelet/x/exp/charmtone"
+ "github.com/charmbracelet/x/term"
+ "github.com/spf13/cobra"
+)
+
+var sessionCmd = &cobra.Command{
+ Use: "session",
+ Aliases: []string{"sessions"},
+ Short: "Manage sessions",
+ Long: "Manage Crush sessions. Agents can use --json for machine-readable output.",
+}
+
+var (
+ sessionListJSON bool
+ sessionShowJSON bool
+ sessionLastJSON bool
+ sessionDeleteJSON bool
+ sessionRenameJSON bool
+)
+
+var sessionListCmd = &cobra.Command{
+ Use: "list",
+ Aliases: []string{"ls"},
+ Short: "List all sessions",
+ Long: "List all sessions. Use --json for machine-readable output.",
+ RunE: runSessionList,
+}
+
+var sessionShowCmd = &cobra.Command{
+ Use: "show <id>",
+ Short: "Show session details",
+ Long: "Show session details. Use --json for machine-readable output. ID can be a UUID, full hash, or hash prefix.",
+ Args: cobra.ExactArgs(1),
+ RunE: runSessionShow,
+}
+
+var sessionLastCmd = &cobra.Command{
+ Use: "last",
+ Short: "Show most recent session",
+ Long: "Show the last updated session. Use --json for machine-readable output.",
+ RunE: runSessionLast,
+}
+
+var sessionDeleteCmd = &cobra.Command{
+ Use: "delete <id>",
+ Aliases: []string{"rm"},
+ Short: "Delete a session",
+ Long: "Delete a session by ID. Use --json for machine-readable output. ID can be a UUID, full hash, or hash prefix.",
+ Args: cobra.ExactArgs(1),
+ RunE: runSessionDelete,
+}
+
+var sessionRenameCmd = &cobra.Command{
+ Use: "rename <id> <title>",
+ Short: "Rename a session",
+ Long: "Rename a session by ID. Use --json for machine-readable output. ID can be a UUID, full hash, or hash prefix.",
+ Args: cobra.MinimumNArgs(2),
+ RunE: runSessionRename,
+}
+
+func init() {
+ sessionListCmd.Flags().BoolVar(&sessionListJSON, "json", false, "output in JSON format")
+ sessionShowCmd.Flags().BoolVar(&sessionShowJSON, "json", false, "output in JSON format")
+ sessionLastCmd.Flags().BoolVar(&sessionLastJSON, "json", false, "output in JSON format")
+ sessionDeleteCmd.Flags().BoolVar(&sessionDeleteJSON, "json", false, "output in JSON format")
+ sessionRenameCmd.Flags().BoolVar(&sessionRenameJSON, "json", false, "output in JSON format")
+ sessionCmd.AddCommand(sessionListCmd)
+ sessionCmd.AddCommand(sessionShowCmd)
+ sessionCmd.AddCommand(sessionLastCmd)
+ sessionCmd.AddCommand(sessionDeleteCmd)
+ sessionCmd.AddCommand(sessionRenameCmd)
+}
+
+type sessionServices struct {
+ sessions session.Service
+ messages message.Service
+}
+
+func sessionSetup(cmd *cobra.Command) (context.Context, *sessionServices, func(), error) {
+ dataDir, _ := cmd.Flags().GetString("data-dir")
+ ctx := cmd.Context()
+
+ if dataDir == "" {
+ cfg, err := config.Init("", "", false)
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("failed to initialize config: %w", err)
+ }
+ dataDir = cfg.Config().Options.DataDirectory
+ }
+
+ conn, err := db.Connect(ctx, dataDir)
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("failed to connect to database: %w", err)
+ }
+
+ queries := db.New(conn)
+ svc := &sessionServices{
+ sessions: session.NewService(queries, conn),
+ messages: message.NewService(queries),
+ }
+ return ctx, svc, func() { conn.Close() }, nil
+}
+
+func runSessionList(cmd *cobra.Command, _ []string) error {
+ ctx, svc, cleanup, err := sessionSetup(cmd)
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ list, err := svc.sessions.List(ctx)
+ if err != nil {
+ return fmt.Errorf("failed to list sessions: %w", err)
+ }
+
+ if sessionListJSON {
+ out := cmd.OutOrStdout()
+ output := make([]sessionJSON, len(list))
+ for i, s := range list {
+ output[i] = sessionJSON{
+ ID: session.HashID(s.ID),
+ UUID: s.ID,
+ Title: s.Title,
+ Created: time.Unix(s.CreatedAt, 0).Format(time.RFC3339),
+ Modified: time.Unix(s.UpdatedAt, 0).Format(time.RFC3339),
+ }
+ }
+ enc := json.NewEncoder(out)
+ enc.SetEscapeHTML(false)
+ return enc.Encode(output)
+ }
+
+ w, cleanup, usingPager := sessionWriter(ctx, len(list))
+ defer cleanup()
+
+ hashStyle := lipgloss.NewStyle().Foreground(charmtone.Malibu)
+ dateStyle := lipgloss.NewStyle().Foreground(charmtone.Damson)
+
+ width := sessionOutputWidth
+ if tw, _, err := term.GetSize(os.Stdout.Fd()); err == nil && tw > 0 {
+ width = tw
+ }
+ // 7 (hash) + 1 (space) + 25 (RFC3339 date) + 1 (space) = 34 chars prefix.
+ titleWidth := width - 34
+ if titleWidth < 10 {
+ titleWidth = 10
+ }
+
+ var writeErr error
+ for _, s := range list {
+ hash := session.HashID(s.ID)[:7]
+ date := time.Unix(s.CreatedAt, 0).Format(time.RFC3339)
+ title := strings.ReplaceAll(s.Title, "\n", " ")
+ title = ansi.Truncate(title, titleWidth, "…")
+ _, writeErr = fmt.Fprintln(w, hashStyle.Render(hash), dateStyle.Render(date), title)
+ if writeErr != nil {
+ break
+ }
+ }
+ if writeErr != nil && usingPager && isBrokenPipe(writeErr) {
+ return nil
+ }
+ return writeErr
+}
+
+type sessionJSON struct {
+ ID string `json:"id"`
+ UUID string `json:"uuid"`
+ Title string `json:"title"`
+ Created string `json:"created"`
+ Modified string `json:"modified"`
+}
+
+type sessionMutationResult struct {
+ ID string `json:"id"`
+ UUID string `json:"uuid"`
+ Title string `json:"title"`
+ Deleted bool `json:"deleted,omitempty"`
+ Renamed bool `json:"renamed,omitempty"`
+}
+
+// resolveSessionID resolves a session ID that can be a UUID, full hash, or hash prefix.
+// Returns an error if the prefix is ambiguous (matches multiple sessions).
+func resolveSessionID(ctx context.Context, svc session.Service, id string) (session.Session, error) {
+ // Try direct UUID lookup first
+ if s, err := svc.Get(ctx, id); err == nil {
+ return s, nil
+ }
+
+ // List all sessions and check for hash matches
+ sessions, err := svc.List(ctx)
+ if err != nil {
+ return session.Session{}, err
+ }
+
+ var matches []session.Session
+ for _, s := range sessions {
+ hash := session.HashID(s.ID)
+ if hash == id || strings.HasPrefix(hash, id) {
+ matches = append(matches, s)
+ }
+ }
+
+ if len(matches) == 0 {
+ return session.Session{}, fmt.Errorf("session not found: %s", id)
+ }
+
+ if len(matches) == 1 {
+ return matches[0], nil
+ }
+
+ // Ambiguous - show matches like Git does
+ var sb strings.Builder
+ fmt.Fprintf(&sb, "session ID '%s' is ambiguous. Matches:\n\n", id)
+ for _, m := range matches {
+ hash := session.HashID(m.ID)
+ created := time.Unix(m.CreatedAt, 0).Format("2006-01-02")
+ // Keep title on one line by replacing newlines with spaces, and truncate.
+ title := strings.ReplaceAll(m.Title, "\n", " ")
+ title = ansi.Truncate(title, 50, "…")
+ fmt.Fprintf(&sb, " %s... %q (created %s)\n", hash[:12], title, created)
+ }
+ sb.WriteString("\nUse more characters or the full hash")
+ return session.Session{}, errors.New(sb.String())
+}
+
+func runSessionShow(cmd *cobra.Command, args []string) error {
+ ctx, svc, cleanup, err := sessionSetup(cmd)
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ sess, err := resolveSessionID(ctx, svc.sessions, args[0])
+ if err != nil {
+ return err
+ }
+
+ msgs, err := svc.messages.List(ctx, sess.ID)
+ if err != nil {
+ return fmt.Errorf("failed to list messages: %w", err)
+ }
+
+ msgPtrs := messagePtrs(msgs)
+ if sessionShowJSON {
+ return outputSessionJSON(cmd.OutOrStdout(), sess, msgPtrs)
+ }
+ return outputSessionHuman(ctx, sess, msgPtrs)
+}
+
+func runSessionDelete(cmd *cobra.Command, args []string) error {
+ ctx, svc, cleanup, err := sessionSetup(cmd)
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ sess, err := resolveSessionID(ctx, svc.sessions, args[0])
+ if err != nil {
+ return err
+ }
+
+ if err := svc.sessions.Delete(ctx, sess.ID); err != nil {
+ return fmt.Errorf("failed to delete session: %w", err)
+ }
+
+ out := cmd.OutOrStdout()
+ if sessionDeleteJSON {
+ enc := json.NewEncoder(out)
+ enc.SetEscapeHTML(false)
+ return enc.Encode(sessionMutationResult{
+ ID: session.HashID(sess.ID),
+ UUID: sess.ID,
+ Title: sess.Title,
+ Deleted: true,
+ })
+ }
+
+ fmt.Fprintf(out, "Deleted session %s\n", session.HashID(sess.ID)[:12])
+ return nil
+}
+
+func runSessionRename(cmd *cobra.Command, args []string) error {
+ ctx, svc, cleanup, err := sessionSetup(cmd)
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ sess, err := resolveSessionID(ctx, svc.sessions, args[0])
+ if err != nil {
+ return err
+ }
+
+ newTitle := strings.Join(args[1:], " ")
+ if err := svc.sessions.Rename(ctx, sess.ID, newTitle); err != nil {
+ return fmt.Errorf("failed to rename session: %w", err)
+ }
+
+ out := cmd.OutOrStdout()
+ if sessionRenameJSON {
+ enc := json.NewEncoder(out)
+ enc.SetEscapeHTML(false)
+ return enc.Encode(sessionMutationResult{
+ ID: session.HashID(sess.ID),
+ UUID: sess.ID,
+ Title: newTitle,
+ Renamed: true,
+ })
+ }
+
+ fmt.Fprintf(out, "Renamed session %s to %q\n", session.HashID(sess.ID)[:12], newTitle)
+ return nil
+}
+
+func runSessionLast(cmd *cobra.Command, _ []string) error {
+ ctx, svc, cleanup, err := sessionSetup(cmd)
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ list, err := svc.sessions.List(ctx)
+ if err != nil {
+ return fmt.Errorf("failed to list sessions: %w", err)
+ }
+
+ if len(list) == 0 {
+ return fmt.Errorf("no sessions found")
+ }
+
+ sess := list[0]
+
+ msgs, err := svc.messages.List(ctx, sess.ID)
+ if err != nil {
+ return fmt.Errorf("failed to list messages: %w", err)
+ }
+
+ msgPtrs := messagePtrs(msgs)
+ if sessionLastJSON {
+ return outputSessionJSON(cmd.OutOrStdout(), sess, msgPtrs)
+ }
+ return outputSessionHuman(ctx, sess, msgPtrs)
+}
+
+const (
+ sessionOutputWidth = 80
+ sessionMaxContentWidth = 120
+)
+
+func messagePtrs(msgs []message.Message) []*message.Message {
+ ptrs := make([]*message.Message, len(msgs))
+ for i := range msgs {
+ ptrs[i] = &msgs[i]
+ }
+ return ptrs
+}
+
+func outputSessionJSON(w io.Writer, sess session.Session, msgs []*message.Message) error {
+ output := sessionShowOutput{
+ Meta: sessionShowMeta{
+ ID: session.HashID(sess.ID),
+ UUID: sess.ID,
+ Title: sess.Title,
+ Created: time.Unix(sess.CreatedAt, 0).Format(time.RFC3339),
+ Modified: time.Unix(sess.UpdatedAt, 0).Format(time.RFC3339),
+ Cost: sess.Cost,
+ PromptTokens: sess.PromptTokens,
+ CompletionTokens: sess.CompletionTokens,
+ TotalTokens: sess.PromptTokens + sess.CompletionTokens,
+ },
+ Messages: make([]sessionShowMessage, len(msgs)),
+ }
+
+ for i, msg := range msgs {
+ output.Messages[i] = sessionShowMessage{
+ ID: msg.ID,
+ Role: string(msg.Role),
+ Created: time.Unix(msg.CreatedAt, 0).Format(time.RFC3339),
+ Model: msg.Model,
+ Provider: msg.Provider,
+ Parts: convertParts(msg.Parts),
+ }
+ }
+
+ enc := json.NewEncoder(w)
+ enc.SetEscapeHTML(false)
+ return enc.Encode(output)
+}
+
+func outputSessionHuman(ctx context.Context, sess session.Session, msgs []*message.Message) error {
+ sty := styles.DefaultStyles()
+ toolResults := chat.BuildToolResultMap(msgs)
+
+ width := sessionOutputWidth
+ if w, _, err := term.GetSize(os.Stdout.Fd()); err == nil && w > 0 {
+ width = w
+ }
+ contentWidth := min(width, sessionMaxContentWidth)
+
+ keyStyle := lipgloss.NewStyle().Foreground(charmtone.Damson)
+ valStyle := lipgloss.NewStyle().Foreground(charmtone.Malibu)
+
+ hash := session.HashID(sess.ID)[:12]
+ created := time.Unix(sess.CreatedAt, 0).Format("Mon Jan 2 15:04:05 2006 -0700")
+
+ // Render to buffer to determine actual height
+ var buf strings.Builder
+
+ fmt.Fprintln(&buf, keyStyle.Render("ID: ")+valStyle.Render(hash))
+ fmt.Fprintln(&buf, keyStyle.Render("UUID: ")+valStyle.Render(sess.ID))
+ fmt.Fprintln(&buf, keyStyle.Render("Title: ")+valStyle.Render(sess.Title))
+ fmt.Fprintln(&buf, keyStyle.Render("Date: ")+valStyle.Render(created))
+ fmt.Fprintln(&buf)
+
+ first := true
+ for _, msg := range msgs {
+ items := chat.ExtractMessageItems(&sty, msg, toolResults)
+ for _, item := range items {
+ if !first {
+ fmt.Fprintln(&buf)
+ }
+ first = false
+ fmt.Fprintln(&buf, item.Render(contentWidth))
+ }
+ }
+ fmt.Fprintln(&buf)
+
+ contentHeight := strings.Count(buf.String(), "\n")
+ w, cleanup, usingPager := sessionWriter(ctx, contentHeight)
+ defer cleanup()
+
+ _, err := io.WriteString(w, buf.String())
+ // Ignore broken pipe errors when using a pager. This happens when the user
+ // exits the pager early (e.g., pressing 'q' in less), which closes the pipe
+ // and causes subsequent writes to fail. These errors are expected user behavior.
+ if err != nil && usingPager && isBrokenPipe(err) {
+ return nil
+ }
+ return err
+}
+
+func isBrokenPipe(err error) bool {
+ if err == nil {
+ return false
+ }
+ // Check for syscall.EPIPE (broken pipe)
+ if errors.Is(err, syscall.EPIPE) {
+ return true
+ }
+ // Also check for "broken pipe" in the error message
+ return strings.Contains(err.Error(), "broken pipe")
+}
+
+// sessionWriter returns a writer, cleanup function, and a bool indicating if a pager is used.
+// When the content fits within the terminal (or stdout is not a TTY), it returns
+// a colorprofile.Writer wrapping stdout. When content exceeds terminal height,
+// it starts a pager process (respecting $PAGER, defaulting to "less -R").
+func sessionWriter(ctx context.Context, contentHeight int) (io.Writer, func(), bool) {
+ // Use NewWriter which automatically detects TTY and strips ANSI when redirected
+ if runtime.GOOS == "windows" || !term.IsTerminal(os.Stdout.Fd()) {
+ return colorprofile.NewWriter(os.Stdout, os.Environ()), func() {}, false
+ }
+
+ _, termHeight, err := term.GetSize(os.Stdout.Fd())
+ if err != nil || contentHeight <= termHeight {
+ return colorprofile.NewWriter(os.Stdout, os.Environ()), func() {}, false
+ }
+
+ // Detect color profile from stderr since stdout is piped to the pager.
+ profile := colorprofile.Detect(os.Stderr, os.Environ())
+
+ pager := os.Getenv("PAGER")
+ if pager == "" {
+ pager = "less -R"
+ }
+
+ parts := strings.Fields(pager)
+ cmd := exec.CommandContext(ctx, parts[0], parts[1:]...) //nolint:gosec
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ pipe, err := cmd.StdinPipe()
+ if err != nil {
+ return colorprofile.NewWriter(os.Stdout, os.Environ()), func() {}, false
+ }
+
+ if err := cmd.Start(); err != nil {
+ return colorprofile.NewWriter(os.Stdout, os.Environ()), func() {}, false
+ }
+
+ return &colorprofile.Writer{
+ Forward: pipe,
+ Profile: profile,
+ }, func() {
+ pipe.Close()
+ _ = cmd.Wait()
+ }, true
+}
+
+type sessionShowMeta struct {
+ ID string `json:"id"`
+ UUID string `json:"uuid"`
+ Title string `json:"title"`
+ Created string `json:"created"`
+ Modified string `json:"modified"`
+ Cost float64 `json:"cost"`
+ PromptTokens int64 `json:"prompt_tokens"`
+ CompletionTokens int64 `json:"completion_tokens"`
+ TotalTokens int64 `json:"total_tokens"`
+}
+
+type sessionShowMessage struct {
+ ID string `json:"id"`
+ Role string `json:"role"`
+ Created string `json:"created"`
+ Model string `json:"model,omitempty"`
+ Provider string `json:"provider,omitempty"`
+ Parts []sessionShowPart `json:"parts"`
+}
+
+type sessionShowPart struct {
+ Type string `json:"type"`
+
+ // Text content
+ Text string `json:"text,omitempty"`
+
+ // Reasoning
+ Thinking string `json:"thinking,omitempty"`
+ StartedAt int64 `json:"started_at,omitempty"`
+ FinishedAt int64 `json:"finished_at,omitempty"`
+
+ // Tool call
+ ToolCallID string `json:"tool_call_id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Input string `json:"input,omitempty"`
+
+ // Tool result
+ Content string `json:"content,omitempty"`
+ IsError bool `json:"is_error,omitempty"`
+ MIMEType string `json:"mime_type,omitempty"`
+
+ // Binary
+ Size int64 `json:"size,omitempty"`
+
+ // Image URL
+ URL string `json:"url,omitempty"`
+ Detail string `json:"detail,omitempty"`
+
+ // Finish
+ Reason string `json:"reason,omitempty"`
+ Time int64 `json:"time,omitempty"`
+}
+
+func convertParts(parts []message.ContentPart) []sessionShowPart {
+ result := make([]sessionShowPart, 0, len(parts))
+ for _, part := range parts {
+ switch p := part.(type) {
+ case message.TextContent:
+ result = append(result, sessionShowPart{
+ Type: "text",
+ Text: p.Text,
+ })
+ case message.ReasoningContent:
+ result = append(result, sessionShowPart{
+ Type: "reasoning",
+ Thinking: p.Thinking,
+ StartedAt: p.StartedAt,
+ FinishedAt: p.FinishedAt,
+ })
+ case message.ToolCall:
+ result = append(result, sessionShowPart{
+ Type: "tool_call",
+ ToolCallID: p.ID,
+ Name: p.Name,
+ Input: p.Input,
+ })
+ case message.ToolResult:
+ result = append(result, sessionShowPart{
+ Type: "tool_result",
+ ToolCallID: p.ToolCallID,
+ Name: p.Name,
+ Content: p.Content,
+ IsError: p.IsError,
+ MIMEType: p.MIMEType,
+ })
+ case message.BinaryContent:
+ result = append(result, sessionShowPart{
+ Type: "binary",
+ MIMEType: p.MIMEType,
+ Size: int64(len(p.Data)),
+ })
+ case message.ImageURLContent:
+ result = append(result, sessionShowPart{
+ Type: "image_url",
+ URL: p.URL,
+ Detail: p.Detail,
+ })
+ case message.Finish:
+ result = append(result, sessionShowPart{
+ Type: "finish",
+ Reason: string(p.Reason),
+ Time: p.Time,
+ })
+ default:
+ result = append(result, sessionShowPart{
+ Type: "unknown",
+ })
+ }
+ }
+ return result
+}
+
+type sessionShowOutput struct {
+ Meta sessionShowMeta `json:"meta"`
+ Messages []sessionShowMessage `json:"messages"`
+}
@@ -131,7 +131,7 @@ func runStats(cmd *cobra.Command, _ []string) error {
if err != nil {
return fmt.Errorf("failed to initialize config: %w", err)
}
- dataDir = cfg.Options.DataDirectory
+ dataDir = cfg.Config().Options.DataDirectory
}
conn, err := db.Connect(ctx, dataDir)
@@ -227,7 +227,7 @@ func isMarkdownFile(name string) bool {
return strings.HasSuffix(strings.ToLower(name), ".md")
}
-func GetMCPPrompt(cfg *config.Config, clientID, promptID string, args map[string]string) (string, error) {
+func GetMCPPrompt(cfg *config.ConfigStore, clientID, promptID string, args map[string]string) (string, error) {
// TODO: we should pass the context down
result, err := mcp.GetPromptMessages(context.Background(), cfg, clientID, promptID, args)
if err != nil {
@@ -8,22 +8,16 @@ import (
"maps"
"net/http"
"net/url"
- "os"
- "path/filepath"
"slices"
"strings"
"time"
"charm.land/catwalk/pkg/catwalk"
- hyperp "github.com/charmbracelet/crush/internal/agent/hyper"
"github.com/charmbracelet/crush/internal/csync"
"github.com/charmbracelet/crush/internal/env"
"github.com/charmbracelet/crush/internal/oauth"
"github.com/charmbracelet/crush/internal/oauth/copilot"
- "github.com/charmbracelet/crush/internal/oauth/hyper"
"github.com/invopop/jsonschema"
- "github.com/tidwall/gjson"
- "github.com/tidwall/sjson"
)
const (
@@ -261,6 +255,7 @@ type Options struct {
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"`
AutoLSP *bool `json:"auto_lsp,omitempty" jsonschema:"description=Automatically setup LSPs based on root markers,default=true"`
Progress *bool `json:"progress,omitempty" jsonschema:"description=Show indeterminate progress updates during long operations,default=true"`
+ DisableNotifications bool `json:"disable_notifications,omitempty" jsonschema:"description=Disable desktop notifications,default=false"`
}
type MCPs map[string]MCPConfig
@@ -397,17 +392,6 @@ type Config struct {
Tools Tools `json:"tools,omitzero" jsonschema:"description=Tool configurations"`
Agents map[string]Agent `json:"-"`
-
- // Internal
- workingDir string `json:"-"`
- // TODO: find a better way to do this this should probably not be part of the config
- resolver VariableResolver
- dataConfigDir string `json:"-"`
- knownProviders []catwalk.Provider `json:"-"`
-}
-
-func (c *Config) WorkingDir() string {
- return c.workingDir
}
func (c *Config) EnabledProviders() []ProviderConfig {
@@ -471,235 +455,8 @@ func (c *Config) SmallModel() *catwalk.Model {
return c.GetModel(model.Provider, model.Model)
}
-func (c *Config) SetCompactMode(enabled bool) error {
- if c.Options == nil {
- c.Options = &Options{}
- }
- c.Options.TUI.CompactMode = enabled
- return c.SetConfigField("options.tui.compact_mode", enabled)
-}
-
-func (c *Config) Resolve(key string) (string, error) {
- if c.resolver == nil {
- return "", fmt.Errorf("no variable resolver configured")
- }
- return c.resolver.ResolveValue(key)
-}
-
-func (c *Config) UpdatePreferredModel(modelType SelectedModelType, model SelectedModel) error {
- c.Models[modelType] = model
- 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
-}
-
-func (c *Config) HasConfigField(key string) bool {
- data, err := os.ReadFile(c.dataConfigDir)
- if err != nil {
- return false
- }
- return gjson.Get(string(data), key).Exists()
-}
-
-func (c *Config) SetConfigField(key string, value any) error {
- data, err := os.ReadFile(c.dataConfigDir)
- if err != nil {
- if os.IsNotExist(err) {
- data = []byte("{}")
- } else {
- return fmt.Errorf("failed to read config file: %w", err)
- }
- }
-
- newValue, err := sjson.Set(string(data), key, value)
- if err != nil {
- return fmt.Errorf("failed to set config field %s: %w", key, err)
- }
- if err := os.MkdirAll(filepath.Dir(c.dataConfigDir), 0o755); err != nil {
- return fmt.Errorf("failed to create config directory %q: %w", c.dataConfigDir, err)
- }
- if err := os.WriteFile(c.dataConfigDir, []byte(newValue), 0o600); err != nil {
- return fmt.Errorf("failed to write config file: %w", err)
- }
- return nil
-}
-
-func (c *Config) RemoveConfigField(key string) error {
- data, err := os.ReadFile(c.dataConfigDir)
- if err != nil {
- return fmt.Errorf("failed to read config file: %w", err)
- }
-
- newValue, err := sjson.Delete(string(data), key)
- if err != nil {
- return fmt.Errorf("failed to delete config field %s: %w", key, err)
- }
- if err := os.MkdirAll(filepath.Dir(c.dataConfigDir), 0o755); err != nil {
- return fmt.Errorf("failed to create config directory %q: %w", c.dataConfigDir, err)
- }
- if err := os.WriteFile(c.dataConfigDir, []byte(newValue), 0o600); err != nil {
- return fmt.Errorf("failed to write config file: %w", err)
- }
- return nil
-}
-
-// RefreshOAuthToken refreshes the OAuth token for the given provider.
-func (c *Config) RefreshOAuthToken(ctx context.Context, providerID string) error {
- providerConfig, exists := c.Providers.Get(providerID)
- if !exists {
- return fmt.Errorf("provider %s not found", providerID)
- }
-
- if providerConfig.OAuthToken == nil {
- return fmt.Errorf("provider %s does not have an OAuth token", providerID)
- }
-
- var newToken *oauth.Token
- var refreshErr error
- switch providerID {
- case string(catwalk.InferenceProviderCopilot):
- newToken, refreshErr = copilot.RefreshToken(ctx, providerConfig.OAuthToken.RefreshToken)
- case hyperp.Name:
- newToken, refreshErr = hyper.ExchangeToken(ctx, providerConfig.OAuthToken.RefreshToken)
- default:
- return fmt.Errorf("OAuth refresh not supported for provider %s", providerID)
- }
- if refreshErr != nil {
- return fmt.Errorf("failed to refresh OAuth token for provider %s: %w", providerID, refreshErr)
- }
-
- slog.Info("Successfully refreshed OAuth token", "provider", providerID)
- providerConfig.OAuthToken = newToken
- providerConfig.APIKey = newToken.AccessToken
-
- switch providerID {
- case string(catwalk.InferenceProviderCopilot):
- providerConfig.SetupGitHubCopilot()
- }
-
- c.Providers.Set(providerID, providerConfig)
-
- if err := cmp.Or(
- c.SetConfigField(fmt.Sprintf("providers.%s.api_key", providerID), newToken.AccessToken),
- c.SetConfigField(fmt.Sprintf("providers.%s.oauth", providerID), newToken),
- ); err != nil {
- return fmt.Errorf("failed to persist refreshed token: %w", err)
- }
-
- return nil
-}
-
-func (c *Config) SetProviderAPIKey(providerID string, apiKey any) error {
- var providerConfig ProviderConfig
- var exists bool
- var setKeyOrToken func()
-
- switch v := apiKey.(type) {
- case string:
- if err := c.SetConfigField(fmt.Sprintf("providers.%s.api_key", providerID), v); err != nil {
- return fmt.Errorf("failed to save api key to config file: %w", err)
- }
- setKeyOrToken = func() { providerConfig.APIKey = v }
- case *oauth.Token:
- if err := cmp.Or(
- c.SetConfigField(fmt.Sprintf("providers.%s.api_key", providerID), v.AccessToken),
- c.SetConfigField(fmt.Sprintf("providers.%s.oauth", providerID), v),
- ); err != nil {
- return err
- }
- setKeyOrToken = func() {
- providerConfig.APIKey = v.AccessToken
- providerConfig.OAuthToken = v
- switch providerID {
- case string(catwalk.InferenceProviderCopilot):
- providerConfig.SetupGitHubCopilot()
- }
- }
- }
-
- providerConfig, exists = c.Providers.Get(providerID)
- if exists {
- setKeyOrToken()
- c.Providers.Set(providerID, providerConfig)
- return nil
- }
-
- var foundProvider *catwalk.Provider
- for _, p := range c.knownProviders {
- if string(p.ID) == providerID {
- foundProvider = &p
- break
- }
- }
-
- if foundProvider != nil {
- // Create new provider config based on known provider
- providerConfig = ProviderConfig{
- ID: providerID,
- Name: foundProvider.Name,
- BaseURL: foundProvider.APIEndpoint,
- Type: foundProvider.Type,
- Disable: false,
- ExtraHeaders: make(map[string]string),
- ExtraParams: make(map[string]string),
- Models: foundProvider.Models,
- }
- setKeyOrToken()
- } else {
- return fmt.Errorf("provider with ID %s not found in known providers", providerID)
- }
- // Store the updated provider config
- c.Providers.Set(providerID, providerConfig)
- 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",
@@ -779,10 +536,6 @@ func (c *Config) SetupAgents() {
c.Agents = agents
}
-func (c *Config) Resolver() VariableResolver {
- return c.resolver
-}
-
func (c *ProviderConfig) TestConnection(resolver VariableResolver) error {
var (
providerID = catwalk.InferenceProvider(c.ID)
@@ -1,48 +1 @@
package config
-
-import (
- "cmp"
- "context"
- "log/slog"
- "testing"
-
- "charm.land/catwalk/pkg/catwalk"
- "github.com/charmbracelet/crush/internal/oauth"
- "github.com/charmbracelet/crush/internal/oauth/copilot"
-)
-
-func (c *Config) ImportCopilot() (*oauth.Token, bool) {
- if testing.Testing() {
- return nil, false
- }
-
- if c.HasConfigField("providers.copilot.api_key") || c.HasConfigField("providers.copilot.oauth") {
- return nil, false
- }
-
- diskToken, hasDiskToken := copilot.RefreshTokenFromDisk()
- if !hasDiskToken {
- return nil, false
- }
-
- slog.Info("Found existing GitHub Copilot token on disk. Authenticating...")
- token, err := copilot.RefreshToken(context.TODO(), diskToken)
- if err != nil {
- slog.Error("Unable to import GitHub Copilot token", "error", err)
- return nil, false
- }
-
- if err := c.SetProviderAPIKey(string(catwalk.InferenceProviderCopilot), token); err != nil {
- return token, false
- }
-
- if err := cmp.Or(
- c.SetConfigField("providers.copilot.api_key", token.AccessToken),
- c.SetConfigField("providers.copilot.oauth", token),
- ); err != nil {
- slog.Error("Unable to save GitHub Copilot token to disk", "error", err)
- }
-
- slog.Info("GitHub Copilot successfully imported")
- return token, true
-}
@@ -31,7 +31,7 @@ func (c *Config) IsDockerMCPEnabled() bool {
}
// EnableDockerMCP adds Docker MCP configuration and persists it.
-func (c *Config) EnableDockerMCP() error {
+func (s *ConfigStore) EnableDockerMCP() error {
if !IsDockerMCPAvailable() {
return fmt.Errorf("docker mcp is not available, please ensure docker is installed and 'docker mcp version' succeeds")
}
@@ -44,13 +44,13 @@ func (c *Config) EnableDockerMCP() error {
}
// Add to in-memory config.
- if c.MCP == nil {
- c.MCP = make(map[string]MCPConfig)
+ if s.config.MCP == nil {
+ s.config.MCP = make(map[string]MCPConfig)
}
- c.MCP[DockerMCPName] = mcpConfig
+ s.config.MCP[DockerMCPName] = mcpConfig
// Persist to config file.
- if err := c.SetConfigField("mcp."+DockerMCPName, mcpConfig); err != nil {
+ if err := s.SetConfigField(ScopeGlobal, "mcp."+DockerMCPName, mcpConfig); err != nil {
return fmt.Errorf("failed to persist docker mcp configuration: %w", err)
}
@@ -58,16 +58,16 @@ func (c *Config) EnableDockerMCP() error {
}
// DisableDockerMCP removes Docker MCP configuration and persists the change.
-func (c *Config) DisableDockerMCP() error {
- if c.MCP == nil {
+func (s *ConfigStore) DisableDockerMCP() error {
+ if s.config.MCP == nil {
return nil
}
// Remove from in-memory config.
- delete(c.MCP, DockerMCPName)
+ delete(s.config.MCP, DockerMCPName)
// Persist to config file by setting to null.
- if err := c.SetConfigField("mcp", c.MCP); err != nil {
+ if err := s.SetConfigField(ScopeGlobal, "mcp", s.config.MCP); err != nil {
return fmt.Errorf("failed to persist docker mcp removal: %w", err)
}
@@ -5,7 +5,6 @@ import (
"path/filepath"
"testing"
- "charm.land/catwalk/pkg/catwalk"
"github.com/charmbracelet/crush/internal/env"
"github.com/stretchr/testify/require"
)
@@ -54,10 +53,12 @@ func TestEnableDockerMCP(t *testing.T) {
configPath := filepath.Join(tmpDir, "crush.json")
cfg := &Config{
- MCP: make(map[string]MCPConfig),
- dataConfigDir: configPath,
+ MCP: make(map[string]MCPConfig),
+ }
+ store := &ConfigStore{
+ config: cfg,
+ globalDataPath: configPath,
resolver: NewShellVariableResolver(env.New()),
- knownProviders: []catwalk.Provider{},
}
// Only run this test if docker mcp is available.
@@ -65,7 +66,7 @@ func TestEnableDockerMCP(t *testing.T) {
t.Skip("Docker MCP not available, skipping test")
}
- err := cfg.EnableDockerMCP()
+ err := store.EnableDockerMCP()
require.NoError(t, err)
// Check in-memory config.
@@ -92,10 +93,12 @@ func TestEnableDockerMCP(t *testing.T) {
configPath := filepath.Join(tmpDir, "crush.json")
cfg := &Config{
- MCP: make(map[string]MCPConfig),
- dataConfigDir: configPath,
+ MCP: make(map[string]MCPConfig),
+ }
+ store := &ConfigStore{
+ config: cfg,
+ globalDataPath: configPath,
resolver: NewShellVariableResolver(env.New()),
- knownProviders: []catwalk.Provider{},
}
// Skip if docker mcp is actually available.
@@ -103,7 +106,7 @@ func TestEnableDockerMCP(t *testing.T) {
t.Skip("Docker MCP is available, skipping unavailable test")
}
- err := cfg.EnableDockerMCP()
+ err := store.EnableDockerMCP()
require.Error(t, err)
require.Contains(t, err.Error(), "docker mcp is not available")
})
@@ -128,15 +131,17 @@ func TestDisableDockerMCP(t *testing.T) {
Disabled: false,
},
},
- dataConfigDir: configPath,
+ }
+ store := &ConfigStore{
+ config: cfg,
+ globalDataPath: configPath,
resolver: NewShellVariableResolver(env.New()),
- knownProviders: []catwalk.Provider{},
}
// Verify it's enabled first.
require.True(t, cfg.IsDockerMCPEnabled())
- err := cfg.DisableDockerMCP()
+ err := store.DisableDockerMCP()
require.NoError(t, err)
// Check in-memory config.
@@ -149,13 +154,15 @@ func TestDisableDockerMCP(t *testing.T) {
t.Parallel()
cfg := &Config{
- MCP: nil,
- dataConfigDir: t.TempDir() + "/crush.json",
+ MCP: nil,
+ }
+ store := &ConfigStore{
+ config: cfg,
+ globalDataPath: filepath.Join(t.TempDir(), "crush.json"),
resolver: NewShellVariableResolver(env.New()),
- knownProviders: []catwalk.Provider{},
}
- err := cfg.DisableDockerMCP()
+ err := store.DisableDockerMCP()
require.NoError(t, err)
})
}
@@ -18,19 +18,20 @@ type ProjectInitFlag struct {
Initialized bool `json:"initialized"`
}
-func Init(workingDir, dataDir string, debug bool) (*Config, error) {
- cfg, err := Load(workingDir, dataDir, debug)
+func Init(workingDir, dataDir string, debug bool) (*ConfigStore, error) {
+ store, err := Load(workingDir, dataDir, debug)
if err != nil {
return nil, err
}
- return cfg, nil
+ return store, nil
}
-func ProjectNeedsInitialization(cfg *Config) (bool, error) {
- if cfg == nil {
+func ProjectNeedsInitialization(store *ConfigStore) (bool, error) {
+ if store == nil {
return false, fmt.Errorf("config not loaded")
}
+ cfg := store.Config()
flagFilePath := filepath.Join(cfg.Options.DataDirectory, InitFlagFilename)
_, err := os.Stat(flagFilePath)
@@ -42,7 +43,7 @@ func ProjectNeedsInitialization(cfg *Config) (bool, error) {
return false, fmt.Errorf("failed to check init flag file: %w", err)
}
- someContextFileExists, err := contextPathsExist(cfg.WorkingDir())
+ someContextFileExists, err := contextPathsExist(store.WorkingDir())
if err != nil {
return false, fmt.Errorf("failed to check for context files: %w", err)
}
@@ -51,7 +52,7 @@ func ProjectNeedsInitialization(cfg *Config) (bool, error) {
}
// If the working directory has no non-ignored files, skip initialization step
- empty, err := dirHasNoVisibleFiles(cfg.WorkingDir())
+ empty, err := dirHasNoVisibleFiles(store.WorkingDir())
if err != nil {
return false, fmt.Errorf("failed to check if directory is empty: %w", err)
}
@@ -90,7 +91,7 @@ func contextPathsExist(dir string) (bool, error) {
return false, nil
}
-// dirHasNoVisibleFiles returns true if the directory has no files/dirs after applying ignore rules
+// dirHasNoVisibleFiles returns true if the directory has no files/dirs after applying ignore rules.
func dirHasNoVisibleFiles(dir string) (bool, error) {
files, _, err := fsext.ListDirectory(dir, nil, 1, 1)
if err != nil {
@@ -99,11 +100,11 @@ func dirHasNoVisibleFiles(dir string) (bool, error) {
return len(files) == 0, nil
}
-func MarkProjectInitialized(cfg *Config) error {
- if cfg == nil {
+func MarkProjectInitialized(store *ConfigStore) error {
+ if store == nil {
return fmt.Errorf("config not loaded")
}
- flagFilePath := filepath.Join(cfg.Options.DataDirectory, InitFlagFilename)
+ flagFilePath := filepath.Join(store.Config().Options.DataDirectory, InitFlagFilename)
file, err := os.Create(flagFilePath)
if err != nil {
@@ -114,13 +115,13 @@ func MarkProjectInitialized(cfg *Config) error {
return nil
}
-func HasInitialDataConfig(cfg *Config) bool {
- if cfg == nil {
+func HasInitialDataConfig(store *ConfigStore) bool {
+ if store == nil {
return false
}
cfgPath := GlobalConfigData()
if _, err := os.Stat(cfgPath); err != nil {
return false
}
- return cfg.IsConfigured()
+ return store.Config().IsConfigured()
}
@@ -29,8 +29,9 @@ import (
const defaultCatwalkURL = "https://catwalk.charm.sh"
-// Load loads the configuration from the default paths.
-func Load(workingDir, dataDir string, debug bool) (*Config, error) {
+// Load loads the configuration from the default paths and returns a
+// ConfigStore that owns both the pure-data Config and all runtime state.
+func Load(workingDir, dataDir string, debug bool) (*ConfigStore, error) {
configPaths := lookupConfigs(workingDir)
cfg, err := loadFromConfigPaths(configPaths)
@@ -38,10 +39,15 @@ func Load(workingDir, dataDir string, debug bool) (*Config, error) {
return nil, fmt.Errorf("failed to load config from paths %v: %w", configPaths, err)
}
- cfg.dataConfigDir = GlobalConfigData()
-
cfg.setDefaults(workingDir, dataDir)
+ store := &ConfigStore{
+ config: cfg,
+ workingDir: workingDir,
+ globalDataPath: GlobalConfigData(),
+ workspacePath: filepath.Join(cfg.Options.DataDirectory, fmt.Sprintf("%s.json", appName)),
+ }
+
if debug {
cfg.Options.Debug = true
}
@@ -52,6 +58,18 @@ func Load(workingDir, dataDir string, debug bool) (*Config, error) {
cfg.Options.Debug,
)
+ // Load workspace config last so it has highest priority.
+ if wsData, err := os.ReadFile(store.workspacePath); err == nil && len(wsData) > 0 {
+ merged, mergeErr := loadFromBytes(append([][]byte{mustMarshalConfig(cfg)}, wsData))
+ if mergeErr == nil {
+ // Preserve defaults that setDefaults already applied.
+ dataDir := cfg.Options.DataDirectory
+ *cfg = *merged
+ cfg.setDefaults(workingDir, dataDir)
+ store.config = cfg
+ }
+ }
+
if !isInsideWorktree() {
const depth = 2
const items = 100
@@ -72,26 +90,36 @@ func Load(workingDir, dataDir string, debug bool) (*Config, error) {
if err != nil {
return nil, err
}
- cfg.knownProviders = providers
+ store.knownProviders = providers
env := env.New()
// Configure providers
valueResolver := NewShellVariableResolver(env)
- cfg.resolver = valueResolver
- if err := cfg.configureProviders(env, valueResolver, cfg.knownProviders); err != nil {
+ store.resolver = valueResolver
+ if err := cfg.configureProviders(store, env, valueResolver, store.knownProviders); err != nil {
return nil, fmt.Errorf("failed to configure providers: %w", err)
}
if !cfg.IsConfigured() {
slog.Warn("No providers configured")
- return cfg, nil
+ return store, nil
}
- if err := cfg.configureSelectedModels(cfg.knownProviders); err != nil {
+ if err := configureSelectedModels(store, store.knownProviders); err != nil {
return nil, fmt.Errorf("failed to configure selected models: %w", err)
}
- cfg.SetupAgents()
- return cfg, nil
+ store.SetupAgents()
+ return store, nil
+}
+
+// mustMarshalConfig marshals the config to JSON bytes, returning empty JSON on
+// error.
+func mustMarshalConfig(cfg *Config) []byte {
+ data, err := json.Marshal(cfg)
+ if err != nil {
+ return []byte("{}")
+ }
+ return data
}
func PushPopCrushEnv() func() {
@@ -122,7 +150,7 @@ func PushPopCrushEnv() func() {
return restore
}
-func (c *Config) configureProviders(env env.Env, resolver VariableResolver, knownProviders []catwalk.Provider) error {
+func (c *Config) configureProviders(store *ConfigStore, env env.Env, resolver VariableResolver, knownProviders []catwalk.Provider) error {
knownProviderNames := make(map[string]bool)
restore := PushPopCrushEnv()
defer restore()
@@ -209,7 +237,7 @@ func (c *Config) configureProviders(env env.Env, resolver VariableResolver, know
switch {
case p.ID == catwalk.InferenceProviderAnthropic && config.OAuthToken != nil:
// Claude Code subscription is not supported anymore. Remove to show onboarding.
- c.RemoveConfigField("providers.anthropic")
+ store.RemoveConfigField(ScopeGlobal, "providers.anthropic")
c.Providers.Del(string(p.ID))
continue
case p.ID == catwalk.InferenceProviderCopilot && config.OAuthToken != nil:
@@ -340,7 +368,6 @@ func (c *Config) configureProviders(env env.Env, resolver VariableResolver, know
}
func (c *Config) setDefaults(workingDir, dataDir string) {
- c.workingDir = workingDir
if c.Options == nil {
c.Options = &Options{}
}
@@ -524,7 +551,8 @@ func (c *Config) defaultModelSelection(knownProviders []catwalk.Provider) (large
return largeModel, smallModel, err
}
-func (c *Config) configureSelectedModels(knownProviders []catwalk.Provider) error {
+func configureSelectedModels(store *ConfigStore, knownProviders []catwalk.Provider) error {
+ c := store.config
defaultLarge, defaultSmall, err := c.defaultModelSelection(knownProviders)
if err != nil {
return fmt.Errorf("failed to select default models: %w", err)
@@ -543,7 +571,7 @@ func (c *Config) configureSelectedModels(knownProviders []catwalk.Provider) erro
if model == nil {
large = defaultLarge
// override the model type to large
- err := c.UpdatePreferredModel(SelectedModelTypeLarge, large)
+ err := store.UpdatePreferredModel(ScopeGlobal, SelectedModelTypeLarge, large)
if err != nil {
return fmt.Errorf("failed to update preferred large model: %w", err)
}
@@ -587,7 +615,7 @@ func (c *Config) configureSelectedModels(knownProviders []catwalk.Provider) erro
if model == nil {
small = defaultSmall
// override the model type to small
- err := c.UpdatePreferredModel(SelectedModelTypeSmall, small)
+ err := store.UpdatePreferredModel(ScopeGlobal, SelectedModelTypeSmall, small)
if err != nil {
return fmt.Errorf("failed to update preferred small model: %w", err)
}
@@ -36,6 +36,11 @@ func TestConfig_LoadFromBytes(t *testing.T) {
require.Equal(t, "https://api.openai.com/v2", pc.BaseURL)
}
+// testStore wraps a Config in a minimal ConfigStore for testing.
+func testStore(cfg *Config) *ConfigStore {
+ return &ConfigStore{config: cfg}
+}
+
func TestConfig_setDefaults(t *testing.T) {
cfg := &Config{}
@@ -53,7 +58,6 @@ func TestConfig_setDefaults(t *testing.T) {
for _, path := range defaultContextPaths {
require.Contains(t, cfg.Options.ContextPaths, path)
}
- require.Equal(t, "/tmp", cfg.workingDir)
}
func TestConfig_configureProviders(t *testing.T) {
@@ -74,7 +78,7 @@ func TestConfig_configureProviders(t *testing.T) {
"OPENAI_API_KEY": "test-key",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
require.Equal(t, 1, cfg.Providers.Len())
@@ -117,7 +121,7 @@ func TestConfig_configureProvidersWithOverride(t *testing.T) {
"OPENAI_API_KEY": "test-key",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
require.Equal(t, 1, cfg.Providers.Len())
@@ -159,7 +163,7 @@ func TestConfig_configureProvidersWithNewProvider(t *testing.T) {
"OPENAI_API_KEY": "test-key",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
// Should be to because of the env variable
require.Equal(t, cfg.Providers.Len(), 2)
@@ -195,7 +199,7 @@ func TestConfig_configureProvidersBedrockWithCredentials(t *testing.T) {
"AWS_SECRET_ACCESS_KEY": "test-secret-key",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 1)
@@ -221,7 +225,7 @@ func TestConfig_configureProvidersBedrockWithoutCredentials(t *testing.T) {
cfg.setDefaults("/tmp", "")
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
// Provider should not be configured without credentials
require.Equal(t, cfg.Providers.Len(), 0)
@@ -246,7 +250,7 @@ func TestConfig_configureProvidersBedrockWithoutUnsupportedModel(t *testing.T) {
"AWS_SECRET_ACCESS_KEY": "test-secret-key",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.Error(t, err)
}
@@ -269,7 +273,7 @@ func TestConfig_configureProvidersVertexAIWithCredentials(t *testing.T) {
"VERTEXAI_LOCATION": "us-central1",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 1)
@@ -301,7 +305,7 @@ func TestConfig_configureProvidersVertexAIWithoutCredentials(t *testing.T) {
"GOOGLE_CLOUD_LOCATION": "us-central1",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
// Provider should not be configured without proper credentials
require.Equal(t, cfg.Providers.Len(), 0)
@@ -326,7 +330,7 @@ func TestConfig_configureProvidersVertexAIMissingProject(t *testing.T) {
"GOOGLE_CLOUD_LOCATION": "us-central1",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
// Provider should not be configured without project
require.Equal(t, cfg.Providers.Len(), 0)
@@ -350,7 +354,7 @@ func TestConfig_configureProvidersSetProviderID(t *testing.T) {
"OPENAI_API_KEY": "test-key",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 1)
@@ -541,7 +545,7 @@ func TestConfig_configureProvidersWithDisabledProvider(t *testing.T) {
"OPENAI_API_KEY": "test-key",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 1)
@@ -569,7 +573,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, []catwalk.Provider{})
+ err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 1)
@@ -592,7 +596,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, []catwalk.Provider{})
+ err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 0)
@@ -614,7 +618,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, []catwalk.Provider{})
+ err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 0)
@@ -639,7 +643,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, []catwalk.Provider{})
+ err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 0)
@@ -664,7 +668,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, []catwalk.Provider{})
+ err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 1)
@@ -692,7 +696,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, []catwalk.Provider{})
+ err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 1)
@@ -722,7 +726,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, []catwalk.Provider{})
+ err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 0)
@@ -757,7 +761,7 @@ func TestConfig_configureProvidersEnhancedCredentialValidation(t *testing.T) {
"GOOGLE_GENAI_USE_VERTEXAI": "false",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 0)
@@ -788,7 +792,7 @@ func TestConfig_configureProvidersEnhancedCredentialValidation(t *testing.T) {
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 0)
@@ -819,7 +823,7 @@ func TestConfig_configureProvidersEnhancedCredentialValidation(t *testing.T) {
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 0)
@@ -852,7 +856,7 @@ func TestConfig_configureProvidersEnhancedCredentialValidation(t *testing.T) {
"OPENAI_API_KEY": "test-key",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
require.Equal(t, cfg.Providers.Len(), 1)
@@ -886,7 +890,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
cfg.setDefaults("/tmp", "")
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
large, small, err := cfg.defaultModelSelection(knownProviders)
@@ -922,7 +926,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
cfg.setDefaults("/tmp", "")
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
_, _, err = cfg.defaultModelSelection(knownProviders)
@@ -952,7 +956,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
cfg.setDefaults("/tmp", "")
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
_, _, err = cfg.defaultModelSelection(knownProviders)
require.Error(t, err)
@@ -995,7 +999,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
cfg.setDefaults("/tmp", "")
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
large, small, err := cfg.defaultModelSelection(knownProviders)
require.NoError(t, err)
@@ -1039,7 +1043,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
cfg.setDefaults("/tmp", "")
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
_, _, err = cfg.defaultModelSelection(knownProviders)
require.Error(t, err)
@@ -1081,7 +1085,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
cfg.setDefaults("/tmp", "")
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
large, small, err := cfg.defaultModelSelection(knownProviders)
require.NoError(t, err)
@@ -1126,7 +1130,7 @@ func TestConfig_configureProvidersDisableDefaultProviders(t *testing.T) {
"OPENAI_API_KEY": "test-key",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.ErrorContains(t, err, "no custom providers")
// openai should NOT be present because it lacks base_url and models.
@@ -1169,7 +1173,7 @@ func TestConfig_configureProvidersDisableDefaultProviders(t *testing.T) {
"OPENAI_API_KEY": "test-key",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
// Only fully specified provider should be present.
@@ -1223,7 +1227,7 @@ func TestConfig_configureProvidersDisableDefaultProviders(t *testing.T) {
"ANTHROPIC_API_KEY": "test-key",
})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
// Both providers should be present.
@@ -1251,7 +1255,7 @@ func TestConfig_configureProvidersDisableDefaultProviders(t *testing.T) {
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, []catwalk.Provider{})
+ err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
require.ErrorContains(t, err, "no custom providers")
// Provider should be rejected for missing models.
@@ -1275,7 +1279,7 @@ func TestConfig_configureProvidersDisableDefaultProviders(t *testing.T) {
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, []catwalk.Provider{})
+ err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
require.ErrorContains(t, err, "no custom providers")
// Provider should be rejected for missing base_url.
@@ -1340,10 +1344,10 @@ func TestConfig_configureSelectedModels(t *testing.T) {
cfg.setDefaults("/tmp", "")
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
- err = cfg.configureSelectedModels(knownProviders)
+ err = configureSelectedModels(testStore(cfg), knownProviders)
require.NoError(t, err)
large := cfg.Models[SelectedModelTypeLarge]
small := cfg.Models[SelectedModelTypeSmall]
@@ -1402,10 +1406,10 @@ func TestConfig_configureSelectedModels(t *testing.T) {
cfg.setDefaults("/tmp", "")
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
- err = cfg.configureSelectedModels(knownProviders)
+ err = configureSelectedModels(testStore(cfg), knownProviders)
require.NoError(t, err)
large := cfg.Models[SelectedModelTypeLarge]
small := cfg.Models[SelectedModelTypeSmall]
@@ -1447,10 +1451,10 @@ func TestConfig_configureSelectedModels(t *testing.T) {
cfg.setDefaults("/tmp", "")
env := env.NewFromMap(map[string]string{})
resolver := NewEnvironmentVariableResolver(env)
- err := cfg.configureProviders(env, resolver, knownProviders)
+ err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
- err = cfg.configureSelectedModels(knownProviders)
+ err = configureSelectedModels(testStore(cfg), knownProviders)
require.NoError(t, err)
large := cfg.Models[SelectedModelTypeLarge]
require.Equal(t, "large-model", large.Model)
@@ -31,15 +31,23 @@ func readRecentModels(t *testing.T, path string) map[string]any {
return rm
}
+// testStoreWithPath creates a ConfigStore backed by a Config for recent model tests.
+func testStoreWithPath(cfg *Config, dir string) *ConfigStore {
+ return &ConfigStore{
+ config: cfg,
+ globalDataPath: filepath.Join(dir, "config.json"),
+ }
+}
+
func TestRecordRecentModel_AddsAndPersists(t *testing.T) {
t.Parallel()
dir := t.TempDir()
cfg := &Config{}
cfg.setDefaults(dir, "")
- cfg.dataConfigDir = filepath.Join(dir, "config.json")
+ store := testStoreWithPath(cfg, dir)
- err := cfg.recordRecentModel(SelectedModelTypeLarge, SelectedModel{Provider: "openai", Model: "gpt-4o"})
+ err := store.recordRecentModel(ScopeGlobal, SelectedModelTypeLarge, SelectedModel{Provider: "openai", Model: "gpt-4o"})
require.NoError(t, err)
// in-memory state
@@ -48,7 +56,7 @@ func TestRecordRecentModel_AddsAndPersists(t *testing.T) {
require.Equal(t, "gpt-4o", cfg.RecentModels[SelectedModelTypeLarge][0].Model)
// persisted state
- rm := readRecentModels(t, cfg.dataConfigDir)
+ rm := readRecentModels(t, store.globalDataPath)
large, ok := rm[string(SelectedModelTypeLarge)].([]any)
require.True(t, ok)
require.Len(t, large, 1)
@@ -64,13 +72,13 @@ func TestRecordRecentModel_DedupeAndMoveToFront(t *testing.T) {
dir := t.TempDir()
cfg := &Config{}
cfg.setDefaults(dir, "")
- cfg.dataConfigDir = filepath.Join(dir, "config.json")
+ store := testStoreWithPath(cfg, dir)
// 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"}))
+ require.NoError(t, store.recordRecentModel(ScopeGlobal, SelectedModelTypeLarge, SelectedModel{Provider: "openai", Model: "gpt-4o"}))
+ require.NoError(t, store.recordRecentModel(ScopeGlobal, 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"}))
+ require.NoError(t, store.recordRecentModel(ScopeGlobal, SelectedModelTypeLarge, SelectedModel{Provider: "openai", Model: "gpt-4o"}))
got := cfg.RecentModels[SelectedModelTypeLarge]
require.Len(t, got, 2)
@@ -84,7 +92,7 @@ func TestRecordRecentModel_TrimsToMax(t *testing.T) {
dir := t.TempDir()
cfg := &Config{}
cfg.setDefaults(dir, "")
- cfg.dataConfigDir = filepath.Join(dir, "config.json")
+ store := testStoreWithPath(cfg, dir)
// Insert 6 unique models; max is 5
entries := []SelectedModel{
@@ -96,7 +104,7 @@ func TestRecordRecentModel_TrimsToMax(t *testing.T) {
{Provider: "p6", Model: "m6"},
}
for _, e := range entries {
- require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, e))
+ require.NoError(t, store.recordRecentModel(ScopeGlobal, SelectedModelTypeLarge, e))
}
// in-memory state
@@ -110,7 +118,7 @@ func TestRecordRecentModel_TrimsToMax(t *testing.T) {
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)
+ rm := readRecentModels(t, store.globalDataPath)
large, ok := rm[string(SelectedModelTypeLarge)].([]any)
require.True(t, ok)
require.Len(t, large, 5)
@@ -129,12 +137,12 @@ func TestRecordRecentModel_SkipsEmptyValues(t *testing.T) {
dir := t.TempDir()
cfg := &Config{}
cfg.setDefaults(dir, "")
- cfg.dataConfigDir = filepath.Join(dir, "config.json")
+ store := testStoreWithPath(cfg, dir)
// Missing provider
- require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, SelectedModel{Provider: "", Model: "m"}))
+ require.NoError(t, store.recordRecentModel(ScopeGlobal, SelectedModelTypeLarge, SelectedModel{Provider: "", Model: "m"}))
// Missing model
- require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, SelectedModel{Provider: "p", Model: ""}))
+ require.NoError(t, store.recordRecentModel(ScopeGlobal, SelectedModelTypeLarge, SelectedModel{Provider: "p", Model: ""}))
_, ok := cfg.RecentModels[SelectedModelTypeLarge]
// Map may be initialized, but should have no entries
@@ -142,8 +150,8 @@ func TestRecordRecentModel_SkipsEmptyValues(t *testing.T) {
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)
+ baseDir := filepath.Dir(store.globalDataPath)
+ fileName := filepath.Base(store.globalDataPath)
_, err := fs.Stat(os.DirFS(baseDir), fileName)
require.True(t, os.IsNotExist(err))
}
@@ -154,13 +162,13 @@ func TestRecordRecentModel_NoPersistOnNoop(t *testing.T) {
dir := t.TempDir()
cfg := &Config{}
cfg.setDefaults(dir, "")
- cfg.dataConfigDir = filepath.Join(dir, "config.json")
+ store := testStoreWithPath(cfg, dir)
entry := SelectedModel{Provider: "openai", Model: "gpt-4o"}
- require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, entry))
+ require.NoError(t, store.recordRecentModel(ScopeGlobal, SelectedModelTypeLarge, entry))
- baseDir := filepath.Dir(cfg.dataConfigDir)
- fileName := filepath.Base(cfg.dataConfigDir)
+ baseDir := filepath.Dir(store.globalDataPath)
+ fileName := filepath.Base(store.globalDataPath)
before, err := fs.ReadFile(os.DirFS(baseDir), fileName)
require.NoError(t, err)
@@ -170,7 +178,7 @@ func TestRecordRecentModel_NoPersistOnNoop(t *testing.T) {
beforeMod := stBefore.ModTime()
// Re-record same entry should be a no-op (no write)
- require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, entry))
+ require.NoError(t, store.recordRecentModel(ScopeGlobal, SelectedModelTypeLarge, entry))
after, err := fs.ReadFile(os.DirFS(baseDir), fileName)
require.NoError(t, err)
@@ -188,17 +196,17 @@ func TestUpdatePreferredModel_UpdatesRecents(t *testing.T) {
dir := t.TempDir()
cfg := &Config{}
cfg.setDefaults(dir, "")
- cfg.dataConfigDir = filepath.Join(dir, "config.json")
+ store := testStoreWithPath(cfg, dir)
sel := SelectedModel{Provider: "openai", Model: "gpt-4o"}
- require.NoError(t, cfg.UpdatePreferredModel(SelectedModelTypeSmall, sel))
+ require.NoError(t, store.UpdatePreferredModel(ScopeGlobal, 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)
+ rm := readRecentModels(t, store.globalDataPath)
small, ok := rm[string(SelectedModelTypeSmall)].([]any)
require.True(t, ok)
require.Len(t, small, 1)
@@ -210,14 +218,14 @@ func TestRecordRecentModel_TypeIsolation(t *testing.T) {
dir := t.TempDir()
cfg := &Config{}
cfg.setDefaults(dir, "")
- cfg.dataConfigDir = filepath.Join(dir, "config.json")
+ store := testStoreWithPath(cfg, dir)
// 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))
+ require.NoError(t, store.recordRecentModel(ScopeGlobal, SelectedModelTypeLarge, largeModel))
+ require.NoError(t, store.recordRecentModel(ScopeGlobal, SelectedModelTypeSmall, smallModel))
// in-memory: verify types maintain separate histories
require.Len(t, cfg.RecentModels[SelectedModelTypeLarge], 1)
@@ -227,14 +235,14 @@ func TestRecordRecentModel_TypeIsolation(t *testing.T) {
// Add another to large, verify small unchanged
anotherLarge := SelectedModel{Provider: "google", Model: "gemini"}
- require.NoError(t, cfg.recordRecentModel(SelectedModelTypeLarge, anotherLarge))
+ require.NoError(t, store.recordRecentModel(ScopeGlobal, 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)
+ rm := readRecentModels(t, store.globalDataPath)
large, ok := rm[string(SelectedModelTypeLarge)].([]any)
require.True(t, ok)
@@ -0,0 +1,11 @@
+package config
+
+// Scope determines which config file is targeted for read/write operations.
+type Scope int
+
+const (
+ // ScopeGlobal targets the global data config (~/.local/share/crush/crush.json).
+ ScopeGlobal Scope = iota
+ // ScopeWorkspace targets the workspace config (.crush/crush.json).
+ ScopeWorkspace
+)
@@ -0,0 +1,336 @@
+package config
+
+import (
+ "cmp"
+ "context"
+ "fmt"
+ "log/slog"
+ "os"
+ "path/filepath"
+ "slices"
+
+ "charm.land/catwalk/pkg/catwalk"
+ hyperp "github.com/charmbracelet/crush/internal/agent/hyper"
+ "github.com/charmbracelet/crush/internal/oauth"
+ "github.com/charmbracelet/crush/internal/oauth/copilot"
+ "github.com/charmbracelet/crush/internal/oauth/hyper"
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+)
+
+// ConfigStore is the single entry point for all config access. It owns the
+// pure-data Config, runtime state (working directory, resolver, known
+// providers), and persistence to both global and workspace config files.
+type ConfigStore struct {
+ config *Config
+ workingDir string
+ resolver VariableResolver
+ globalDataPath string // ~/.local/share/crush/crush.json
+ workspacePath string // .crush/crush.json
+ knownProviders []catwalk.Provider
+}
+
+// Config returns the pure-data config struct (read-only after load).
+func (s *ConfigStore) Config() *Config {
+ return s.config
+}
+
+// WorkingDir returns the current working directory.
+func (s *ConfigStore) WorkingDir() string {
+ return s.workingDir
+}
+
+// Resolver returns the variable resolver.
+func (s *ConfigStore) Resolver() VariableResolver {
+ return s.resolver
+}
+
+// Resolve resolves a variable reference using the configured resolver.
+func (s *ConfigStore) Resolve(key string) (string, error) {
+ if s.resolver == nil {
+ return "", fmt.Errorf("no variable resolver configured")
+ }
+ return s.resolver.ResolveValue(key)
+}
+
+// KnownProviders returns the list of known providers.
+func (s *ConfigStore) KnownProviders() []catwalk.Provider {
+ return s.knownProviders
+}
+
+// SetupAgents configures the coder and task agents on the config.
+func (s *ConfigStore) SetupAgents() {
+ s.config.SetupAgents()
+}
+
+// configPath returns the file path for the given scope.
+func (s *ConfigStore) configPath(scope Scope) string {
+ switch scope {
+ case ScopeWorkspace:
+ return s.workspacePath
+ default:
+ return s.globalDataPath
+ }
+}
+
+// HasConfigField checks whether a key exists in the config file for the given
+// scope.
+func (s *ConfigStore) HasConfigField(scope Scope, key string) bool {
+ data, err := os.ReadFile(s.configPath(scope))
+ if err != nil {
+ return false
+ }
+ return gjson.Get(string(data), key).Exists()
+}
+
+// SetConfigField sets a key/value pair in the config file for the given scope.
+func (s *ConfigStore) SetConfigField(scope Scope, key string, value any) error {
+ path := s.configPath(scope)
+ data, err := os.ReadFile(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ data = []byte("{}")
+ } else {
+ return fmt.Errorf("failed to read config file: %w", err)
+ }
+ }
+
+ newValue, err := sjson.Set(string(data), key, value)
+ if err != nil {
+ return fmt.Errorf("failed to set config field %s: %w", key, err)
+ }
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
+ return fmt.Errorf("failed to create config directory %q: %w", path, err)
+ }
+ if err := os.WriteFile(path, []byte(newValue), 0o600); err != nil {
+ return fmt.Errorf("failed to write config file: %w", err)
+ }
+ return nil
+}
+
+// RemoveConfigField removes a key from the config file for the given scope.
+func (s *ConfigStore) RemoveConfigField(scope Scope, key string) error {
+ path := s.configPath(scope)
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return fmt.Errorf("failed to read config file: %w", err)
+ }
+
+ newValue, err := sjson.Delete(string(data), key)
+ if err != nil {
+ return fmt.Errorf("failed to delete config field %s: %w", key, err)
+ }
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
+ return fmt.Errorf("failed to create config directory %q: %w", path, err)
+ }
+ if err := os.WriteFile(path, []byte(newValue), 0o600); err != nil {
+ return fmt.Errorf("failed to write config file: %w", err)
+ }
+ return nil
+}
+
+// UpdatePreferredModel updates the preferred model for the given type and
+// persists it to the config file at the given scope.
+func (s *ConfigStore) UpdatePreferredModel(scope Scope, modelType SelectedModelType, model SelectedModel) error {
+ s.config.Models[modelType] = model
+ if err := s.SetConfigField(scope, fmt.Sprintf("models.%s", modelType), model); err != nil {
+ return fmt.Errorf("failed to update preferred model: %w", err)
+ }
+ if err := s.recordRecentModel(scope, modelType, model); err != nil {
+ return err
+ }
+ return nil
+}
+
+// SetCompactMode sets the compact mode setting and persists it.
+func (s *ConfigStore) SetCompactMode(scope Scope, enabled bool) error {
+ if s.config.Options == nil {
+ s.config.Options = &Options{}
+ }
+ s.config.Options.TUI.CompactMode = enabled
+ return s.SetConfigField(scope, "options.tui.compact_mode", enabled)
+}
+
+// SetProviderAPIKey sets the API key for a provider and persists it.
+func (s *ConfigStore) SetProviderAPIKey(scope Scope, providerID string, apiKey any) error {
+ var providerConfig ProviderConfig
+ var exists bool
+ var setKeyOrToken func()
+
+ switch v := apiKey.(type) {
+ case string:
+ if err := s.SetConfigField(scope, fmt.Sprintf("providers.%s.api_key", providerID), v); err != nil {
+ return fmt.Errorf("failed to save api key to config file: %w", err)
+ }
+ setKeyOrToken = func() { providerConfig.APIKey = v }
+ case *oauth.Token:
+ if err := cmp.Or(
+ s.SetConfigField(scope, fmt.Sprintf("providers.%s.api_key", providerID), v.AccessToken),
+ s.SetConfigField(scope, fmt.Sprintf("providers.%s.oauth", providerID), v),
+ ); err != nil {
+ return err
+ }
+ setKeyOrToken = func() {
+ providerConfig.APIKey = v.AccessToken
+ providerConfig.OAuthToken = v
+ switch providerID {
+ case string(catwalk.InferenceProviderCopilot):
+ providerConfig.SetupGitHubCopilot()
+ }
+ }
+ }
+
+ providerConfig, exists = s.config.Providers.Get(providerID)
+ if exists {
+ setKeyOrToken()
+ s.config.Providers.Set(providerID, providerConfig)
+ return nil
+ }
+
+ var foundProvider *catwalk.Provider
+ for _, p := range s.knownProviders {
+ if string(p.ID) == providerID {
+ foundProvider = &p
+ break
+ }
+ }
+
+ if foundProvider != nil {
+ providerConfig = ProviderConfig{
+ ID: providerID,
+ Name: foundProvider.Name,
+ BaseURL: foundProvider.APIEndpoint,
+ Type: foundProvider.Type,
+ Disable: false,
+ ExtraHeaders: make(map[string]string),
+ ExtraParams: make(map[string]string),
+ Models: foundProvider.Models,
+ }
+ setKeyOrToken()
+ } else {
+ return fmt.Errorf("provider with ID %s not found in known providers", providerID)
+ }
+ s.config.Providers.Set(providerID, providerConfig)
+ return nil
+}
+
+// RefreshOAuthToken refreshes the OAuth token for the given provider.
+func (s *ConfigStore) RefreshOAuthToken(ctx context.Context, scope Scope, providerID string) error {
+ providerConfig, exists := s.config.Providers.Get(providerID)
+ if !exists {
+ return fmt.Errorf("provider %s not found", providerID)
+ }
+
+ if providerConfig.OAuthToken == nil {
+ return fmt.Errorf("provider %s does not have an OAuth token", providerID)
+ }
+
+ var newToken *oauth.Token
+ var refreshErr error
+ switch providerID {
+ case string(catwalk.InferenceProviderCopilot):
+ newToken, refreshErr = copilot.RefreshToken(ctx, providerConfig.OAuthToken.RefreshToken)
+ case hyperp.Name:
+ newToken, refreshErr = hyper.ExchangeToken(ctx, providerConfig.OAuthToken.RefreshToken)
+ default:
+ return fmt.Errorf("OAuth refresh not supported for provider %s", providerID)
+ }
+ if refreshErr != nil {
+ return fmt.Errorf("failed to refresh OAuth token for provider %s: %w", providerID, refreshErr)
+ }
+
+ slog.Info("Successfully refreshed OAuth token", "provider", providerID)
+ providerConfig.OAuthToken = newToken
+ providerConfig.APIKey = newToken.AccessToken
+
+ switch providerID {
+ case string(catwalk.InferenceProviderCopilot):
+ providerConfig.SetupGitHubCopilot()
+ }
+
+ s.config.Providers.Set(providerID, providerConfig)
+
+ if err := cmp.Or(
+ s.SetConfigField(scope, fmt.Sprintf("providers.%s.api_key", providerID), newToken.AccessToken),
+ s.SetConfigField(scope, fmt.Sprintf("providers.%s.oauth", providerID), newToken),
+ ); err != nil {
+ return fmt.Errorf("failed to persist refreshed token: %w", err)
+ }
+
+ return nil
+}
+
+// recordRecentModel records a model in the recent models list.
+func (s *ConfigStore) recordRecentModel(scope Scope, modelType SelectedModelType, model SelectedModel) error {
+ if model.Provider == "" || model.Model == "" {
+ return nil
+ }
+
+ if s.config.RecentModels == nil {
+ s.config.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 := s.config.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
+ }
+
+ s.config.RecentModels[modelType] = updated
+
+ if err := s.SetConfigField(scope, fmt.Sprintf("recent_models.%s", modelType), updated); err != nil {
+ return fmt.Errorf("failed to persist recent models: %w", err)
+ }
+
+ return nil
+}
+
+// ImportCopilot attempts to import a GitHub Copilot token from disk.
+func (s *ConfigStore) ImportCopilot() (*oauth.Token, bool) {
+ if s.HasConfigField(ScopeGlobal, "providers.copilot.api_key") || s.HasConfigField(ScopeGlobal, "providers.copilot.oauth") {
+ return nil, false
+ }
+
+ diskToken, hasDiskToken := copilot.RefreshTokenFromDisk()
+ if !hasDiskToken {
+ return nil, false
+ }
+
+ slog.Info("Found existing GitHub Copilot token on disk. Authenticating...")
+ token, err := copilot.RefreshToken(context.TODO(), diskToken)
+ if err != nil {
+ slog.Error("Unable to import GitHub Copilot token", "error", err)
+ return nil, false
+ }
+
+ if err := s.SetProviderAPIKey(ScopeGlobal, string(catwalk.InferenceProviderCopilot), token); err != nil {
+ return token, false
+ }
+
+ if err := cmp.Or(
+ s.SetConfigField(ScopeGlobal, "providers.copilot.api_key", token.AccessToken),
+ s.SetConfigField(ScopeGlobal, "providers.copilot.oauth", token),
+ ); err != nil {
+ slog.Error("Unable to save GitHub Copilot token to disk", "error", err)
+ }
+
+ slog.Info("GitHub Copilot successfully imported")
+ return token, true
+}
@@ -120,6 +120,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
if q.recordFileReadStmt, err = db.PrepareContext(ctx, recordFileRead); err != nil {
return nil, fmt.Errorf("error preparing query RecordFileRead: %w", err)
}
+ if q.renameSessionStmt, err = db.PrepareContext(ctx, renameSession); err != nil {
+ return nil, fmt.Errorf("error preparing query RenameSession: %w", err)
+ }
if q.updateMessageStmt, err = db.PrepareContext(ctx, updateMessage); err != nil {
return nil, fmt.Errorf("error preparing query UpdateMessage: %w", err)
}
@@ -294,6 +297,11 @@ func (q *Queries) Close() error {
err = fmt.Errorf("error closing recordFileReadStmt: %w", cerr)
}
}
+ if q.renameSessionStmt != nil {
+ if cerr := q.renameSessionStmt.Close(); cerr != nil {
+ err = fmt.Errorf("error closing renameSessionStmt: %w", cerr)
+ }
+ }
if q.updateMessageStmt != nil {
if cerr := q.updateMessageStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing updateMessageStmt: %w", cerr)
@@ -380,6 +388,7 @@ type Queries struct {
listSessionsStmt *sql.Stmt
listUserMessagesBySessionStmt *sql.Stmt
recordFileReadStmt *sql.Stmt
+ renameSessionStmt *sql.Stmt
updateMessageStmt *sql.Stmt
updateSessionStmt *sql.Stmt
updateSessionTitleAndUsageStmt *sql.Stmt
@@ -421,6 +430,7 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries {
listSessionsStmt: q.listSessionsStmt,
listUserMessagesBySessionStmt: q.listUserMessagesBySessionStmt,
recordFileReadStmt: q.recordFileReadStmt,
+ renameSessionStmt: q.renameSessionStmt,
updateMessageStmt: q.updateMessageStmt,
updateSessionStmt: q.updateSessionStmt,
updateSessionTitleAndUsageStmt: q.updateSessionTitleAndUsageStmt,
@@ -34,7 +34,7 @@ type Message struct {
type ReadFile struct {
SessionID string `json:"session_id"`
Path string `json:"path"`
- ReadAt int64 `json:"read_at"` // Unix timestamp when file was last read
+ ReadAt int64 `json:"read_at"`
}
type Session struct {
@@ -41,6 +41,7 @@ type Querier interface {
ListSessions(ctx context.Context) ([]Session, error)
ListUserMessagesBySession(ctx context.Context, sessionID string) ([]Message, error)
RecordFileRead(ctx context.Context, arg RecordFileReadParams) error
+ RenameSession(ctx context.Context, arg RenameSessionParams) error
UpdateMessage(ctx context.Context, arg UpdateMessageParams) error
UpdateSession(ctx context.Context, arg UpdateSessionParams) (Session, error)
UpdateSessionTitleAndUsage(ctx context.Context, arg UpdateSessionTitleAndUsageParams) error
@@ -22,32 +22,10 @@ type GetFileReadParams struct {
func (q *Queries) GetFileRead(ctx context.Context, arg GetFileReadParams) (ReadFile, error) {
row := q.queryRow(ctx, q.getFileReadStmt, getFileRead, arg.SessionID, arg.Path)
var i ReadFile
- err := row.Scan(
- &i.SessionID,
- &i.Path,
- &i.ReadAt,
- )
+ err := row.Scan(&i.SessionID, &i.Path, &i.ReadAt)
return i, err
}
-const recordFileRead = `-- name: RecordFileRead :exec
-INSERT INTO read_files (
- session_id,
- path,
- read_at
-) VALUES (
- ?,
- ?,
- strftime('%s', 'now')
-) ON CONFLICT(path, session_id) DO UPDATE SET
- read_at = excluded.read_at
-`
-
-type RecordFileReadParams struct {
- SessionID string `json:"session_id"`
- Path string `json:"path"`
-}
-
const listSessionReadFiles = `-- name: ListSessionReadFiles :many
SELECT session_id, path, read_at FROM read_files
WHERE session_id = ?
@@ -63,11 +41,7 @@ func (q *Queries) ListSessionReadFiles(ctx context.Context, sessionID string) ([
items := []ReadFile{}
for rows.Next() {
var i ReadFile
- if err := rows.Scan(
- &i.SessionID,
- &i.Path,
- &i.ReadAt,
- ); err != nil {
+ if err := rows.Scan(&i.SessionID, &i.Path, &i.ReadAt); err != nil {
return nil, err
}
items = append(items, i)
@@ -81,10 +55,25 @@ func (q *Queries) ListSessionReadFiles(ctx context.Context, sessionID string) ([
return items, nil
}
+const recordFileRead = `-- name: RecordFileRead :exec
+INSERT INTO read_files (
+ session_id,
+ path,
+ read_at
+) VALUES (
+ ?,
+ ?,
+ strftime('%s', 'now')
+) ON CONFLICT(path, session_id) DO UPDATE SET
+ read_at = excluded.read_at
+`
+
+type RecordFileReadParams struct {
+ SessionID string `json:"session_id"`
+ Path string `json:"path"`
+}
+
func (q *Queries) RecordFileRead(ctx context.Context, arg RecordFileReadParams) error {
- _, err := q.exec(ctx, q.recordFileReadStmt, recordFileRead,
- arg.SessionID,
- arg.Path,
- )
+ _, err := q.exec(ctx, q.recordFileReadStmt, recordFileRead, arg.SessionID, arg.Path)
return err
}
@@ -150,6 +150,23 @@ func (q *Queries) ListSessions(ctx context.Context) ([]Session, error) {
return items, nil
}
+const renameSession = `-- name: RenameSession :exec
+UPDATE sessions
+SET
+ title = ?
+WHERE id = ?
+`
+
+type RenameSessionParams struct {
+ Title string `json:"title"`
+ ID string `json:"id"`
+}
+
+func (q *Queries) RenameSession(ctx context.Context, arg RenameSessionParams) error {
+ _, err := q.exec(ctx, q.renameSessionStmt, renameSession, arg.Title, arg.ID)
+ return err
+}
+
const updateSession = `-- name: UpdateSession :one
UPDATE sessions
SET
@@ -206,7 +223,8 @@ SET
title = ?,
prompt_tokens = prompt_tokens + ?,
completion_tokens = completion_tokens + ?,
- cost = cost + ?
+ cost = cost + ?,
+ updated_at = strftime('%s', 'now')
WHERE id = ?
`
@@ -52,10 +52,17 @@ SET
title = ?,
prompt_tokens = prompt_tokens + ?,
completion_tokens = completion_tokens + ?,
- cost = cost + ?
+ cost = cost + ?,
+ updated_at = strftime('%s', 'now')
WHERE id = ?;
+-- name: RenameSession :exec
+UPDATE sessions
+SET
+ title = ?
+WHERE id = ?;
+
-- name: DeleteSession :exec
DELETE FROM sessions
WHERE id = ?;
@@ -84,7 +84,7 @@ func send(event string, props ...any) {
// Error logs an error event to PostHog with the error type and message.
func Error(errToLog any, props ...any) {
- if client == nil || errToLog == nil {
+ if client == nil || distinctId == "" || errToLog == nil {
return
}
posthogErr := client.Enqueue(posthog.NewDefaultException(
@@ -26,18 +26,18 @@ var unavailable = csync.NewMap[string, struct{}]()
// Manager handles lazy initialization of LSP clients based on file types.
type Manager struct {
clients *csync.Map[string, *Client]
- cfg *config.Config
+ cfg *config.ConfigStore
manager *powernapconfig.Manager
callback func(name string, client *Client)
}
// NewManager creates a new LSP manager service.
-func NewManager(cfg *config.Config) *Manager {
+func NewManager(cfg *config.ConfigStore) *Manager {
manager := powernapconfig.NewManager()
manager.LoadDefaults()
// Merge user-configured LSPs into the manager.
- for name, clientConfig := range cfg.LSP {
+ for name, clientConfig := range cfg.Config().LSP {
if clientConfig.Disabled {
slog.Debug("LSP disabled by user config", "name", name)
manager.RemoveServer(name)
@@ -194,7 +194,7 @@ func (s *Manager) startServer(ctx context.Context, name, filepath string, server
cfg,
s.cfg.Resolver(),
s.cfg.WorkingDir(),
- s.cfg.Options.DebugLSP,
+ s.cfg.Config().Options.DebugLSP,
)
if err != nil {
slog.Error("Failed to create LSP client", "name", name, "error", err)
@@ -244,7 +244,7 @@ func (s *Manager) startServer(ctx context.Context, name, filepath string, server
}
func (s *Manager) isUserConfigured(name string) bool {
- cfg, ok := s.cfg.LSP[name]
+ cfg, ok := s.cfg.Config().LSP[name]
return ok && !cfg.Disabled
}
@@ -258,7 +258,7 @@ func (s *Manager) buildConfig(name string, server *powernapconfig.ServerConfig)
InitOptions: server.InitOptions,
Options: server.Settings,
}
- if userCfg, ok := s.cfg.LSP[name]; ok {
+ if userCfg, ok := s.cfg.Config().LSP[name]; ok {
cfg.Timeout = userCfg.Timeout
}
return cfg
@@ -134,6 +134,12 @@ func (s *permissionService) Request(ctx context.Context, opts CreatePermissionRe
return true, nil
}
+ // Check if the tool/action combination is in the allowlist
+ commandKey := opts.ToolName + ":" + opts.Action
+ if slices.Contains(s.allowedTools, commandKey) || slices.Contains(s.allowedTools, opts.ToolName) {
+ return true, nil
+ }
+
// tell the UI that a permission was requested
s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
ToolCallID: opts.ToolCallID,
@@ -141,12 +147,6 @@ func (s *permissionService) Request(ctx context.Context, opts CreatePermissionRe
s.requestMu.Lock()
defer s.requestMu.Unlock()
- // Check if the tool/action combination is in the allowlist
- commandKey := opts.ToolName + ":" + opts.Action
- if slices.Contains(s.allowedTools, commandKey) || slices.Contains(s.allowedTools, opts.ToolName) {
- return true, nil
- }
-
s.autoApproveSessionsMu.RLock()
autoApprove := s.autoApproveSessions[opts.SessionID]
s.autoApproveSessionsMu.RUnlock()
@@ -12,6 +12,7 @@ import (
"github.com/charmbracelet/crush/internal/event"
"github.com/charmbracelet/crush/internal/pubsub"
"github.com/google/uuid"
+ "github.com/zeebo/xxh3"
)
type TodoStatus string
@@ -22,6 +23,13 @@ const (
TodoStatusCompleted TodoStatus = "completed"
)
+// HashID returns the XXH3 hash of a session ID (UUID) as a hex string.
+func HashID(id string) string {
+ h := xxh3.New()
+ h.WriteString(id)
+ return fmt.Sprintf("%x", h.Sum(nil))
+}
+
type Todo struct {
Content string `json:"content"`
Status TodoStatus `json:"status"`
@@ -61,6 +69,7 @@ type Service interface {
List(ctx context.Context) ([]Session, error)
Save(ctx context.Context, session Session) (Session, error)
UpdateTitleAndUsage(ctx context.Context, sessionID, title string, promptTokens, completionTokens int64, cost float64) error
+ Rename(ctx context.Context, id string, title string) error
Delete(ctx context.Context, id string) error
// Agent tool session management
@@ -198,6 +207,15 @@ func (s *service) UpdateTitleAndUsage(ctx context.Context, sessionID, title stri
})
}
+// Rename updates only the title of a session without touching updated_at or
+// usage fields.
+func (s *service) Rename(ctx context.Context, id string, title string) error {
+ return s.q.RenameSession(ctx, db.RenameSessionParams{
+ ID: id,
+ Title: title,
+ })
+}
+
func (s *service) List(ctx context.Context) ([]Session, error) {
dbSessions, err := s.q.ListSessions(ctx)
if err != nil {
@@ -2,79 +2,199 @@
## General Guidelines
-- Never use commands to send messages when you can directly mutate children or state.
+- Never use commands to send messages when you can directly mutate children
+ or state.
- Keep things simple; do not overcomplicate.
- Create files if needed to separate logic; do not nest models.
- Never do IO or expensive work in `Update`; always use a `tea.Cmd`.
-- Never change the model state inside of a command use messages and than update the state in the main loop
-- Use the `github.com/charmbracelet/x/ansi` package for any string manipulation
- that might involves ANSI codes. Do not manipulate ANSI strings at byte level!
- Some useful functions:
- * `ansi.Cut`
- * `ansi.StringWidth`
- * `ansi.Strip`
- * `ansi.Truncate`
-
+- Never change the model state inside of a command. Use messages and update
+ the state in the main `Update` loop.
+- Use the `github.com/charmbracelet/x/ansi` package for any string
+ manipulation that might involve ANSI codes. Do not manipulate ANSI strings
+ at byte level! Some useful functions:
+ - `ansi.Cut`
+ - `ansi.StringWidth`
+ - `ansi.Strip`
+ - `ansi.Truncate`
## Architecture
+### Rendering Pipeline
+
+The UI uses a **hybrid rendering** approach:
+
+1. **Screen-based (Ultraviolet)**: The top-level `UI` model creates a
+ `uv.ScreenBuffer`, and components draw into sub-regions using
+ `uv.NewStyledString(str).Draw(scr, rect)`. Layout is rectangle-based via
+ a `uiLayout` struct with fields like `layout.header`, `layout.main`,
+ `layout.editor`, `layout.sidebar`, `layout.pills`, `layout.status`.
+2. **String-based**: Sub-components like `list.List` and `completions` render
+ to strings, which are painted onto the screen buffer.
+3. **`View()`** creates the screen buffer, calls `Draw()`, then
+ `canvas.Render()` flattens it to a string for Bubble Tea.
+
### Main Model (`model/ui.go`)
-Keep most of the logic and state in the main model. This is where:
-- Message routing happens
+The `UI` struct is the top-level Bubble Tea model. Key fields:
+
+- `width`, `height` — terminal dimensions
+- `layout uiLayout` — computed layout rectangles
+- `state uiState` — `uiOnboarding | uiInitialize | uiLanding | uiChat`
+- `focus uiFocusState` — `uiFocusNone | uiFocusEditor | uiFocusMain`
+- `chat *Chat` — wraps `list.List` for the message view
+- `textarea textarea.Model` — the input editor
+- `dialog *dialog.Overlay` — stacked dialog system
+- `completions`, `attachments` — sub-components
+
+Keep most logic and state here. This is where:
+
+- Message routing happens (giant `switch msg.(type)` in `Update`)
- Focus and UI state is managed
- Layout calculations are performed
- Dialogs are orchestrated
-### Components Should Be Dumb
+### Centralized Message Handling
+
+The `UI` model is the **sole Bubble Tea model**. Sub-components (`Chat`,
+`List`, `Attachments`, `Completions`, etc.) do not participate in the
+standard Elm architecture message loop. They are stateful structs with
+imperative methods that the main model calls directly:
+
+- **`Chat`** and **`List`** have no `Update` method at all. The main model
+ calls targeted methods like `HandleMouseDown()`, `ScrollBy()`,
+ `SetMessages()`, `Animate()`.
+- **`Attachments`** and **`Completions`** have non-standard `Update`
+ signatures (e.g., returning `bool` for "consumed") that act as guards, not
+ as full Bubble Tea models.
+- **Sidebar** is not its own model: it's a `drawSidebar()` method on `UI`.
+
+When writing new components, follow this pattern:
+
+- Expose imperative methods for state changes (not `Update(tea.Msg)`).
+- Return `tea.Cmd` from methods when side effects are needed.
+- Handle rendering via `Render(width int) string` or
+ `Draw(scr uv.Screen, area uv.Rectangle)`.
+- Let the main `UI.Update()` decide when and how to call into the component.
-Components should not handle bubbletea messages directly. Instead:
-- Expose methods for state changes
-- Return `tea.Cmd` from methods when side effects are needed
-- Handle their own rendering via `Render(width int) string`
+### Chat View (`model/chat.go`)
-### Chat Logic (`model/chat.go`)
+The `Chat` struct wraps a `list.List` with an ID-to-index map, mouse
+tracking (drag, double/triple click), animation management, and a `follow`
+flag for auto-scroll. It bridges screen-based and string-based rendering:
-Most chat-related logic belongs here. Individual chat items in `chat/` should be simple renderers that cache their output and invalidate when data changes (see `cachedMessageItem` in `chat/messages.go`).
+```go
+func (m *Chat) Draw(scr uv.Screen, area uv.Rectangle) {
+ uv.NewStyledString(m.list.Render()).Draw(scr, area)
+}
+```
+
+Individual chat items in `chat/` should be simple renderers that cache their
+output and invalidate when data changes (see `cachedMessageItem` in
+`chat/messages.go`).
## Key Patterns
### Composition Over Inheritance
-Use struct embedding for shared behaviors. See `chat/messages.go` for examples of reusable embedded structs for highlighting, caching, and focus.
+Use struct embedding for shared behaviors. See `chat/messages.go` for
+examples of reusable embedded structs for highlighting, caching, and focus.
+
+### Interface Hierarchy
+
+The chat message system uses layered interface composition:
+
+- **`list.Item`** — base: `Render(width int) string`
+- **`MessageItem`** — extends `list.Item` + `list.RawRenderable` +
+ `Identifiable`
+- **`ToolMessageItem`** — extends `MessageItem` with tool call/result/status
+ methods
+- **Opt-in capabilities**: `Focusable`, `Highlightable`, `Expandable`,
+ `Animatable`, `Compactable`, `KeyEventHandler`
+
+Key interface locations:
+
+- List item interfaces: `list/item.go`
+- Chat message interfaces: `chat/messages.go`
+- Tool message interfaces: `chat/tools.go`
+- Dialog interface: `dialog/dialog.go`
-### Interfaces
+### Tool Renderers
-- List item interfaces are in `list/item.go`
-- Chat message interfaces are in `chat/messages.go`
-- Dialog interface is in `dialog/dialog.go`
+Each tool has a dedicated renderer in `chat/`. The `ToolRenderer` interface
+requires:
+
+```go
+RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string
+```
+
+`NewToolMessageItem` in `chat/tools.go` is the central factory that routes
+tool names to specific types:
+
+| File | Tools rendered |
+| --------------------- | ---------------------------------------------- |
+| `chat/bash.go` | Bash, JobOutput, JobKill |
+| `chat/file.go` | View, Write, Edit, MultiEdit, Download |
+| `chat/search.go` | Glob, Grep, LS, Sourcegraph |
+| `chat/fetch.go` | Fetch, WebFetch, WebSearch |
+| `chat/agent.go` | Agent, AgenticFetch |
+| `chat/diagnostics.go` | Diagnostics |
+| `chat/references.go` | References |
+| `chat/lsp_restart.go` | LSPRestart |
+| `chat/todos.go` | Todos |
+| `chat/mcp.go` | MCP tools (`mcp_` prefix) |
+| `chat/generic.go` | Fallback for unrecognized tools |
+| `chat/assistant.go` | Assistant messages (thinking, content, errors) |
+| `chat/user.go` | User messages (input + attachments) |
### Styling
-- All styles are defined in `styles/styles.go`
-- Access styles via `*common.Common` passed to components
-- Use semantic color fields rather than hardcoded colors
+- All styles are defined in `styles/styles.go` (massive `Styles` struct with
+ nested groups for Header, Pills, Dialog, Help, etc.).
+- Access styles via `*common.Common` passed to components.
+- Use semantic color fields rather than hardcoded colors.
### Dialogs
-- Implement the dialog interface in `dialog/dialog.go`
-- Return message types from `Update()` to signal actions to the main model
-- Use the overlay system for managing dialog lifecycle
+- Implement the `Dialog` interface in `dialog/dialog.go`:
+ `ID()`, `HandleMsg()` returning an `Action`, `Draw()` onto `uv.Screen`.
+- `Overlay` manages a stack of dialogs with push/pop/contains operations.
+- Dialogs draw last and overlay everything else.
+- Use `RenderContext` from `dialog/common.go` for consistent layout (title
+ gradients, width, gap, cursor offset helpers).
+
+### Shared Context
+
+The `common.Common` struct holds `*app.App` and `*styles.Styles`. Thread it
+through all components that need access to app state or styles.
## File Organization
-- `model/` - Main UI model and major components (chat, sidebar, etc.)
-- `chat/` - Chat message item types and renderers
-- `dialog/` - Dialog implementations
-- `list/` - Generic list component with lazy rendering
-- `common/` - Shared utilities and the Common struct
-- `styles/` - All style definitions
-- `anim/` - Animation system
-- `logo/` - Logo rendering
+- `model/` — Main UI model and major sub-models (chat, sidebar, header,
+ status, pills, session, onboarding, keys, etc.)
+- `chat/` — Chat message item types and tool renderers
+- `dialog/` — Dialog implementations (models, sessions, commands,
+ permissions, API key, OAuth, filepicker, reasoning, quit)
+- `list/` — Generic lazy-rendered scrollable list with viewport tracking
+- `common/` — Shared `Common` struct, layout helpers, markdown rendering,
+ diff rendering, scrollbar
+- `completions/` — Autocomplete popup with filterable list
+- `attachments/` — File attachment management
+- `styles/` — All style definitions, color tokens, icons
+- `diffview/` — Unified and split diff rendering with syntax highlighting
+- `anim/` — Animated spinnner
+- `image/` — Terminal image rendering (Kitty graphics)
+- `logo/` — Logo rendering
+- `util/` — Small shared utilities and message types
## Common Gotchas
-- Always account for padding/borders in width calculations
-- Use `tea.Batch()` when returning multiple commands
-- Pass `*common.Common` to components that need styles or app access
-- When writing tea.Cmd's prefer creating methods in the model instead of writing inline functions
+- Always account for padding/borders in width calculations.
+- Use `tea.Batch()` when returning multiple commands.
+- Pass `*common.Common` to components that need styles or app access.
+- When writing tea.Cmd's prefer creating methods in the model instead of writing inline functions.
+- The `list.List` only renders visible items (lazy). No render cache exists
+ at the list level — items should cache internally if rendering is
+ expensive.
+- Dialog messages are intercepted first in `Update` before other routing.
+- Focus state determines key event routing: `uiFocusEditor` sends keys to
+ the textarea, `uiFocusMain` sends them to the chat list.
@@ -58,7 +58,7 @@ func (c *Capabilities) Update(msg any) {
}
case tea.TerminalVersionMsg:
c.TerminalVersion = m.Name
- case uv.ModeReportEvent:
+ case tea.ModeReportMsg:
switch m.Mode {
case ansi.ModeFocusEvent:
c.ReportFocusEvents = modeSupported(m.Value)
@@ -77,7 +77,7 @@ func QueryCmd(env uv.Environ) tea.Cmd {
shouldQueryFor := shouldQueryCapabilities(env)
if shouldQueryFor {
sb.WriteString(ansi.RequestNameVersion)
- // sb.WriteString(ansi.RequestModeFocusEvent) // TODO: re-enable when we need notifications.
+ sb.WriteString(ansi.RequestModeFocusEvent)
sb.WriteString(ansi.WindowOp(14)) // Window size in pixels
kittyReq := ansi.KittyGraphics([]byte("AAAA"), "i=31", "s=1", "v=1", "a=q", "t=d", "f=24")
if _, isTmux := env.LookupEnv("TMUX"); isTmux {
@@ -26,11 +26,16 @@ type Common struct {
Styles *styles.Styles
}
-// Config returns the configuration associated with this [Common] instance.
+// Config returns the pure-data configuration associated with this [Common] instance.
func (c *Common) Config() *config.Config {
return c.App.Config()
}
+// Store returns the config store associated with this [Common] instance.
+func (c *Common) Store() *config.ConfigStore {
+ return c.App.Store()
+}
+
// DefaultCommon returns the default common UI configurations.
func DefaultCommon(app *app.App) *Common {
s := styles.DefaultStyles()
@@ -10,6 +10,8 @@ import (
"github.com/charmbracelet/crush/internal/home"
"github.com/charmbracelet/crush/internal/ui/styles"
"github.com/charmbracelet/x/ansi"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
)
// PrettyPath formats a file path with home directory shortening and applies
@@ -19,6 +21,14 @@ func PrettyPath(t *styles.Styles, path string, width int) string {
return t.Muted.Width(width).Render(formatted)
}
+// FormatReasoningEffort formats a reasoning effort level for display.
+func FormatReasoningEffort(effort string) string {
+ if effort == "xhigh" {
+ return "X-High"
+ }
+ return cases.Title(language.English).String(effort)
+}
+
// ModelContextInfo contains token usage and cost information for a model.
type ModelContextInfo struct {
ContextUsed int64
@@ -44,13 +44,14 @@ type ActionSelectModel struct {
// Messages for commands
type (
- ActionNewSession struct{}
- ActionToggleHelp struct{}
- ActionToggleCompactMode struct{}
- ActionToggleThinking struct{}
- ActionTogglePills struct{}
- ActionExternalEditor struct{}
- ActionToggleYoloMode struct{}
+ ActionNewSession struct{}
+ ActionToggleHelp struct{}
+ ActionToggleCompactMode struct{}
+ ActionToggleThinking struct{}
+ ActionTogglePills struct{}
+ ActionExternalEditor struct{}
+ ActionToggleYoloMode struct{}
+ ActionToggleNotifications struct{}
// ActionInitializeProject is a message to initialize a project.
ActionInitializeProject struct{}
ActionSummarize struct {
@@ -296,7 +296,7 @@ func (m *APIKeyInput) verifyAPIKey() tea.Msg {
Type: m.provider.Type,
BaseURL: m.provider.APIEndpoint,
}
- err := providerConfig.TestConnection(m.com.Config().Resolver())
+ err := providerConfig.TestConnection(m.com.Store().Resolver())
// intentionally wait for at least 750ms to make sure the user sees the spinner
elapsed := time.Since(start)
@@ -312,9 +312,9 @@ func (m *APIKeyInput) verifyAPIKey() tea.Msg {
}
func (m *APIKeyInput) saveKeyAndContinue() Action {
- cfg := m.com.Config()
+ store := m.com.Store()
- err := cfg.SetProviderAPIKey(string(m.provider.ID), m.input.Value())
+ err := store.SetProviderAPIKey(config.ScopeGlobal, string(m.provider.ID), m.input.Value())
if err != nil {
return ActionCmd{util.ReportError(fmt.Errorf("failed to save API key: %w", err))}
}
@@ -437,8 +437,11 @@ func (c *Commands) defaultCommands() []*CommandItem {
}
}
- // Add external editor command if $EDITOR is available
- // TODO: Use [tea.EnvMsg] to get environment variable instead of os.Getenv
+ // Add external editor command if $EDITOR is available.
+ //
+ // TODO: Use [tea.EnvMsg] to get environment variable instead of os.Getenv;
+ // because os.Getenv does IO is breaks the TEA paradigm and is generally an
+ // antipattern.
if os.Getenv("EDITOR") != "" {
commands = append(commands, NewCommandItem(c.com.Styles, "open_external_editor", "Open External Editor", "ctrl+o", ActionExternalEditor{}))
}
@@ -466,6 +469,15 @@ func (c *Commands) defaultCommands() []*CommandItem {
commands = append(commands, NewCommandItem(c.com.Styles, "toggle_pills", label, "ctrl+t", ActionTogglePills{}))
}
+ // Add a command for toggling notifications.
+ cfg = c.com.Config()
+ notificationsDisabled := cfg != nil && cfg.Options != nil && cfg.Options.DisableNotifications
+ notificationLabel := "Disable Notifications"
+ if notificationsDisabled {
+ notificationLabel = "Enable Notifications"
+ }
+ commands = append(commands, NewCommandItem(c.com.Styles, "toggle_notifications", notificationLabel, "", ActionToggleNotifications{}))
+
commands = append(commands,
NewCommandItem(c.com.Styles, "toggle_yolo", "Toggle Yolo Mode", "", ActionToggleYoloMode{}),
NewCommandItem(c.com.Styles, "toggle_help", "Toggle Help", "ctrl+g", ActionToggleHelp{}),
@@ -123,7 +123,7 @@ func (f *FilePicker) SetImageCapabilities(caps *common.Capabilities) {
// WorkingDir returns the current working directory of the [FilePicker].
func (f *FilePicker) WorkingDir() string {
- wd := f.com.Config().WorkingDir()
+ wd := f.com.Store().WorkingDir()
if len(wd) > 0 {
return wd
}
@@ -490,7 +490,7 @@ func (m *Models) setProviderItems() error {
if len(validRecentItems) != len(recentItems) {
// FIXME: Does this need to be here? Is it mutating the config during a read?
- if err := cfg.SetConfigField(fmt.Sprintf("recent_models.%s", selectedType), validRecentItems); err != nil {
+ if err := m.com.Store().SetConfigField(config.ScopeGlobal, fmt.Sprintf("recent_models.%s", selectedType), validRecentItems); err != nil {
return fmt.Errorf("failed to update recent models: %w", err)
}
}
@@ -373,9 +373,9 @@ func (d *OAuth) copyCodeAndOpenURL() tea.Cmd {
}
func (m *OAuth) saveKeyAndContinue() Action {
- cfg := m.com.Config()
+ store := m.com.Store()
- err := cfg.SetProviderAPIKey(string(m.provider.ID), m.token)
+ err := store.SetProviderAPIKey(config.ScopeGlobal, string(m.provider.ID), m.token)
if err != nil {
return ActionCmd{util.ReportError(fmt.Errorf("failed to save API key: %w", err))}
}
@@ -13,8 +13,6 @@ import (
"github.com/charmbracelet/crush/internal/ui/styles"
uv "github.com/charmbracelet/ultraviolet"
"github.com/sahilm/fuzzy"
- "golang.org/x/text/cases"
- "golang.org/x/text/language"
)
const (
@@ -241,13 +239,12 @@ func (r *Reasoning) setReasoningItems() error {
currentEffort = model.DefaultReasoningEffort
}
- caser := cases.Title(language.English)
items := make([]list.FilterableItem, 0, len(model.ReasoningLevels))
selectedIndex := 0
for i, effort := range model.ReasoningLevels {
item := &ReasoningItem{
effort: effort,
- title: caser.String(effort),
+ title: common.FormatReasoningEffort(effort),
isCurrent: effort == currentEffort,
t: r.com.Styles,
}
@@ -143,8 +143,7 @@ func renderHeaderDetails(
metadata = dot + metadata
const dirTrimLimit = 4
- cfg := com.Config()
- cwd := fsext.DirTrim(fsext.PrettyPath(cfg.WorkingDir()), dirTrimLimit)
+ cwd := fsext.DirTrim(fsext.PrettyPath(com.Store().WorkingDir()), dirTrimLimit)
cwd = t.Header.WorkingDir.Render(cwd)
result := cwd + metadata
@@ -22,7 +22,7 @@ func (m *UI) selectedLargeModel() *agent.Model {
func (m *UI) landingView() string {
t := m.com.Styles
width := m.layout.main.Dx()
- cwd := common.PrettyPath(t, m.com.Config().WorkingDir(), width)
+ cwd := common.PrettyPath(t, m.com.Store().WorkingDir(), width)
parts := []string{
cwd,
@@ -19,7 +19,7 @@ import (
// markProjectInitialized marks the current project as initialized in the config.
func (m *UI) markProjectInitialized() tea.Msg {
// TODO: handle error so we show it in the tui footer
- err := config.MarkProjectInitialized(m.com.Config())
+ err := config.MarkProjectInitialized(m.com.Store())
if err != nil {
slog.Error(err.Error())
}
@@ -52,10 +52,10 @@ func (m *UI) initializeProject() tea.Cmd {
if cmd := m.newSession(); cmd != nil {
cmds = append(cmds, cmd)
}
- cfg := m.com.Config()
+ cfg := m.com.Store()
initialize := func() tea.Msg {
- initPrompt, err := agent.InitializePrompt(*cfg)
+ initPrompt, err := agent.InitializePrompt(cfg)
if err != nil {
return util.InfoMsg{Type: util.InfoTypeError, Msg: err.Error()}
}
@@ -77,10 +77,9 @@ func (m *UI) skipInitializeProject() tea.Cmd {
// initializeView renders the project initialization prompt with Yes/No buttons.
func (m *UI) initializeView() string {
- cfg := m.com.Config()
s := m.com.Styles.Initialize
- cwd := home.Short(cfg.WorkingDir())
- initFile := cfg.Options.InitializeAs
+ cwd := home.Short(m.com.Store().WorkingDir())
+ initFile := m.com.Config().Options.InitializeAs
header := s.Header.Render("Would you like to initialize this project?")
path := s.Accent.PaddingLeft(2).Render(cwd)
@@ -9,8 +9,6 @@ import (
"github.com/charmbracelet/crush/internal/ui/logo"
uv "github.com/charmbracelet/ultraviolet"
"github.com/charmbracelet/ultraviolet/layout"
- "golang.org/x/text/cases"
- "golang.org/x/text/language"
)
// modelInfo renders the current model information including reasoning
@@ -35,9 +33,8 @@ func (m *UI) modelInfo(width int) string {
reasoningInfo = "Thinking Off"
}
} else {
- formatter := cases.Title(language.English, cases.NoLower)
reasoningEffort := cmp.Or(model.ModelCfg.ReasoningEffort, model.CatwalkCfg.DefaultReasoningEffort)
- reasoningInfo = formatter.String(fmt.Sprintf("Reasoning %s", reasoningEffort))
+ reasoningInfo = fmt.Sprintf("Reasoning %s", common.FormatReasoningEffort(reasoningEffort))
}
}
}
@@ -115,7 +112,7 @@ func (m *UI) drawSidebar(scr uv.Screen, area uv.Rectangle) {
height := area.Dy()
title := t.Muted.Width(width).MaxHeight(2).Render(m.session.Title)
- cwd := common.PrettyPath(t, m.com.Config().WorkingDir(), width)
+ cwd := common.PrettyPath(t, m.com.Store().WorkingDir(), width)
sidebarLogo := m.sidebarLogo
if height < logoHeightBreakpoint {
sidebarLogo = logo.SmallRender(m.com.Styles, width)
@@ -141,7 +138,7 @@ func (m *UI) drawSidebar(scr uv.Screen, area uv.Rectangle) {
lspSection := m.lspInfo(width, maxLSPs, true)
mcpSection := m.mcpInfo(width, maxMCPs, true)
- filesSection := m.filesInfo(m.com.Config().WorkingDir(), width, maxFiles, true)
+ filesSection := m.filesInfo(m.com.Store().WorkingDir(), width, maxFiles, true)
uv.NewStyledString(
lipgloss.NewStyle().
@@ -100,9 +100,12 @@ func (s *Status) Draw(scr uv.Screen, area uv.Rectangle) {
}
ind := indStyle.String()
- messageWidth := max(0, area.Dx()-lipgloss.Width(ind)-msgStyle.GetHorizontalPadding())
- msg := ansi.Truncate(s.msg.Msg, messageWidth, "…")
- msg += strings.Repeat(" ", max(0, messageWidth-lipgloss.Width(msg)))
+ indWidth := lipgloss.Width(ind)
+ msg := strings.Join(strings.Split(s.msg.Msg, "\n"), " ")
+ msgWidth := lipgloss.Width(msg)
+ msg = ansi.Truncate(msg, area.Dx()-indWidth-msgWidth, "…")
+ padWidth := max(0, area.Dx()-indWidth-msgWidth)
+ msg += strings.Repeat(" ", padWidth)
info := msgStyle.Render(msg)
// Draw the info message over the help view
@@ -25,6 +25,7 @@ import (
tea "charm.land/bubbletea/v2"
"charm.land/catwalk/pkg/catwalk"
"charm.land/lipgloss/v2"
+ "github.com/charmbracelet/crush/internal/agent/notify"
agenttools "github.com/charmbracelet/crush/internal/agent/tools"
"github.com/charmbracelet/crush/internal/agent/tools/mcp"
"github.com/charmbracelet/crush/internal/app"
@@ -45,6 +46,7 @@ import (
"github.com/charmbracelet/crush/internal/ui/dialog"
fimage "github.com/charmbracelet/crush/internal/ui/image"
"github.com/charmbracelet/crush/internal/ui/logo"
+ "github.com/charmbracelet/crush/internal/ui/notification"
"github.com/charmbracelet/crush/internal/ui/styles"
"github.com/charmbracelet/crush/internal/ui/util"
"github.com/charmbracelet/crush/internal/version"
@@ -201,6 +203,9 @@ type UI struct {
// sidebarLogo keeps a cached version of the sidebar sidebarLogo.
sidebarLogo string
+ // Notification state
+ notifyBackend notification.Backend
+ notifyWindowFocused bool
// custom commands & mcp commands
customCommands []commands.CustomCommand
mcpPrompts []commands.MCPPrompt
@@ -280,17 +285,19 @@ func New(com *common.Common) *UI {
header := newHeader(com)
ui := &UI{
- com: com,
- dialog: dialog.NewOverlay(),
- keyMap: keyMap,
- textarea: ta,
- chat: ch,
- header: header,
- completions: comp,
- attachments: attachments,
- todoSpinner: todoSpinner,
- lspStates: make(map[string]app.LSPClientInfo),
- mcpStates: make(map[string]mcp.ClientInfo),
+ com: com,
+ dialog: dialog.NewOverlay(),
+ keyMap: keyMap,
+ textarea: ta,
+ chat: ch,
+ header: header,
+ completions: comp,
+ attachments: attachments,
+ todoSpinner: todoSpinner,
+ lspStates: make(map[string]app.LSPClientInfo),
+ mcpStates: make(map[string]mcp.ClientInfo),
+ notifyBackend: notification.NoopBackend{},
+ notifyWindowFocused: true,
}
status := NewStatus(com, ui)
@@ -310,7 +317,7 @@ func New(com *common.Common) *UI {
desiredFocus := uiFocusEditor
if !com.Config().IsConfigured() {
desiredState = uiOnboarding
- } else if n, _ := config.ProjectNeedsInitialization(com.Config()); n {
+ } else if n, _ := config.ProjectNeedsInitialization(com.Store()); n {
desiredState = uiInitialize
}
@@ -342,6 +349,32 @@ func (m *UI) Init() tea.Cmd {
return tea.Batch(cmds...)
}
+// sendNotification returns a command that sends a notification if allowed by policy.
+func (m *UI) sendNotification(n notification.Notification) tea.Cmd {
+ if !m.shouldSendNotification() {
+ return nil
+ }
+
+ backend := m.notifyBackend
+ return func() tea.Msg {
+ if err := backend.Send(n); err != nil {
+ slog.Error("Failed to send notification", "error", err)
+ }
+ return nil
+ }
+}
+
+// shouldSendNotification returns true if notifications should be sent based on
+// current state. Focus reporting must be supported, window must not focused,
+// and notifications must not be disabled in config.
+func (m *UI) shouldSendNotification() bool {
+ cfg := m.com.Config()
+ if cfg != nil && cfg.Options != nil && cfg.Options.DisableNotifications {
+ return false
+ }
+ return m.caps.ReportFocusEvents && !m.notifyWindowFocused
+}
+
// setState changes the UI state and focus.
func (m *UI) setState(state uiState, focus uiFocusState) {
if state == uiLanding {
@@ -397,6 +430,18 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.sendProgressBar = slices.Contains(msg, "WT_SESSION")
}
cmds = append(cmds, common.QueryCmd(uv.Environ(msg)))
+ case tea.ModeReportMsg:
+ if m.caps.ReportFocusEvents {
+ m.notifyBackend = notification.NewNativeBackend(notification.Icon)
+ }
+ case tea.FocusMsg:
+ m.notifyWindowFocused = true
+ case tea.BlurMsg:
+ m.notifyWindowFocused = false
+ case pubsub.Event[notify.Notification]:
+ if cmd := m.handleAgentNotification(msg.Payload); cmd != nil {
+ cmds = append(cmds, cmd)
+ }
case loadSessionMsg:
if m.forceCompactMode {
m.isCompact = true
@@ -534,12 +579,20 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case mcp.EventPromptsListChanged:
return m, handleMCPPromptsEvent(msg.Payload.Name)
case mcp.EventToolsListChanged:
- return m, handleMCPToolsEvent(m.com.Config(), msg.Payload.Name)
+ return m, handleMCPToolsEvent(m.com.Store(), msg.Payload.Name)
+ case mcp.EventResourcesListChanged:
+ return m, handleMCPResourcesEvent(msg.Payload.Name)
}
case pubsub.Event[permission.PermissionRequest]:
if cmd := m.openPermissionsDialog(msg.Payload); cmd != nil {
cmds = append(cmds, cmd)
}
+ if cmd := m.sendNotification(notification.Notification{
+ Title: "Crush is waiting...",
+ Message: fmt.Sprintf("Permission required to execute \"%s\"", msg.Payload.ToolName),
+ }); cmd != nil {
+ cmds = append(cmds, cmd)
+ }
case pubsub.Event[permission.PermissionNotification]:
m.handlePermissionNotification(msg.Payload)
case cancelTimerExpiredMsg:
@@ -1176,24 +1229,40 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
cmds = append(cmds, msg.Cmd)
}
- // Session dialog messages
+ // Session dialog messages.
case dialog.ActionSelectSession:
m.dialog.CloseDialog(dialog.SessionsID)
cmds = append(cmds, m.loadSession(msg.Session.ID))
- // Open dialog message
+ // Open dialog message.
case dialog.ActionOpenDialog:
m.dialog.CloseDialog(dialog.CommandsID)
if cmd := m.openDialog(msg.DialogID); cmd != nil {
cmds = append(cmds, cmd)
}
- // Command dialog messages
+ // Command dialog messages.
case dialog.ActionToggleYoloMode:
yolo := !m.com.App.Permissions.SkipRequests()
m.com.App.Permissions.SetSkipRequests(yolo)
m.setEditorPrompt(yolo)
m.dialog.CloseDialog(dialog.CommandsID)
+ case dialog.ActionToggleNotifications:
+ cfg := m.com.Config()
+ if cfg != nil && cfg.Options != nil {
+ disabled := !cfg.Options.DisableNotifications
+ cfg.Options.DisableNotifications = disabled
+ if err := m.com.Store().SetConfigField(config.ScopeGlobal, "options.disable_notifications", disabled); err != nil {
+ cmds = append(cmds, util.ReportError(err))
+ } else {
+ status := "enabled"
+ if disabled {
+ status = "disabled"
+ }
+ cmds = append(cmds, util.CmdHandler(util.NewInfoMsg("Notifications "+status)))
+ }
+ }
+ m.dialog.CloseDialog(dialog.CommandsID)
case dialog.ActionNewSession:
if m.isAgentBusy() {
cmds = append(cmds, util.ReportWarn("Agent is busy, please wait before starting a new session..."))
@@ -1248,7 +1317,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
currentModel := cfg.Models[agentCfg.Model]
currentModel.Think = !currentModel.Think
- if err := cfg.UpdatePreferredModel(agentCfg.Model, currentModel); err != nil {
+ if err := m.com.Store().UpdatePreferredModel(config.ScopeGlobal, agentCfg.Model, currentModel); err != nil {
return util.ReportError(err)()
}
m.com.App.UpdateAgentModel(context.TODO())
@@ -1295,7 +1364,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
// Attempt to import GitHub Copilot tokens from VSCode if available.
if isCopilot && !isConfigured() && !msg.ReAuthenticate {
- m.com.Config().ImportCopilot()
+ m.com.Store().ImportCopilot()
}
if !isConfigured() || msg.ReAuthenticate {
@@ -1306,12 +1375,12 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
break
}
- if err := cfg.UpdatePreferredModel(msg.ModelType, msg.Model); err != nil {
+ if err := m.com.Store().UpdatePreferredModel(config.ScopeGlobal, msg.ModelType, msg.Model); err != nil {
cmds = append(cmds, util.ReportError(err))
} else if _, ok := cfg.Models[config.SelectedModelTypeSmall]; !ok {
// Ensure small model is set is unset.
smallModel := m.com.App.GetDefaultSmallModel(providerID)
- if err := cfg.UpdatePreferredModel(config.SelectedModelTypeSmall, smallModel); err != nil {
+ if err := m.com.Store().UpdatePreferredModel(config.ScopeGlobal, config.SelectedModelTypeSmall, smallModel); err != nil {
cmds = append(cmds, util.ReportError(err))
}
}
@@ -1357,7 +1426,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
currentModel := cfg.Models[agentCfg.Model]
currentModel.ReasoningEffort = msg.Effort
- if err := cfg.UpdatePreferredModel(agentCfg.Model, currentModel); err != nil {
+ if err := m.com.Store().UpdatePreferredModel(config.ScopeGlobal, agentCfg.Model, currentModel); err != nil {
cmds = append(cmds, util.ReportError(err))
break
}
@@ -1968,7 +2037,8 @@ func (m *UI) View() tea.View {
v.BackgroundColor = m.com.Styles.Background
}
v.MouseMode = tea.MouseModeCellMotion
- v.WindowTitle = "crush " + home.Short(m.com.Config().WorkingDir())
+ v.ReportFocus = m.caps.ReportFocusEvents
+ v.WindowTitle = "crush " + home.Short(m.com.Store().WorkingDir())
canvas := uv.NewScreenBuffer(m.width, m.height)
v.Cursor = m.Draw(canvas, canvas.Bounds())
@@ -2207,7 +2277,7 @@ func (m *UI) FullHelp() [][]key.Binding {
func (m *UI) toggleCompactMode() tea.Cmd {
m.forceCompactMode = !m.forceCompactMode
- err := m.com.Config().SetCompactMode(m.forceCompactMode)
+ err := m.com.Store().SetCompactMode(config.ScopeGlobal, m.forceCompactMode)
if err != nil {
return util.ReportError(err)
}
@@ -2589,7 +2659,7 @@ func (m *UI) insertMCPResourceCompletion(item completions.ResourceCompletionValu
return func() tea.Msg {
contents, err := mcp.ReadResource(
context.Background(),
- m.com.Config(),
+ m.com.Store(),
item.MCPName,
item.URI,
)
@@ -2984,6 +3054,20 @@ func (m *UI) handlePermissionNotification(notification permission.PermissionNoti
}
}
+// handleAgentNotification translates domain agent events into desktop
+// notifications using the UI notification backend.
+func (m *UI) handleAgentNotification(n notify.Notification) tea.Cmd {
+ switch n.Type {
+ case notify.TypeAgentFinished:
+ return m.sendNotification(notification.Notification{
+ Title: "Crush is waiting...",
+ Message: fmt.Sprintf("Agent's turn completed in \"%s\"", n.SessionTitle),
+ })
+ default:
+ return nil
+ }
+}
+
// newSession clears the current session state and prepares for a new session.
// The actual session creation happens when the user sends their first message.
// Returns a command to reload prompt history.
@@ -3135,13 +3219,13 @@ func (m *UI) pasteImageFromClipboard() tea.Msg {
textData, textErr := readClipboard(clipboardFormatText)
if textErr != nil || len(textData) == 0 {
- return util.NewInfoMsg("Clipboard is empty or does not contain an image")
+ return nil // Clipboard is empty or does not contain an image
}
path := strings.TrimSpace(string(textData))
path = strings.ReplaceAll(path, "\\ ", " ")
if _, statErr := os.Stat(path); statErr != nil {
- return util.NewInfoMsg("Clipboard does not contain an image or valid file path")
+ return nil // Clipboard does not contain an image or valid file path
}
lowerPath := strings.ToLower(path)
@@ -3237,7 +3321,7 @@ func (m *UI) drawSessionDetails(scr uv.Screen, area uv.Rectangle) {
lspSection := m.lspInfo(sectionWidth, maxItemsPerSection, false)
mcpSection := m.mcpInfo(sectionWidth, maxItemsPerSection, false)
- filesSection := m.filesInfo(m.com.Config().WorkingDir(), sectionWidth, maxItemsPerSection, false)
+ filesSection := m.filesInfo(m.com.Store().WorkingDir(), sectionWidth, maxItemsPerSection, false)
sections := lipgloss.JoinHorizontal(lipgloss.Top, filesSection, " ", lspSection, " ", mcpSection)
uv.NewStyledString(
s.CompactDetails.View.
@@ -3255,7 +3339,7 @@ func (m *UI) drawSessionDetails(scr uv.Screen, area uv.Rectangle) {
func (m *UI) runMCPPrompt(clientID, promptID string, arguments map[string]string) tea.Cmd {
load := func() tea.Msg {
- prompt, err := commands.GetMCPPrompt(m.com.Config(), clientID, promptID, arguments)
+ prompt, err := commands.GetMCPPrompt(m.com.Store(), clientID, promptID, arguments)
if err != nil {
// TODO: make this better
return util.ReportError(err)()
@@ -3296,7 +3380,7 @@ func handleMCPPromptsEvent(name string) tea.Cmd {
}
}
-func handleMCPToolsEvent(cfg *config.Config, name string) tea.Cmd {
+func handleMCPToolsEvent(cfg *config.ConfigStore, name string) tea.Cmd {
return func() tea.Msg {
mcp.RefreshTools(
context.Background(),
@@ -3327,14 +3411,14 @@ func (m *UI) copyChatHighlight() tea.Cmd {
}
func (m *UI) enableDockerMCP() tea.Msg {
- cfg := m.com.Config()
- if err := cfg.EnableDockerMCP(); err != nil {
+ store := m.com.Store()
+ if err := store.EnableDockerMCP(); err != nil {
return util.ReportError(err)()
}
// Initialize the Docker MCP client immediately.
ctx := context.Background()
- if err := mcp.InitializeSingle(ctx, config.DockerMCPName, cfg); err != nil {
+ if err := mcp.InitializeSingle(ctx, config.DockerMCPName, store); err != nil {
return util.ReportError(fmt.Errorf("docker MCP enabled but failed to start: %w", err))()
}
@@ -3342,14 +3426,14 @@ func (m *UI) enableDockerMCP() tea.Msg {
}
func (m *UI) disableDockerMCP() tea.Msg {
+ store := m.com.Store()
// Close the Docker MCP client.
- if err := mcp.DisableSingle(m.com.Config(), config.DockerMCPName); err != nil {
+ if err := mcp.DisableSingle(store, config.DockerMCPName); err != nil {
return util.ReportError(fmt.Errorf("failed to disable docker MCP: %w", err))()
}
// Remove from config and persist.
- cfg := m.com.Config()
- if err := cfg.DisableDockerMCP(); err != nil {
+ if err := store.DisableDockerMCP(); err != nil {
return util.ReportError(err)()
}
@@ -0,0 +1,7 @@
+//go:build darwin
+
+package notification
+
+// Icon is currently empty on darwin because platform icon support is broken. Do
+// use the icon for OSC notifications, just not native.
+var Icon any = ""
@@ -0,0 +1,13 @@
+//go:build !darwin
+
+package notification
+
+import (
+ _ "embed"
+)
+
+//go:embed crush-icon-solo.png
+var icon []byte
+
+// Icon contains the embedded PNG icon data for desktop notifications.
+var Icon any = icon
@@ -0,0 +1,49 @@
+package notification
+
+import (
+ "log/slog"
+
+ "github.com/gen2brain/beeep"
+)
+
+// NativeBackend sends desktop notifications using the native OS notification
+// system via beeep.
+type NativeBackend struct {
+ // icon is the notification icon data (platform-specific).
+ icon any
+ // notifyFunc is the function used to send notifications (swappable for testing).
+ notifyFunc func(title, message string, icon any) error
+}
+
+// NewNativeBackend creates a new native notification backend.
+func NewNativeBackend(icon any) *NativeBackend {
+ beeep.AppName = "Crush"
+ return &NativeBackend{
+ icon: icon,
+ notifyFunc: beeep.Notify,
+ }
+}
+
+// Send sends a desktop notification using the native OS notification system.
+func (b *NativeBackend) Send(n Notification) error {
+ slog.Debug("Sending native notification", "title", n.Title, "message", n.Message)
+
+ err := b.notifyFunc(n.Title, n.Message, b.icon)
+ if err != nil {
+ slog.Error("Failed to send notification", "error", err)
+ } else {
+ slog.Debug("Notification sent successfully")
+ }
+
+ return err
+}
+
+// SetNotifyFunc allows replacing the notification function for testing.
+func (b *NativeBackend) SetNotifyFunc(fn func(title, message string, icon any) error) {
+ b.notifyFunc = fn
+}
+
+// ResetNotifyFunc resets the notification function to the default.
+func (b *NativeBackend) ResetNotifyFunc() {
+ b.notifyFunc = beeep.Notify
+}
@@ -0,0 +1,10 @@
+package notification
+
+// NoopBackend is a no-op notification backend that does nothing.
+// This is the default backend used when notifications are not supported.
+type NoopBackend struct{}
+
+// Send does nothing and returns nil.
+func (NoopBackend) Send(_ Notification) error {
+ return nil
+}
@@ -0,0 +1,15 @@
+// Package notification provides desktop notification support for the UI.
+package notification
+
+// Notification represents a desktop notification request.
+type Notification struct {
+ Title string
+ Message string
+}
+
+// Backend defines the interface for sending desktop notifications.
+// Implementations are pure transport - policy decisions (config, focus state)
+// are handled by the caller.
+type Backend interface {
+ Send(n Notification) error
+}
@@ -0,0 +1,43 @@
+package notification_test
+
+import (
+ "testing"
+
+ "github.com/charmbracelet/crush/internal/ui/notification"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNoopBackend_Send(t *testing.T) {
+ t.Parallel()
+
+ backend := notification.NoopBackend{}
+ err := backend.Send(notification.Notification{
+ Title: "Test Title",
+ Message: "Test Message",
+ })
+ require.NoError(t, err)
+}
+
+func TestNativeBackend_Send(t *testing.T) {
+ t.Parallel()
+
+ backend := notification.NewNativeBackend(nil)
+
+ var capturedTitle, capturedMessage string
+ var capturedIcon any
+ backend.SetNotifyFunc(func(title, message string, icon any) error {
+ capturedTitle = title
+ capturedMessage = message
+ capturedIcon = icon
+ return nil
+ })
+
+ err := backend.Send(notification.Notification{
+ Title: "Hello",
+ Message: "World",
+ })
+ require.NoError(t, err)
+ require.Equal(t, "Hello", capturedTitle)
+ require.Equal(t, "World", capturedMessage)
+ require.Nil(t, capturedIcon)
+}
@@ -451,6 +451,11 @@
"type": "boolean",
"description": "Show indeterminate progress updates during long operations",
"default": true
+ },
+ "disable_notifications": {
+ "type": "boolean",
+ "description": "Disable desktop notifications",
+ "default": false
}
},
"additionalProperties": false,