wip: initial google provider

Kujtim Hoxha created

Change summary

go.mod                               |  17 
go.sum                               | 120 ++++++
providers/google/google.go           | 535 ++++++++++++++++++++++++++++++
providers/google/provider_options.go |  42 ++
4 files changed, 714 insertions(+)

Detailed changes

go.mod 🔗

@@ -11,11 +11,28 @@ require (
 )
 
 require (
+	cloud.google.com/go v0.116.0 // indirect
+	cloud.google.com/go/auth v0.9.3 // indirect
+	cloud.google.com/go/compute/metadata v0.5.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/google/go-cmp v0.6.0 // indirect
+	github.com/google/s2a-go v0.1.8 // indirect
+	github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
+	github.com/gorilla/websocket v1.5.3 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/tidwall/gjson v1.18.0 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.1 // indirect
 	github.com/tidwall/sjson v1.2.5 // indirect
+	go.opencensus.io v0.24.0 // indirect
+	golang.org/x/crypto v0.40.0 // indirect
+	golang.org/x/net v0.41.0 // indirect
+	golang.org/x/sys v0.34.0 // indirect
+	golang.org/x/text v0.27.0 // indirect
+	google.golang.org/genai v1.22.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
+	google.golang.org/grpc v1.66.2 // indirect
+	google.golang.org/protobuf v1.34.2 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )

go.sum 🔗

@@ -1,15 +1,67 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
+cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
+cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U=
+cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk=
+cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
+cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/anthropics/anthropic-sdk-go v1.9.1 h1:raRhZKmayVSVZtLpLDd6IsMXvxLeeSU03/2IBTerWlg=
 github.com/anthropics/anthropic-sdk-go v1.9.1/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
 github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
+github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
+github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
+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/openai/openai-go/v2 v2.1.1 h1:/RMA/V3D+yF/Cc4jHXFt6lkqSOWRf5roRi+DvZaDYQI=
 github.com/openai/openai-go/v2 v2.1.1/go.mod h1:sIUkR+Cu/PMUVkSKhkk742PRURkQOCFhiwJ7eRSBqmk=
 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.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.1/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=
@@ -22,7 +74,75 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
 github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
+golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
+golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
+golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
+golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genai v1.22.0 h1:5hrEhXXWJQZa3tdPocl4vQ/0w6myEAxdNns2Kmx0f4Y=
+google.golang.org/genai v1.22.0/go.mod h1:QPj5NGJw+3wEOHg+PrsWwJKvG6UC84ex5FR7qAYsN/M=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
+google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

providers/google/google.go 🔗

@@ -0,0 +1,535 @@
+package google
+
+import (
+	"context"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"maps"
+	"net/http"
+	"strings"
+
+	"github.com/charmbracelet/ai"
+	"google.golang.org/genai"
+)
+
+type provider struct {
+	options options
+}
+type options struct {
+	apiKey  string
+	name    string
+	headers map[string]string
+	client  *http.Client
+}
+
+type Option = func(*options)
+
+func New(opts ...Option) ai.Provider {
+	options := options{
+		headers: map[string]string{},
+	}
+	for _, o := range opts {
+		o(&options)
+	}
+
+	if options.name == "" {
+		options.name = "anthropic"
+	}
+
+	return &provider{
+		options: options,
+	}
+}
+
+func WithAPIKey(apiKey string) Option {
+	return func(o *options) {
+		o.apiKey = apiKey
+	}
+}
+
+func WithName(name string) Option {
+	return func(o *options) {
+		o.name = name
+	}
+}
+
+func WithHeaders(headers map[string]string) Option {
+	return func(o *options) {
+		maps.Copy(o.headers, headers)
+	}
+}
+
+func WithHTTPClient(client *http.Client) Option {
+	return func(o *options) {
+		o.client = client
+	}
+}
+
+type languageModel struct {
+	provider        string
+	modelID         string
+	client          *genai.Client
+	providerOptions options
+}
+
+// LanguageModel implements ai.Provider.
+func (g *provider) LanguageModel(modelID string) (ai.LanguageModel, error) {
+	cc := &genai.ClientConfig{
+		APIKey:     g.options.apiKey,
+		Backend:    genai.BackendGeminiAPI,
+		HTTPClient: g.options.client,
+	}
+	client, err := genai.NewClient(context.Background(), cc)
+	if err != nil {
+		return nil, err
+	}
+	return &languageModel{
+		modelID:         modelID,
+		provider:        fmt.Sprintf("%s.generative-ai", g.options.name),
+		providerOptions: g.options,
+		client:          client,
+	}, nil
+}
+
+func (a languageModel) prepareParams(call ai.Call) (*genai.GenerateContentConfig, []*genai.Content, []ai.CallWarning, error) {
+	config := &genai.GenerateContentConfig{}
+	providerOptions := &providerOptions{}
+	if v, ok := call.ProviderOptions["google"]; ok {
+		err := ai.ParseOptions(v, providerOptions)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+	}
+
+	systemInstructions, content, warnings := toGooglePrompt(call.Prompt)
+
+	if providerOptions.ThinkingConfig != nil &&
+		providerOptions.ThinkingConfig.IncludeThoughts != nil &&
+		*providerOptions.ThinkingConfig.IncludeThoughts &&
+		strings.HasPrefix(a.provider, "google.vertex.") {
+		warnings = append(warnings, ai.CallWarning{
+			Type: ai.CallWarningTypeOther,
+			Message: "The 'includeThoughts' option is only supported with the Google Vertex provider " +
+				"and might not be supported or could behave unexpectedly with the current Google provider " +
+				fmt.Sprintf("(%s)", a.provider),
+		})
+	}
+
+	isGemmaModel := strings.HasPrefix(strings.ToLower(a.modelID), "gemma-")
+
+	if isGemmaModel && systemInstructions != nil && len(systemInstructions.Parts) > 0 {
+		if len(content) > 0 && content[0].Role == genai.RoleUser {
+			systemParts := []string{}
+			for _, sp := range systemInstructions.Parts {
+				systemParts = append(systemParts, sp.Text)
+			}
+			systemMsg := strings.Join(systemParts, "\n")
+			content[0].Parts = append([]*genai.Part{
+				{
+					Text: systemMsg + "\n\n",
+				},
+			}, content[0].Parts...)
+			systemInstructions = nil
+		}
+	}
+
+	config.SystemInstruction = systemInstructions
+
+	if call.MaxOutputTokens != nil {
+		config.MaxOutputTokens = int32(*call.MaxOutputTokens)
+	}
+
+	if call.Temperature != nil {
+		tmp := float32(*call.Temperature)
+		config.Temperature = &tmp
+	}
+	if call.TopK != nil {
+		tmp := float32(*call.TopK)
+		config.TopK = &tmp
+	}
+	if call.TopP != nil {
+		tmp := float32(*call.TopP)
+		config.TopP = &tmp
+	}
+	if call.FrequencyPenalty != nil {
+		tmp := float32(*call.FrequencyPenalty)
+		config.FrequencyPenalty = &tmp
+	}
+	if call.PresencePenalty != nil {
+		tmp := float32(*call.PresencePenalty)
+		config.PresencePenalty = &tmp
+	}
+
+	if providerOptions.ThinkingConfig != nil {
+		config.ThinkingConfig = &genai.ThinkingConfig{}
+		if providerOptions.ThinkingConfig.IncludeThoughts != nil {
+			config.ThinkingConfig.IncludeThoughts = *providerOptions.ThinkingConfig.IncludeThoughts
+		}
+		if providerOptions.ThinkingConfig.ThinkingBudget != nil {
+			tmp := int32(*providerOptions.ThinkingConfig.ThinkingBudget)
+			config.ThinkingConfig.ThinkingBudget = &tmp
+		}
+	}
+	for _, safetySetting := range providerOptions.SafetySettings {
+		config.SafetySettings = append(config.SafetySettings, &genai.SafetySetting{
+			Category:  genai.HarmCategory(safetySetting.Category),
+			Threshold: genai.HarmBlockThreshold(safetySetting.Threshold),
+		})
+	}
+	if providerOptions.CachedContent != "" {
+		config.CachedContent = providerOptions.CachedContent
+	}
+
+	if len(call.Tools) > 0 {
+		tools, toolChoice, toolWarnings := toGoogleTools(call.Tools, call.ToolChoice)
+		config.ToolConfig = toolChoice
+		config.Tools = append(config.Tools, &genai.Tool{
+			FunctionDeclarations: tools,
+		})
+		warnings = append(warnings, toolWarnings...)
+	}
+
+	return config, content, warnings, nil
+}
+
+func toGooglePrompt(prompt ai.Prompt) (*genai.Content, []*genai.Content, []ai.CallWarning) {
+	var systemInstructions *genai.Content
+	var content []*genai.Content
+	var warnings []ai.CallWarning
+
+	finishedSystemBlock := false
+	for _, msg := range prompt {
+		switch msg.Role {
+		case ai.MessageRoleSystem:
+			if finishedSystemBlock {
+				// skip multiple system messages that are separated by user/assistant messages
+				// TODO: see if we need to send error here?
+				continue
+			}
+			finishedSystemBlock = true
+
+			var systemMessages []string
+			for _, part := range msg.Content {
+				text, ok := ai.AsMessagePart[ai.TextPart](part)
+				if !ok || text.Text == "" {
+					continue
+				}
+				systemMessages = append(systemMessages, text.Text)
+			}
+			if len(systemMessages) > 0 {
+				systemInstructions = &genai.Content{
+					Parts: []*genai.Part{
+						{
+							Text: strings.Join(systemMessages, "\n"),
+						},
+					},
+				}
+			}
+		case ai.MessageRoleUser:
+			var parts []*genai.Part
+			for _, part := range msg.Content {
+				switch part.GetType() {
+				case ai.ContentTypeText:
+					text, ok := ai.AsMessagePart[ai.TextPart](part)
+					if !ok || text.Text == "" {
+						continue
+					}
+					parts = append(parts, &genai.Part{
+						Text: text.Text,
+					})
+				case ai.ContentTypeFile:
+					file, ok := ai.AsMessagePart[ai.FilePart](part)
+					if !ok {
+						continue
+					}
+					var encoded []byte
+					base64.StdEncoding.Encode(encoded, file.Data)
+					parts = append(parts, &genai.Part{
+						InlineData: &genai.Blob{
+							Data:     encoded,
+							MIMEType: file.MediaType,
+						},
+					})
+				}
+			}
+			if len(parts) > 0 {
+				content = append(content, &genai.Content{
+					Role:  genai.RoleUser,
+					Parts: parts,
+				})
+			}
+		case ai.MessageRoleAssistant:
+			var parts []*genai.Part
+			for _, part := range msg.Content {
+				switch part.GetType() {
+				case ai.ContentTypeText:
+					text, ok := ai.AsMessagePart[ai.TextPart](part)
+					if !ok || text.Text == "" {
+						continue
+					}
+					parts = append(parts, &genai.Part{
+						Text: text.Text,
+					})
+				case ai.ContentTypeToolCall:
+					toolCall, ok := ai.AsMessagePart[ai.ToolCallPart](part)
+					if !ok {
+						continue
+					}
+
+					var result map[string]any
+					err := json.Unmarshal([]byte(toolCall.Input), &result)
+					if err != nil {
+						continue
+					}
+					parts = append(parts, &genai.Part{
+						FunctionCall: &genai.FunctionCall{
+							ID:   toolCall.ToolCallID,
+							Name: toolCall.ToolName,
+							Args: result,
+						},
+					})
+				}
+			}
+			if len(parts) > 0 {
+				content = append(content, &genai.Content{
+					Role:  genai.RoleModel,
+					Parts: parts,
+				})
+			}
+		case ai.MessageRoleTool:
+			var parts []*genai.Part
+			for _, part := range msg.Content {
+				switch part.GetType() {
+				case ai.ContentTypeToolResult:
+					result, ok := ai.AsMessagePart[ai.ToolResultPart](part)
+					if !ok {
+						continue
+					}
+					var toolCall ai.ToolCallPart
+					for _, m := range prompt {
+						if m.Role == ai.MessageRoleAssistant {
+							for _, content := range m.Content {
+								tc, ok := ai.AsMessagePart[ai.ToolCallPart](content)
+								if !ok {
+									continue
+								}
+								if tc.ToolCallID == result.ToolCallID {
+									toolCall = tc
+									break
+								}
+							}
+						}
+					}
+					switch result.Output.GetType() {
+					case ai.ToolResultContentTypeText:
+						content, ok := ai.AsToolResultOutputType[ai.ToolResultOutputContentText](result.Output)
+						if !ok {
+							continue
+						}
+						response := map[string]any{"result": content.Text}
+						parts = append(parts, &genai.Part{
+							FunctionResponse: &genai.FunctionResponse{
+								ID:       result.ToolCallID,
+								Response: response,
+								Name:     toolCall.ToolName,
+							},
+						})
+
+					case ai.ToolResultContentTypeError:
+						content, ok := ai.AsToolResultOutputType[ai.ToolResultOutputContentError](result.Output)
+						if !ok {
+							continue
+						}
+						response := map[string]any{"result": content.Error.Error()}
+						parts = append(parts, &genai.Part{
+							FunctionResponse: &genai.FunctionResponse{
+								ID:       result.ToolCallID,
+								Response: response,
+								Name:     toolCall.ToolName,
+							},
+						})
+
+					}
+				}
+			}
+			if len(parts) > 0 {
+				content = append(content, &genai.Content{
+					Role:  genai.RoleUser,
+					Parts: parts,
+				})
+			}
+		}
+	}
+	return systemInstructions, content, warnings
+}
+
+// Generate implements ai.LanguageModel.
+func (g *languageModel) Generate(ctx context.Context, call ai.Call) (*ai.Response, error) {
+	// params, err := g.prepareParams(call)
+	// if err != nil {
+	// 	return nil, err
+	// }
+	panic("unimplemented")
+}
+
+// Model implements ai.LanguageModel.
+func (g *languageModel) Model() string {
+	return g.modelID
+}
+
+// Provider implements ai.LanguageModel.
+func (g *languageModel) Provider() string {
+	return g.provider
+}
+
+// Stream implements ai.LanguageModel.
+func (g *languageModel) Stream(context.Context, ai.Call) (ai.StreamResponse, error) {
+	panic("unimplemented")
+}
+
+func toGoogleTools(tools []ai.Tool, toolChoice *ai.ToolChoice) (googleTools []*genai.FunctionDeclaration, googleToolChoice *genai.ToolConfig, warnings []ai.CallWarning) {
+	for _, tool := range tools {
+		if tool.GetType() == ai.ToolTypeFunction {
+			ft, ok := tool.(ai.FunctionTool)
+			if !ok {
+				continue
+			}
+
+			required := []string{}
+			var properties map[string]any
+			if props, ok := ft.InputSchema["properties"]; ok {
+				properties, _ = props.(map[string]any)
+			}
+			if req, ok := ft.InputSchema["required"]; ok {
+				if reqArr, ok := req.([]string); ok {
+					required = reqArr
+				}
+			}
+			declaration := &genai.FunctionDeclaration{
+				Name:        ft.Name,
+				Description: ft.Description,
+				Parameters: &genai.Schema{
+					Type:       genai.TypeObject,
+					Properties: convertSchemaProperties(properties),
+					Required:   required,
+				},
+			}
+			googleTools = append(googleTools, declaration)
+			continue
+		}
+		// TODO: handle provider tool calls
+		warnings = append(warnings, ai.CallWarning{
+			Type:    ai.CallWarningTypeUnsupportedTool,
+			Tool:    tool,
+			Message: "tool is not supported",
+		})
+	}
+	if toolChoice == nil {
+		return
+	}
+	switch *toolChoice {
+	case ai.ToolChoiceAuto:
+		googleToolChoice = &genai.ToolConfig{
+			FunctionCallingConfig: &genai.FunctionCallingConfig{
+				Mode: genai.FunctionCallingConfigModeAuto,
+			},
+		}
+	case ai.ToolChoiceRequired:
+		googleToolChoice = &genai.ToolConfig{
+			FunctionCallingConfig: &genai.FunctionCallingConfig{
+				Mode: genai.FunctionCallingConfigModeAny,
+			},
+		}
+	case ai.ToolChoiceNone:
+		googleToolChoice = &genai.ToolConfig{
+			FunctionCallingConfig: &genai.FunctionCallingConfig{
+				Mode: genai.FunctionCallingConfigModeNone,
+			},
+		}
+	default:
+		googleToolChoice = &genai.ToolConfig{
+			FunctionCallingConfig: &genai.FunctionCallingConfig{
+				Mode: genai.FunctionCallingConfigModeAny,
+				AllowedFunctionNames: []string{
+					string(*toolChoice),
+				},
+			},
+		}
+	}
+	return
+}
+
+func convertSchemaProperties(parameters map[string]any) map[string]*genai.Schema {
+	properties := make(map[string]*genai.Schema)
+
+	for name, param := range parameters {
+		properties[name] = convertToSchema(param)
+	}
+
+	return properties
+}
+
+func convertToSchema(param any) *genai.Schema {
+	schema := &genai.Schema{Type: genai.TypeString}
+
+	paramMap, ok := param.(map[string]any)
+	if !ok {
+		return schema
+	}
+
+	if desc, ok := paramMap["description"].(string); ok {
+		schema.Description = desc
+	}
+
+	typeVal, hasType := paramMap["type"]
+	if !hasType {
+		return schema
+	}
+
+	typeStr, ok := typeVal.(string)
+	if !ok {
+		return schema
+	}
+
+	schema.Type = mapJSONTypeToGoogle(typeStr)
+
+	switch typeStr {
+	case "array":
+		schema.Items = processArrayItems(paramMap)
+	case "object":
+		if props, ok := paramMap["properties"].(map[string]any); ok {
+			schema.Properties = convertSchemaProperties(props)
+		}
+	}
+
+	return schema
+}
+
+func processArrayItems(paramMap map[string]any) *genai.Schema {
+	items, ok := paramMap["items"].(map[string]any)
+	if !ok {
+		return nil
+	}
+
+	return convertToSchema(items)
+}
+
+func mapJSONTypeToGoogle(jsonType string) genai.Type {
+	switch jsonType {
+	case "string":
+		return genai.TypeString
+	case "number":
+		return genai.TypeNumber
+	case "integer":
+		return genai.TypeInteger
+	case "boolean":
+		return genai.TypeBoolean
+	case "array":
+		return genai.TypeArray
+	case "object":
+		return genai.TypeObject
+	default:
+		return genai.TypeString // Default to string for unknown types
+	}
+}

providers/google/provider_options.go 🔗

@@ -0,0 +1,42 @@
+package google
+
+type thinkingConfig struct {
+	ThinkingBudget  *int64 `json:"thinking_budget"`
+	IncludeThoughts *bool  `json:"include_thoughts"`
+}
+
+type safetySetting struct {
+	// 'HARM_CATEGORY_UNSPECIFIED',
+	// 'HARM_CATEGORY_HATE_SPEECH',
+	// 'HARM_CATEGORY_DANGEROUS_CONTENT',
+	// 'HARM_CATEGORY_HARASSMENT',
+	// 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
+	// 'HARM_CATEGORY_CIVIC_INTEGRITY',
+	Category string `json:"category"`
+
+	// 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
+	// 'BLOCK_LOW_AND_ABOVE',
+	// 'BLOCK_MEDIUM_AND_ABOVE',
+	// 'BLOCK_ONLY_HIGH',
+	// 'BLOCK_NONE',
+	// 'OFF',
+	Threshold string `json:"threshold"`
+}
+type providerOptions struct {
+	ThinkingConfig *thinkingConfig `json:"thinking_config"`
+
+	// Optional.
+	// The name of the cached content used as context to serve the prediction.
+	// Format: cachedContents/{cachedContent}
+	CachedContent string `json:"cached_content"`
+
+	// Optional. A list of unique safety settings for blocking unsafe content.
+	SafetySettings []safetySetting `json:"safety_settings"`
+	// 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
+	// 'BLOCK_LOW_AND_ABOVE',
+	// 'BLOCK_MEDIUM_AND_ABOVE',
+	// 'BLOCK_ONLY_HIGH',
+	// 'BLOCK_NONE',
+	// 'OFF',
+	Threshold string `json:"threshold"`
+}