diff --git a/examples/go.mod b/examples/go.mod index 1a521ceaa..12cb0c85f 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -1,6 +1,6 @@ module trpc.group/trpc-go/trpc-agent-go/examples -go 1.23.0 +go 1.24.6 replace ( trpc.group/trpc-go/trpc-agent-go => ../ @@ -19,9 +19,11 @@ replace ( trpc.group/trpc-go/trpc-agent-go/storage/tcvector => ../storage/tcvector trpc.group/trpc-go/trpc-agent-go/tool/arxivsearch => ../tool/arxivsearch trpc.group/trpc-go/trpc-agent-go/tool/google => ../tool/google + trpc.group/trpc-go/trpc-agent-go/tool/openapi => ../tool/openapi ) require ( + github.com/getkin/kin-openapi v0.133.0 github.com/google/uuid v1.6.0 github.com/mattn/go-sqlite3 v1.14.32 github.com/openai/openai-go v1.12.0 @@ -29,10 +31,11 @@ require ( go.opentelemetry.io/otel/metric v1.38.0 go.opentelemetry.io/otel/trace v1.38.0 go.uber.org/zap v1.27.0 - trpc.group/trpc-go/trpc-a2a-go v0.2.5-0.20251023030722-7f02b57fd14a + trpc.group/trpc-go/trpc-a2a-go v0.2.5 trpc.group/trpc-go/trpc-agent-go v0.4.0 trpc.group/trpc-go/trpc-agent-go/codeexecutor/container v0.4.0 - trpc.group/trpc-go/trpc-agent-go/codeexecutor/jupyter v0.0.0-00010101000000-000000000000 + trpc.group/trpc-go/trpc-agent-go/codeexecutor/jupyter v0.0.0-20251111082011-79f521f76893 + trpc.group/trpc-go/trpc-agent-go/tool/openapi v0.0.0-00010101000000-000000000000 trpc.group/trpc-go/trpc-mcp-go v0.0.10 ) @@ -40,7 +43,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Microsoft/go-winio v0.4.14 // indirect github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -50,28 +53,26 @@ require ( github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/getkin/kin-openapi v0.124.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/swag v0.22.8 // indirect - github.com/goccy/go-json v0.10.3 // indirect - github.com/golang-jwt/jwt/v5 v5.2.3 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/invopop/yaml v0.2.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.6 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/jwx/v2 v2.1.4 // indirect + github.com/lestrrat-go/jwx/v2 v2.1.6 // indirect github.com/lestrrat-go/option v1.0.1 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect @@ -79,41 +80,42 @@ require ( github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/panjf2000/ants/v2 v2.10.0 // indirect + github.com/panjf2000/ants/v2 v2.11.3 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rs/cors v1.11.1 // indirect - github.com/segmentio/asm v1.2.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/tidwall/gjson v1.14.4 // indirect - github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect + github.com/woodsbury/decimal128 v1.3.0 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.30.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index d523073a6..f9e1a423b 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -6,8 +6,8 @@ github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+q github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE= github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -29,23 +29,25 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= -github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= 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/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= -github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= -github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= -github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= -github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -54,15 +56,13 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= -github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -72,20 +72,20 @@ 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/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= -github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= +github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.1.4 h1:uBCMmJX8oRZStmKuMMOFb0Yh9xmEMgNJLgjuKKt4/qc= -github.com/lestrrat-go/jwx/v2 v2.1.4/go.mod h1:nWRbDFR1ALG2Z6GJbBXzfQaYyvn751KuuyySN2yR6is= +github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA= +github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +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/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/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -108,14 +108,18 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0= github.com/openai/openai-go v1.12.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8= -github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I= +github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= +github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -127,8 +131,8 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= -github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -136,21 +140,18 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= -github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +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/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -158,6 +159,8 @@ 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/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= 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.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -166,18 +169,16 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 h1:ZtfnDL+tUrs1F0Pzfwbg2d59Gru9NCH3bgSHBM6LDwU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 h1:NmnYCiR0qNufkldjVvyQfZTHSdzeHoZ41zggMsdMcLM= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0/go.mod h1:UVAO61+umUsHLtYb8KXXRoHtxUkdOPkYidzW3gipRLQ= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0 h1:wNMDy/LVGLj2h3p6zg4d0gypKfWKSWI14E1C4smOgl8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0/go.mod h1:YfbDdXAAkemWJK3H/DshvlrxqFB2rtW4rY6ky/3x/H0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= @@ -186,51 +187,51 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6 go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 h1:8BWFtrvJRbplrKV5VHlIm4YM726eeBPPAL2QDNWhRrU= +google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 h1:tK4fkUnnRhig9TsTp4otV1FxwBFYgbKUq1RY0V6KZ4U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/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.0/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= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -trpc.group/trpc-go/trpc-a2a-go v0.2.5-0.20251023030722-7f02b57fd14a h1:dOon6HF2sPRFnhCLEiAeKPc21JHL2eX7UBWjIR8PLaY= -trpc.group/trpc-go/trpc-a2a-go v0.2.5-0.20251023030722-7f02b57fd14a/go.mod h1:Gtytau9Uoc3oPo/dpHvKit+tQn9Qlk5XFG1RiZTGqfk= +trpc.group/trpc-go/trpc-a2a-go v0.2.5 h1:X3pAlWD128LaS9TtXsUDZoJWPVuPZDkZKUecKRxmWn4= +trpc.group/trpc-go/trpc-a2a-go v0.2.5/go.mod h1:Gtytau9Uoc3oPo/dpHvKit+tQn9Qlk5XFG1RiZTGqfk= trpc.group/trpc-go/trpc-mcp-go v0.0.10 h1:kKPfevmikMojfOgtUBf5SJQ/v6aDugckodgyH1uDu2Q= trpc.group/trpc-go/trpc-mcp-go v0.0.10/go.mod h1:OT6rLglkdaQ17D2T1Y87Y/ckItzdsEldDbw7dHAbGEA= diff --git a/examples/openapitool/main.go b/examples/openapitool/main.go new file mode 100644 index 000000000..e1fa4984e --- /dev/null +++ b/examples/openapitool/main.go @@ -0,0 +1,237 @@ +// +// Tencent is pleased to support the open source community by making trpc-agent-go available. +// +// Copyright (C) 2025 Tencent. All rights reserved. +// +// trpc-agent-go is licensed under the Apache License Version 2.0. +// +// + +// Package main demonstrates how to use the OpenAPI Tool. +package main + +import ( + "bufio" + "context" + "flag" + "fmt" + "os" + "strings" + "time" + + "trpc.group/trpc-go/trpc-agent-go/agent/llmagent" + "trpc.group/trpc-go/trpc-agent-go/event" + "trpc.group/trpc-go/trpc-agent-go/log" + "trpc.group/trpc-go/trpc-agent-go/model" + "trpc.group/trpc-go/trpc-agent-go/model/openai" + "trpc.group/trpc-go/trpc-agent-go/runner" + "trpc.group/trpc-go/trpc-agent-go/tool" + "trpc.group/trpc-go/trpc-agent-go/tool/openapi" +) + +var ( + modelName = flag.String("model", "deepseek-chat", "Name of the model to use") + modelBaseURL = flag.String("base_url", "", "Base URL for the model API") + modelToken = flag.String("api_token", "", "Authentication token for the model API") + openAPISpec = flag.String("openapi_spec", "./petstore3.yaml", "Path to the OpenAPI specification file") +) + +func main() { + // Parse command line flags. + flag.Parse() + + fmt.Printf("šŸš€ Chat with LLMAgent\n") + fmt.Println(strings.Repeat("=", 50)) + + // Create and run the chat. + chat := &llmAgentChat{ + modelName: *modelName, + baseURL: *modelBaseURL, + Token: *modelToken, + openAPISpec: *openAPISpec, + } + if err := chat.run(); err != nil { + log.Fatalf("Chat failed: %v", err) + } +} + +// llmAgentChat manages the conversation. +type llmAgentChat struct { + modelName string + baseURL string + Token string + openAPISpec string + + runner runner.Runner + userID string + sessionID string +} + +// run starts the interactive chat session. +func (c *llmAgentChat) run() error { + ctx := context.Background() + + // Setup the agent. + if err := c.setup(ctx); err != nil { + return fmt.Errorf("setup failed: %w", err) + } + + // Start interactive chat. + return c.startChat(ctx) +} + +// setup creates the LLMAgent. +func (c *llmAgentChat) setup(_ context.Context) error { + // Create a model instance. + modelInstance := openai.New(c.modelName, + openai.WithBaseURL(c.baseURL), + openai.WithAPIKey(c.Token), + ) + + // Create generation config. + genConfig := model.GenerationConfig{ + MaxTokens: intPtr(1000), + Temperature: floatPtr(0.7), + Stream: true, + } + + openAPIToolSet, err := openapi.NewToolSet( + openapi.WithSpecLoader(openapi.NewFileLoader("./petstore3.yaml")), + ) + if err != nil { + return fmt.Errorf("failed to create openapi toolset: %w", err) + } + + // Create an LLMAgent with configuration. + llmAgent := llmagent.New( + "chat-assistant", + llmagent.WithModel(modelInstance), + llmagent.WithDescription("A helpful AI assistant for interactive demonstrations"), + llmagent.WithInstruction("You are a helpful AI assistant. Be conversational and engaging. "+ + "Answer questions clearly and provide helpful information."), + llmagent.WithGenerationConfig(genConfig), + llmagent.WithToolSets([]tool.ToolSet{openAPIToolSet}), + ) + c.runner = runner.NewRunner("chat-assistant-app", llmAgent) + + // Setup identifiers. + c.userID = "user" + c.sessionID = fmt.Sprintf("session-%d", time.Now().Unix()) + + fmt.Printf("āœ… Chat ready!\n\n") + + return nil +} + +// startChat runs the interactive conversation loop. +func (c *llmAgentChat) startChat(ctx context.Context) error { + scanner := bufio.NewScanner(os.Stdin) + + fmt.Println("šŸ’” Commands:") + fmt.Println(" /exit - End the conversation") + fmt.Println() + + for { + fmt.Print("šŸ‘¤ You: ") + if !scanner.Scan() { + break + } + + userInput := strings.TrimSpace(scanner.Text()) + if userInput == "" { + continue + } + + // Handle special commands. + switch strings.ToLower(userInput) { + case "/exit": + fmt.Println("šŸ‘‹ Goodbye!") + return nil + } + + // Process the user message. + if err := c.processMessage(ctx, userInput); err != nil { + fmt.Printf("āŒ Error: %v\n", err) + } + + fmt.Println() // Add spacing between turns. + } + + if err := scanner.Err(); err != nil { + return fmt.Errorf("input scanner error: %w", err) + } + + return nil +} + +// processMessage handles a single message exchange. +func (c *llmAgentChat) processMessage(ctx context.Context, userMessage string) error { + message := model.NewUserMessage(userMessage) + // Run the agent. + eventChan, err := c.runner.Run(ctx, c.userID, c.sessionID, message) + if err != nil { + return fmt.Errorf("failed to run LLMAgent: %w", err) + } + + // Process response. + return c.processResponse(eventChan) +} + +// processResponse handles the streaming response. +func (c *llmAgentChat) processResponse(eventChan <-chan *event.Event) error { + fmt.Print("šŸ¤– Assistant: ") + + var fullContent strings.Builder + + for event := range eventChan { + if err := c.handleEvent(event, &fullContent); err != nil { + return err + } + + // Check if this is the final event. + if event.Done { + fmt.Printf("\n") + break + } + } + + return nil +} + +// handleEvent processes a single event from the event channel. +func (c *llmAgentChat) handleEvent(event *event.Event, fullContent *strings.Builder) error { + if event.Error != nil { + fmt.Printf("\nāŒ Error: %s\n", event.Error.Message) + return nil + } + + // Handle content. + if len(event.Response.Choices) > 0 { + choice := event.Response.Choices[0] + content := c.extractContent(choice) + + if content != "" { + fmt.Print(content) + fullContent.WriteString(content) + } + } + + return nil +} + +// extractContent extracts content based on streaming mode. +func (c *llmAgentChat) extractContent(choice model.Choice) string { + // In streaming mode, use delta content for real-time display. + // In non-streaming mode, use full message content. + return choice.Delta.Content +} + +// intPtr returns a pointer to the given int value. +func intPtr(i int) *int { + return &i +} + +// floatPtr returns a pointer to the given float64 value. +func floatPtr(f float64) *float64 { + return &f +} diff --git a/examples/openapitool/mockserver/main.go b/examples/openapitool/mockserver/main.go new file mode 100644 index 000000000..3f5b0fc33 --- /dev/null +++ b/examples/openapitool/mockserver/main.go @@ -0,0 +1,250 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/google/uuid" +) + +func main() { + ctx := context.Background() + + // Load OpenAPI specification + loader := &openapi3.Loader{Context: ctx, IsExternalRefsAllowed: true} + doc, err := loader.LoadFromFile("../petstore3.yaml") + if err != nil { + log.Fatalf("Failed to load OpenAPI spec: %v", err) + } + + // Validate the specification + if err := doc.Validate(ctx); err != nil { + log.Fatalf("OpenAPI spec validation failed: %v", err) + } + + // Create HTTP server + handler := &MockServerHandler{doc: doc} + handler.setupRoutes() + + log.Println("Starting OpenAPI Mock Server on :8080") + log.Println("Available endpoints:") + for _, path := range doc.Paths.InMatchingOrder() { + pathItem := doc.Paths.Find(path) + if pathItem != nil { + if pathItem.Get != nil { + log.Printf(" GET %s", path) + } + if pathItem.Post != nil { + log.Printf(" POST %s", path) + } + if pathItem.Put != nil { + log.Printf(" PUT %s", path) + } + if pathItem.Delete != nil { + log.Printf(" DELETE %s", path) + } + } + } + + if err := http.ListenAndServe(":8080", handler); err != nil { + log.Fatalf("Server failed: %v", err) + } +} + +type MockServerHandler struct { + doc *openapi3.T + mux *http.ServeMux +} + +func (h *MockServerHandler) setupRoutes() { + h.mux = http.NewServeMux() + + const pathPrefix = "/api/v3" + // Setup routes for all paths in the OpenAPI spec + for path, pathItem := range h.doc.Paths.Map() { + absPath := pathPrefix + path + if pathItem.Get != nil { + h.mux.HandleFunc("GET "+absPath, h.createHandler(absPath, pathItem.Get)) + } + if pathItem.Post != nil { + h.mux.HandleFunc("POST "+absPath, h.createHandler(absPath, pathItem.Post)) + } + if pathItem.Put != nil { + h.mux.HandleFunc("PUT "+absPath, h.createHandler(absPath, pathItem.Put)) + } + if pathItem.Delete != nil { + h.mux.HandleFunc("DELETE "+absPath, h.createHandler(absPath, pathItem.Delete)) + } + } + + // Add a health check endpoint + h.mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"status": "healthy"}) + }) +} + +func (h *MockServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Printf("Handling %s %s", r.Method, r.URL.Path) + // Handle CORS + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + + // Handle the request + h.mux.ServeHTTP(w, r) +} + +func (h *MockServerHandler) createHandler(path string, operation *openapi3.Operation) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + log.Printf("Handling %s %s", r.Method, r.URL.Path) + + // Set content type based on Accept header or default to JSON + contentType := "application/json" + if accept := r.Header.Get("Accept"); accept != "" { + if strings.Contains(accept, "application/xml") { + contentType = "application/xml" + } else if strings.Contains(accept, "application/json") { + contentType = "application/json" + } + } + w.Header().Set("Content-Type", contentType) + + // Generate mock response based on the operation's responses + responses := operation.Responses + if responses == nil { + h.sendError(w, "No responses defined", http.StatusInternalServerError) + return + } + + // Try to get a 200 response first, then fallback to other success codes + var response *openapi3.Response + for _, code := range []string{"200", "201", "204"} { + if resp, exists := responses.Map()[code]; exists && resp.Value != nil { + response = resp.Value + break + } + } + + if response == nil { + // If no success response found, use the first available response + for _, resp := range responses.Map() { + if resp.Value != nil { + response = resp.Value + break + } + } + } + + if response == nil { + h.sendError(w, "No valid response found", http.StatusInternalServerError) + return + } + + // Generate mock data based on response schema + mockData := h.generateMockData(response, path, operation.OperationID) + + // Set appropriate status code + statusCode := http.StatusOK + + w.WriteHeader(statusCode) + + if contentType == "application/xml" { + // Simple XML response (for demonstration) + fmt.Fprintf(w, ` + + success + Mock response for %s + %v +`, operation.OperationID, mockData) + } else { + // JSON response + json.NewEncoder(w).Encode(mockData) + } + } +} + +func (h *MockServerHandler) generateMockData(response *openapi3.Response, path, operationID string) interface{} { + // Simple mock data generation based on operation ID and path + switch operationID { + case "getPetById": + return map[string]any{ + "id": 123, + "name": "Mock Pet", + "category": map[string]any{ + "id": 1, + "name": "Dogs", + }, + "photoUrls": []string{"http://example.com/photo1.jpg"}, + "tags": []map[string]any{ + {"id": 1, "name": "friendly"}, + }, + "status": "available", + } + case "findPetsByStatus": + return []map[string]any{ + { + "id": 1, + "name": "Pet 1", + "status": "available", + }, + { + "id": 2, + "name": "Pet 2", + "status": "pending", + }, + } + case "getInventory": + return map[string]int{ + "available": 5, + "pending": 2, + "sold": 3, + } + case "getOrderById": + return map[string]any{ + "id": 456, + "petId": 123, + "quantity": 1, + "shipDate": "2023-01-01T12:00:00Z", + "status": "approved", + "complete": false, + } + case "getUserByName": + return map[string]any{ + "id": 789, + "username": "mockuser", + "firstName": "Mock", + "lastName": "User", + "email": "mock@example.com", + "userStatus": 1, + } + default: + // Generic response for other operations + return map[string]any{ + "id": uuid.New().String(), + "message": fmt.Sprintf("Mock response for %s", operationID), + "path": path, + "success": true, + } + } +} + +func (h *MockServerHandler) sendError(w http.ResponseWriter, message string, statusCode int) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + json.NewEncoder(w).Encode(map[string]interface{}{ + "error": message, + "code": statusCode, + "success": false, + }) +} diff --git a/examples/openapitool/petstore3.json b/examples/openapitool/petstore3.json new file mode 100644 index 000000000..ca79508fc --- /dev/null +++ b/examples/openapitool/petstore3.json @@ -0,0 +1,1270 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "Swagger Petstore - OpenAPI 3.0", + "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", + "termsOfService": "https://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.27" + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "https://swagger.io" + }, + "servers": [ + { + "url": "http://localhost:8080/api/v3" + } + ], + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "https://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders", + "externalDocs": { + "description": "Find out more about our store", + "url": "https://swagger.io" + } + }, + { + "name": "user", + "description": "Operations about user" + } + ], + "paths": { + "/pet": { + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet.", + "description": "Update an existing pet by Id.", + "operationId": "updatePet", + "requestBody": { + "description": "Update an existent pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "422": { + "description": "Validation exception" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store.", + "description": "Add a new pet to the store.", + "operationId": "addPet", + "requestBody": { + "description": "Create a new pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Validation exception" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status.", + "description": "Multiple status values can be provided with comma separated strings.", + "operationId": "findPetsByStatus", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "explode": true, + "schema": { + "type": "string", + "default": "available", + "enum": [ + "available", + "pending", + "sold" + ] + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid status value" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags.", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": true, + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid tag value" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID.", + "description": "Returns a single pet.", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "api_key": [] + }, + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data.", + "description": "Updates a pet resource based on the form data.", + "operationId": "updatePetWithForm", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "name", + "in": "query", + "description": "Name of pet that needs to be updated", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "description": "Status of pet that needs to be updated", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet.", + "description": "Delete a pet.", + "operationId": "deletePet", + "parameters": [ + { + "name": "api_key", + "in": "header", + "description": "", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Pet deleted" + }, + "400": { + "description": "Invalid pet value" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "Uploads an image.", + "description": "Upload image of the pet.", + "operationId": "uploadFile", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "additionalMetadata", + "in": "query", + "description": "Additional Metadata", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + }, + "400": { + "description": "No file uploaded" + }, + "404": { + "description": "Pet not found" + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status.", + "description": "Returns a map of status codes to quantities.", + "operationId": "getInventory", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + } + }, + "default": { + "description": "Unexpected error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet.", + "description": "Place a new order in the store.", + "operationId": "placeOrder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Validation exception" + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID.", + "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.", + "operationId": "getOrderById", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of order that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + }, + "default": { + "description": "Unexpected error" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by identifier.", + "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or non-integers will generate API errors.", + "operationId": "deleteOrder", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "order deleted" + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user.", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "requestBody": { + "description": "Created user object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array.", + "description": "Creates list of users with given input array.", + "operationId": "createUsersWithListInput", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system.", + "description": "Log into the system.", + "operationId": "loginUser", + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + }, + "X-Expires-After": { + "description": "date in UTC when token expires", + "schema": { + "type": "string", + "format": "date-time" + } + } + }, + "content": { + "application/xml": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Invalid username/password supplied" + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session.", + "description": "Log user out of the system.", + "operationId": "logoutUser", + "parameters": [], + "responses": { + "200": { + "description": "successful operation" + }, + "default": { + "description": "Unexpected error" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name.", + "description": "Get user detail based on username.", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + }, + "default": { + "description": "Unexpected error" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Update user resource.", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Update an existent user in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation" + }, + "400": { + "description": "bad request" + }, + "404": { + "description": "user not found" + }, + "default": { + "description": "Unexpected error" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user resource.", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "User deleted" + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + }, + "default": { + "description": "Unexpected error" + } + } + } + } + }, + "components": { + "schemas": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "petId": { + "type": "integer", + "format": "int64", + "example": 198772 + }, + "quantity": { + "type": "integer", + "format": "int32", + "example": 7 + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "example": "approved", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "order" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { + "name": "category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "username": { + "type": "string", + "example": "theUser" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "James" + }, + "email": { + "type": "string", + "example": "john@email.com" + }, + "password": { + "type": "string", + "example": "12345" + }, + "phone": { + "type": "string", + "example": "12345" + }, + "userStatus": { + "type": "integer", + "description": "User Status", + "format": "int32", + "example": 1 + } + }, + "xml": { + "name": "user" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "tag" + } + }, + "Pet": { + "required": [ + "name", + "photoUrls" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "name": { + "type": "string", + "example": "doggie" + }, + "category": { + "$ref": "#/components/schemas/Category" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "xml": { + "name": "##default" + } + } + }, + "requestBodies": { + "Pet": { + "description": "Pet object that needs to be added to the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "UserArray": { + "description": "List of user object", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore3.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} diff --git a/examples/openapitool/petstore3.yaml b/examples/openapitool/petstore3.yaml new file mode 100644 index 000000000..53f313687 --- /dev/null +++ b/examples/openapitool/petstore3.yaml @@ -0,0 +1,833 @@ +openapi: 3.0.4 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + termsOfService: https://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.27 +externalDocs: + description: Find out more about Swagger + url: https://swagger.io +servers: + - url: http://localhost:8080/api/v3 +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: https://swagger.io + - name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: https://swagger.io + - name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet. + description: Update an existing pet by Id. + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + $ref: "#/components/schemas/Pet" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Pet" + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + $ref: "#/components/schemas/Pet" + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store. + description: Add a new pet to the store. + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + $ref: "#/components/schemas/Pet" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Pet" + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + $ref: "#/components/schemas/Pet" + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status. + description: Multiple status values can be provided with comma separated strings. + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + type: array + items: + $ref: "#/components/schemas/Pet" + "400": + description: Invalid status value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags. + description: + "Multiple tags can be provided with comma separated strings. Use\ + \ tag1, tag2, tag3 for testing." + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + explode: true + schema: + type: array + items: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + type: array + items: + $ref: "#/components/schemas/Pet" + "400": + description: Invalid tag value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID. + description: Returns a single pet. + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + $ref: "#/components/schemas/Pet" + "400": + description: Invalid ID supplied + "404": + description: Pet not found + default: + description: Unexpected error + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data. + description: Updates a pet resource based on the form data. + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + $ref: "#/components/schemas/Pet" + "400": + description: Invalid input + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet. + description: Delete a pet. + operationId: deletePet + parameters: + - name: api_key + in: header + description: "" + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: Pet deleted + "400": + description: Invalid pet value + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: Uploads an image. + description: Upload image of the pet. + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + "400": + description: No file uploaded + "404": + description: Pet not found + default: + description: Unexpected error + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status. + description: Returns a map of status codes to quantities. + operationId: getInventory + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + default: + description: Unexpected error + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet. + description: Place a new order in the store. + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + application/xml: + schema: + $ref: "#/components/schemas/Order" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Order" + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + "400": + description: Invalid input + "422": + description: Validation exception + default: + description: Unexpected error + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID. + description: + For valid response try integer IDs with value <= 5 or > 10. Other + values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + application/xml: + schema: + $ref: "#/components/schemas/Order" + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + delete: + tags: + - store + summary: Delete purchase order by identifier. + description: + For valid response try integer IDs with value < 1000. Anything + above 1000 or non-integers will generate API errors. + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: order deleted + "400": + description: Invalid ID supplied + "404": + description: Order not found + default: + description: Unexpected error + /user: + post: + tags: + - user + summary: Create user. + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: "#/components/schemas/User" + application/xml: + schema: + $ref: "#/components/schemas/User" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/User" + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/User" + application/xml: + schema: + $ref: "#/components/schemas/User" + default: + description: Unexpected error + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array. + description: Creates list of users with given input array. + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/User" + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/User" + application/xml: + schema: + $ref: "#/components/schemas/User" + default: + description: Unexpected error + /user/login: + get: + tags: + - user + summary: Logs user into the system. + description: Log into the system. + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + "200": + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + "400": + description: Invalid username/password supplied + default: + description: Unexpected error + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session. + description: Log user out of the system. + operationId: logoutUser + parameters: [] + responses: + "200": + description: successful operation + default: + description: Unexpected error + /user/{username}: + get: + tags: + - user + summary: Get user by user name. + description: Get user detail based on username. + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing + required: true + schema: + type: string + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/User" + application/xml: + schema: + $ref: "#/components/schemas/User" + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error + put: + tags: + - user + summary: Update user resource. + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: "#/components/schemas/User" + application/xml: + schema: + $ref: "#/components/schemas/User" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/User" + responses: + "200": + description: successful operation + "400": + description: bad request + "404": + description: user not found + default: + description: Unexpected error + delete: + tags: + - user + summary: Delete user resource. + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + "200": + description: User deleted + "400": + description: Invalid username supplied + "404": + description: User not found + default: + description: Unexpected error +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: "#/components/schemas/Category" + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: "#/components/schemas/Tag" + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: "##default" + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + application/xml: + schema: + $ref: "#/components/schemas/Pet" + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/User" + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/tool/openapi/go.mod b/tool/openapi/go.mod new file mode 100644 index 000000000..69093c5c1 --- /dev/null +++ b/tool/openapi/go.mod @@ -0,0 +1,25 @@ +module trpc.group/trpc-go/trpc-agent-go/tool/openapi + +go 1.24.6 + +require github.com/getkin/kin-openapi v0.133.0 + +require ( + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/zap v1.27.0 // indirect + trpc.group/trpc-go/trpc-a2a-go v0.2.5-0.20251023030722-7f02b57fd14a // indirect +) + +require ( + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/woodsbury/decimal128 v1.3.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + trpc.group/trpc-go/trpc-agent-go v0.4.0 +) diff --git a/tool/openapi/go.sum b/tool/openapi/go.sum new file mode 100644 index 000000000..c9389c4b6 --- /dev/null +++ b/tool/openapi/go.sum @@ -0,0 +1,49 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +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/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +trpc.group/trpc-go/trpc-a2a-go v0.2.5-0.20251023030722-7f02b57fd14a h1:dOon6HF2sPRFnhCLEiAeKPc21JHL2eX7UBWjIR8PLaY= +trpc.group/trpc-go/trpc-a2a-go v0.2.5-0.20251023030722-7f02b57fd14a/go.mod h1:Gtytau9Uoc3oPo/dpHvKit+tQn9Qlk5XFG1RiZTGqfk= +trpc.group/trpc-go/trpc-agent-go v0.4.0 h1:Hgc6K62gjIWfIpvHvLb6rx61iM4/ow9vXQ6GeVXJbhc= +trpc.group/trpc-go/trpc-agent-go v0.4.0/go.mod h1:jttyxOwedWRaXB6IbiRqGstHbPyT/k6vPQeOOzlQNYc= diff --git a/tool/openapi/openapi.go b/tool/openapi/openapi.go new file mode 100644 index 000000000..88fdbaa3f --- /dev/null +++ b/tool/openapi/openapi.go @@ -0,0 +1,140 @@ +// +// Tencent is pleased to support the open source community by making trpc-agent-go available. +// +// Copyright (C) 2025 Tencent. All rights reserved. +// +// trpc-agent-go is licensed under the Apache License Version 2.0. +// +// + +// Package openapi provides a toolset for a given openapi API specification. +package openapi + +import ( + "context" + "fmt" + "net/http" + "time" + + openapi "github.com/getkin/kin-openapi/openapi3" + "trpc.group/trpc-go/trpc-agent-go/log" + "trpc.group/trpc-go/trpc-agent-go/tool" +) + +const ( + // defaultUserAgent is the default user agent for HTTP requests. + defaultUserAgent = "trpc-agent-go-openapi/1.0" + // defaultTimeout is the default timeout for HTTP requests. + defaultTimeout = 30 * time.Second +) + +// Option is a functional option for configuring the OpenAPI tool. +type Option func(*config) + +// config holds the configuration for the OpenAPI tool. +type config struct { + name string + userAgent string + + specLoader SpecLoader + httpClient *http.Client +} + +// WithSpecLoader sets the spec loader to use. +func WithSpecLoader(loader SpecLoader) Option { + return func(c *config) { + c.specLoader = loader + } +} + +// WithUserAgent sets the user agent for HTTP requests. +func WithUserAgent(userAgent string) Option { + return func(c *config) { + c.userAgent = userAgent + } +} + +// WithHTTPClient sets the HTTP client to use. +func WithHTTPClient(httpClient *http.Client) Option { + return func(c *config) { + c.httpClient = httpClient + } +} + +// WithName sets the name of the file toolset. +func WithName(name string) Option { + return func(c *config) { + c.name = name + } +} + +// openAPIToolSet is a set of tools. +type openAPIToolSet struct { + spec *specProcessor + name string + + config *config + tools []tool.Tool +} + +// Tools implements the ToolSet interface. +func (ts *openAPIToolSet) Tools(ctx context.Context) []tool.Tool { + return ts.tools +} + +// Close implements the ToolSet interface. +func (ts *openAPIToolSet) Close() error { + // No resources to clean up for file tools. + return nil +} + +// Name implements the ToolSet interface. +func (ts *openAPIToolSet) Name() string { + return ts.config.name +} + +// NewToolSet creates a new OpenAPI tool set with the provided options. +func NewToolSet(opts ...Option) (tool.ToolSet, error) { + c := &config{ + userAgent: defaultUserAgent, + httpClient: &http.Client{ + Timeout: defaultTimeout, + }, + } + for _, opt := range opts { + opt(c) + } + + specCtx := context.Background() + specDoc, err := loadSpec(specCtx, c.specLoader) + if err != nil { + log.Debugf("load openAPI spec err: %v", err) + return nil, err + } + specProcessor := newSpecProcessor(specDoc) + if err := specProcessor.processOperations(); err != nil { + return nil, err + } + + toolSet := &openAPIToolSet{config: c} + for _, op := range specProcessor.operations { + tl := newOpenAPITool(toolSet, op) + toolSet.tools = append(toolSet.tools, tl) + } + + return toolSet, nil +} + +func loadSpec(ctx context.Context, loader SpecLoader) (*openapi.T, error) { + if loader == nil { + return nil, fmt.Errorf("OpenAPI spec loader not provided") + } + doc, err := loader.Load(ctx) + if err != nil { + return nil, err + } + if err := doc.Validate(ctx); err != nil { + return nil, err + } + return doc, nil +} diff --git a/tool/openapi/openapi_spec.go b/tool/openapi/openapi_spec.go new file mode 100644 index 000000000..395ea6d1e --- /dev/null +++ b/tool/openapi/openapi_spec.go @@ -0,0 +1,348 @@ +// +// Tencent is pleased to support the open source community by making trpc-agent-go available. +// +// Copyright (C) 2025 Tencent. All rights reserved. +// +// trpc-agent-go is licensed under the Apache License Version 2.0. +// +// + +package openapi + +import ( + "context" + "fmt" + "io" + "net/url" + "sort" + "strings" + + openapi "github.com/getkin/kin-openapi/openapi3" + "trpc.group/trpc-go/trpc-agent-go/tool" +) + +// SpecLoader loads the OpenAPI spec. +type SpecLoader interface { + // Load loads the OpenAPI spec. + Load(ctx context.Context) (*openapi.T, error) +} + +type dataSpecLoader struct { + r io.Reader +} + +// Load loads the OpenAPI spec from io.Reader. +func (d *dataSpecLoader) Load(ctx context.Context) (*openapi.T, error) { + loader := openapi.Loader{Context: ctx} + return loader.LoadFromIoReader(d.r) +} + +// NewDataLoader creates a new data spec loader. +func NewDataLoader(r io.Reader) *dataSpecLoader { + return &dataSpecLoader{r: r} +} + +type fileSpecLoader struct { + path string +} + +// Load loads the OpenAPI spec from file. +func (f *fileSpecLoader) Load(ctx context.Context) (*openapi.T, error) { + loader := openapi.Loader{Context: ctx} + return loader.LoadFromFile(f.path) +} + +// NewFileLoader creates a new file spec loader. +func NewFileLoader(filePath string) *fileSpecLoader { + return &fileSpecLoader{path: filePath} +} + +type urlSpecLoader struct { + uri string +} + +// Load loads the OpenAPI spec from url. +func (u *urlSpecLoader) Load(ctx context.Context) (*openapi.T, error) { + uri, err := url.Parse(u.uri) + if err != nil { + return nil, err + } + loader := openapi.Loader{Context: ctx} + return loader.LoadFromURI(uri) +} + +// NewURILoader creates a new url spec loader. +func NewURILoader(uri string) *urlSpecLoader { + return &urlSpecLoader{uri: uri} +} + +func newSpecProcessor(doc *openapi.T) *specProcessor { + return &specProcessor{doc: doc} +} + +type specProcessor struct { + doc *openapi.T + operations []*Operation +} + +// InfoTitle returns the title of the spec. +func (s *specProcessor) InfoTitle() string { + return s.doc.Info.Title +} + +func (s *specProcessor) processOperations() error { + if len(s.doc.Servers) == 0 { + return fmt.Errorf("no server defined") + } + baseURL := s.doc.Servers[0].URL + + // TODO: security scheme, if any + for path, pathItem := range s.doc.Paths.Map() { + if pathItem == nil { + continue + } + // TODO: add path-level parameters + for method, specOperation := range methodOperations(pathItem) { + if specOperation == nil { + continue + } + op := newOperation( + operationName(specOperation, path, method), + operationDesc(specOperation), + newOperationEndpoint(baseURL, path, method), + ) + op.collectSpecOperationParameters(specOperation.Parameters) + op.collectSpecRequestParameters(specOperation.RequestBody) + op.collectResponseParameter(specOperation.Responses) + s.operations = append(s.operations, op) + } + } + return nil +} + +func operationName(op *openapi.Operation, path, method string) string { + n := op.OperationID + if n == "" { + n = strings.ToLower(method + "_" + path) + } + return n +} + +func operationDesc(op *openapi.Operation) string { + desc := op.Description + if desc == "" { + desc = op.Summary + } + return desc +} + +func newOperation(name, desc string, endpoint *operationEndpoint) *Operation { + return &Operation{ + name: name, + description: desc, + endpoint: endpoint, + } +} + +// Operation represents an operation in the spec. +// parameters are collected from the following sources: +// - path parameters +// - operation parameters +// - request body parameters +// - response parameters +type Operation struct { + name string + description string + + endpoint *operationEndpoint + requestParams []*APIParameter + responseParam *APIParameter +} + +func (o *Operation) toolInputSchema() *tool.Schema { + s := &tool.Schema{ + Type: openapi.TypeObject, + Properties: make(map[string]*tool.Schema), + } + for _, param := range o.requestParams { + s.Properties[param.OriginalName] = convertOpenAPISchemaToToolSchema(param.schema) + } + return s +} + +func (o *Operation) toolOutputSchema() *tool.Schema { + return &tool.Schema{ + Type: openapi.TypeObject, + Properties: make(map[string]*tool.Schema), + } +} + +func (o *Operation) collectSpecOperationParameters(params openapi.Parameters) { + if len(params) == 0 { + return + } + var ps []*APIParameter + for _, param := range params { + if param == nil { + continue + } + pv := param.Value + ps = append(ps, &APIParameter{ + OriginalName: pv.Name, + Description: pv.Description, + Location: ParameterLocation(pv.In), + Required: pv.Required, + schema: pv.Schema.Value, + }) + } + o.collectOperationParameters(ps) +} + +func (o *Operation) collectSpecRequestParameters(request *openapi.RequestBodyRef) { + if request == nil { + return + } + if request.Value == nil { + return + } + for _, content := range request.Value.Content { + var ps []*APIParameter + if content.Schema.Value.Type.Is(openapi.TypeObject) { + for propName, propSchema := range content.Schema.Value.Properties { + if propSchema == nil { + continue + } + ps = append(ps, &APIParameter{ + OriginalName: propName, + Description: propSchema.Value.Description, + Location: BodyParameter, + schema: propSchema.Value, + }) + } + } else if content.Schema.Value.Type.Is(openapi.TypeArray) { + ps = append(ps, &APIParameter{ + OriginalName: openapi.TypeArray, + Description: content.Schema.Value.Items.Value.Description, + Location: BodyParameter, + schema: content.Schema.Value.Items.Value, + }) + } else { + ps = append(ps, &APIParameter{ + OriginalName: "", + Description: content.Schema.Value.Description, + Location: BodyParameter, + schema: content.Schema.Value, + }) + } + o.collectOperationParameters(ps) + } +} + +func (o *Operation) collectOperationParameters(params []*APIParameter) { + o.requestParams = append(o.requestParams, params...) +} + +func (o *Operation) collectResponseParameter(responses *openapi.Responses) { + var ( + schema *openapi.Schema + statusCodes []string + ) + + for code := range responses.Map() { + if strings.HasPrefix(code, "2") { + statusCodes = append(statusCodes, code) + } + } + sort.Strings(statusCodes) + if len(statusCodes) != 0 { + validCode := statusCodes[0] + if v := responses.Value(validCode); v != nil { + if v.Value != nil && len(v.Value.Content) != 0 { + for _, c := range v.Value.Content { + if c.Schema == nil { + continue + } + schema = c.Schema.Value + break + } + } + } + } + o.responseParam = &APIParameter{ + Location: ResponseParameter, + schema: schema, + } +} + +type operationEndpoint struct { + baseURL string + path string + method string +} + +func newOperationEndpoint(baseURL, path, method string) *operationEndpoint { + return &operationEndpoint{ + baseURL: baseURL, + path: path, + method: method, + } +} + +// convertOpenAPISchemaToToolSchema converts kin-openapi/openapi3.Schema to tool.Schema +func convertOpenAPISchemaToToolSchema(openapiSchema *openapi.Schema) *tool.Schema { + if openapiSchema == nil { + return &tool.Schema{Type: "object"} + } + toolSchema := &tool.Schema{} + + // Convert type + // openapi scheme types include: "string", "number", "integer", "boolean", "array", "object" + // tool.Schema type is similar, we take the first type if multiple are defined + if len(openapiSchema.Type.Slice()) > 0 { + toolSchema.Type = openapiSchema.Type.Slice()[0] + } else { + toolSchema.Type = "object" // Default type + } + + // Convert description + toolSchema.Description = openapiSchema.Description + + // Convert properties + if openapiSchema.Properties != nil { + toolSchema.Properties = make(map[string]*tool.Schema) + for propName, propSchema := range openapiSchema.Properties { + if propSchema.Value != nil { + toolSchema.Properties[propName] = convertOpenAPISchemaToToolSchema(propSchema.Value) + } + } + } + + // Convert required fields + if len(openapiSchema.Required) > 0 { + toolSchema.Required = openapiSchema.Required + } + + // Convert items (for array types) + if openapiSchema.Items != nil && openapiSchema.Items.Value != nil { + toolSchema.Items = convertOpenAPISchemaToToolSchema(openapiSchema.Items.Value) + } + + // Convert additional properties + if has := openapiSchema.AdditionalProperties.Has; has != nil && *has { + if openapiSchema.AdditionalProperties.Schema != nil && openapiSchema.AdditionalProperties.Schema.Value != nil { + toolSchema.AdditionalProperties = convertOpenAPISchemaToToolSchema(openapiSchema.AdditionalProperties.Schema.Value) + } + } + + // Convert default value + if openapiSchema.Default != nil { + toolSchema.Default = openapiSchema.Default + } + + // Convert enum values + if len(openapiSchema.Enum) > 0 { + toolSchema.Enum = openapiSchema.Enum + } + + return toolSchema +} diff --git a/tool/openapi/openapi_spec_test.go b/tool/openapi/openapi_spec_test.go new file mode 100644 index 000000000..434f630a1 --- /dev/null +++ b/tool/openapi/openapi_spec_test.go @@ -0,0 +1,41 @@ +package openapi + +import ( + "context" + "testing" +) + +func Test_urlSpecLoader_Load(t *testing.T) { + tests := []struct { + name string // description of this test case + // Named input parameters for receiver constructor. + uri string + wantErr bool + }{ + { + name: "LoadFromURI_INVALID_URL", + uri: "http://localhost:8080", + wantErr: true, + }, + { + name: "LoadFromURI_OK", + uri: "https://petstore3.swagger.io/api/v3/openapi.json", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + u := NewURILoader(tt.uri) + _, gotErr := u.Load(context.Background()) + if gotErr != nil { + if !tt.wantErr { + t.Errorf("Load() failed: %v", gotErr) + } + return + } + if tt.wantErr { + t.Fatal("Load() succeeded unexpectedly") + } + }) + } +} diff --git a/tool/openapi/openapi_test.go b/tool/openapi/openapi_test.go new file mode 100644 index 000000000..42191b6e9 --- /dev/null +++ b/tool/openapi/openapi_test.go @@ -0,0 +1,130 @@ +// +// Tencent is pleased to support the open source community by making trpc-agent-go available. +// +// Copyright (C) 2025 Tencent. All rights reserved. +// +// trpc-agent-go is licensed under the Apache License Version 2.0. +// +// + +// Package openapi provides a toolset for a given openapi API specification. +package openapi + +import ( + "bytes" + "testing" +) + +const ( + invalidSpecNoServerProvided = ` +openapi: 3.1.0 +info: + title: test-openapi-tool +servers: + - url: http://localhost/api/v31 +` + invalidSpec = ` +openapi: 3.0.4 +info: + title: test-openapi-tool +servers: + - url: http://localhost/api/v3 +paths: + /pet: + put: + summary: Update an existing pet. + description: Update an existing pet by Id. + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "422": + description: Validation exception + default: + description: Unexpected error +` +) + +func TestNewToolSet(t *testing.T) { + type args struct { + opts []Option + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "invalid_spec_no_server_provided", + args: args{ + opts: []Option{ + WithSpecLoader(NewDataLoader(bytes.NewReader([]byte(invalidSpecNoServerProvided)))), + }, + }, + wantErr: true, + }, + { + name: "invalid_spec_cannot_resolve_reference", + args: args{ + opts: []Option{ + WithSpecLoader(NewDataLoader(bytes.NewReader([]byte(invalidSpec)))), + }, + }, + wantErr: true, + }, + { + name: "create_toolset_ok_yaml", + args: args{ + opts: []Option{ + WithSpecLoader(NewFileLoader("../../examples/openapitool/petstore3.yaml")), + }, + }, + wantErr: false, + }, + { + name: "create_toolset_ok_json", + args: args{ + opts: []Option{ + WithSpecLoader(NewFileLoader("../../examples/openapitool/petstore3.json")), + }, + }, + wantErr: false, + }, + { + name: "create_toolset_ok_uri", + args: args{ + opts: []Option{ + WithSpecLoader(NewURILoader("https://petstore3.swagger.io/api/v3/openapi.json")), + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := NewToolSet(tt.args.opts...) + if (err != nil) != tt.wantErr { + t.Errorf("NewToolSet() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} diff --git a/tool/openapi/operation.go b/tool/openapi/operation.go new file mode 100644 index 000000000..23893341f --- /dev/null +++ b/tool/openapi/operation.go @@ -0,0 +1,75 @@ +// +// Tencent is pleased to support the open source community by making trpc-agent-go available. +// +// Copyright (C) 2025 Tencent. All rights reserved. +// +// trpc-agent-go is licensed under the Apache License Version 2.0. +// +// + +package openapi + +import ( + "iter" + "net/http" + + openapi "github.com/getkin/kin-openapi/openapi3" +) + +// ParameterLocation defines the location of a parameter. +type ParameterLocation string + +const ( + PathParameter ParameterLocation = "path" + QueryParameter ParameterLocation = "query" + HeaderParameter ParameterLocation = "header" + BodyParameter ParameterLocation = "body" + CookieParameter ParameterLocation = "cookie" + ResponseParameter ParameterLocation = "response" +) + +// APIParameter represents an API parameter. +type APIParameter struct { + OriginalName string `json:"original_name"` + Description string `json:"description"` + Location ParameterLocation `json:"location"` + Required bool `json:"required"` + + level string + schema *openapi.Schema +} + +var ( + pathItemMethods = [...]string{ + http.MethodConnect, + http.MethodDelete, + http.MethodGet, + http.MethodHead, + http.MethodOptions, + http.MethodPatch, + http.MethodPost, + http.MethodPut, + http.MethodTrace, + } +) + +func methodOperations(p *openapi.PathItem) iter.Seq2[string, *openapi.Operation] { + ops := [...]*openapi.Operation{ + p.Connect, + p.Delete, + p.Get, + p.Head, + p.Options, + p.Patch, + p.Post, + p.Put, + p.Trace, + } + return func(yield func(string, *openapi.Operation) bool) { + for i := range len(pathItemMethods) { + if !yield(pathItemMethods[i], ops[i]) { + return + } + } + } +} diff --git a/tool/openapi/operation_test.go b/tool/openapi/operation_test.go new file mode 100644 index 000000000..091073b68 --- /dev/null +++ b/tool/openapi/operation_test.go @@ -0,0 +1,41 @@ +package openapi + +import ( + "context" + "testing" + + openapi "github.com/getkin/kin-openapi/openapi3" +) + +func Test_methodOperations(t *testing.T) { + loader := NewURILoader("https://petstore3.swagger.io/api/v3/openapi.json") + doc, _ := loader.Load(context.Background()) + l := doc.Paths.Len() + if l == 0 { + return + } + var item *openapi.PathItem + for _, pi := range doc.Paths.Map() { + if pi != nil { + item = pi + break + } + } + tests := []struct { + name string // description of this test case + // Named input parameters for target function. + p *openapi.PathItem + }{ + { + name: "methodOperations_OK", + p: item, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for method := range methodOperations(tt.p) { + t.Logf("range methodOperations() = %v", method) + } + }) + } +} diff --git a/tool/openapi/tool.go b/tool/openapi/tool.go new file mode 100644 index 000000000..8bcdfbdd7 --- /dev/null +++ b/tool/openapi/tool.go @@ -0,0 +1,147 @@ +// +// Tencent is pleased to support the open source community by making trpc-agent-go available. +// +// Copyright (C) 2025 Tencent. All rights reserved. +// +// trpc-agent-go is licensed under the Apache License Version 2.0. +// +// + +package openapi + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + "trpc.group/trpc-go/trpc-agent-go/log" + "trpc.group/trpc-go/trpc-agent-go/tool" +) + +type openAPITool struct { + inputSchema *tool.Schema + outputSchema *tool.Schema + + operation *Operation + ts *openAPIToolSet +} + +func newOpenAPITool(ts *openAPIToolSet, operation *Operation) tool.CallableTool { + o := &openAPITool{ + operation: operation, + ts: ts, + } + o.inputSchema = operation.toolInputSchema() + o.outputSchema = operation.toolOutputSchema() + return o +} + +// Call executes the API call. +// parameter replace: "query", "header", "path" or "cookie" +func (o *openAPITool) Call(ctx context.Context, jsonArgs []byte) (any, error) { + log.Debug("Calling OpenAPI tool", "name", o.operation.name) + args := make(map[string]any) + if err := json.Unmarshal(jsonArgs, &args); err != nil { + return nil, err + } + + for _, param := range o.operation.requestParams { + _, ok := args[param.OriginalName] + if !ok && param.Required && param.schema.Default != nil { + args[param.OriginalName] = param.schema.Default + } + } + + req, err := o.prepareRequest(ctx, args) + if err != nil { + return nil, err + } + + resp, err := o.ts.config.httpClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // Read response + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + // Parse response based on status code + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + var result any + if err := json.Unmarshal(respBody, &result); err != nil { + // If JSON parsing fails, return as string + return string(respBody), nil + } + return result, nil + } + + return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(respBody)) +} + +func (o *openAPITool) prepareRequest(ctx context.Context, args map[string]any) (*http.Request, error) { + apiParams := make(map[string]*APIParameter) + for _, param := range o.operation.requestParams { + apiParams[param.OriginalName] = param + } + + var ( + queryParams = make(map[string]any) + pathParams = make(map[string]any) + headerParams = make(map[string]any) + cookieParams = make(map[string]any) + ) + + for argName, argValue := range args { + param, ok := apiParams[argName] + if !ok { + continue + } + switch param.Location { + case QueryParameter: + queryParams[param.OriginalName] = argValue + case PathParameter: + pathParams[param.OriginalName] = argValue + case HeaderParameter: + headerParams[param.OriginalName] = argValue + case CookieParameter: + cookieParams[param.OriginalName] = argValue + default: + // TODO(Ericxwang): support body parameter + } + } + + endpointURL := makeRequestURL(o.operation.endpoint, pathParams) + req, err := http.NewRequestWithContext(ctx, o.operation.endpoint.method, endpointURL, nil) + if err != nil { + return nil, err + } + // Add headers + req.Header.Add("Content-Type", "application/json") + req.Header.Add("User-Agent", o.ts.config.userAgent) + return req, nil +} + +func makeRequestURL(endpoint *operationEndpoint, pathParams map[string]any) string { + endpointURL := endpoint.baseURL + endpoint.path + for arg, value := range pathParams { + endpointURL = strings.ReplaceAll(endpointURL, fmt.Sprintf("{%s}", arg), fmt.Sprintf("%v", value)) + } + return endpointURL +} + +// Declaration returns the declaration of the tool. +func (o *openAPITool) Declaration() *tool.Declaration { + return &tool.Declaration{ + Name: o.operation.name, + Description: o.operation.description, + InputSchema: o.inputSchema, + OutputSchema: o.outputSchema, + } +}