diff --git a/.github/workflows/go-ci.yaml b/.github/workflows/go-ci.yaml index 09883eb..da1482a 100644 --- a/.github/workflows/go-ci.yaml +++ b/.github/workflows/go-ci.yaml @@ -7,28 +7,28 @@ on: branches: [ main ] env: - GO_VERSION: '1.27.1' + GO_VERSION: '1.27.3' jobs: # Lint job lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: go-version: ${{ env.GO_VERSION }} - uses: golangci/golangci-lint-action@v9 with: - version: v2.13.2 + version: v2.15.0 args: --timeout 5m # Test job test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: go-version: ${{ env.GO_VERSION }} - run: go test ./... -v @@ -37,13 +37,13 @@ jobs: security: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: go-version: ${{ env.GO_VERSION }} # gosec is installed rather than run as its container action: the # published image ships its own Go, which lags behind and fails to load # the module ("go.mod requires go >= X"). Installing it here builds gosec # with the toolchain above, so CI's Go version is ours to choose. - - run: go install github.com/securego/gosec/v2/cmd/gosec@v2.29.0 + - run: go install github.com/securego/gosec/v2/cmd/gosec@v2.30.0 - run: gosec ./... diff --git a/Dockerfile b/Dockerfile index 6300eb7..7ce5aac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ RUN go mod download COPY . . # Build the application -RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o telegram-bot . +RUN CGO_ENABLED=1 GOOS=linux go build -o telegram-bot . # Runtime stage FROM alpine:latest diff --git a/README.md b/README.md index cd7cc93..12cbabc 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,7 @@ A scalable, multi-bot solution for Telegram using Go, GORM, and the Anthropic AP ### Native Deployment -1. Install using `go get`: - - ```bash - go get -u github.com/HugeFrog24/go-telegram-bot - cd go-telegram-bot - ``` +1. Clone the repository as above. Building needs Go 1.27 or later and a C compiler, because the SQLite driver uses cgo. 2. Configure as above, then build: ```bash @@ -73,10 +68,11 @@ Each bot is one JSON file in `config/` (see `config/default.json` for the templa | Key | Type | Default | Description | | ------------------ | ------ | ----------- | ----------- | -| `max_tokens` | number | `1000` | Maximum output tokens per reply. **Thinking tokens count toward this limit** — raise it (e.g. `4000`+) whenever `thinking` is `"adaptive"`, or a turn can spend the whole budget on reasoning and produce no text. | +| `max_tokens` | number | `1000` | Maximum output tokens per reply. **Thinking tokens count toward this limit** — raise it (e.g. `4000`+) whenever `thinking` is `"adaptive"`, or a turn can spend the whole budget on reasoning and produce no text. Claude 5-generation models (Haiku 5.5, Sonnet 5 and later, Opus 5 and later, Fable) think even when `thinking` is omitted; on those, keep `effort` low or raise this limit. | +| `temperature` | number | *(ignored)* | No longer sent: since API version `2026-10-21` the Claude API rejects `temperature`, `top_p` and `top_k` on every model. Old configs still load, and the bot logs a startup notice; remove the key. | | `thinking` | string | *(omitted)* | Reasoning mode: `"adaptive"` (the model decides when and how much to think) or `"disabled"`. Omit the key entirely to use the model's own API default. If the configured model doesn't support the chosen mode, the API rejects the request with a 400 — owners/admins see the raw error, regular users get the generic fallback. Check [Anthropic's model docs](https://platform.claude.com/docs/en/build-with-claude/adaptive-thinking) for per-model support. | | `thinking_display` | string | *(omitted)* | `"summarized"` or `"omitted"`. Only valid together with `"thinking": "adaptive"`. Controls whether the API returns a readable summary of the reasoning (logged, never sent to chat). Thinking is billed the same either way; when omitted, the API's per-model default applies. | -| `effort` | string | *(omitted)* | `"low"`, `"medium"`, `"high"`, `"xhigh"` or `"max"`. Caps how much reasoning and token spend a turn may use; the API default is `high`. For short-reply chat bots `"low"` or `"medium"` cuts cost noticeably. Rejected with a 400 on models older than Claude 4.6 (Haiku 4.5, Sonnet 4.5) — omit it there. | +| `effort` | string | *(omitted)* | `"low"`, `"medium"`, `"high"`, `"xhigh"` or `"max"`. Caps how much reasoning and token spend a turn may use; the API default is `high` on most models and `medium` on Haiku 5.5 and Opus 5.5. For short-reply chat bots `"low"` or `"medium"` cuts cost noticeably. Rejected with a 400 on models older than Claude 4.6 (Haiku 4.5, Sonnet 4.5) — omit it there. | | `debounce_ms` | number | *(omitted)* | Quiet window, in milliseconds, for coalescing rapid follow-up messages into a single turn. Omit or set `0` to disable. See [Coalescing rapid messages](#coalescing-rapid-messages) below. | | `stream_drafts` | bool | `false` | Streams each reply into one Telegram draft that grows in place, with a native Stop button, and sends it as a single message when done. Private chats only. See [Draft streaming and the Stop button](#draft-streaming-and-the-stop-button) below. | | `cache_history` | bool | `true` | Places a prompt-cache breakpoint on the trailing conversation block, so each turn reads the prior history from cache instead of reprocessing it at full price. Set `false` to cache only the system prompt. | diff --git a/anthropic.go b/anthropic.go index 47da324..9263481 100644 --- a/anthropic.go +++ b/anthropic.go @@ -9,8 +9,8 @@ import ( "sync/atomic" "time" - "github.com/anthropics/anthropic-sdk-go" - "github.com/anthropics/anthropic-sdk-go/packages/param" + "github.com/anthropics/anthropic-sdk-go/v2" + "github.com/anthropics/anthropic-sdk-go/v2/packages/param" ) var ErrModelNotFound = errors.New("model not found or deprecated") @@ -58,10 +58,6 @@ func (b *Bot) getAnthropicResponse(ctx context.Context, chatID int64, messages [ params.System = blocks } - if b.config.Temperature != nil { - params.Temperature = param.NewOpt(float64(*b.config.Temperature)) - } - if thinking, ok := thinkingParamFromConfig(b.config.Thinking, b.config.ThinkingDisplay); ok { params.Thinking = thinking } @@ -159,7 +155,7 @@ func (b *Bot) getAnthropicResponse(ctx context.Context, chatID int64, messages [ } if full == "" { - return "", emptyStreamError(string(lastMsg.StopReason), + return "", emptyStreamError(string(lastMsg.StopReason), string(lastMsg.StopDetails.Category), lastMsg.Usage.OutputTokensDetails.ThinkingTokens, params.MaxTokens) } return full, nil @@ -466,10 +462,16 @@ func joinNonEmpty(a, b string) string { } } -func emptyStreamError(stopReason string, thinkingTokens, maxTokens int64) error { - if stopReason == "max_tokens" { +func emptyStreamError(stopReason, refusalCategory string, thinkingTokens, maxTokens int64) error { + switch stopReason { + case "max_tokens": return fmt.Errorf("output budget exhausted before any text (thinking used %d of %d max_tokens) - raise max_tokens", thinkingTokens, maxTokens) + case "refusal": + if refusalCategory == "" { + return fmt.Errorf("the model declined the request (refusal)") + } + return fmt.Errorf("the model declined the request (refusal, category %s)", refusalCategory) } return fmt.Errorf("unexpected response format from Anthropic") } diff --git a/anthropic_files.go b/anthropic_files.go index 79ed408..41ab201 100644 --- a/anthropic_files.go +++ b/anthropic_files.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/anthropics/anthropic-sdk-go" + "github.com/anthropics/anthropic-sdk-go/v2" ) const fileNotFoundPrefix = "File not found: " diff --git a/anthropic_test.go b/anthropic_test.go index 207a967..ec597ba 100644 --- a/anthropic_test.go +++ b/anthropic_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/anthropics/anthropic-sdk-go" + "github.com/anthropics/anthropic-sdk-go/v2" ) func TestTimeContextFor(t *testing.T) { @@ -295,16 +295,22 @@ func TestFetchHosts(t *testing.T) { } func TestEmptyStreamError(t *testing.T) { - err := emptyStreamError("max_tokens", 3900, 4000) + err := emptyStreamError("max_tokens", "", 3900, 4000) for _, want := range []string{"output budget exhausted", "3900", "4000"} { if !strings.Contains(err.Error(), want) { t.Errorf("max_tokens case: %q missing %q", err.Error(), want) } } - if got := emptyStreamError("end_turn", 0, 1000).Error(); got != "unexpected response format from Anthropic" { + if got := emptyStreamError("refusal", "cyber", 0, 1000).Error(); got != "the model declined the request (refusal, category cyber)" { + t.Errorf("refusal case = %q", got) + } + if got := emptyStreamError("refusal", "", 0, 1000).Error(); got != "the model declined the request (refusal)" { + t.Errorf("uncategorized refusal case = %q", got) + } + if got := emptyStreamError("end_turn", "", 0, 1000).Error(); got != "unexpected response format from Anthropic" { t.Errorf("generic case = %q", got) } - if got := emptyStreamError("", 0, 1000).Error(); got != "unexpected response format from Anthropic" { + if got := emptyStreamError("", "", 0, 1000).Error(); got != "unexpected response format from Anthropic" { t.Errorf("no-stop-reason case = %q", got) } } diff --git a/bot.go b/bot.go index 2264930..2754e3e 100644 --- a/bot.go +++ b/bot.go @@ -8,8 +8,8 @@ import ( "sync" "time" - "github.com/anthropics/anthropic-sdk-go" - "github.com/anthropics/anthropic-sdk-go/option" + "github.com/anthropics/anthropic-sdk-go/v2" + "github.com/anthropics/anthropic-sdk-go/v2/option" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" "gorm.io/gorm" @@ -83,7 +83,13 @@ func NewBot(db *gorm.DB, config BotConfig, clock Clock, tgClient TelegramClient) return nil, err } - anthropicClient := anthropic.NewClient(option.WithAPIKey(config.AnthropicAPIKey)) + // An explicitly passed credential is used as is, so an empty key would go + // out as an empty bearer token; leave it out to use the SDK's default chain. + var anthropicOpts []option.RequestOption + if config.AnthropicAPIKey != "" { + anthropicOpts = append(anthropicOpts, option.WithAPIKey(config.AnthropicAPIKey)) + } + anthropicClient := anthropic.NewClient(anthropicOpts...) b := &Bot{ db: db, @@ -339,10 +345,9 @@ func (b *Bot) buildContextMessages(chatMemory *ChatMemory) []anthropic.BetaMessa if msg.IsUser { param = anthropic.NewBetaUserMessage(blocks...) } else { - param = anthropic.BetaMessageParam{ - Role: anthropic.BetaMessageParamRoleAssistant, + param = anthropic.BetaMessageParam{OfAssistant: &anthropic.BetaAssistantMessageParam{ Content: blocks, - } + }} } contextMessages = append(contextMessages, param) } @@ -366,7 +371,13 @@ func markTrailingCacheBreakpoint(messages []anthropic.BetaMessageParam) { if len(messages) == 0 { return } - blocks := messages[len(messages)-1].Content + var blocks []anthropic.BetaContentBlockParamUnion + switch last := messages[len(messages)-1]; { + case last.OfUser != nil: + blocks = last.OfUser.Content + case last.OfAssistant != nil: + blocks = last.OfAssistant.Content + } if len(blocks) == 0 { return } diff --git a/config.go b/config.go index d396a2f..f48eca5 100644 --- a/config.go +++ b/config.go @@ -76,7 +76,7 @@ type BotConfig struct { MessagePerDay int `json:"messages_per_day"` TempBanDuration string `json:"temp_ban_duration"` Model string `json:"model"` - Temperature *float32 `json:"temperature,omitempty"` + Temperature *float32 `json:"temperature,omitempty"` // never sent (rejected since API 2026-10-21); parsed only to flag stale configs MaxTokens int `json:"max_tokens,omitempty"` Thinking string `json:"thinking,omitempty"` ThinkingDisplay string `json:"thinking_display,omitempty"` @@ -175,6 +175,11 @@ func loadAllConfigs(dir string) ([]BotConfig, error) { // logConfigAdvisories emits non-fatal boot-time notes about settings that are // valid but likely to surprise: silently ineffective, or costlier than intended. func logConfigAdvisories(config *BotConfig) { + if config.Temperature != nil { + InfoLogger.Printf("[%s] temperature=%g is ignored: the Claude API no longer accepts sampling parameters; remove the key", + config.ID, *config.Temperature) + } + if config.Thinking == ThinkingModeAdaptive && config.MaxTokens > 0 && config.MaxTokens < 4000 { InfoLogger.Printf("[%s] thinking=adaptive with max_tokens=%d: thinking tokens count toward max_tokens; consider >= 4000", config.ID, config.MaxTokens) diff --git a/config/default.json b/config/default.json index 2a3c5dc..9a9e62e 100644 --- a/config/default.json +++ b/config/default.json @@ -11,9 +11,9 @@ "messages_per_hour": 20, "messages_per_day": 100, "temp_ban_duration": "24h", - "model": "claude-haiku-4-5", - "temperature": 0.7, + "model": "claude-haiku-5-5", "max_tokens": 1000, + "effort": "low", "debounce_ms": 2500, "debug_screening": false, "system_prompts": { diff --git a/elevenlabs.go b/elevenlabs.go index 705857f..05da21b 100644 --- a/elevenlabs.go +++ b/elevenlabs.go @@ -56,7 +56,7 @@ func (b *Bot) transcribeVoice(ctx context.Context, fileID string) (string, error var buf bytes.Buffer mw := multipart.NewWriter(&buf) - if err := mw.WriteField("model_id", "scribe_v1"); err != nil { + if err := mw.WriteField("model_id", "scribe_v2"); err != nil { return "", fmt.Errorf("multipart write error: %w", err) } part, err := mw.CreateFormFile("file", "audio.ogg") diff --git a/go.mod b/go.mod index 9a82bf6..8b16c51 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/HugeFrog24/go-telegram-bot -go 1.26.0 +go 1.27.0 require ( - github.com/anthropics/anthropic-sdk-go v1.71.0 - github.com/go-telegram/bot v1.25.0 + github.com/anthropics/anthropic-sdk-go/v2 v2.3.0 + github.com/go-telegram/bot v1.27.0 github.com/stretchr/testify v1.12.1 - golang.org/x/sync v0.23.0 + golang.org/x/sync v0.24.0 golang.org/x/time v0.16.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.2 @@ -15,18 +15,26 @@ require ( require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.6.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/go-logr/logr v1.4.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/invopop/jsonschema v0.14.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-sqlite3 v1.14.52 // indirect - github.com/pb33f/ordered-map/v2 v2.3.1 // indirect + github.com/pb33f/go-yaml v0.1.1 // indirect + github.com/pb33f/ordered-map/v2 v2.3.2 // indirect github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 // indirect - github.com/stretchr/objx v0.5.3 // indirect - github.com/tidwall/gjson v1.19.0 // indirect + github.com/stretchr/objx v0.8.0 // indirect + github.com/tidwall/gjson v1.20.0 // indirect github.com/tidwall/match v1.2.0 // indirect - github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/pretty v1.2.2 // indirect github.com/tidwall/sjson v1.2.5 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.47.0 // indirect + go.opentelemetry.io/otel/log v1.47.0 // indirect + go.opentelemetry.io/otel/metric v1.47.0 // indirect + go.opentelemetry.io/otel/trace v1.47.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect - go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect - golang.org/x/text v0.42.0 // indirect + golang.org/x/text v0.43.0 // indirect ) diff --git a/go.sum b/go.sum index 88ce414..a7d1aba 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,70 @@ -github.com/anthropics/anthropic-sdk-go v1.71.0 h1:DK9xG3s5t+xUIOp+R+1LIRjkU/wG57YyQO3630+gYOE= -github.com/anthropics/anthropic-sdk-go v1.71.0/go.mod h1:x+lPk/cCl48uRegeP0hlYYBN1b7bEBTveInIMgLicnY= +cloud.google.com/go/auth v0.7.2 h1:uiha352VrCDMXg+yoBtaD0tUF4Kv9vrtrWPYXwutnDE= +cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= +cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= +cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +github.com/anthropics/anthropic-sdk-go/v2 v2.3.0 h1:wEaN13ToNCcjdqP+47ALC78Jt+DkGMmwqL5zzPQTGkQ= +github.com/anthropics/anthropic-sdk-go/v2 v2.3.0/go.mod h1:LAOInMKgn6reTIrPvKXa5weyqzxsJkxSBuzhnxzukpQ= +github.com/aws/aws-sdk-go-v2 v1.38.0 h1:UCRQ5mlqcFk9HJDIqENSLR3wiG1VTWlyUfLDEvY7RxU= +github.com/aws/aws-sdk-go-v2 v1.38.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM= +github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= +github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= +github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= +github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.6.1 h1:I0phFv0PlbLHnM7TZAVjZ2MJ2/eWRTDyuO7GLR98IEs= github.com/buger/jsonparser v1.6.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/go-telegram/bot v1.25.0 h1:C072msZtr+GjJiQsX/UJLirz+jzSNZVWkckxWufR7Ww= -github.com/go-telegram/bot v1.25.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/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-telegram/bot v1.27.0 h1:PG9yBDzM8O26oUerE9SZwWlHon4RpH8WcNslZ7CEZmE= +github.com/go-telegram/bot v1.27.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= +github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg= github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -16,35 +73,77 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/mattn/go-sqlite3 v1.14.52 h1:wVbm2Qnf4OXkqhBTSPuCRZDRnxfbVrrmiCEroVdog8U= github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= -github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= -github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= +github.com/modelcontextprotocol/go-sdk v1.3.1 h1:TfqtNKOIWN4Z1oqmPAiWDC2Jq7K9OdJaooe0teoXASI= +github.com/modelcontextprotocol/go-sdk v1.3.1/go.mod h1:DgVX498dMD8UJlseK1S5i1T4tFz2fkBk4xogC3D15nw= +github.com/pb33f/go-yaml v0.1.1 h1:yWGmhVdwzionRnSc/hEJwcfq6PaZL2QdYzyReXBbX7Q= +github.com/pb33f/go-yaml v0.1.1/go.mod h1:QvWdkHP5VjFYAeHMQUkaPQqREaQnBNOXmWzAjNrnSdo= +github.com/pb33f/ordered-map/v2 v2.3.2 h1:wDyaZ2Pv9QLh64X4utCeD5Zoi9nIv2aW3lwv172O5PQ= +github.com/pb33f/ordered-map/v2 v2.3.2/go.mod h1:1OhFrXu3OYw3kM+FXF+Ug3ugmmZ9LE8z0JfQMLvtV0w= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 h1:uOfcYT+3QungH6tIGSVCR/Y3KJmgJiHcojJbMTPDZAI= github.com/standard-webhooks/standard-webhooks/libraries v0.0.1/go.mod h1:L1MQhA6x4dn9r007T033lsaZMv9EmBAdXyU/+EF40fo= -github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= -github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/objx v0.8.0 h1:rm9oMkeprG/wHifkZP4EuDww9S0mvEaQ/p9cXTNT4cw= +github.com/stretchr/objx v0.8.0/go.mod h1:xSzHnPHkdEv7vtgdQej8atlCuU1mdX+ELge8bn0hCsE= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= -github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/gjson v1.20.0 h1:+agJ3rEzKcCXKDKo0ml26UROcpcAlnZyjq+TjbY5Fto= +github.com/tidwall/gjson v1.20.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.2 h1:dz1jrRuE7or/74V490B4/GP1pZm5WKlt2bgCP5A83w8= +github.com/tidwall/pretty v1.2.2/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM= +go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU= +go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI= +go.opentelemetry.io/otel/log v1.47.0/go.mod h1:9byitSQ5pLC6PpqwGXjqdMKya6ZTswHRZh2vvXT33nw= +go.opentelemetry.io/otel/metric v1.47.0 h1:4PptaldXx3Eat1XjMZ68pPJEs5wrhlemctZE9a3UdWY= +go.opentelemetry.io/otel/metric v1.47.0/go.mod h1:ADGSXxRrXM6bjbvLo535EstVFlPpPYZm4LBKixjDHwU= +go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= +go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= +go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrFNWSrM= +go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= -go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= -golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= -golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= -golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= -golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= +golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc= +golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s= golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= +google.golang.org/api v0.189.0 h1:equMo30LypAkdkLMBqfeIqtyAnlyig1JSZArl4XPwdI= +google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade h1:oCRSWfwGXQsqlVdErcyTt4A93Y8fo0/9D4b1gnI++qo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= diff --git a/handlers.go b/handlers.go index ac71395..8467286 100644 --- a/handlers.go +++ b/handlers.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/anthropics/anthropic-sdk-go" + "github.com/anthropics/anthropic-sdk-go/v2" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" "golang.org/x/sync/errgroup" diff --git a/prompt_cache_test.go b/prompt_cache_test.go index 443e44f..cc9ea9d 100644 --- a/prompt_cache_test.go +++ b/prompt_cache_test.go @@ -3,8 +3,8 @@ package main import ( "testing" - "github.com/anthropics/anthropic-sdk-go" - "github.com/anthropics/anthropic-sdk-go/packages/param" + "github.com/anthropics/anthropic-sdk-go/v2" + "github.com/anthropics/anthropic-sdk-go/v2/packages/param" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,14 +21,14 @@ func TestMarkTrailingCacheBreakpoint(t *testing.T) { markTrailingCacheBreakpoint(msgs) - last := msgs[1].Content[1].OfText + last := msgs[1].OfUser.Content[1].OfText require.NotNil(t, last) assert.False(t, param.IsOmitted(last.CacheControl), "the trailing block carries the breakpoint") // Everything earlier stays unmarked: one breakpoint, not one per block. - assert.True(t, param.IsOmitted(msgs[1].Content[0].OfText.CacheControl)) - assert.True(t, param.IsOmitted(msgs[0].Content[0].OfText.CacheControl)) + assert.True(t, param.IsOmitted(msgs[1].OfUser.Content[0].OfText.CacheControl)) + assert.True(t, param.IsOmitted(msgs[0].OfUser.Content[0].OfText.CacheControl)) }) t.Run("marks a trailing image block", func(t *testing.T) { @@ -40,8 +40,8 @@ func TestMarkTrailingCacheBreakpoint(t *testing.T) { markTrailingCacheBreakpoint(msgs) - require.NotNil(t, msgs[0].Content[0].OfImage) - assert.False(t, param.IsOmitted(msgs[0].Content[0].OfImage.CacheControl)) + require.NotNil(t, msgs[0].OfUser.Content[0].OfImage) + assert.False(t, param.IsOmitted(msgs[0].OfUser.Content[0].OfImage.CacheControl)) }) t.Run("tolerates empty input", func(t *testing.T) { @@ -65,7 +65,9 @@ func TestPrepareContextMessages_CacheHistoryToggle(t *testing.T) { //NOSONAR go: t.Run("enabled by default", func(t *testing.T) { msgs := b.prepareContextMessages(memory) require.Len(t, msgs, 2) - assert.False(t, param.IsOmitted(msgs[1].Content[0].OfText.CacheControl)) + require.NotNil(t, msgs[0].OfUser, "user memory becomes a user turn") + require.NotNil(t, msgs[1].OfAssistant, "bot memory becomes an assistant turn") + assert.False(t, param.IsOmitted(msgs[1].OfAssistant.Content[0].OfText.CacheControl)) }) t.Run("opt-out leaves history unmarked", func(t *testing.T) { @@ -75,7 +77,7 @@ func TestPrepareContextMessages_CacheHistoryToggle(t *testing.T) { //NOSONAR go: msgs := b.prepareContextMessages(memory) require.Len(t, msgs, 2) - assert.True(t, param.IsOmitted(msgs[1].Content[0].OfText.CacheControl)) + assert.True(t, param.IsOmitted(msgs[1].OfAssistant.Content[0].OfText.CacheControl)) }) }