first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:52:23 +03:00
commit 880f412e2c
2662 changed files with 866266 additions and 0 deletions

View File

159
plugins/logging/go.mod Normal file
View File

@@ -0,0 +1,159 @@
module github.com/maximhq/bifrost/plugins/logging
go 1.26.2
require (
github.com/bytedance/sonic v1.15.0
github.com/maximhq/bifrost/core v1.5.4
github.com/maximhq/bifrost/framework v1.3.4
)
require (
cel.dev/expr v0.25.1 // indirect
cloud.google.com/go v0.123.0 // indirect
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
cloud.google.com/go/iam v1.5.3 // indirect
cloud.google.com/go/monitoring v1.24.3 // indirect
cloud.google.com/go/storage v1.61.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.11 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.24.2 // indirect
github.com/go-openapi/errors v0.22.5 // indirect
github.com/go-openapi/jsonpointer v0.22.4 // indirect
github.com/go-openapi/jsonreference v0.21.4 // indirect
github.com/go-openapi/loads v0.23.2 // indirect
github.com/go-openapi/runtime v0.29.2 // indirect
github.com/go-openapi/spec v0.22.2 // indirect
github.com/go-openapi/strfmt v0.25.0 // indirect
github.com/go-openapi/swag v0.25.4 // indirect
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
github.com/go-openapi/swag/conv v0.25.4 // indirect
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
github.com/go-openapi/swag/loading v0.25.4 // indirect
github.com/go-openapi/swag/mangling v0.25.4 // indirect
github.com/go-openapi/swag/netutils v0.25.4 // indirect
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
github.com/go-openapi/validate v0.25.1 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
github.com/googleapis/gax-go/v2 v2.19.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.9.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.9.1 // indirect
github.com/mark3labs/mcp-go v0.43.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/oapi-codegen/runtime v1.1.1 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/pinecone-io/go-pinecone/v5 v5.3.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/qdrant/go-client v1.16.2 // indirect
github.com/redis/go-redis/v9 v9.17.2 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.68.0 // indirect
github.com/weaviate/weaviate v1.36.5 // indirect
github.com/weaviate/weaviate-go-client/v5 v5.7.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.mongodb.org/mongo-driver v1.17.6 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.40.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.starlark.net v0.0.0-20260102030733-3fee463870c9 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.23.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/api v0.274.0 // indirect
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/postgres v1.6.0 // indirect
gorm.io/driver/sqlite v1.6.0 // indirect
gorm.io/gorm v1.31.1 // indirect
)

389
plugins/logging/go.sum Normal file
View File

@@ -0,0 +1,389 @@
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
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=
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
cloud.google.com/go/logging v1.13.2 h1:qqlHCBvieJT9Cdq4QqYx1KPadCQ2noD4FK02eNqHAjA=
cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak=
cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8=
cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
cloud.google.com/go/storage v1.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=
cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk=
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
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.14 h1:n+UcGWAIZHkXzYt87uMFBv/l8THYELoX6gVcUvgl6fI=
github.com/aws/aws-sdk-go-v2/credentials v1.19.14/go.mod h1:cJKuyWB59Mqi0jM3nFYQRmnHVQIcgoxjEMAbLkpr62w=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 h1:NUS3K4BTDArQqNu2ih7yeDLaS3bmHD0YndtA6UP884g=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21/go.mod h1:YWNWJQNjKigKY1RHVJCuupeWDrrHjRqHm0N9rdrWzYI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
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/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 h1:QKZH0S178gCmFEgst8hN0mCX1KxLgHBKKY/CLqwP8lg=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9/go.mod h1:7yuQJoT+OoH8aqIxw9vwF+8KpvLZ8AWmvmUWHsGQZvI=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 h1:lFd1+ZSEYJZYvv9d6kXzhkZu07si3f+GQ1AaYwa2LUM=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 h1:dzztQ1YmfPrxdrOiuZRMF6fuOwWlWpD2StNLTceKpys=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19/go.mod h1:YO8TrYtFdl5w/4vmjL8zaBSsiNp3w0L1FfKVKenZT7w=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 h1:p8ogvvLugcR/zLBXTXrTkj0RYBUdErbMnAFFp12Lm/U=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw=
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/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/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
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/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOUInlE=
github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
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-openapi/analysis v0.24.2 h1:6p7WXEuKy1llDgOH8FooVeO+Uq2za9qoAOq4ZN08B50=
github.com/go-openapi/analysis v0.24.2/go.mod h1:x27OOHKANE0lutg2ml4kzYLoHGMKgRm1Cj2ijVOjJuE=
github.com/go-openapi/errors v0.22.5 h1:Yfv4O/PRYpNF3BNmVkEizcHb3uLVVsrDt3LNdgAKRY4=
github.com/go-openapi/errors v0.22.5/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk=
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJp4=
github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY=
github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0=
github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0=
github.com/go-openapi/spec v0.22.2 h1:KEU4Fb+Lp1qg0V4MxrSCPv403ZjBl8Lx1a83gIPU8Qc=
github.com/go-openapi/spec v0.22.2/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs=
github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ=
github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8=
github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=
github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=
github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=
github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=
github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=
github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=
github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=
github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-openapi/validate v0.25.1 h1:sSACUI6Jcnbo5IWqbYHgjibrhhmt3vR6lCzKZnmAgBw=
github.com/go-openapi/validate v0.25.1/go.mod h1:RMVyVFYte0gbSTaZ0N4KmTn6u/kClvAFp+mAVfS/DQc=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
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/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/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
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.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/maximhq/bifrost/core v1.5.4 h1:hf0BhoHVVpY1EQ4FkyRzW4IBYjrolxdZV0ucgWfHhcE=
github.com/maximhq/bifrost/core v1.5.4/go.mod h1:z1/vOalbDAD7v7sYbXQsqR+2qIFP0jKOSIStw6Q4P4U=
github.com/maximhq/bifrost/framework v1.3.4 h1:nZPv1FYry1njexZ0Hb6CZQXybwRFKGMTRyGWz2HGcio=
github.com/maximhq/bifrost/framework v1.3.4/go.mod h1:e0defDjWWFi6c2Zs3AOkMcRbYzjww4sjkyZtARrP4Zk=
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pinecone-io/go-pinecone/v5 v5.3.0 h1:0YQlEtmXGWK/I8ztkOVM6PuBYgFJZhjSdb0ddU+bHPE=
github.com/pinecone-io/go-pinecone/v5 v5.3.0/go.mod h1:6Fg85fcyvMUQFf9KW7zniN81kelSYvsjF+KPLdc1MGA=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/qdrant/go-client v1.16.2 h1:UUMJJfvXTByhwhH1DwWdbkhZ2cTdvSqVkXSIfBrVWSg=
github.com/qdrant/go-client v1.16.2/go.mod h1:I+EL3h4HRoRTeHtbfOd/4kDXwCukZfkd41j/9wryGkw=
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 h1:qIQ0tWF9vxGtkJa24bR+2i53WBCz1nW/Pc47oVYauC4=
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok=
github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4=
github.com/weaviate/weaviate v1.36.5 h1:lCiuEfQ08+5wK0DkTCUBb6ayNep9QpBH6JJhmZaRfzk=
github.com/weaviate/weaviate v1.36.5/go.mod h1:ljzrgEmGKn3CRzDdcxvhmBUUZIcghwIYd1Lmn54f3Z8=
github.com/weaviate/weaviate-go-client/v5 v5.7.1 h1:vEMxh486QqRqWaq58UEe/TiTbGbo9T5x7ZPFd5QENvQ=
github.com/weaviate/weaviate-go-client/v5 v5.7.1/go.mod h1:T/JDErjN074GrnYIa0AgK1TGUGP/6A/8vqXNPlv4c6E=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
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.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
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/detectors/gcp v1.40.0 h1:Awaf8gmW99tZTOWqkLCOl6aw1/rxAWVlHsHIZ3fT2sA=
go.opentelemetry.io/contrib/detectors/gcp v1.40.0/go.mod h1:99OY9ZCqyLkzJLTh5XhECpLRSxcZl+ZDKBEO+jMBFR4=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.starlark.net v0.0.0-20260102030733-3fee463870c9 h1:nV1OyvU+0CYrp5eKfQ3rD03TpFYYhH08z31NK1HmtTk=
go.starlark.net v0.0.0-20260102030733-3fee463870c9/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.274.0 h1:aYhycS5QQCwxHLwfEHRRLf9yNsfvp1JadKKWBE54RFA=
google.golang.org/api v0.274.0/go.mod h1:JbAt7mF+XVmWu6xNP8/+CTiGH30ofmCmk9nM8d8fHew=
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/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=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=

1293
plugins/logging/main.go Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,627 @@
package logging
import (
"context"
"path/filepath"
"strings"
"testing"
"time"
"github.com/maximhq/bifrost/core/schemas"
"github.com/maximhq/bifrost/framework/logstore"
)
type testLogger struct{}
func (testLogger) Debug(string, ...any) {}
func (testLogger) Info(string, ...any) {}
func (testLogger) Warn(string, ...any) {}
func (testLogger) Error(string, ...any) {}
func (testLogger) Fatal(string, ...any) {}
func (testLogger) SetLevel(schemas.LogLevel) {}
func (testLogger) SetOutputType(schemas.LoggerOutputType) {}
func (testLogger) LogHTTPRequest(schemas.LogLevel, string) schemas.LogEventBuilder {
return schemas.NoopLogEvent
}
func newTestStore(t *testing.T) logstore.LogStore {
t.Helper()
store, err := logstore.NewLogStore(context.Background(), &logstore.Config{
Enabled: true,
Type: logstore.LogStoreTypeSQLite,
Config: &logstore.SQLiteConfig{
Path: filepath.Join(t.TempDir(), "logging.db"),
},
}, testLogger{})
if err != nil {
t.Fatalf("NewLogStore() error = %v", err)
}
return store
}
func TestUpdateLogEntryPreservesResponsesInputContentSummary(t *testing.T) {
store := newTestStore(t)
plugin := &LoggerPlugin{
store: store,
logger: testLogger{},
}
requestID := "req-1"
now := time.Now().UTC()
inputText := "request-side text"
initial := &InitialLogData{
Object: "responses",
Provider: "openai",
Model: "gpt-4o-mini",
ResponsesInputHistory: []schemas.ResponsesMessage{{
Content: &schemas.ResponsesMessageContent{
ContentStr: &inputText,
},
}},
}
if err := plugin.insertInitialLogEntry(context.Background(), requestID, "", now, 0, nil, initial); err != nil {
t.Fatalf("insertInitialLogEntry() error = %v", err)
}
responsesText := "responses output"
update := &UpdateLogData{
Status: "success",
ResponsesOutput: []schemas.ResponsesMessage{{
Content: &schemas.ResponsesMessageContent{
ContentStr: &responsesText,
},
}},
}
if err := plugin.updateLogEntry(context.Background(), requestID, "", "", 10, "", "", "", "", 0, nil, "", update); err != nil {
t.Fatalf("updateLogEntry() error = %v", err)
}
logEntry, err := store.FindByID(context.Background(), requestID)
if err != nil {
t.Fatalf("FindByID() error = %v", err)
}
if !strings.Contains(logEntry.ContentSummary, inputText) {
t.Fatalf("expected content summary to preserve responses input, got %q", logEntry.ContentSummary)
}
if strings.Contains(logEntry.ContentSummary, responsesText) {
t.Fatalf("expected content summary to avoid overwriting with responses output-only data, got %q", logEntry.ContentSummary)
}
}
func TestUpdateLogEntryUpdatesContentSummaryForChatOutput(t *testing.T) {
store := newTestStore(t)
plugin := &LoggerPlugin{
store: store,
logger: testLogger{},
}
requestID := "req-chat"
now := time.Now().UTC()
initial := &InitialLogData{
Object: "chat_completion",
Provider: "openai",
Model: "gpt-4o-mini",
}
if err := plugin.insertInitialLogEntry(context.Background(), requestID, "", now, 0, nil, initial); err != nil {
t.Fatalf("insertInitialLogEntry() error = %v", err)
}
chatText := "assistant output"
update := &UpdateLogData{
Status: "success",
ChatOutput: &schemas.ChatMessage{
Role: schemas.ChatMessageRoleAssistant,
Content: &schemas.ChatMessageContent{
ContentStr: &chatText,
},
},
}
if err := plugin.updateLogEntry(context.Background(), requestID, "", "", 10, "", "", "", "", 0, nil, "", update); err != nil {
t.Fatalf("updateLogEntry() error = %v", err)
}
logEntry, err := store.FindByID(context.Background(), requestID)
if err != nil {
t.Fatalf("FindByID() error = %v", err)
}
if !strings.Contains(logEntry.ContentSummary, chatText) {
t.Fatalf("expected content summary to include chat output, got %q", logEntry.ContentSummary)
}
}
func TestUpdateLogEntrySuppressesChatOutputWhenContentLoggingDisabled(t *testing.T) {
store := newTestStore(t)
disableContentLogging := true
plugin := &LoggerPlugin{
store: store,
logger: testLogger{},
disableContentLogging: &disableContentLogging,
}
requestID := "req-chat-disabled"
now := time.Now().UTC()
initial := &InitialLogData{
Object: "chat_completion",
Provider: "openai",
Model: "gpt-4o-mini",
}
if err := plugin.insertInitialLogEntry(context.Background(), requestID, "", now, 0, nil, initial); err != nil {
t.Fatalf("insertInitialLogEntry() error = %v", err)
}
chatText := "assistant output should not be logged"
update := &UpdateLogData{
Status: "success",
ChatOutput: &schemas.ChatMessage{
Role: schemas.ChatMessageRoleAssistant,
Content: &schemas.ChatMessageContent{
ContentStr: &chatText,
},
},
}
if err := plugin.updateLogEntry(context.Background(), requestID, "", "", 10, "", "", "", "", 0, nil, "", update); err != nil {
t.Fatalf("updateLogEntry() error = %v", err)
}
logEntry, err := store.FindByID(context.Background(), requestID)
if err != nil {
t.Fatalf("FindByID() error = %v", err)
}
if logEntry.OutputMessage != "" {
t.Fatalf("expected output_message to be suppressed, got %q", logEntry.OutputMessage)
}
if strings.Contains(logEntry.ContentSummary, chatText) {
t.Fatalf("expected content summary to suppress chat output, got %q", logEntry.ContentSummary)
}
}
func TestStoreOrEnqueueRetryPreservesAllEntries(t *testing.T) {
// Simulate fallback/retry scenario where multiple PostLLMHook calls
// store entries under the same traceID. All entries must be preserved.
plugin := &LoggerPlugin{
logger: testLogger{},
writeQueue: make(chan *writeQueueEntry, 10),
}
traceID := "trace-retry-test"
ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline)
ctx.SetValue(schemas.BifrostContextKeyTraceID, traceID)
// Simulate 3 retry attempts storing entries under the same traceID
entry1 := &logstore.Log{ID: "req-attempt-1", Model: "gpt-4o"}
entry2 := &logstore.Log{ID: "req-attempt-2", Model: "gpt-4o"}
entry3 := &logstore.Log{ID: "req-attempt-3", Model: "claude-3-5-sonnet"}
plugin.storeOrEnqueueEntry(ctx, entry1, nil)
plugin.storeOrEnqueueEntry(ctx, entry2, nil)
plugin.storeOrEnqueueEntry(ctx, entry3, nil)
// Verify all 3 entries are stored
val, ok := plugin.pendingLogsToInject.Load(traceID)
if !ok {
t.Fatal("expected pending entries for traceID, got none")
}
pending, ok := val.(*pendingInjectEntries)
if !ok {
t.Fatal("expected *pendingInjectEntries type")
}
if len(pending.entries) != 3 {
t.Fatalf("expected 3 entries, got %d", len(pending.entries))
}
if pending.entries[0].ID != "req-attempt-1" || pending.entries[1].ID != "req-attempt-2" || pending.entries[2].ID != "req-attempt-3" {
t.Fatalf("entries not in expected order: %v, %v, %v", pending.entries[0].ID, pending.entries[1].ID, pending.entries[2].ID)
}
// Now test Inject flushes all entries with plugin logs attached
trace := &schemas.Trace{
TraceID: traceID,
PluginLogs: []schemas.PluginLogEntry{
{PluginName: "hello-world", Level: schemas.LogLevelInfo, Message: "test log"},
},
}
if err := plugin.Inject(context.Background(), trace); err != nil {
t.Fatalf("Inject() error = %v", err)
}
// Verify all 3 entries were enqueued to writeQueue
if len(plugin.writeQueue) != 3 {
t.Fatalf("expected 3 entries in writeQueue, got %d", len(plugin.writeQueue))
}
// Verify plugin logs were attached to each entry
for i := 0; i < 3; i++ {
qe := <-plugin.writeQueue
if qe.log.PluginLogs == "" {
t.Fatalf("entry %d: expected PluginLogs to be set", i)
}
}
// Verify pendingLogsToInject was cleaned up
if _, ok := plugin.pendingLogsToInject.Load(traceID); ok {
t.Fatal("expected pendingLogsToInject to be cleaned up after Inject")
}
}
func TestApplyRealtimeOutputToEntryBackfillsUserTranscriptFromRawRequest(t *testing.T) {
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "Hello!"
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: `{"type":"conversation.item.input_audio_transcription.completed","transcript":"Hello."}`,
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 1 {
t.Fatalf("len(InputHistoryParsed) = %d, want 1", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Role != schemas.ChatMessageRoleUser {
t.Fatalf("InputHistoryParsed[0].Role = %q, want user", entry.InputHistoryParsed[0].Role)
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != "Hello." {
t.Fatalf("InputHistoryParsed[0] = %+v, want transcript", entry.InputHistoryParsed[0])
}
if entry.OutputMessageParsed == nil || entry.OutputMessageParsed.Content == nil || entry.OutputMessageParsed.Content.ContentStr == nil || *entry.OutputMessageParsed.Content.ContentStr != assistantText {
t.Fatalf("OutputMessageParsed = %+v, want assistant text", entry.OutputMessageParsed)
}
if !strings.Contains(entry.ContentSummary, "Hello.") {
t.Fatalf("ContentSummary = %q, want user transcript", entry.ContentSummary)
}
if !strings.Contains(entry.ContentSummary, "Hello!") {
t.Fatalf("ContentSummary = %q, want assistant text", entry.ContentSummary)
}
}
func TestApplyRealtimeOutputToEntryBackfillsMissingTranscriptPlaceholder(t *testing.T) {
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "Hi there!"
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: `{"type":"conversation.item.input_audio_transcription.completed","transcript":""}`,
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 1 {
t.Fatalf("len(InputHistoryParsed) = %d, want 1", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != realtimeMissingTranscriptText {
t.Fatalf("InputHistoryParsed[0] = %+v, want missing transcript placeholder", entry.InputHistoryParsed[0])
}
if !strings.Contains(entry.ContentSummary, realtimeMissingTranscriptText) {
t.Fatalf("ContentSummary = %q, want missing transcript placeholder", entry.ContentSummary)
}
}
func TestApplyRealtimeOutputToEntryBackfillsDoneMissingTranscriptPlaceholder(t *testing.T) {
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "Hi there!"
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: `{"type":"conversation.item.done","item":{"id":"item_user","type":"message","role":"user","status":"completed","content":[{"type":"input_audio","transcript":null}]}}`,
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 1 {
t.Fatalf("len(InputHistoryParsed) = %d, want 1", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != realtimeMissingTranscriptText {
t.Fatalf("InputHistoryParsed[0] = %+v, want missing transcript placeholder", entry.InputHistoryParsed[0])
}
}
func TestApplyRealtimeOutputToEntryBackfillsRetrievedUserAndToolHistory(t *testing.T) {
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "I checked that for you."
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: strings.Join([]string{
`{"type":"conversation.item.retrieved","item":{"id":"item_user","type":"message","role":"user","status":"completed","content":[{"type":"input_text","text":"Where is my order?"}]}}`,
`{"type":"conversation.item.retrieved","item":{"id":"item_tool","type":"function_call_output","call_id":"call_123","status":"completed","output":"{\"status\":\"delivered\"}"}}`,
}, "\n\n"),
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 2 {
t.Fatalf("len(InputHistoryParsed) = %d, want 2", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Role != schemas.ChatMessageRoleUser {
t.Fatalf("InputHistoryParsed[0].Role = %q, want user", entry.InputHistoryParsed[0].Role)
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != "Where is my order?" {
t.Fatalf("InputHistoryParsed[0] = %+v, want user content", entry.InputHistoryParsed[0])
}
if entry.InputHistoryParsed[1].Role != schemas.ChatMessageRoleTool {
t.Fatalf("InputHistoryParsed[1].Role = %q, want tool", entry.InputHistoryParsed[1].Role)
}
if entry.InputHistoryParsed[1].Content == nil || entry.InputHistoryParsed[1].Content.ContentStr == nil || *entry.InputHistoryParsed[1].Content.ContentStr != `{"status":"delivered"}` {
t.Fatalf("InputHistoryParsed[1] = %+v, want tool content", entry.InputHistoryParsed[1])
}
if entry.InputHistoryParsed[1].ChatToolMessage == nil || entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID == nil || *entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID != "call_123" {
t.Fatalf("InputHistoryParsed[1].ChatToolMessage = %+v, want tool call id", entry.InputHistoryParsed[1].ChatToolMessage)
}
}
func TestApplyRealtimeOutputToEntryBackfillsCreatedUserAndToolHistory(t *testing.T) {
t.Parallel()
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
ExtraFields: schemas.BifrostResponseExtraFields{
RawRequest: strings.Join([]string{
`{"type":"conversation.item.created","item":{"id":"item_user","type":"message","role":"user","status":"completed","content":[{"type":"input_text","text":"I need help"}]}}`,
`{"type":"conversation.item.created","item":{"id":"item_tool","type":"function_call_output","call_id":"call_456","status":"completed","output":"{\"status\":\"ok\"}"}}`,
}, "\n\n"),
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if len(entry.InputHistoryParsed) != 2 {
t.Fatalf("len(InputHistoryParsed) = %d, want 2", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Role != schemas.ChatMessageRoleUser {
t.Fatalf("InputHistoryParsed[0].Role = %q, want user", entry.InputHistoryParsed[0].Role)
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != "I need help" {
t.Fatalf("InputHistoryParsed[0] = %+v, want user content", entry.InputHistoryParsed[0])
}
if entry.InputHistoryParsed[1].Role != schemas.ChatMessageRoleTool {
t.Fatalf("InputHistoryParsed[1].Role = %q, want tool", entry.InputHistoryParsed[1].Role)
}
if entry.InputHistoryParsed[1].Content == nil || entry.InputHistoryParsed[1].Content.ContentStr == nil || *entry.InputHistoryParsed[1].Content.ContentStr != `{"status":"ok"}` {
t.Fatalf("InputHistoryParsed[1] = %+v, want tool content", entry.InputHistoryParsed[1])
}
if entry.InputHistoryParsed[1].ChatToolMessage == nil || entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID == nil || *entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID != "call_456" {
t.Fatalf("InputHistoryParsed[1].ChatToolMessage = %+v, want tool call id", entry.InputHistoryParsed[1].ChatToolMessage)
}
}
func TestApplyRealtimeOutputToEntryBackfillsAddedUserAndToolHistory(t *testing.T) {
t.Parallel()
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "Done."
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: strings.Join([]string{
`{"type":"conversation.item.added","item":{"id":"item_user","type":"message","role":"user","status":"completed","content":[{"type":"input_text","text":"hello from added item"}]}}`,
`{"type":"conversation.item.added","item":{"id":"item_tool","type":"function_call_output","call_id":"call_added","status":"completed","output":"{\"status\":\"ok\"}"}}`,
}, "\n\n"),
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 2 {
t.Fatalf("len(InputHistoryParsed) = %d, want 2", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != "hello from added item" {
t.Fatalf("InputHistoryParsed[0] = %+v, want added user content", entry.InputHistoryParsed[0])
}
if entry.InputHistoryParsed[1].ChatToolMessage == nil || entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID == nil || *entry.InputHistoryParsed[1].ChatToolMessage.ToolCallID != "call_added" {
t.Fatalf("InputHistoryParsed[1].ChatToolMessage = %+v, want added tool call id", entry.InputHistoryParsed[1].ChatToolMessage)
}
}
func TestApplyRealtimeOutputToEntryMergesRawTranscriptIntoStructuredRealtimeHistory(t *testing.T) {
t.Parallel()
plugin := &LoggerPlugin{}
entry := &logstore.Log{
InputHistoryParsed: []schemas.ChatMessage{
{
Role: schemas.ChatMessageRoleUser,
Content: &schemas.ChatMessageContent{
ContentStr: schemas.Ptr("Can you help with my ticket?"),
},
},
{
Role: schemas.ChatMessageRoleTool,
Content: &schemas.ChatMessageContent{
ContentStr: schemas.Ptr(`{"status":"open"}`),
},
ChatToolMessage: &schemas.ChatToolMessage{
ToolCallID: schemas.Ptr("call_789"),
},
},
},
}
assistantText := "Let me check."
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: strings.Join([]string{
`{"type":"conversation.item.input_audio_transcription.completed","transcript":"Hello."}`,
`{"type":"conversation.item.retrieved","item":{"id":"item_tool","type":"function_call_output","call_id":"call_789","status":"completed","output":"{\"status\":\"open\"}"}}`,
}, "\n\n"),
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, true)
if err := entry.SerializeFields(); err != nil {
t.Fatalf("SerializeFields() error = %v", err)
}
if len(entry.InputHistoryParsed) != 3 {
t.Fatalf("len(InputHistoryParsed) = %d, want 3", len(entry.InputHistoryParsed))
}
if entry.InputHistoryParsed[0].Content == nil || entry.InputHistoryParsed[0].Content.ContentStr == nil || *entry.InputHistoryParsed[0].Content.ContentStr != "Can you help with my ticket?" {
t.Fatalf("InputHistoryParsed[0] = %+v, want structured user content", entry.InputHistoryParsed[0])
}
if entry.InputHistoryParsed[1].Role != schemas.ChatMessageRoleUser {
t.Fatalf("InputHistoryParsed[1].Role = %q, want user", entry.InputHistoryParsed[1].Role)
}
if entry.InputHistoryParsed[1].Content == nil || entry.InputHistoryParsed[1].Content.ContentStr == nil || *entry.InputHistoryParsed[1].Content.ContentStr != "Hello." {
t.Fatalf("InputHistoryParsed[1] = %+v, want raw transcript merge", entry.InputHistoryParsed[1])
}
if entry.InputHistoryParsed[2].Role != schemas.ChatMessageRoleTool {
t.Fatalf("InputHistoryParsed[2].Role = %q, want tool", entry.InputHistoryParsed[2].Role)
}
if entry.InputHistoryParsed[2].ChatToolMessage == nil || entry.InputHistoryParsed[2].ChatToolMessage.ToolCallID == nil || *entry.InputHistoryParsed[2].ChatToolMessage.ToolCallID != "call_789" {
t.Fatalf("InputHistoryParsed[2].ChatToolMessage = %+v, want original tool call id", entry.InputHistoryParsed[2].ChatToolMessage)
}
if strings.Count(entry.ContentSummary, "Hello.") != 1 {
t.Fatalf("ContentSummary = %q, want one merged transcript", entry.ContentSummary)
}
}
func TestApplyRealtimeOutputToEntryDoesNotPersistRawWhenShouldStoreRawFalse(t *testing.T) {
plugin := &LoggerPlugin{}
entry := &logstore.Log{}
assistantText := "Hello!"
messageType := schemas.ResponsesMessageTypeMessage
assistantRole := schemas.ResponsesInputMessageRoleAssistant
result := &schemas.BifrostResponse{
ResponsesResponse: &schemas.BifrostResponsesResponse{
Output: []schemas.ResponsesMessage{{
Type: &messageType,
Role: &assistantRole,
Content: &schemas.ResponsesMessageContent{
ContentStr: &assistantText,
},
}},
ExtraFields: schemas.BifrostResponseExtraFields{
RequestType: schemas.RealtimeRequest,
RawRequest: `{"type":"conversation.item.input_audio_transcription.completed","transcript":"Hello."}`,
RawResponse: `{"type":"response.done"}`,
},
},
}
plugin.applyRealtimeOutputToEntry(entry, result, false)
if entry.RawRequest != "" {
t.Fatalf("expected RawRequest to remain empty when shouldStoreRaw=false, got %q", entry.RawRequest)
}
if entry.RawResponse != "" {
t.Fatalf("expected RawResponse to remain empty when shouldStoreRaw=false, got %q", entry.RawResponse)
}
if len(entry.InputHistoryParsed) == 0 {
t.Fatal("expected InputHistoryParsed to still be backfilled when shouldStoreRaw=false")
}
if entry.InputHistoryParsed[0].Role != schemas.ChatMessageRoleUser {
t.Fatalf("InputHistoryParsed[0].Role = %q, want user", entry.InputHistoryParsed[0].Role)
}
}

754
plugins/logging/utils.go Normal file
View File

@@ -0,0 +1,754 @@
// Package logging provides utility functions and interfaces for the GORM-based logging plugin
package logging
import (
"context"
"errors"
"fmt"
"strings"
"time"
bifrost "github.com/maximhq/bifrost/core"
"github.com/maximhq/bifrost/core/schemas"
"github.com/maximhq/bifrost/framework/logstore"
"github.com/maximhq/bifrost/framework/streaming"
)
// KeyPair represents an ID-Name pair for keys
type KeyPair struct {
ID string `json:"id"`
Name string `json:"name"`
}
// LogManager defines the main interface that combines all logging functionality
type LogManager interface {
// GetLog retrieves a single log entry by ID (includes all fields, including raw_request/raw_response)
GetLog(ctx context.Context, id string) (*logstore.Log, error)
// Search searches for log entries based on filters and pagination
Search(ctx context.Context, filters *logstore.SearchFilters, pagination *logstore.PaginationOptions) (*logstore.SearchResult, error)
// GetSessionLogs returns paginated logs for a single parent_request_id session.
GetSessionLogs(ctx context.Context, sessionID string, pagination *logstore.PaginationOptions) (*logstore.SessionDetailResult, error)
// GetSessionSummary returns aggregate totals for a single parent_request_id session.
GetSessionSummary(ctx context.Context, sessionID string) (*logstore.SessionSummaryResult, error)
// GetStats calculates statistics for logs matching the given filters
GetStats(ctx context.Context, filters *logstore.SearchFilters) (*logstore.SearchStats, error)
// GetHistogram returns time-bucketed request counts for the given filters
GetHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.HistogramResult, error)
// GetTokenHistogram returns time-bucketed token usage for the given filters
GetTokenHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.TokenHistogramResult, error)
// GetCostHistogram returns time-bucketed cost data with model breakdown for the given filters
GetCostHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.CostHistogramResult, error)
// GetModelHistogram returns time-bucketed model usage with success/error breakdown for the given filters
GetModelHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ModelHistogramResult, error)
// GetLatencyHistogram returns time-bucketed latency percentiles for the given filters
GetLatencyHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.LatencyHistogramResult, error)
// GetProviderCostHistogram returns time-bucketed cost data with provider breakdown for the given filters
GetProviderCostHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ProviderCostHistogramResult, error)
// GetProviderTokenHistogram returns time-bucketed token usage with provider breakdown for the given filters
GetProviderTokenHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ProviderTokenHistogramResult, error)
// GetProviderLatencyHistogram returns time-bucketed latency percentiles with provider breakdown for the given filters
GetProviderLatencyHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ProviderLatencyHistogramResult, error)
// GetModelRankings returns models ranked by usage with trend comparison
GetModelRankings(ctx context.Context, filters *logstore.SearchFilters) (*logstore.ModelRankingResult, error)
// Get the number of dropped requests
GetDroppedRequests(ctx context.Context) int64
// GetAvailableModels returns all unique models from logs
GetAvailableModels(ctx context.Context) []string
// GetAvailableAliases returns all unique alias values from logs
GetAvailableAliases(ctx context.Context) []string
// GetAvailableSelectedKeys returns all unique selected key ID-Name pairs from logs
GetAvailableSelectedKeys(ctx context.Context) []KeyPair
// GetAvailableVirtualKeys returns all unique virtual key ID-Name pairs from logs
GetAvailableVirtualKeys(ctx context.Context) []KeyPair
// GetAvailableRoutingRules returns all unique routing rule ID-Name pairs from logs
GetAvailableRoutingRules(ctx context.Context) []KeyPair
// GetAvailableRoutingEngines returns all unique routing engine types from logs
GetAvailableRoutingEngines(ctx context.Context) []string
// GetAvailableTeams returns all unique team ID-Name pairs from logs
GetAvailableTeams(ctx context.Context) []KeyPair
// GetAvailableCustomers returns all unique customer ID-Name pairs from logs
GetAvailableCustomers(ctx context.Context) []KeyPair
// GetAvailableUsers returns all unique user IDs from logs
GetAvailableUsers(ctx context.Context) []KeyPair
// GetAvailableBusinessUnits returns all unique business unit ID-Name pairs from logs
GetAvailableBusinessUnits(ctx context.Context) []KeyPair
// GetAvailableMetadataKeys returns distinct metadata keys and their values from recent logs
GetAvailableMetadataKeys(ctx context.Context) (map[string][]string, error)
// GetDimensionCostHistogram returns time-bucketed cost data grouped by the specified dimension
GetDimensionCostHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64, dimension logstore.HistogramDimension) (*logstore.DimensionCostHistogramResult, error)
// GetDimensionTokenHistogram returns time-bucketed token usage grouped by the specified dimension
GetDimensionTokenHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64, dimension logstore.HistogramDimension) (*logstore.DimensionTokenHistogramResult, error)
// GetDimensionLatencyHistogram returns time-bucketed latency percentiles grouped by the specified dimension
GetDimensionLatencyHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64, dimension logstore.HistogramDimension) (*logstore.DimensionLatencyHistogramResult, error)
// DeleteLog deletes a log entry by its ID
DeleteLog(ctx context.Context, id string) error
// DeleteLogs deletes multiple log entries by their IDs
DeleteLogs(ctx context.Context, ids []string) error
// RecalculateCosts recomputes missing costs for logs matching the filters
RecalculateCosts(ctx context.Context, filters *logstore.SearchFilters, limit int) (*RecalculateCostResult, error)
// MCP Tool Log methods
// SearchMCPToolLogs searches for MCP tool log entries based on filters and pagination
SearchMCPToolLogs(ctx context.Context, filters *logstore.MCPToolLogSearchFilters, pagination *logstore.PaginationOptions) (*logstore.MCPToolLogSearchResult, error)
// GetMCPToolLogStats calculates statistics for MCP tool logs matching the given filters
GetMCPToolLogStats(ctx context.Context, filters *logstore.MCPToolLogSearchFilters) (*logstore.MCPToolLogStats, error)
// GetAvailableToolNames returns all unique tool names from MCP tool logs
GetAvailableToolNames(ctx context.Context) ([]string, error)
// GetAvailableServerLabels returns all unique server labels from MCP tool logs
GetAvailableServerLabels(ctx context.Context) ([]string, error)
// GetAvailableMCPVirtualKeys returns all unique virtual key ID-Name pairs from MCP tool logs
GetAvailableMCPVirtualKeys(ctx context.Context) []KeyPair
// GetMCPHistogram returns time-bucketed MCP tool call volume
GetMCPHistogram(ctx context.Context, filters logstore.MCPToolLogSearchFilters, bucketSizeSeconds int64) (*logstore.MCPHistogramResult, error)
// GetMCPCostHistogram returns time-bucketed MCP cost data
GetMCPCostHistogram(ctx context.Context, filters logstore.MCPToolLogSearchFilters, bucketSizeSeconds int64) (*logstore.MCPCostHistogramResult, error)
// GetMCPTopTools returns the top N MCP tools by call count
GetMCPTopTools(ctx context.Context, filters logstore.MCPToolLogSearchFilters, limit int) (*logstore.MCPTopToolsResult, error)
// DeleteMCPToolLogs deletes multiple MCP tool log entries by their IDs
DeleteMCPToolLogs(ctx context.Context, ids []string) error
}
// PluginLogManager implements LogManager interface wrapping the plugin
type PluginLogManager struct {
plugin *LoggerPlugin
}
func (p *PluginLogManager) GetLog(ctx context.Context, id string) (*logstore.Log, error) {
return p.plugin.GetLog(ctx, id)
}
func (p *PluginLogManager) Search(ctx context.Context, filters *logstore.SearchFilters, pagination *logstore.PaginationOptions) (*logstore.SearchResult, error) {
if filters == nil || pagination == nil {
return nil, fmt.Errorf("filters and pagination cannot be nil")
}
return p.plugin.SearchLogs(ctx, *filters, *pagination)
}
func (p *PluginLogManager) GetSessionLogs(ctx context.Context, sessionID string, pagination *logstore.PaginationOptions) (*logstore.SessionDetailResult, error) {
if pagination == nil {
return nil, fmt.Errorf("pagination cannot be nil")
}
if strings.TrimSpace(sessionID) == "" {
return nil, fmt.Errorf("sessionID cannot be empty")
}
return p.plugin.GetSessionLogs(ctx, sessionID, *pagination)
}
func (p *PluginLogManager) GetSessionSummary(ctx context.Context, sessionID string) (*logstore.SessionSummaryResult, error) {
if strings.TrimSpace(sessionID) == "" {
return nil, fmt.Errorf("sessionID cannot be empty")
}
return p.plugin.GetSessionSummary(ctx, sessionID)
}
func (p *PluginLogManager) GetStats(ctx context.Context, filters *logstore.SearchFilters) (*logstore.SearchStats, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetStats(ctx, *filters)
}
func (p *PluginLogManager) GetHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.HistogramResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetHistogram(ctx, *filters, bucketSizeSeconds)
}
func (p *PluginLogManager) GetTokenHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.TokenHistogramResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetTokenHistogram(ctx, *filters, bucketSizeSeconds)
}
func (p *PluginLogManager) GetCostHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.CostHistogramResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetCostHistogram(ctx, *filters, bucketSizeSeconds)
}
func (p *PluginLogManager) GetModelHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ModelHistogramResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetModelHistogram(ctx, *filters, bucketSizeSeconds)
}
func (p *PluginLogManager) GetLatencyHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.LatencyHistogramResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetLatencyHistogram(ctx, *filters, bucketSizeSeconds)
}
func (p *PluginLogManager) GetProviderCostHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ProviderCostHistogramResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetProviderCostHistogram(ctx, *filters, bucketSizeSeconds)
}
func (p *PluginLogManager) GetProviderTokenHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ProviderTokenHistogramResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetProviderTokenHistogram(ctx, *filters, bucketSizeSeconds)
}
func (p *PluginLogManager) GetProviderLatencyHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64) (*logstore.ProviderLatencyHistogramResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetProviderLatencyHistogram(ctx, *filters, bucketSizeSeconds)
}
func (p *PluginLogManager) GetModelRankings(ctx context.Context, filters *logstore.SearchFilters) (*logstore.ModelRankingResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetModelRankings(ctx, *filters)
}
func (p *PluginLogManager) GetDroppedRequests(ctx context.Context) int64 {
return p.plugin.droppedRequests.Load()
}
// GetAvailableModels returns all unique models from logs
func (p *PluginLogManager) GetAvailableModels(ctx context.Context) []string {
return p.plugin.GetAvailableModels(ctx)
}
// GetAvailableAliases returns all unique alias values from logs
func (p *PluginLogManager) GetAvailableAliases(ctx context.Context) []string {
return p.plugin.GetAvailableAliases(ctx)
}
// GetAvailableSelectedKeys returns all unique selected key ID-Name pairs from logs
func (p *PluginLogManager) GetAvailableSelectedKeys(ctx context.Context) []KeyPair {
return p.plugin.GetAvailableSelectedKeys(ctx)
}
// GetAvailableVirtualKeys returns all unique virtual key ID-Name pairs from logs
func (p *PluginLogManager) GetAvailableVirtualKeys(ctx context.Context) []KeyPair {
return p.plugin.GetAvailableVirtualKeys(ctx)
}
// GetAvailableRoutingRules returns all unique routing rule ID-Name pairs from logs
func (p *PluginLogManager) GetAvailableRoutingRules(ctx context.Context) []KeyPair {
return p.plugin.GetAvailableRoutingRules(ctx)
}
// GetAvailableRoutingEngines returns all unique routing engine types from logs
func (p *PluginLogManager) GetAvailableRoutingEngines(ctx context.Context) []string {
return p.plugin.GetAvailableRoutingEngines(ctx)
}
// GetAvailableTeams returns all unique team ID-Name pairs from logs.
func (p *PluginLogManager) GetAvailableTeams(ctx context.Context) []KeyPair {
return p.plugin.GetAvailableTeams(ctx)
}
// GetAvailableCustomers returns all unique customer ID-Name pairs from logs.
func (p *PluginLogManager) GetAvailableCustomers(ctx context.Context) []KeyPair {
return p.plugin.GetAvailableCustomers(ctx)
}
// GetAvailableUsers returns all unique user IDs from logs.
func (p *PluginLogManager) GetAvailableUsers(ctx context.Context) []KeyPair {
return p.plugin.GetAvailableUsers(ctx)
}
// GetAvailableBusinessUnits returns all unique business unit ID-Name pairs from logs.
func (p *PluginLogManager) GetAvailableBusinessUnits(ctx context.Context) []KeyPair {
return p.plugin.GetAvailableBusinessUnits(ctx)
}
// GetDimensionCostHistogram returns time-bucketed cost data grouped by the specified dimension.
func (p *PluginLogManager) GetDimensionCostHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64, dimension logstore.HistogramDimension) (*logstore.DimensionCostHistogramResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetDimensionCostHistogram(ctx, *filters, bucketSizeSeconds, dimension)
}
// GetDimensionTokenHistogram returns time-bucketed token usage grouped by the specified dimension.
func (p *PluginLogManager) GetDimensionTokenHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64, dimension logstore.HistogramDimension) (*logstore.DimensionTokenHistogramResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetDimensionTokenHistogram(ctx, *filters, bucketSizeSeconds, dimension)
}
// GetDimensionLatencyHistogram returns time-bucketed latency percentiles grouped by the specified dimension.
func (p *PluginLogManager) GetDimensionLatencyHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64, dimension logstore.HistogramDimension) (*logstore.DimensionLatencyHistogramResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.GetDimensionLatencyHistogram(ctx, *filters, bucketSizeSeconds, dimension)
}
func (p *PluginLogManager) GetAvailableMetadataKeys(ctx context.Context) (map[string][]string, error) {
if p.plugin == nil || p.plugin.store == nil {
return map[string][]string{}, nil
}
return p.plugin.store.GetDistinctMetadataKeys(ctx)
}
// DeleteLog deletes a log from the log store
func (p *PluginLogManager) DeleteLog(ctx context.Context, id string) error {
if p.plugin == nil || p.plugin.store == nil {
return fmt.Errorf("log store not initialized")
}
return p.plugin.store.DeleteLog(ctx, id)
}
// DeleteLogs deletes multiple logs from the log store
func (p *PluginLogManager) DeleteLogs(ctx context.Context, ids []string) error {
if p.plugin == nil || p.plugin.store == nil {
return fmt.Errorf("log store not initialized")
}
return p.plugin.store.DeleteLogs(ctx, ids)
}
func (p *PluginLogManager) RecalculateCosts(ctx context.Context, filters *logstore.SearchFilters, limit int) (*RecalculateCostResult, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.RecalculateCosts(ctx, *filters, limit)
}
// SearchMCPToolLogs searches for MCP tool log entries based on filters and pagination
func (p *PluginLogManager) SearchMCPToolLogs(ctx context.Context, filters *logstore.MCPToolLogSearchFilters, pagination *logstore.PaginationOptions) (*logstore.MCPToolLogSearchResult, error) {
if filters == nil || pagination == nil {
return nil, fmt.Errorf("filters and pagination cannot be nil")
}
return p.plugin.store.SearchMCPToolLogs(ctx, *filters, *pagination)
}
// GetMCPToolLogStats calculates statistics for MCP tool logs matching the given filters
func (p *PluginLogManager) GetMCPToolLogStats(ctx context.Context, filters *logstore.MCPToolLogSearchFilters) (*logstore.MCPToolLogStats, error) {
if filters == nil {
return nil, fmt.Errorf("filters cannot be nil")
}
return p.plugin.store.GetMCPToolLogStats(ctx, *filters)
}
// GetAvailableToolNames returns all unique tool names from MCP tool logs
func (p *PluginLogManager) GetAvailableToolNames(ctx context.Context) ([]string, error) {
if p == nil || p.plugin == nil || p.plugin.store == nil {
return []string{}, nil
}
return p.plugin.store.GetAvailableToolNames(ctx)
}
// GetAvailableServerLabels returns all unique server labels from MCP tool logs
func (p *PluginLogManager) GetAvailableServerLabels(ctx context.Context) ([]string, error) {
if p == nil || p.plugin == nil || p.plugin.store == nil {
return []string{}, nil
}
return p.plugin.store.GetAvailableServerLabels(ctx)
}
// GetAvailableMCPVirtualKeys returns all unique virtual key ID-Name pairs from MCP tool logs
func (p *PluginLogManager) GetAvailableMCPVirtualKeys(ctx context.Context) []KeyPair {
if p == nil || p.plugin == nil {
return []KeyPair{}
}
return p.plugin.GetAvailableMCPVirtualKeys(ctx)
}
// GetMCPHistogram returns time-bucketed MCP tool call volume
func (p *PluginLogManager) GetMCPHistogram(ctx context.Context, filters logstore.MCPToolLogSearchFilters, bucketSizeSeconds int64) (*logstore.MCPHistogramResult, error) {
if p.plugin == nil || p.plugin.store == nil {
return &logstore.MCPHistogramResult{}, nil
}
return p.plugin.store.GetMCPHistogram(ctx, filters, bucketSizeSeconds)
}
// GetMCPCostHistogram returns time-bucketed MCP cost data
func (p *PluginLogManager) GetMCPCostHistogram(ctx context.Context, filters logstore.MCPToolLogSearchFilters, bucketSizeSeconds int64) (*logstore.MCPCostHistogramResult, error) {
if p.plugin == nil || p.plugin.store == nil {
return &logstore.MCPCostHistogramResult{}, nil
}
return p.plugin.store.GetMCPCostHistogram(ctx, filters, bucketSizeSeconds)
}
// GetMCPTopTools returns the top N MCP tools by call count
func (p *PluginLogManager) GetMCPTopTools(ctx context.Context, filters logstore.MCPToolLogSearchFilters, limit int) (*logstore.MCPTopToolsResult, error) {
if p.plugin == nil || p.plugin.store == nil {
return &logstore.MCPTopToolsResult{}, nil
}
return p.plugin.store.GetMCPTopTools(ctx, filters, limit)
}
// DeleteMCPToolLogs deletes multiple MCP tool log entries by their IDs
func (p *PluginLogManager) DeleteMCPToolLogs(ctx context.Context, ids []string) error {
if p.plugin == nil || p.plugin.store == nil {
return fmt.Errorf("log store not initialized")
}
return p.plugin.store.DeleteMCPToolLogs(ctx, ids)
}
// GetPluginLogManager returns a LogManager interface for this plugin
func (p *LoggerPlugin) GetPluginLogManager() *PluginLogManager {
return &PluginLogManager{
plugin: p,
}
}
// retryOnNotFound retries a function up to 3 times with 1-second delays if it returns logstore.ErrNotFound
func retryOnNotFound(ctx context.Context, operation func() error) error {
const maxRetries = 3
const retryDelay = time.Second
var lastErr error
for attempt := range maxRetries {
err := operation()
if err == nil {
return nil
}
// Check if the error is logstore.ErrNotFound
if !errors.Is(err, logstore.ErrNotFound) {
return err
}
lastErr = err
// Don't wait after the last attempt
if attempt < maxRetries-1 {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(retryDelay):
// Continue to next retry
}
}
}
return lastErr
}
// extractInputHistory extracts input history from request input
func (p *LoggerPlugin) extractInputHistory(request *schemas.BifrostRequest) ([]schemas.ChatMessage, []schemas.ResponsesMessage) {
if request.ChatRequest != nil {
return request.ChatRequest.Input, []schemas.ResponsesMessage{}
}
if request.RequestType == schemas.RealtimeRequest && request.ResponsesRequest != nil {
return extractRealtimeInputHistory(request.ResponsesRequest.Input), []schemas.ResponsesMessage{}
}
if request.ResponsesRequest != nil && len(request.ResponsesRequest.Input) > 0 {
return []schemas.ChatMessage{}, request.ResponsesRequest.Input
}
if request.TextCompletionRequest != nil {
if request.TextCompletionRequest.Input == nil {
return []schemas.ChatMessage{}, []schemas.ResponsesMessage{}
}
var text string
if request.TextCompletionRequest.Input.PromptStr != nil {
text = *request.TextCompletionRequest.Input.PromptStr
} else {
var stringBuilder strings.Builder
for _, prompt := range request.TextCompletionRequest.Input.PromptArray {
stringBuilder.WriteString(prompt)
}
text = stringBuilder.String()
}
return []schemas.ChatMessage{
{
Role: schemas.ChatMessageRoleUser,
Content: &schemas.ChatMessageContent{
ContentStr: &text,
},
},
}, []schemas.ResponsesMessage{}
}
if request.EmbeddingRequest != nil {
// Large payload passthrough can intentionally leave Input nil to avoid
// materializing giant request bodies. Logging should degrade gracefully.
if request.EmbeddingRequest.Input == nil {
return []schemas.ChatMessage{}, []schemas.ResponsesMessage{}
}
texts := request.EmbeddingRequest.Input.Texts
if len(texts) == 0 && request.EmbeddingRequest.Input.Text != nil {
texts = []string{*request.EmbeddingRequest.Input.Text}
}
contentBlocks := make([]schemas.ChatContentBlock, len(texts))
for i, text := range texts {
// Create a per-iteration copy to avoid reusing the same memory address
t := text
contentBlocks[i] = schemas.ChatContentBlock{
Type: schemas.ChatContentBlockTypeText,
Text: &t,
}
}
return []schemas.ChatMessage{
{
Role: schemas.ChatMessageRoleUser,
Content: &schemas.ChatMessageContent{
ContentBlocks: contentBlocks,
},
},
}, []schemas.ResponsesMessage{}
}
if request.RerankRequest != nil {
query := request.RerankRequest.Query
return []schemas.ChatMessage{
{
Role: schemas.ChatMessageRoleUser,
Content: &schemas.ChatMessageContent{
ContentStr: &query,
},
},
}, []schemas.ResponsesMessage{}
}
if request.CountTokensRequest != nil && len(request.CountTokensRequest.Input) > 0 {
return []schemas.ChatMessage{}, request.CountTokensRequest.Input
}
return []schemas.ChatMessage{}, []schemas.ResponsesMessage{}
}
func extractRealtimeInputHistory(input []schemas.ResponsesMessage) []schemas.ChatMessage {
messages := make([]schemas.ChatMessage, 0, len(input))
for _, item := range input {
if item.Type == nil {
continue
}
switch *item.Type {
case schemas.ResponsesMessageTypeMessage:
if item.Role == nil || item.Content == nil {
continue
}
content := extractRealtimeResponsesContent(item.Content)
if content == "" {
continue
}
messages = append(messages, schemas.ChatMessage{
Role: mapRealtimeResponsesRole(*item.Role),
Content: &schemas.ChatMessageContent{
ContentStr: schemas.Ptr(content),
},
})
case schemas.ResponsesMessageTypeFunctionCallOutput,
schemas.ResponsesMessageTypeCustomToolCallOutput,
schemas.ResponsesMessageTypeLocalShellCallOutput,
schemas.ResponsesMessageTypeComputerCallOutput:
content := extractRealtimeToolOutputContent(item.ResponsesToolMessage)
if content == "" {
continue
}
messages = append(messages, schemas.ChatMessage{
Role: schemas.ChatMessageRoleTool,
Content: &schemas.ChatMessageContent{
ContentStr: schemas.Ptr(content),
},
ChatToolMessage: &schemas.ChatToolMessage{
ToolCallID: item.ResponsesToolMessage.CallID,
},
})
}
}
return messages
}
func mapRealtimeResponsesRole(role schemas.ResponsesMessageRoleType) schemas.ChatMessageRole {
switch role {
case schemas.ResponsesInputMessageRoleAssistant:
return schemas.ChatMessageRoleAssistant
case schemas.ResponsesInputMessageRoleSystem:
return schemas.ChatMessageRoleSystem
case schemas.ResponsesInputMessageRoleDeveloper:
return schemas.ChatMessageRoleDeveloper
default:
return schemas.ChatMessageRoleUser
}
}
func extractRealtimeResponsesContent(content *schemas.ResponsesMessageContent) string {
if content == nil {
return ""
}
if content.ContentStr != nil {
return strings.TrimSpace(*content.ContentStr)
}
parts := make([]string, 0, len(content.ContentBlocks))
for _, block := range content.ContentBlocks {
switch {
case block.Text != nil && strings.TrimSpace(*block.Text) != "":
parts = append(parts, strings.TrimSpace(*block.Text))
case block.ResponsesOutputMessageContentRefusal != nil && strings.TrimSpace(block.Refusal) != "":
parts = append(parts, strings.TrimSpace(block.Refusal))
}
}
return strings.TrimSpace(strings.Join(parts, "\n"))
}
func extractRealtimeToolOutputContent(toolMessage *schemas.ResponsesToolMessage) string {
if toolMessage == nil || toolMessage.Output == nil {
return ""
}
switch {
case toolMessage.Output.ResponsesToolCallOutputStr != nil:
return strings.TrimSpace(*toolMessage.Output.ResponsesToolCallOutputStr)
case len(toolMessage.Output.ResponsesFunctionToolCallOutputBlocks) > 0:
content := &schemas.ResponsesMessageContent{ContentBlocks: toolMessage.Output.ResponsesFunctionToolCallOutputBlocks}
return extractRealtimeResponsesContent(content)
default:
return ""
}
}
// convertToProcessedStreamResponse converts a StreamAccumulatorResult to ProcessedStreamResponse
// for use with the logging plugin's streaming log update functionality.
func convertToProcessedStreamResponse(result *schemas.StreamAccumulatorResult, requestType schemas.RequestType) *streaming.ProcessedStreamResponse {
if result == nil {
return nil
}
// Determine stream type from request type
var streamType streaming.StreamType
switch requestType {
case schemas.TextCompletionStreamRequest:
streamType = streaming.StreamTypeText
case schemas.ChatCompletionStreamRequest:
streamType = streaming.StreamTypeChat
case schemas.ResponsesStreamRequest:
streamType = streaming.StreamTypeResponses
case schemas.SpeechStreamRequest:
streamType = streaming.StreamTypeAudio
case schemas.TranscriptionStreamRequest:
streamType = streaming.StreamTypeTranscription
case schemas.ImageGenerationStreamRequest:
streamType = streaming.StreamTypeImage
default:
streamType = streaming.StreamTypeChat
}
// Build accumulated data
data := &streaming.AccumulatedData{
RequestID: result.RequestID,
Model: result.RequestedModel,
Status: result.Status,
Stream: true,
Latency: result.Latency,
TimeToFirstToken: result.TimeToFirstToken,
OutputMessage: result.OutputMessage,
OutputMessages: result.OutputMessages,
ErrorDetails: result.ErrorDetails,
TokenUsage: result.TokenUsage,
Cost: result.Cost,
AudioOutput: result.AudioOutput,
TranscriptionOutput: result.TranscriptionOutput,
ImageGenerationOutput: result.ImageGenerationOutput,
FinishReason: result.FinishReason,
RawResponse: result.RawResponse,
}
// Handle tool calls if present
if result.OutputMessage != nil && result.OutputMessage.ChatAssistantMessage != nil {
data.ToolCalls = result.OutputMessage.ChatAssistantMessage.ToolCalls
}
resp := &streaming.ProcessedStreamResponse{
RequestID: result.RequestID,
StreamType: streamType,
Provider: result.Provider,
RequestedModel: result.RequestedModel,
ResolvedModel: result.ResolvedModel,
Data: data,
}
if result.RawRequest != nil {
rawReq := result.RawRequest
resp.RawRequest = &rawReq
}
return resp
}
func mergeRealtimeMetadata(metadata map[string]interface{}, ctx *schemas.BifrostContext) map[string]interface{} {
if ctx == nil {
return metadata
}
set := func(key string, ctxKey schemas.BifrostContextKey) {
if value := bifrost.GetStringFromContext(ctx, ctxKey); value != "" {
if metadata == nil {
metadata = make(map[string]interface{})
}
metadata[key] = value
}
}
set("realtime_session_id", schemas.BifrostContextKeyRealtimeSessionID)
set("provider_session_id", schemas.BifrostContextKeyRealtimeProviderSessionID)
set("realtime_source", schemas.BifrostContextKeyRealtimeSource)
set("realtime_event_type", schemas.BifrostContextKeyRealtimeEventType)
if bifrost.GetStringFromContext(ctx, schemas.BifrostContextKeyRealtimeSessionID) != "" {
if metadata == nil {
metadata = make(map[string]interface{})
}
metadata["realtime"] = true
}
return metadata
}
// formatRoutingEngineLogs formats routing engine logs into a human-readable string.
// Format: [timestamp] [engine] - message
// Parameters:
// - logs: Slice of routing engine log entries
//
// Returns:
// - string: Formatted log string (empty string if no logs)
func formatRoutingEngineLogs(logs []schemas.RoutingEngineLogEntry) string {
if len(logs) == 0 {
return ""
}
var sb strings.Builder
for _, log := range logs {
sb.WriteString(fmt.Sprintf("[%d] [%s] - %s\n", log.Timestamp, log.Engine, log.Message))
}
return sb.String()
}

1
plugins/logging/version Normal file
View File

@@ -0,0 +1 @@
1.5.4

424
plugins/logging/writer.go Normal file
View File

@@ -0,0 +1,424 @@
package logging
import (
"sync"
"time"
"github.com/maximhq/bifrost/core/schemas"
"github.com/maximhq/bifrost/framework/logstore"
)
const (
// maxBatchSize is the maximum number of entries to collect before flushing
maxBatchSize = 1000
// batchInterval is the maximum time to wait before flushing a partial batch
batchInterval = 2 * time.Second
// maxBatchBytes is the approximate byte-size ceiling for a batch (100 MB).
// When the cumulative estimated size of queued entries hits this limit the
// batch is flushed immediately, even if maxBatchSize hasn't been reached.
maxBatchBytes = 100 * 1024 * 1024
// writeQueueCapacity is the buffer size for the write queue channel
writeQueueCapacity = 10000
// maxDeferredUsageConcurrency limits concurrent deferred usage DB updates
maxDeferredUsageConcurrency = 5
// pendingLogTTL is how long a pending log entry can stay in memory before cleanup
pendingLogTTL = 5 * time.Minute
)
// PendingLogData holds PreLLMHook input data until PostLLMHook fires.
// Stored in pendingLogs sync.Map keyed by requestID.
type PendingLogData struct {
RequestID string
ParentRequestID string
Timestamp time.Time
FallbackIndex int
Status string
RoutingEnginesUsed []string
InitialData *InitialLogData
CreatedAt time.Time // For cleanup of stale entries
}
// pendingInjectEntries wraps a slice of log entries so it can be used with sync.Map.
// The mutex protects concurrent appends to the entries slice within the same traceID.
type pendingInjectEntries struct {
mu sync.Mutex
entries []*logstore.Log
createdAt time.Time
}
// writeQueueEntry is an entry pushed to the batch write queue.
type writeQueueEntry struct {
log *logstore.Log // Complete log entry ready for INSERT
callback func(entry *logstore.Log) // Post-commit callback receives the inserted entry (no DB re-read needed)
}
// batchWriter is the single writer goroutine that drains the write queue
// and processes entries in batched transactions.
func (p *LoggerPlugin) batchWriter() {
defer p.wg.Done()
batch := make([]*writeQueueEntry, 0, maxBatchSize)
batchBytes := 0
timer := time.NewTimer(batchInterval)
timer.Stop()
timerRunning := false
flush := func() {
if timerRunning {
if !timer.Stop() {
select {
case <-timer.C:
default:
}
}
timerRunning = false
}
p.safeProcessBatch(batch)
clear(batch)
batch = batch[:0]
batchBytes = 0
}
for {
select {
case entry, ok := <-p.writeQueue:
if !ok {
// Channel closed - flush remaining batch and exit
p.safeProcessBatch(batch)
return
}
batch = append(batch, entry)
batchBytes += estimateLogEntrySize(entry.log)
if len(batch) >= maxBatchSize || batchBytes >= maxBatchBytes {
flush()
} else if !timerRunning {
timer.Reset(batchInterval)
timerRunning = true
}
case <-timer.C:
timerRunning = false
if len(batch) > 0 {
flush()
}
}
}
}
// safeProcessBatch wraps processBatch with panic recovery so a single
// bad entry cannot kill the batchWriter goroutine.
func (p *LoggerPlugin) safeProcessBatch(batch []*writeQueueEntry) {
defer func() {
if r := recover(); r != nil {
p.logger.Error("panic in batch writer processBatch (recovered, %d entries dropped): %v", len(batch), r)
p.droppedRequests.Add(int64(len(batch)))
}
}()
p.processBatch(batch)
}
// processBatch executes a batch of log entries in a single database transaction.
func (p *LoggerPlugin) processBatch(batch []*writeQueueEntry) {
if len(batch) == 0 {
return
}
// Collect all log entries for batch insert
logs := make([]*logstore.Log, 0, len(batch))
for _, entry := range batch {
if entry.log != nil {
logs = append(logs, entry.log)
}
}
if len(logs) > 0 {
if err := p.store.BatchCreateIfNotExists(p.ctx, logs); err != nil {
p.logger.Warn("batch insert failed for %d entries, falling back to individual inserts: %v", len(logs), err)
// Individual fallback — isolate the bad entry instead of losing the whole batch
for _, log := range logs {
if err := p.store.BatchCreateIfNotExists(p.ctx, []*logstore.Log{log}); err != nil {
p.logger.Warn("individual insert failed for log %s: %v", log.ID, err)
p.droppedRequests.Add(1)
}
}
}
}
// Collect callbacks that need to fire, then run them in a single goroutine.
// This avoids blocking the batch writer (synchronous was causing 1+ second stalls
// during WebSocket broadcast) without creating a goroutine per entry (which caused
// goroutine explosion to 13K+).
type cbPair struct {
cb func(*logstore.Log)
log *logstore.Log
}
var callbacks []cbPair
for _, entry := range batch {
if entry.callback != nil {
callbacks = append(callbacks, cbPair{cb: entry.callback, log: entry.log})
}
}
if len(callbacks) > 0 {
go func(callbacks []cbPair) {
defer func() {
if r := recover(); r != nil {
p.logger.Warn("log callback panicked: %v", r)
}
}()
for _, pair := range callbacks {
pair.cb(pair.log)
}
}(callbacks)
}
}
// cleanupStalePendingLogs removes entries from pendingLogs that have been
// waiting longer than pendingLogTTL. This handles cases where PostLLMHook
// never fires for a request (e.g., request was cancelled before reaching the provider).
func (p *LoggerPlugin) cleanupStalePendingLogs() {
cutoff := time.Now().Add(-pendingLogTTL)
p.pendingLogsEntries.Range(func(key, value any) bool {
if pending, ok := value.(*PendingLogData); ok {
if pending.CreatedAt.Before(cutoff) {
p.pendingLogsEntries.Delete(key)
}
}
return true
})
p.pendingLogsToInject.Range(func(key, value any) bool {
if pending, ok := value.(*pendingInjectEntries); ok {
if pending.createdAt.Before(cutoff) {
p.pendingLogsToInject.Delete(key)
}
}
return true
})
}
// enqueueLogEntry pushes a complete log entry to the write queue.
// If the queue is full, the entry is dropped to prevent Postgres slowness
// from cascading into request handling goroutines.
func (p *LoggerPlugin) enqueueLogEntry(entry *logstore.Log, callback func(entry *logstore.Log)) {
if p.closed.Load() {
return
}
defer func() {
if r := recover(); r != nil {
// Channel was closed between the check and send; entry is dropped
p.droppedRequests.Add(1)
}
}()
select {
case p.writeQueue <- &writeQueueEntry{log: entry, callback: callback}:
// enqueued successfully
default:
p.droppedRequests.Add(1)
p.logger.Warn("log write queue full, dropping log entry %s", entry.ID)
}
}
// estimateLogEntrySize returns a rough byte-size estimate for a log entry
// based on its serialized text fields. This is intentionally cheap — no
// marshaling, just string lengths — and is used to cap batch memory.
//
// NOTE: At enqueue time the string fields may still be empty (data lives in the
// Parsed struct fields until GORM's BeforeCreate hook serializes them), so this
// can undercount significantly. That is acceptable — the byte limit is a
// coarse safety valve, not a precise memory cap. Overshooting by 2× is fine;
// maxBatchSize is the primary batching control.
func estimateLogEntrySize(log *logstore.Log) int {
if log == nil {
return 0
}
// Sum the dominant text/blob fields. Fixed-width columns (IDs, timestamps,
// ints, bools) are negligible compared to these and covered by the 512-byte
// baseline below.
n := len(log.InputHistory) +
len(log.ResponsesInputHistory) +
len(log.OutputMessage) +
len(log.ResponsesOutput) +
len(log.EmbeddingOutput) +
len(log.RerankOutput) +
len(log.OCROutput) +
len(log.Params) +
len(log.Tools) +
len(log.ToolCalls) +
len(log.SpeechInput) +
len(log.SpeechOutput) +
len(log.TranscriptionInput) +
len(log.TranscriptionOutput) +
len(log.ImageGenerationInput) +
len(log.ImageGenerationOutput) +
len(log.VideoGenerationInput) +
len(log.VideoGenerationOutput) +
len(log.VideoRetrieveOutput) +
len(log.VideoDownloadOutput) +
len(log.VideoListOutput) +
len(log.VideoDeleteOutput) +
len(log.ListModelsOutput) +
len(log.TokenUsage) +
len(log.ErrorDetails) +
len(log.RawRequest) +
len(log.RawResponse) +
len(log.PassthroughRequestBody) +
len(log.PassthroughResponseBody) +
len(log.ContentSummary) +
len(log.CacheDebug) +
len(log.RoutingEngineLogs)
// Baseline for fixed-width columns and struct overhead
return n + 512
}
// buildInitialLogEntry constructs a logstore.Log from PendingLogData (input)
// without writing to the database. Used for the UI callback in PreLLMHook.
func buildInitialLogEntry(pending *PendingLogData) *logstore.Log {
entry := &logstore.Log{
ID: pending.RequestID,
Timestamp: pending.Timestamp,
Object: pending.InitialData.Object,
Provider: pending.InitialData.Provider,
Model: pending.InitialData.Model,
FallbackIndex: pending.FallbackIndex,
Status: "processing",
Stream: false,
CreatedAt: pending.Timestamp,
InputHistoryParsed: pending.InitialData.InputHistory,
ResponsesInputHistoryParsed: pending.InitialData.ResponsesInputHistory,
ParamsParsed: pending.InitialData.Params,
ToolsParsed: pending.InitialData.Tools,
PassthroughRequestBody: pending.InitialData.PassthroughRequestBody,
}
if pending.ParentRequestID != "" {
entry.ParentRequestID = &pending.ParentRequestID
}
if len(pending.RoutingEnginesUsed) > 0 {
entry.RoutingEnginesUsed = pending.RoutingEnginesUsed
}
return entry
}
// buildCompleteLogEntryFromPending constructs a logstore.Log with both input (from PendingLogData)
// and output fields fully populated. The caller provides a function to apply output-specific fields.
func buildCompleteLogEntryFromPending(pending *PendingLogData) *logstore.Log {
entry := &logstore.Log{
ID: pending.RequestID,
Timestamp: pending.Timestamp,
Object: pending.InitialData.Object,
Provider: pending.InitialData.Provider,
Model: pending.InitialData.Model,
FallbackIndex: pending.FallbackIndex,
Status: "success",
CreatedAt: pending.Timestamp,
// Set parsed fields for serialization via GORM hooks
InputHistoryParsed: pending.InitialData.InputHistory,
ResponsesInputHistoryParsed: pending.InitialData.ResponsesInputHistory,
ParamsParsed: pending.InitialData.Params,
ToolsParsed: pending.InitialData.Tools,
SpeechInputParsed: pending.InitialData.SpeechInput,
TranscriptionInputParsed: pending.InitialData.TranscriptionInput,
OCRInputParsed: pending.InitialData.OCRInput,
ImageGenerationInputParsed: pending.InitialData.ImageGenerationInput,
ImageEditInputParsed: pending.InitialData.ImageEditInput,
ImageVariationInputParsed: pending.InitialData.ImageVariationInput,
VideoGenerationInputParsed: pending.InitialData.VideoGenerationInput,
PassthroughRequestBody: pending.InitialData.PassthroughRequestBody,
}
if pending.ParentRequestID != "" {
entry.ParentRequestID = &pending.ParentRequestID
}
if len(pending.RoutingEnginesUsed) > 0 {
entry.RoutingEnginesUsed = pending.RoutingEnginesUsed
}
return entry
}
// applyModelAlias sets entry.Model to resolvedModel (falling back to requestedModel if empty)
// and entry.Alias to requestedModel when the two differ (i.e. an alias mapping was applied).
func applyModelAlias(entry *logstore.Log, requestedModel, resolvedModel string) {
if resolvedModel != "" && resolvedModel != requestedModel {
entry.Model = resolvedModel
entry.Alias = &requestedModel
} else {
// No alias mapping; keep whichever value is non-empty as the model.
if resolvedModel != "" {
entry.Model = resolvedModel
} else if requestedModel != "" {
entry.Model = requestedModel
}
entry.Alias = nil
}
}
// applyOutputFieldsToEntry sets common output fields on a log entry.
func applyOutputFieldsToEntry(
entry *logstore.Log,
selectedKeyID, selectedKeyName string,
virtualKeyID, virtualKeyName string,
routingRuleID, routingRuleName string,
selectedPromptID, selectedPromptName, selectedPromptVersion string,
teamID, teamName string,
customerID, customerName string,
userID, userName string,
businessUnitID, businessUnitName string,
numberOfRetries int,
latency int64,
attemptTrail []schemas.KeyAttemptRecord,
) {
entry.SelectedKeyID = selectedKeyID
entry.SelectedKeyName = selectedKeyName
if virtualKeyID != "" {
entry.VirtualKeyID = &virtualKeyID
}
if virtualKeyName != "" {
entry.VirtualKeyName = &virtualKeyName
}
if routingRuleID != "" {
entry.RoutingRuleID = &routingRuleID
}
if routingRuleName != "" {
entry.RoutingRuleName = &routingRuleName
}
if selectedPromptID != "" {
entry.SelectedPromptID = &selectedPromptID
}
if selectedPromptName != "" {
entry.SelectedPromptName = &selectedPromptName
}
if selectedPromptVersion != "" {
entry.SelectedPromptVersion = &selectedPromptVersion
}
if teamID != "" {
entry.TeamID = &teamID
}
if teamName != "" {
entry.TeamName = &teamName
}
if customerID != "" {
entry.CustomerID = &customerID
}
if customerName != "" {
entry.CustomerName = &customerName
}
if userID != "" {
entry.UserID = &userID
}
if userName != "" {
entry.UserName = &userName
}
if businessUnitID != "" {
entry.BusinessUnitID = &businessUnitID
}
if businessUnitName != "" {
entry.BusinessUnitName = &businessUnitName
}
if numberOfRetries != 0 {
entry.NumberOfRetries = numberOfRetries
}
if latency != 0 {
latF := float64(latency)
entry.Latency = &latF
}
if len(attemptTrail) > 0 {
entry.AttemptTrailParsed = attemptTrail
}
}