feat: gitea client
This commit is contained in:
193
vendor/github.com/google/generative-ai-go/genai/caching.go
generated
vendored
Normal file
193
vendor/github.com/google/generative-ai-go/genai/caching.go
generated
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package genai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
gl "cloud.google.com/go/ai/generativelanguage/apiv1beta"
|
||||
pb "cloud.google.com/go/ai/generativelanguage/apiv1beta/generativelanguagepb"
|
||||
"google.golang.org/api/iterator"
|
||||
durationpb "google.golang.org/protobuf/types/known/durationpb"
|
||||
fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type cacheClient = gl.CacheClient
|
||||
|
||||
var (
|
||||
newCacheClient = gl.NewCacheClient
|
||||
newCacheRESTClient = gl.NewCacheRESTClient
|
||||
)
|
||||
|
||||
// GenerativeModelFromCachedContent returns a [GenerativeModel] that uses the given [CachedContent].
|
||||
// The argument should come from a call to [Client.CreateCachedContent] or [Client.GetCachedContent].
|
||||
func (c *Client) GenerativeModelFromCachedContent(cc *CachedContent) *GenerativeModel {
|
||||
return &GenerativeModel{
|
||||
c: c,
|
||||
fullName: cc.Model,
|
||||
CachedContentName: cc.Name,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCachedContent creates a new CachedContent.
|
||||
// The argument should contain a model name and some data to be cached, which can include
|
||||
// contents, a system instruction, tools and/or tool configuration. It can also
|
||||
// include an expiration time or TTL. But it should not include a name; the system
|
||||
// will generate one.
|
||||
//
|
||||
// The return value will contain the name, which should be used to refer to the CachedContent
|
||||
// in other API calls. It will also hold various metadata like expiration and creation time.
|
||||
// It will not contain any of the actual content provided as input.
|
||||
//
|
||||
// You can use the return value to create a model with [Client.GenerativeModelFromCachedContent].
|
||||
// Or you can set [GenerativeModel.CachedContentName] to the name of the CachedContent, in which
|
||||
// case you must ensure that the model provided in this call matches the name in the [GenerativeModel].
|
||||
func (c *Client) CreateCachedContent(ctx context.Context, cc *CachedContent) (*CachedContent, error) {
|
||||
if cc.Name != "" {
|
||||
return nil, errors.New("genai.CreateCachedContent: do not provide a name; one will be generated")
|
||||
}
|
||||
pcc := cc.toProto()
|
||||
pcc.Model = Ptr(fullModelName(cc.Model))
|
||||
req := &pb.CreateCachedContentRequest{
|
||||
CachedContent: pcc,
|
||||
}
|
||||
debugPrint(req)
|
||||
return c.cachedContentFromProto(c.cc.CreateCachedContent(ctx, req))
|
||||
}
|
||||
|
||||
// GetCachedContent retrieves the CachedContent with the given name.
|
||||
func (c *Client) GetCachedContent(ctx context.Context, name string) (*CachedContent, error) {
|
||||
return c.cachedContentFromProto(c.cc.GetCachedContent(ctx, &pb.GetCachedContentRequest{Name: name}))
|
||||
}
|
||||
|
||||
// DeleteCachedContent deletes the CachedContent with the given name.
|
||||
func (c *Client) DeleteCachedContent(ctx context.Context, name string) error {
|
||||
return c.cc.DeleteCachedContent(ctx, &pb.DeleteCachedContentRequest{Name: name})
|
||||
}
|
||||
|
||||
// CachedContentToUpdate specifies which fields of a CachedContent to modify in a call to
|
||||
// [Client.UpdateCachedContent].
|
||||
type CachedContentToUpdate struct {
|
||||
// If non-nil, update the expire time or TTL.
|
||||
Expiration *ExpireTimeOrTTL
|
||||
}
|
||||
|
||||
// UpdateCachedContent modifies the [CachedContent] according to the values
|
||||
// of the [CachedContentToUpdate] struct.
|
||||
// It returns the modified CachedContent.
|
||||
//
|
||||
// The argument CachedContent must have its Name field populated.
|
||||
// If its UpdateTime field is non-zero, it will be compared with the update time
|
||||
// of the stored CachedContent and the call will fail if they differ.
|
||||
// This avoids a race condition when two updates are attempted concurrently.
|
||||
// All other fields of the argument CachedContent are ignored.
|
||||
func (c *Client) UpdateCachedContent(ctx context.Context, cc *CachedContent, ccu *CachedContentToUpdate) (*CachedContent, error) {
|
||||
if ccu == nil || ccu.Expiration == nil {
|
||||
return nil, errors.New("genai.UpdateCachedContent: no update specified")
|
||||
}
|
||||
cc2 := &CachedContent{
|
||||
Name: cc.Name,
|
||||
UpdateTime: cc.UpdateTime,
|
||||
Expiration: *ccu.Expiration,
|
||||
}
|
||||
mask := "expire_time"
|
||||
if ccu.Expiration.ExpireTime.IsZero() {
|
||||
mask = "ttl"
|
||||
}
|
||||
req := &pb.UpdateCachedContentRequest{
|
||||
CachedContent: cc2.toProto(),
|
||||
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{mask}},
|
||||
}
|
||||
debugPrint(req)
|
||||
return c.cachedContentFromProto(c.cc.UpdateCachedContent(ctx, req))
|
||||
}
|
||||
|
||||
// ListCachedContents lists all the CachedContents associated with the project and location.
|
||||
func (c *Client) ListCachedContents(ctx context.Context) *CachedContentIterator {
|
||||
return &CachedContentIterator{
|
||||
it: c.cc.ListCachedContents(ctx, &pb.ListCachedContentsRequest{}),
|
||||
}
|
||||
}
|
||||
|
||||
// A CachedContentIterator iterates over CachedContents.
|
||||
type CachedContentIterator struct {
|
||||
it *gl.CachedContentIterator
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *CachedContentIterator) Next() (*CachedContent, error) {
|
||||
m, err := it.it.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (CachedContent{}).fromProto(m), nil
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *CachedContentIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.it.PageInfo()
|
||||
}
|
||||
|
||||
func (c *Client) cachedContentFromProto(pcc *pb.CachedContent, err error) (*CachedContent, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cc := (CachedContent{}).fromProto(pcc)
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// ExpireTimeOrTTL describes the time when a resource expires.
|
||||
// If ExpireTime is non-zero, it is the expiration time.
|
||||
// Otherwise, the expiration time is the value of TTL ("time to live") added
|
||||
// to the current time.
|
||||
type ExpireTimeOrTTL struct {
|
||||
ExpireTime time.Time
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
// populateCachedContentTo populates some fields of p from v.
|
||||
func populateCachedContentTo(p *pb.CachedContent, v *CachedContent) {
|
||||
exp := v.Expiration
|
||||
if !exp.ExpireTime.IsZero() {
|
||||
p.Expiration = &pb.CachedContent_ExpireTime{
|
||||
ExpireTime: timestamppb.New(exp.ExpireTime),
|
||||
}
|
||||
} else if exp.TTL != 0 {
|
||||
p.Expiration = &pb.CachedContent_Ttl{
|
||||
Ttl: durationpb.New(exp.TTL),
|
||||
}
|
||||
}
|
||||
// If both fields of v.Expiration are zero, leave p.Expiration unset.
|
||||
}
|
||||
|
||||
// populateCachedContentFrom populates some fields of v from p.
|
||||
func populateCachedContentFrom(v *CachedContent, p *pb.CachedContent) {
|
||||
if p.Expiration == nil {
|
||||
return
|
||||
}
|
||||
switch e := p.Expiration.(type) {
|
||||
case *pb.CachedContent_ExpireTime:
|
||||
v.Expiration.ExpireTime = pvTimeFromProto(e.ExpireTime)
|
||||
case *pb.CachedContent_Ttl:
|
||||
v.Expiration.TTL = e.Ttl.AsDuration()
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type of CachedContent.Expiration: %T", p.Expiration))
|
||||
}
|
||||
}
|
||||
89
vendor/github.com/google/generative-ai-go/genai/chat.go
generated
vendored
Normal file
89
vendor/github.com/google/generative-ai-go/genai/chat.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package genai
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// A ChatSession provides interactive chat.
|
||||
type ChatSession struct {
|
||||
m *GenerativeModel
|
||||
History []*Content
|
||||
}
|
||||
|
||||
// StartChat starts a chat session.
|
||||
func (m *GenerativeModel) StartChat() *ChatSession {
|
||||
return &ChatSession{m: m}
|
||||
}
|
||||
|
||||
// SendMessage sends a request to the model as part of a chat session.
|
||||
func (cs *ChatSession) SendMessage(ctx context.Context, parts ...Part) (*GenerateContentResponse, error) {
|
||||
// Call the underlying client with the entire history plus the argument Content.
|
||||
cs.History = append(cs.History, NewUserContent(parts...))
|
||||
req, err := cs.m.newGenerateContentRequest(cs.History...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.GenerationConfig.CandidateCount = Ptr[int32](1)
|
||||
resp, err := cs.m.generateContent(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs.addToHistory(resp.Candidates)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SendMessageStream is like SendMessage, but with a streaming request.
|
||||
func (cs *ChatSession) SendMessageStream(ctx context.Context, parts ...Part) *GenerateContentResponseIterator {
|
||||
cs.History = append(cs.History, NewUserContent(parts...))
|
||||
req, err := cs.m.newGenerateContentRequest(cs.History...)
|
||||
if err != nil {
|
||||
return &GenerateContentResponseIterator{err: err}
|
||||
}
|
||||
req.GenerationConfig.CandidateCount = Ptr[int32](1)
|
||||
streamClient, err := cs.m.c.gc.StreamGenerateContent(ctx, req)
|
||||
return &GenerateContentResponseIterator{
|
||||
sc: streamClient,
|
||||
err: err,
|
||||
cs: cs,
|
||||
}
|
||||
}
|
||||
|
||||
// By default, use the first candidate for history. The user can modify that if they want.
|
||||
func (cs *ChatSession) addToHistory(cands []*Candidate) bool {
|
||||
if len(cands) > 0 {
|
||||
c := cands[0].Content
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
c.Role = roleModel
|
||||
cs.History = append(cs.History, copySanitizedModelContent(c))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// copySanitizedModelContent creates a (shallow) copy of c with role set to
|
||||
// model and empty text parts removed.
|
||||
func copySanitizedModelContent(c *Content) *Content {
|
||||
newc := &Content{Role: roleModel}
|
||||
for _, part := range c.Parts {
|
||||
if t, ok := part.(Text); !ok || len(string(t)) > 0 {
|
||||
newc.Parts = append(newc.Parts, part)
|
||||
}
|
||||
}
|
||||
return newc
|
||||
}
|
||||
476
vendor/github.com/google/generative-ai-go/genai/client.go
generated
vendored
Normal file
476
vendor/github.com/google/generative-ai-go/genai/client.go
generated
vendored
Normal file
@@ -0,0 +1,476 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// For the following go:generate line to work, install the protoveener tool:
|
||||
// git clone https://github.com/googleapis/google-cloud-go
|
||||
// cd google-cloud-go
|
||||
// go install ./internal/protoveneer/cmd/protoveneer
|
||||
//
|
||||
//go:generate ./generate.sh
|
||||
|
||||
package genai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
gl "cloud.google.com/go/ai/generativelanguage/apiv1beta"
|
||||
pb "cloud.google.com/go/ai/generativelanguage/apiv1beta/generativelanguagepb"
|
||||
"github.com/google/generative-ai-go/genai/internal"
|
||||
gld "github.com/google/generative-ai-go/genai/internal/generativelanguage/v1beta" // discovery client
|
||||
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
// A Client is a Google generative AI client.
|
||||
type Client struct {
|
||||
gc *gl.GenerativeClient
|
||||
mc *gl.ModelClient
|
||||
fc *gl.FileClient
|
||||
cc *gl.CacheClient
|
||||
ds *gld.Service
|
||||
}
|
||||
|
||||
// NewClient creates a new Google generative AI client.
|
||||
//
|
||||
// Clients should be reused instead of created as needed. The methods of Client
|
||||
// are safe for concurrent use by multiple goroutines.
|
||||
//
|
||||
// You may configure the client by passing in options from the [google.golang.org/api/option]
|
||||
// package.
|
||||
func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
|
||||
if !hasAuthOption(opts) {
|
||||
return nil, errors.New(`You need an auth option to use this client.
|
||||
for an API Key: Visit https://ai.google.dev to get one, put it in an environment variable like GEMINI_API_KEY,
|
||||
then pass it as an option:
|
||||
genai.NewClient(ctx, option.WithAPIKey(os.Getenv("GEMINI_API_KEY")))
|
||||
(If you're doing that already, then maybe the environment variable is empty or unset.)
|
||||
Import the option package as "google.golang.org/api/option".`)
|
||||
}
|
||||
gc, err := gl.NewGenerativeRESTClient(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating generative client: %w", err)
|
||||
}
|
||||
mc, err := gl.NewModelRESTClient(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating model client: %w", err)
|
||||
}
|
||||
fc, err := gl.NewFileRESTClient(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating file client: %w", err)
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/google/generative-ai-go/issues/151
|
||||
optsForCache := removeHTTPClientOption(opts)
|
||||
cc, err := gl.NewCacheClient(ctx, optsForCache...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating cache client: %w", err)
|
||||
}
|
||||
|
||||
ds, err := gld.NewService(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating discovery client: %w", err)
|
||||
}
|
||||
|
||||
kvs := []string{"gccl", "v" + internal.Version, "genai-go", internal.Version}
|
||||
if a, ok := optionOfType[*clientInfo](opts); ok {
|
||||
kvs = append(kvs, a.key, a.value)
|
||||
}
|
||||
gc.SetGoogleClientInfo(kvs...)
|
||||
mc.SetGoogleClientInfo(kvs...)
|
||||
fc.SetGoogleClientInfo(kvs...)
|
||||
|
||||
return &Client{gc, mc, fc, cc, ds}, nil
|
||||
}
|
||||
|
||||
// hasAuthOption reports whether an authentication-related option was provided.
|
||||
//
|
||||
// There is no good way to make these checks, because the types of the options
|
||||
// are unexported, and the struct that they populates is in an internal package.
|
||||
func hasAuthOption(opts []option.ClientOption) bool {
|
||||
for _, opt := range opts {
|
||||
v := reflect.ValueOf(opt)
|
||||
ts := v.Type().String()
|
||||
|
||||
switch ts {
|
||||
case "option.withAPIKey":
|
||||
return v.String() != ""
|
||||
|
||||
case "option.withHTTPClient",
|
||||
"option.withTokenSource",
|
||||
"option.withCredFile",
|
||||
"option.withCredentialsJSON":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// removeHTTPClientOption removes option.withHTTPClient from the given list
|
||||
// of options, if it exists; it returns the new (filtered) list.
|
||||
func removeHTTPClientOption(opts []option.ClientOption) []option.ClientOption {
|
||||
var newOpts []option.ClientOption
|
||||
for _, opt := range opts {
|
||||
ts := reflect.ValueOf(opt).Type().String()
|
||||
if ts != "option.withHTTPClient" {
|
||||
newOpts = append(newOpts, opt)
|
||||
}
|
||||
}
|
||||
return newOpts
|
||||
}
|
||||
|
||||
// Close closes the client.
|
||||
func (c *Client) Close() error {
|
||||
return errors.Join(c.gc.Close(), c.mc.Close(), c.fc.Close())
|
||||
}
|
||||
|
||||
// GenerativeModel is a model that can generate text.
|
||||
// Create one with [Client.GenerativeModel], then configure
|
||||
// it by setting the exported fields.
|
||||
type GenerativeModel struct {
|
||||
c *Client
|
||||
fullName string
|
||||
|
||||
GenerationConfig
|
||||
SafetySettings []*SafetySetting
|
||||
Tools []*Tool
|
||||
ToolConfig *ToolConfig // configuration for tools
|
||||
// SystemInstruction (also known as "system prompt") is a more forceful prompt to the model.
|
||||
// The model will adhere the instructions more strongly than if they appeared in a normal prompt.
|
||||
SystemInstruction *Content
|
||||
// The name of the CachedContent to use.
|
||||
// Must have already been created with [Client.CreateCachedContent].
|
||||
CachedContentName string
|
||||
}
|
||||
|
||||
// GenerativeModel creates a new instance of the named generative model.
|
||||
// For instance, "gemini-1.0-pro" or "models/gemini-1.0-pro".
|
||||
//
|
||||
// To access a tuned model named NAME, pass "tunedModels/NAME".
|
||||
func (c *Client) GenerativeModel(name string) *GenerativeModel {
|
||||
return &GenerativeModel{
|
||||
c: c,
|
||||
fullName: fullModelName(name),
|
||||
}
|
||||
}
|
||||
|
||||
func fullModelName(name string) string {
|
||||
if strings.ContainsRune(name, '/') {
|
||||
return name
|
||||
}
|
||||
return "models/" + name
|
||||
}
|
||||
|
||||
// GenerateContent produces a single request and response.
|
||||
func (m *GenerativeModel) GenerateContent(ctx context.Context, parts ...Part) (*GenerateContentResponse, error) {
|
||||
content := NewUserContent(parts...)
|
||||
req, err := m.newGenerateContentRequest(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := m.c.gc.GenerateContent(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return protoToResponse(res)
|
||||
}
|
||||
|
||||
// GenerateContentStream returns an iterator that enumerates responses.
|
||||
func (m *GenerativeModel) GenerateContentStream(ctx context.Context, parts ...Part) *GenerateContentResponseIterator {
|
||||
iter := &GenerateContentResponseIterator{}
|
||||
req, err := m.newGenerateContentRequest(NewUserContent(parts...))
|
||||
if err != nil {
|
||||
iter.err = err
|
||||
} else {
|
||||
iter.sc, iter.err = m.c.gc.StreamGenerateContent(ctx, req)
|
||||
}
|
||||
return iter
|
||||
}
|
||||
|
||||
func (m *GenerativeModel) generateContent(ctx context.Context, req *pb.GenerateContentRequest) (*GenerateContentResponse, error) {
|
||||
streamClient, err := m.c.gc.StreamGenerateContent(ctx, req)
|
||||
iter := &GenerateContentResponseIterator{
|
||||
sc: streamClient,
|
||||
err: err,
|
||||
}
|
||||
for {
|
||||
_, err := iter.Next()
|
||||
if err == iterator.Done {
|
||||
return iter.MergedResponse(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *GenerativeModel) newGenerateContentRequest(contents ...*Content) (*pb.GenerateContentRequest, error) {
|
||||
return pvCatchPanic(func() *pb.GenerateContentRequest {
|
||||
var cc *string
|
||||
if m.CachedContentName != "" {
|
||||
cc = &m.CachedContentName
|
||||
}
|
||||
req := &pb.GenerateContentRequest{
|
||||
Model: m.fullName,
|
||||
Contents: transformSlice(contents, (*Content).toProto),
|
||||
SafetySettings: transformSlice(m.SafetySettings, (*SafetySetting).toProto),
|
||||
Tools: transformSlice(m.Tools, (*Tool).toProto),
|
||||
ToolConfig: m.ToolConfig.toProto(),
|
||||
GenerationConfig: m.GenerationConfig.toProto(),
|
||||
SystemInstruction: m.SystemInstruction.toProto(),
|
||||
CachedContent: cc,
|
||||
}
|
||||
debugPrint(req)
|
||||
return req
|
||||
})
|
||||
}
|
||||
|
||||
// GenerateContentResponseIterator is an iterator over GnerateContentResponse.
|
||||
type GenerateContentResponseIterator struct {
|
||||
sc pb.GenerativeService_StreamGenerateContentClient
|
||||
err error
|
||||
merged *GenerateContentResponse
|
||||
cs *ChatSession
|
||||
}
|
||||
|
||||
// Next returns the next response.
|
||||
func (iter *GenerateContentResponseIterator) Next() (*GenerateContentResponse, error) {
|
||||
if iter.err != nil {
|
||||
return nil, iter.err
|
||||
}
|
||||
resp, err := iter.sc.Recv()
|
||||
iter.err = err
|
||||
if err == io.EOF {
|
||||
if iter.cs != nil && iter.merged != nil {
|
||||
iter.cs.addToHistory(iter.merged.Candidates)
|
||||
}
|
||||
return nil, iterator.Done
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gcp, err := protoToResponse(resp)
|
||||
if err != nil {
|
||||
iter.err = err
|
||||
return nil, err
|
||||
}
|
||||
// Merge this response in with the ones we've already seen.
|
||||
iter.merged = joinResponses(iter.merged, gcp)
|
||||
// If this is part of a ChatSession, remember the response for the history.
|
||||
return gcp, nil
|
||||
}
|
||||
|
||||
func protoToResponse(resp *pb.GenerateContentResponse) (*GenerateContentResponse, error) {
|
||||
gcp, err := fromProto[GenerateContentResponse](resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if gcp == nil {
|
||||
return nil, errors.New("empty response from model")
|
||||
}
|
||||
// Assume a non-nil PromptFeedback is an error.
|
||||
// TODO: confirm.
|
||||
if gcp.PromptFeedback != nil && gcp.PromptFeedback.BlockReason != BlockReasonUnspecified {
|
||||
return nil, &BlockedError{PromptFeedback: gcp.PromptFeedback}
|
||||
}
|
||||
|
||||
// If any candidate is blocked, error.
|
||||
// TODO: is this too harsh?
|
||||
for _, c := range gcp.Candidates {
|
||||
if c.FinishReason == FinishReasonSafety || c.FinishReason == FinishReasonRecitation {
|
||||
return nil, &BlockedError{Candidate: c}
|
||||
}
|
||||
}
|
||||
return gcp, nil
|
||||
}
|
||||
|
||||
// MergedResponse returns the result of combining all the streamed responses seen so far.
|
||||
// After iteration completes, the merged response should match the response obtained without streaming
|
||||
// (that is, if [GenerativeModel.GenerateContent] were called).
|
||||
func (iter *GenerateContentResponseIterator) MergedResponse() *GenerateContentResponse {
|
||||
return iter.merged
|
||||
}
|
||||
|
||||
// CountTokens counts the number of tokens in the content.
|
||||
func (m *GenerativeModel) CountTokens(ctx context.Context, parts ...Part) (*CountTokensResponse, error) {
|
||||
req, err := m.newCountTokensRequest(NewUserContent(parts...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := m.c.gc.CountTokens(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromProto[CountTokensResponse](res)
|
||||
}
|
||||
|
||||
func (m *GenerativeModel) newCountTokensRequest(contents ...*Content) (*pb.CountTokensRequest, error) {
|
||||
gcr, err := m.newGenerateContentRequest(contents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &pb.CountTokensRequest{
|
||||
Model: m.fullName,
|
||||
GenerateContentRequest: gcr,
|
||||
}
|
||||
debugPrint(req)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Info returns information about the model.
|
||||
func (m *GenerativeModel) Info(ctx context.Context) (*ModelInfo, error) {
|
||||
return m.c.modelInfo(ctx, m.fullName)
|
||||
}
|
||||
|
||||
func (c *Client) modelInfo(ctx context.Context, fullName string) (*ModelInfo, error) {
|
||||
req := &pb.GetModelRequest{Name: fullName}
|
||||
debugPrint(req)
|
||||
res, err := c.mc.GetModel(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromProto[ModelInfo](res)
|
||||
}
|
||||
|
||||
// A BlockedError indicates that the model's response was blocked.
|
||||
// There can be two underlying causes: the prompt or a candidate response.
|
||||
type BlockedError struct {
|
||||
// If non-nil, the model's response was blocked.
|
||||
// Consult the FinishReason field for details.
|
||||
Candidate *Candidate
|
||||
|
||||
// If non-nil, there was a problem with the prompt.
|
||||
PromptFeedback *PromptFeedback
|
||||
}
|
||||
|
||||
func (e *BlockedError) Error() string {
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "blocked: ")
|
||||
if e.Candidate != nil {
|
||||
fmt.Fprintf(&b, "candidate: %s", e.Candidate.FinishReason)
|
||||
}
|
||||
if e.PromptFeedback != nil {
|
||||
if e.Candidate != nil {
|
||||
fmt.Fprintf(&b, ", ")
|
||||
}
|
||||
fmt.Fprintf(&b, "prompt: %v", e.PromptFeedback.BlockReason)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// joinResponses merges the two responses, which should be the result of a streaming call.
|
||||
// The first argument is modified.
|
||||
func joinResponses(dest, src *GenerateContentResponse) *GenerateContentResponse {
|
||||
if dest == nil {
|
||||
return src
|
||||
}
|
||||
dest.Candidates = joinCandidateLists(dest.Candidates, src.Candidates)
|
||||
// Keep dest.PromptFeedback.
|
||||
// TODO: Take the last UsageMetadata.
|
||||
return dest
|
||||
}
|
||||
|
||||
func joinCandidateLists(dest, src []*Candidate) []*Candidate {
|
||||
indexToSrcCandidate := map[int32]*Candidate{}
|
||||
for _, s := range src {
|
||||
indexToSrcCandidate[s.Index] = s
|
||||
}
|
||||
for _, d := range dest {
|
||||
s := indexToSrcCandidate[d.Index]
|
||||
if s != nil {
|
||||
d.Content = joinContent(d.Content, s.Content)
|
||||
// Take the last of these.
|
||||
d.FinishReason = s.FinishReason
|
||||
// d.FinishMessage = s.FinishMessage
|
||||
d.SafetyRatings = s.SafetyRatings
|
||||
d.CitationMetadata = joinCitationMetadata(d.CitationMetadata, s.CitationMetadata)
|
||||
}
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
func joinCitationMetadata(dest, src *CitationMetadata) *CitationMetadata {
|
||||
if dest == nil {
|
||||
return src
|
||||
}
|
||||
if src == nil {
|
||||
return dest
|
||||
}
|
||||
dest.CitationSources = append(dest.CitationSources, src.CitationSources...)
|
||||
return dest
|
||||
}
|
||||
|
||||
func joinContent(dest, src *Content) *Content {
|
||||
if dest == nil {
|
||||
return src
|
||||
}
|
||||
if src == nil {
|
||||
return dest
|
||||
}
|
||||
// Assume roles are the same.
|
||||
dest.Parts = joinParts(dest.Parts, src.Parts)
|
||||
return dest
|
||||
}
|
||||
|
||||
func joinParts(dest, src []Part) []Part {
|
||||
return mergeTexts(append(dest, src...))
|
||||
}
|
||||
|
||||
func mergeTexts(in []Part) []Part {
|
||||
var out []Part
|
||||
i := 0
|
||||
for i < len(in) {
|
||||
if t, ok := in[i].(Text); ok {
|
||||
texts := []string{string(t)}
|
||||
var j int
|
||||
for j = i + 1; j < len(in); j++ {
|
||||
if t, ok := in[j].(Text); ok {
|
||||
texts = append(texts, string(t))
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// j is just after the last Text.
|
||||
out = append(out, Text(strings.Join(texts, "")))
|
||||
i = j
|
||||
} else {
|
||||
out = append(out, in[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// transformSlice applies f to each element of from and returns
|
||||
// a new slice with the results.
|
||||
func transformSlice[From, To any](from []From, f func(From) To) []To {
|
||||
if from == nil {
|
||||
return nil
|
||||
}
|
||||
to := make([]To, len(from))
|
||||
for i, e := range from {
|
||||
to[i] = f(e)
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
func fromProto[V interface{ fromProto(P) *V }, P any](p P) (*V, error) {
|
||||
var v V
|
||||
return pvCatchPanic(func() *V { return v.fromProto(p) })
|
||||
}
|
||||
237
vendor/github.com/google/generative-ai-go/genai/config.yaml
generated
vendored
Normal file
237
vendor/github.com/google/generative-ai-go/genai/config.yaml
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
# Copyright 2023 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Configuration for the protoveneer tool.
|
||||
|
||||
package: genai
|
||||
|
||||
protoImportPath: cloud.google.com/go/ai/generativelanguage/apiv1beta/generativelanguagepb
|
||||
|
||||
types:
|
||||
HarmCategory:
|
||||
protoPrefix: HarmCategory_HARM_CATEGORY_
|
||||
docVerb: specifies
|
||||
|
||||
SafetySetting_HarmBlockThreshold:
|
||||
name: HarmBlockThreshold
|
||||
protoPrefix: SafetySetting_BLOCK_
|
||||
veneerPrefix: HarmBlock
|
||||
docVerb: specifies
|
||||
valueNames:
|
||||
SafetySetting_HARM_BLOCK_THRESHOLD_UNSPECIFIED: HarmBlockUnspecified
|
||||
|
||||
SafetyRating_HarmProbability:
|
||||
name: HarmProbability
|
||||
protoPrefix: SafetyRating_
|
||||
docVerb: specifies
|
||||
valueNames:
|
||||
SafetyRating_HARM_PROBABILITY_UNSPECIFIED: HarmProbabilityUnspecified
|
||||
|
||||
Candidate_FinishReason:
|
||||
name: FinishReason
|
||||
protoPrefix: Candidate_
|
||||
|
||||
GenerateContentResponse:
|
||||
doc: 'is the response from a GenerateContent or GenerateContentStream call.'
|
||||
|
||||
GenerateContentResponse_PromptFeedback_BlockReason:
|
||||
name: BlockReason
|
||||
protoPrefix: GenerateContentResponse_PromptFeedback_
|
||||
|
||||
Content:
|
||||
fields:
|
||||
Parts:
|
||||
type: '[]Part'
|
||||
|
||||
Blob:
|
||||
fields:
|
||||
MimeType:
|
||||
name: MIMEType
|
||||
docVerb: contains
|
||||
|
||||
FileData:
|
||||
fields:
|
||||
MimeType:
|
||||
name: MIMEType
|
||||
doc: |
|
||||
The IANA standard MIME type of the source data.
|
||||
If present, this overrides the MIME type specified or inferred
|
||||
when the file was uploaded.
|
||||
The supported MIME types are documented on [this page].
|
||||
|
||||
[this page]: https://ai.google.dev/gemini-api/docs/prompting_with_media?lang=go#supported_file_formats
|
||||
FileUri:
|
||||
name: URI
|
||||
doc: 'The URI returned from UploadFile or GetFile.'
|
||||
|
||||
GenerationConfig:
|
||||
fields:
|
||||
ResponseMimeType:
|
||||
name: ResponseMIMEType
|
||||
|
||||
SafetySetting:
|
||||
|
||||
SafetyRating:
|
||||
docVerb: 'is the'
|
||||
|
||||
CitationMetadata:
|
||||
|
||||
CitationSource:
|
||||
docVerb: contains
|
||||
fields:
|
||||
Uri:
|
||||
name: URI
|
||||
License:
|
||||
type: string
|
||||
|
||||
Candidate:
|
||||
fields:
|
||||
Index:
|
||||
type: int32
|
||||
GroundingAttributions:
|
||||
omit: true
|
||||
|
||||
GenerateContentResponse_PromptFeedback:
|
||||
name: PromptFeedback
|
||||
docVerb: contains
|
||||
|
||||
CountTokensResponse:
|
||||
|
||||
TaskType:
|
||||
protoPrefix: TaskType
|
||||
valueNames:
|
||||
TaskType_TASK_TYPE_UNSPECIFIED: TaskTypeUnspecified
|
||||
|
||||
EmbedContentResponse:
|
||||
BatchEmbedContentsResponse:
|
||||
|
||||
ContentEmbedding:
|
||||
|
||||
Model:
|
||||
name: ModelInfo
|
||||
doc: 'is information about a language model.'
|
||||
fields:
|
||||
BaseModelId:
|
||||
name: BaseModelID
|
||||
Temperature:
|
||||
type: float32
|
||||
TopP:
|
||||
type: float32
|
||||
TopK:
|
||||
type: int32
|
||||
|
||||
# Types for function calling
|
||||
Tool:
|
||||
fields:
|
||||
FunctionDeclarations:
|
||||
doc: |
|
||||
Optional. A list of FunctionDeclarations available to the model that
|
||||
can be used for function calling. The model or system does not execute
|
||||
the function. Instead the defined function may be returned as a [FunctionCall]
|
||||
part with arguments to the client side for execution. The next conversation
|
||||
turn may contain a [FunctionResponse] with the role "function" generation
|
||||
context for the next model turn.
|
||||
ToolConfig:
|
||||
FunctionDeclaration:
|
||||
FunctionCall:
|
||||
FunctionResponse:
|
||||
Schema:
|
||||
|
||||
Type:
|
||||
protoPrefix: Type_
|
||||
veneerPrefix: ''
|
||||
|
||||
FunctionCallingConfig:
|
||||
doc: 'holds configuration for function calling.'
|
||||
|
||||
FunctionCallingConfig_Mode:
|
||||
name: FunctionCallingMode
|
||||
protoPrefix: FunctionCallingConfig
|
||||
veneerPrefix: FunctionCalling
|
||||
valueNames:
|
||||
FunctionCallingConfig_MODE_UNSPECIFIED: FunctionCallingUnspecified
|
||||
|
||||
File:
|
||||
populateToFrom: populateFileTo, populateFileFrom
|
||||
fields:
|
||||
Uri:
|
||||
name: URI
|
||||
MimeType:
|
||||
name: MIMEType
|
||||
Metadata:
|
||||
type: '*FileMetadata'
|
||||
noConvert: true
|
||||
doc: 'Metadata for the File.'
|
||||
|
||||
VideoMetadata:
|
||||
fields:
|
||||
VideoDuration:
|
||||
name: Duration
|
||||
|
||||
File_State:
|
||||
name: FileState
|
||||
docVerb: represents
|
||||
protoPrefix: File
|
||||
veneerPrefix: FileState
|
||||
valueNames:
|
||||
File_STATE_UNSPECIFIED: FileStateUnspecified
|
||||
|
||||
GenerateContentResponse_UsageMetadata:
|
||||
name: UsageMetadata
|
||||
fields:
|
||||
PromptTokenCount:
|
||||
type: int32
|
||||
CandidatesTokenCount:
|
||||
type: int32
|
||||
TotalTokenCount:
|
||||
type: int32
|
||||
|
||||
CachedContent:
|
||||
populateToFrom: populateCachedContentTo, populateCachedContentFrom
|
||||
fields:
|
||||
Expiration:
|
||||
type: ExpireTimeOrTTL
|
||||
noConvert: true
|
||||
Name:
|
||||
type: string
|
||||
Model:
|
||||
type: string
|
||||
DisplayName:
|
||||
type: string
|
||||
|
||||
CachedContent_UsageMetadata:
|
||||
name: CachedContentUsageMetadata
|
||||
|
||||
CodeExecution:
|
||||
ExecutableCode:
|
||||
CodeExecutionResult:
|
||||
|
||||
ExecutableCode_Language:
|
||||
name: ExecutableCodeLanguage
|
||||
protoPrefix: ExecutableCode
|
||||
veneerPrefix: ExecutableCode
|
||||
|
||||
CodeExecutionResult_Outcome:
|
||||
name: CodeExecutionResultOutcome
|
||||
protoPrefix: CodeExecutionResult
|
||||
veneerPrefix: CodeExecutionResult
|
||||
valueNames:
|
||||
CodeExecutionResult_OUTCOME_OK: CodeExecutionResultOutcomeOK
|
||||
|
||||
# Omit everything not explicitly configured.
|
||||
omitTypes:
|
||||
- '*'
|
||||
|
||||
converters:
|
||||
Part: partToProto, partFromProto
|
||||
182
vendor/github.com/google/generative-ai-go/genai/content.go
generated
vendored
Normal file
182
vendor/github.com/google/generative-ai-go/genai/content.go
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package genai
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
pb "cloud.google.com/go/ai/generativelanguage/apiv1beta/generativelanguagepb"
|
||||
)
|
||||
|
||||
const (
|
||||
roleUser = "user"
|
||||
roleModel = "model"
|
||||
)
|
||||
|
||||
// A Part is a piece of model content.
|
||||
// A Part can be one of the following types:
|
||||
// - Text
|
||||
// - Blob
|
||||
// - FunctionCall
|
||||
// - FunctionResponse
|
||||
// - ExecutableCode
|
||||
// - CodeExecutionResult
|
||||
type Part interface {
|
||||
toPart() *pb.Part
|
||||
}
|
||||
|
||||
func partToProto(p Part) *pb.Part {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return p.toPart()
|
||||
}
|
||||
|
||||
func partFromProto(p *pb.Part) Part {
|
||||
switch d := p.Data.(type) {
|
||||
case *pb.Part_Text:
|
||||
return Text(d.Text)
|
||||
case *pb.Part_InlineData:
|
||||
return Blob{
|
||||
MIMEType: d.InlineData.MimeType,
|
||||
Data: d.InlineData.Data,
|
||||
}
|
||||
case *pb.Part_FunctionCall:
|
||||
return *(FunctionCall{}).fromProto(d.FunctionCall)
|
||||
|
||||
case *pb.Part_FunctionResponse:
|
||||
panic("FunctionResponse unimplemented")
|
||||
|
||||
case *pb.Part_ExecutableCode:
|
||||
return (ExecutableCode{}).fromProto(d.ExecutableCode)
|
||||
case *pb.Part_CodeExecutionResult:
|
||||
return (CodeExecutionResult{}).fromProto(d.CodeExecutionResult)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown Part.Data type %T", p.Data))
|
||||
}
|
||||
}
|
||||
|
||||
// A Text is a piece of text, like a question or phrase.
|
||||
type Text string
|
||||
|
||||
func (t Text) toPart() *pb.Part {
|
||||
return &pb.Part{
|
||||
Data: &pb.Part_Text{Text: string(t)},
|
||||
}
|
||||
}
|
||||
|
||||
func (b Blob) toPart() *pb.Part {
|
||||
return &pb.Part{
|
||||
Data: &pb.Part_InlineData{
|
||||
InlineData: b.toProto(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ImageData is a convenience function for creating an image
|
||||
// Blob for input to a model.
|
||||
// The format should be the second part of the MIME type, after "image/".
|
||||
// For example, for a PNG image, pass "png".
|
||||
func ImageData(format string, data []byte) Blob {
|
||||
return Blob{
|
||||
MIMEType: "image/" + format,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (f FunctionCall) toPart() *pb.Part {
|
||||
return &pb.Part{
|
||||
Data: &pb.Part_FunctionCall{
|
||||
FunctionCall: f.toProto(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f FunctionResponse) toPart() *pb.Part {
|
||||
return &pb.Part{
|
||||
Data: &pb.Part_FunctionResponse{
|
||||
FunctionResponse: f.toProto(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (fd FileData) toPart() *pb.Part {
|
||||
return &pb.Part{
|
||||
Data: &pb.Part_FileData{
|
||||
FileData: fd.toProto(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (ec ExecutableCode) toPart() *pb.Part {
|
||||
return &pb.Part{
|
||||
Data: &pb.Part_ExecutableCode{
|
||||
ExecutableCode: ec.toProto(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c CodeExecutionResult) toPart() *pb.Part {
|
||||
return &pb.Part{
|
||||
Data: &pb.Part_CodeExecutionResult{
|
||||
CodeExecutionResult: c.toProto(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Ptr returns a pointer to its argument.
|
||||
// It can be used to initialize pointer fields:
|
||||
//
|
||||
// model.Temperature = genai.Ptr[float32](0.1)
|
||||
func Ptr[T any](t T) *T { return &t }
|
||||
|
||||
// SetCandidateCount sets the CandidateCount field.
|
||||
func (c *GenerationConfig) SetCandidateCount(x int32) { c.CandidateCount = &x }
|
||||
|
||||
// SetMaxOutputTokens sets the MaxOutputTokens field.
|
||||
func (c *GenerationConfig) SetMaxOutputTokens(x int32) { c.MaxOutputTokens = &x }
|
||||
|
||||
// SetTemperature sets the Temperature field.
|
||||
func (c *GenerationConfig) SetTemperature(x float32) { c.Temperature = &x }
|
||||
|
||||
// SetTopP sets the TopP field.
|
||||
func (c *GenerationConfig) SetTopP(x float32) { c.TopP = &x }
|
||||
|
||||
// SetTopK sets the TopK field.
|
||||
func (c *GenerationConfig) SetTopK(x int32) { c.TopK = &x }
|
||||
|
||||
// FunctionCalls return all the FunctionCall parts in the candidate.
|
||||
func (c *Candidate) FunctionCalls() []FunctionCall {
|
||||
if c.Content == nil {
|
||||
return nil
|
||||
}
|
||||
var fcs []FunctionCall
|
||||
for _, p := range c.Content.Parts {
|
||||
if fc, ok := p.(FunctionCall); ok {
|
||||
fcs = append(fcs, fc)
|
||||
}
|
||||
}
|
||||
return fcs
|
||||
}
|
||||
|
||||
// NewUserContent returns a *Content with a "user" role set and one or more
|
||||
// parts.
|
||||
func NewUserContent(parts ...Part) *Content {
|
||||
content := &Content{Role: roleUser, Parts: []Part{}}
|
||||
for _, part := range parts {
|
||||
content.Parts = append(content.Parts, part)
|
||||
}
|
||||
return content
|
||||
}
|
||||
38
vendor/github.com/google/generative-ai-go/genai/debug.go
generated
vendored
Normal file
38
vendor/github.com/google/generative-ai-go/genai/debug.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file contains debugging support functions.
|
||||
|
||||
package genai
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// printRequests controls whether request protobufs are written to stderr.
|
||||
var printRequests = false
|
||||
|
||||
func debugPrint(m proto.Message) {
|
||||
if !printRequests {
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "--------")
|
||||
fmt.Fprintf(os.Stderr, "%T\n", m)
|
||||
fmt.Fprint(os.Stderr, prototext.Format(m))
|
||||
fmt.Fprintln(os.Stderr, "^^^^^^^^")
|
||||
}
|
||||
65
vendor/github.com/google/generative-ai-go/genai/doc.go
generated
vendored
Normal file
65
vendor/github.com/google/generative-ai-go/genai/doc.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// # [Deprecated] Google AI Go SDK for the Gemini API
|
||||
//
|
||||
// With Gemini 2.0, we took the chance to create a single unified SDK for all developers who want
|
||||
// to use Google's GenAI models (Gemini, Veo, Imagen, etc). As part of that process, we took all of
|
||||
// the feedback from this SDK and what developers like about other SDKs in the ecosystem to create
|
||||
// the [Google Gen AI SDK].
|
||||
//
|
||||
// The Gemini API docs are fully updated to show examples of the new Google Gen AI SDK:
|
||||
// [Get started].
|
||||
//
|
||||
// We know how disruptive an SDK change can be and don't take this change lightly, but our goal
|
||||
// is to create an extremely simple and clear path for developers to build with our models so it
|
||||
// felt necessary to make this change.
|
||||
//
|
||||
// Thank you for building with Gemini and [let us know] if you need any help!
|
||||
//
|
||||
// Please be advised that this repository is now considered legacy. For the latest features, performance
|
||||
// improvements, and active development, we strongly recommend migrating to the official
|
||||
// [Google Generative AI SDK for Go].
|
||||
//
|
||||
// Support Plan for this Repository:
|
||||
//
|
||||
// - Limited Maintenance: Development is now restricted to critical bug fixes only. No new features will be added.
|
||||
// - Purpose: This limited support aims to provide stability for users while they transition to the new SDK.
|
||||
// - End-of-Life Date: All support for this repository (including bug fixes) will permanently end on August 31st, 2025.
|
||||
//
|
||||
// We encourage all users to begin planning their migration to the [Google Generative AI SDK] to ensure
|
||||
// continued access to the latest capabilities and support.
|
||||
//
|
||||
//
|
||||
// # Getting started
|
||||
//
|
||||
// NOTE: This client uses the v1beta version of the API.
|
||||
//
|
||||
// Reading the [examples] is the best way to learn how to use this package.
|
||||
//
|
||||
// # Authorization
|
||||
//
|
||||
// You will need an API key to use the service.
|
||||
// See the [setup tutorial] for details.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// [examples]: https://pkg.go.dev/github.com/google/generative-ai-go/genai#pkg-examples
|
||||
// [setup tutorial]: https://ai.google.dev/tutorials/setup
|
||||
// [Google Gen AI SDK]: https://github.com/googleapis/go-genai
|
||||
// [Get started]: https://ai.google.dev/gemini-api/docs/quickstart?lang=go
|
||||
// [let us know]: https://discuss.ai.google.dev/c/gemini-api/4
|
||||
// [Google Generative AI SDK for Go]: https://github.com/googleapis/go-genai
|
||||
// [Google Generative AI SDK]: https://github.com/googleapis/go-genai
|
||||
package genai
|
||||
126
vendor/github.com/google/generative-ai-go/genai/embed.go
generated
vendored
Normal file
126
vendor/github.com/google/generative-ai-go/genai/embed.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package genai
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pb "cloud.google.com/go/ai/generativelanguage/apiv1beta/generativelanguagepb"
|
||||
)
|
||||
|
||||
// EmbeddingModel creates a new instance of the named embedding model.
|
||||
// Example name: "embedding-001" or "models/embedding-001".
|
||||
func (c *Client) EmbeddingModel(name string) *EmbeddingModel {
|
||||
return &EmbeddingModel{
|
||||
c: c,
|
||||
name: name,
|
||||
fullName: fullModelName(name),
|
||||
}
|
||||
}
|
||||
|
||||
// EmbeddingModel is a model that computes embeddings.
|
||||
// Create one with [Client.EmbeddingModel].
|
||||
type EmbeddingModel struct {
|
||||
c *Client
|
||||
name string
|
||||
fullName string
|
||||
// TaskType describes how the embedding will be used.
|
||||
TaskType TaskType
|
||||
}
|
||||
|
||||
// Name returns the name of the EmbeddingModel.
|
||||
func (m *EmbeddingModel) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
// EmbedContent returns an embedding for the list of parts.
|
||||
func (m *EmbeddingModel) EmbedContent(ctx context.Context, parts ...Part) (*EmbedContentResponse, error) {
|
||||
return m.EmbedContentWithTitle(ctx, "", parts...)
|
||||
}
|
||||
|
||||
// EmbedContentWithTitle returns an embedding for the list of parts.
|
||||
// If the given title is non-empty, it is passed to the model and
|
||||
// the task type is set to TaskTypeRetrievalDocument.
|
||||
func (m *EmbeddingModel) EmbedContentWithTitle(ctx context.Context, title string, parts ...Part) (*EmbedContentResponse, error) {
|
||||
req := newEmbedContentRequest(m.fullName, m.TaskType, title, parts)
|
||||
res, err := m.c.gc.EmbedContent(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (EmbedContentResponse{}).fromProto(res), nil
|
||||
}
|
||||
|
||||
func newEmbedContentRequest(model string, tt TaskType, title string, parts []Part) *pb.EmbedContentRequest {
|
||||
req := &pb.EmbedContentRequest{
|
||||
Model: model,
|
||||
Content: NewUserContent(parts...).toProto(),
|
||||
}
|
||||
// A non-empty title overrides the task type.
|
||||
if title != "" {
|
||||
req.Title = &title
|
||||
tt = TaskTypeRetrievalDocument
|
||||
}
|
||||
if tt != TaskTypeUnspecified {
|
||||
taskType := pb.TaskType(tt)
|
||||
req.TaskType = &taskType
|
||||
}
|
||||
debugPrint(req)
|
||||
return req
|
||||
}
|
||||
|
||||
// An EmbeddingBatch holds a collection of embedding requests.
|
||||
type EmbeddingBatch struct {
|
||||
tt TaskType
|
||||
req *pb.BatchEmbedContentsRequest
|
||||
}
|
||||
|
||||
// NewBatch returns a new, empty EmbeddingBatch with the same TaskType as the model.
|
||||
// Make multiple calls to [EmbeddingBatch.AddContent] or [EmbeddingBatch.AddContentWithTitle].
|
||||
// Then pass the EmbeddingBatch to [EmbeddingModel.BatchEmbedContents] to get
|
||||
// all the embeddings in a single call to the model.
|
||||
func (m *EmbeddingModel) NewBatch() *EmbeddingBatch {
|
||||
return &EmbeddingBatch{
|
||||
tt: m.TaskType,
|
||||
req: &pb.BatchEmbedContentsRequest{
|
||||
Model: m.fullName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AddContent adds a content to the batch.
|
||||
func (b *EmbeddingBatch) AddContent(parts ...Part) *EmbeddingBatch {
|
||||
b.AddContentWithTitle("", parts...)
|
||||
return b
|
||||
}
|
||||
|
||||
// AddContent adds a content to the batch with a title.
|
||||
func (b *EmbeddingBatch) AddContentWithTitle(title string, parts ...Part) *EmbeddingBatch {
|
||||
b.req.Requests = append(b.req.Requests, newEmbedContentRequest(b.req.Model, b.tt, title, parts))
|
||||
return b
|
||||
}
|
||||
|
||||
// BatchEmbedContents returns the embeddings for all the contents in the batch.
|
||||
func (m *EmbeddingModel) BatchEmbedContents(ctx context.Context, b *EmbeddingBatch) (*BatchEmbedContentsResponse, error) {
|
||||
res, err := m.c.gc.BatchEmbedContents(ctx, b.req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (BatchEmbedContentsResponse{}).fromProto(res), nil
|
||||
}
|
||||
|
||||
// Info returns information about the model.
|
||||
func (m *EmbeddingModel) Info(ctx context.Context) (*ModelInfo, error) {
|
||||
return m.c.modelInfo(ctx, m.fullName)
|
||||
}
|
||||
189
vendor/github.com/google/generative-ai-go/genai/files.go
generated
vendored
Normal file
189
vendor/github.com/google/generative-ai-go/genai/files.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package genai
|
||||
|
||||
//go:generate ../devtools/generate_discovery_client.sh
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
gl "cloud.google.com/go/ai/generativelanguage/apiv1beta"
|
||||
pb "cloud.google.com/go/ai/generativelanguage/apiv1beta/generativelanguagepb"
|
||||
gld "github.com/google/generative-ai-go/genai/internal/generativelanguage/v1beta" // discovery client
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
// UploadFileOptions are options for [Client.UploadFile].
|
||||
type UploadFileOptions struct {
|
||||
// A more readable name for the file.
|
||||
DisplayName string
|
||||
|
||||
// The IANA standard MIME type of the file. It will be stored with the file as metadata.
|
||||
// If omitted, the service will try to infer it. You may instead wish to use
|
||||
// [http.DetectContentType].
|
||||
// The supported MIME types are documented on [this page].
|
||||
//
|
||||
// [this page]: https://ai.google.dev/gemini-api/docs/document-processing?lang=go#technical-details
|
||||
MIMEType string
|
||||
}
|
||||
|
||||
// UploadFile copies the contents of the given io.Reader to file storage associated
|
||||
// with the service, and returns information about the resulting file.
|
||||
//
|
||||
// The name is a relatively short, unique identifier for the file (rather than a typical
|
||||
// filename).
|
||||
// Typically it should be left empty, in which case a unique name will be generated.
|
||||
// Otherwise, it can contain up to 40 characters that are lowercase
|
||||
// alphanumeric or dashes (-), not starting or ending with a dash.
|
||||
// To generate your own unique names, consider a cryptographic hash algorithm like SHA-1.
|
||||
// The string "files/" is prepended to the name if it does not contain a '/'.
|
||||
//
|
||||
// Use the returned file's URI field with a [FileData] Part to use it for generation.
|
||||
//
|
||||
// It is an error to upload a file that already exists.
|
||||
func (c *Client) UploadFile(ctx context.Context, name string, r io.Reader, opts *UploadFileOptions) (*File, error) {
|
||||
if name != "" {
|
||||
name = userNameToServiceName(name)
|
||||
}
|
||||
req := &gld.CreateFileRequest{
|
||||
File: &gld.File{Name: name},
|
||||
}
|
||||
if opts != nil && opts.DisplayName != "" {
|
||||
req.File.DisplayName = opts.DisplayName
|
||||
}
|
||||
call := c.ds.Media.Upload(req)
|
||||
var mopts []googleapi.MediaOption
|
||||
if opts != nil && opts.MIMEType != "" {
|
||||
mopts = append(mopts, googleapi.ContentType(opts.MIMEType))
|
||||
}
|
||||
call.Media(r, mopts...)
|
||||
res, err := call.Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Don't return the result, because it contains a file as represented by the
|
||||
// discovery client and we'd have to write code to convert it to this package's
|
||||
// File type.
|
||||
// Instead, make a GetFile call to get the proto file, which our generated code can convert.
|
||||
return c.GetFile(ctx, res.File.Name)
|
||||
}
|
||||
|
||||
// UploadFileFromPath is a convenience method wrapping [UploadFile]. It takes
|
||||
// a path to read the file from, and uses a default auto-generated ID for the
|
||||
// uploaded file.
|
||||
func (c *Client) UploadFileFromPath(ctx context.Context, path string, opts *UploadFileOptions) (*File, error) {
|
||||
osf, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer osf.Close()
|
||||
|
||||
return c.UploadFile(ctx, "", osf, opts)
|
||||
}
|
||||
|
||||
// GetFile returns the named file.
|
||||
func (c *Client) GetFile(ctx context.Context, name string) (*File, error) {
|
||||
req := &pb.GetFileRequest{Name: userNameToServiceName(name)}
|
||||
debugPrint(req)
|
||||
pf, err := c.fc.GetFile(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (File{}).fromProto(pf), nil
|
||||
}
|
||||
|
||||
// DeleteFile deletes the file with the given name.
|
||||
// It is an error to delete a file that does not exist.
|
||||
func (c *Client) DeleteFile(ctx context.Context, name string) error {
|
||||
req := &pb.DeleteFileRequest{Name: userNameToServiceName(name)}
|
||||
debugPrint(req)
|
||||
return c.fc.DeleteFile(ctx, req)
|
||||
}
|
||||
|
||||
// userNameToServiceName converts a name supplied by the user to a name required by the service.
|
||||
func userNameToServiceName(name string) string {
|
||||
if strings.ContainsRune(name, '/') {
|
||||
return name
|
||||
}
|
||||
return "files/" + name
|
||||
}
|
||||
|
||||
// ListFiles returns an iterator over the uploaded files.
|
||||
func (c *Client) ListFiles(ctx context.Context) *FileIterator {
|
||||
return &FileIterator{
|
||||
it: c.fc.ListFiles(ctx, &pb.ListFilesRequest{}),
|
||||
}
|
||||
}
|
||||
|
||||
// A FileIterator iterates over Files.
|
||||
type FileIterator struct {
|
||||
it *gl.FileIterator
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *FileIterator) Next() (*File, error) {
|
||||
m, err := it.it.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (File{}).fromProto(m), nil
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *FileIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.it.PageInfo()
|
||||
}
|
||||
|
||||
// FileMetadata holds metadata about a file.
|
||||
type FileMetadata struct {
|
||||
// Set if the file contains video.
|
||||
Video *VideoMetadata
|
||||
}
|
||||
|
||||
func populateFileTo(p *pb.File, f *File) {
|
||||
p.Metadata = nil
|
||||
if f == nil || f.Metadata == nil {
|
||||
return
|
||||
}
|
||||
if f.Metadata.Video != nil {
|
||||
p.Metadata = &pb.File_VideoMetadata{
|
||||
VideoMetadata: f.Metadata.Video.toProto(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func populateFileFrom(f *File, p *pb.File) {
|
||||
f.Metadata = nil
|
||||
if p == nil || p.Metadata == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if p.Metadata != nil {
|
||||
switch m := p.Metadata.(type) {
|
||||
case *pb.File_VideoMetadata:
|
||||
f.Metadata = &FileMetadata{
|
||||
Video: (VideoMetadata{}).fromProto(m.VideoMetadata),
|
||||
}
|
||||
default:
|
||||
// ignore other types
|
||||
// TODO: signal a problem
|
||||
}
|
||||
}
|
||||
}
|
||||
32
vendor/github.com/google/generative-ai-go/genai/generate.sh
generated
vendored
Normal file
32
vendor/github.com/google/generative-ai-go/genai/generate.sh
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
# Copyright 2023 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
version=$(awk '$1 == "cloud.google.com/go/ai" {print $2}' ../go.mod)
|
||||
|
||||
if [[ $version = '' ]]; then
|
||||
echo >&2 "could not get version of cloud.google.com/go/ai from ../go.mod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dir=~/go/pkg/mod/cloud.google.com/go/ai@$version/generativelanguage/apiv1beta/generativelanguagepb
|
||||
|
||||
if [[ ! -d $dir ]]; then
|
||||
echo >&2 "$dir does not exist or is not a directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "generating from $dir"
|
||||
protoveneer -license license.txt config.yaml $dir
|
||||
|
||||
1785
vendor/github.com/google/generative-ai-go/genai/generativelanguagepb_veneer.gen.go
generated
vendored
Normal file
1785
vendor/github.com/google/generative-ai-go/genai/generativelanguagepb_veneer.gen.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4352
vendor/github.com/google/generative-ai-go/genai/internal/generativelanguage/v1beta/generativelanguage-api.json
generated
vendored
Normal file
4352
vendor/github.com/google/generative-ai-go/genai/internal/generativelanguage/v1beta/generativelanguage-api.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
10941
vendor/github.com/google/generative-ai-go/genai/internal/generativelanguage/v1beta/generativelanguage-gen.go
generated
vendored
Normal file
10941
vendor/github.com/google/generative-ai-go/genai/internal/generativelanguage/v1beta/generativelanguage-gen.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
12
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/README
generated
vendored
Normal file
12
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/README
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
This directory was copied from github.com/googleapis/google-api-go-client/internal/gensupport.
|
||||
It is needed for the discovery client in ../generativelanguage.
|
||||
|
||||
To update, first clone github.com/googleapis/google-api-go-client
|
||||
into a directory we will call DIR below.
|
||||
Then, from the repo root:
|
||||
```
|
||||
rm genai/internal/gensupport/*.go
|
||||
cp $DIR/internal/gensupport/*.go genai/internal/gensupport
|
||||
```
|
||||
Then edit the params.go and resumable.go files to replace the reference to `internal.Version`
|
||||
with the literal string from $DIR/internal/version.go, and remove the import of `internal`.
|
||||
89
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/buffer.go
generated
vendored
Normal file
89
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/buffer.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gensupport
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
// MediaBuffer buffers data from an io.Reader to support uploading media in
|
||||
// retryable chunks. It should be created with NewMediaBuffer.
|
||||
type MediaBuffer struct {
|
||||
media io.Reader
|
||||
|
||||
chunk []byte // The current chunk which is pending upload. The capacity is the chunk size.
|
||||
err error // Any error generated when populating chunk by reading media.
|
||||
|
||||
// The absolute position of chunk in the underlying media.
|
||||
off int64
|
||||
}
|
||||
|
||||
// NewMediaBuffer initializes a MediaBuffer.
|
||||
func NewMediaBuffer(media io.Reader, chunkSize int) *MediaBuffer {
|
||||
return &MediaBuffer{media: media, chunk: make([]byte, 0, chunkSize)}
|
||||
}
|
||||
|
||||
// Chunk returns the current buffered chunk, the offset in the underlying media
|
||||
// from which the chunk is drawn, and the size of the chunk.
|
||||
// Successive calls to Chunk return the same chunk between calls to Next.
|
||||
func (mb *MediaBuffer) Chunk() (chunk io.Reader, off int64, size int, err error) {
|
||||
// There may already be data in chunk if Next has not been called since the previous call to Chunk.
|
||||
if mb.err == nil && len(mb.chunk) == 0 {
|
||||
mb.err = mb.loadChunk()
|
||||
}
|
||||
return bytes.NewReader(mb.chunk), mb.off, len(mb.chunk), mb.err
|
||||
}
|
||||
|
||||
// loadChunk will read from media into chunk, up to the capacity of chunk.
|
||||
func (mb *MediaBuffer) loadChunk() error {
|
||||
bufSize := cap(mb.chunk)
|
||||
mb.chunk = mb.chunk[:bufSize]
|
||||
|
||||
read := 0
|
||||
var err error
|
||||
for err == nil && read < bufSize {
|
||||
var n int
|
||||
n, err = mb.media.Read(mb.chunk[read:])
|
||||
read += n
|
||||
}
|
||||
mb.chunk = mb.chunk[:read]
|
||||
return err
|
||||
}
|
||||
|
||||
// Next advances to the next chunk, which will be returned by the next call to Chunk.
|
||||
// Calls to Next without a corresponding prior call to Chunk will have no effect.
|
||||
func (mb *MediaBuffer) Next() {
|
||||
mb.off += int64(len(mb.chunk))
|
||||
mb.chunk = mb.chunk[0:0]
|
||||
}
|
||||
|
||||
type readerTyper struct {
|
||||
io.Reader
|
||||
googleapi.ContentTyper
|
||||
}
|
||||
|
||||
// ReaderAtToReader adapts a ReaderAt to be used as a Reader.
|
||||
// If ra implements googleapi.ContentTyper, then the returned reader
|
||||
// will also implement googleapi.ContentTyper, delegating to ra.
|
||||
func ReaderAtToReader(ra io.ReaderAt, size int64) io.Reader {
|
||||
r := io.NewSectionReader(ra, 0, size)
|
||||
if typer, ok := ra.(googleapi.ContentTyper); ok {
|
||||
return readerTyper{r, typer}
|
||||
}
|
||||
return r
|
||||
}
|
||||
20
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/doc.go
generated
vendored
Normal file
20
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/doc.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package gensupport is an internal implementation detail used by code
|
||||
// generated by the google-api-go-generator tool.
|
||||
//
|
||||
// This package may be modified at any time without regard for backwards
|
||||
// compatibility. It should not be used directly by API users.
|
||||
package gensupport
|
||||
34
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/error.go
generated
vendored
Normal file
34
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/error.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gensupport
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/googleapis/gax-go/v2/apierror"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
// WrapError creates an [apierror.APIError] from err, wraps it in err, and
|
||||
// returns err. If err is not a [googleapi.Error] (or a
|
||||
// [google.golang.org/grpc/status.Status]), it returns err without modification.
|
||||
func WrapError(err error) error {
|
||||
var herr *googleapi.Error
|
||||
apiError, ok := apierror.ParseError(err, false)
|
||||
if ok && errors.As(err, &herr) {
|
||||
herr.Wrap(apiError)
|
||||
}
|
||||
return err
|
||||
}
|
||||
246
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/json.go
generated
vendored
Normal file
246
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/json.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gensupport
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MarshalJSON returns a JSON encoding of schema containing only selected fields.
|
||||
// A field is selected if any of the following is true:
|
||||
// - it has a non-empty value
|
||||
// - its field name is present in forceSendFields and it is not a nil pointer or nil interface
|
||||
// - its field name is present in nullFields.
|
||||
//
|
||||
// The JSON key for each selected field is taken from the field's json: struct tag.
|
||||
func MarshalJSON(schema interface{}, forceSendFields, nullFields []string) ([]byte, error) {
|
||||
if len(forceSendFields) == 0 && len(nullFields) == 0 {
|
||||
return json.Marshal(schema)
|
||||
}
|
||||
|
||||
mustInclude := make(map[string]bool)
|
||||
for _, f := range forceSendFields {
|
||||
mustInclude[f] = true
|
||||
}
|
||||
useNull := make(map[string]bool)
|
||||
useNullMaps := make(map[string]map[string]bool)
|
||||
for _, nf := range nullFields {
|
||||
parts := strings.SplitN(nf, ".", 2)
|
||||
field := parts[0]
|
||||
if len(parts) == 1 {
|
||||
useNull[field] = true
|
||||
} else {
|
||||
if useNullMaps[field] == nil {
|
||||
useNullMaps[field] = map[string]bool{}
|
||||
}
|
||||
useNullMaps[field][parts[1]] = true
|
||||
}
|
||||
}
|
||||
|
||||
dataMap, err := schemaToMap(schema, mustInclude, useNull, useNullMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(dataMap)
|
||||
}
|
||||
|
||||
func schemaToMap(schema interface{}, mustInclude, useNull map[string]bool, useNullMaps map[string]map[string]bool) (map[string]interface{}, error) {
|
||||
m := make(map[string]interface{})
|
||||
s := reflect.ValueOf(schema)
|
||||
st := s.Type()
|
||||
|
||||
for i := 0; i < s.NumField(); i++ {
|
||||
jsonTag := st.Field(i).Tag.Get("json")
|
||||
if jsonTag == "" {
|
||||
continue
|
||||
}
|
||||
tag, err := parseJSONTag(jsonTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tag.ignore {
|
||||
continue
|
||||
}
|
||||
|
||||
v := s.Field(i)
|
||||
f := st.Field(i)
|
||||
|
||||
if useNull[f.Name] {
|
||||
if !isEmptyValue(v) {
|
||||
return nil, fmt.Errorf("field %q in NullFields has non-empty value", f.Name)
|
||||
}
|
||||
m[tag.apiName] = nil
|
||||
continue
|
||||
}
|
||||
|
||||
if !includeField(v, f, mustInclude) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If map fields are explicitly set to null, use a map[string]interface{}.
|
||||
if f.Type.Kind() == reflect.Map && useNullMaps[f.Name] != nil {
|
||||
ms, ok := v.Interface().(map[string]string)
|
||||
if !ok {
|
||||
mi, err := initMapSlow(v, f.Name, useNullMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[tag.apiName] = mi
|
||||
continue
|
||||
}
|
||||
mi := map[string]interface{}{}
|
||||
for k, v := range ms {
|
||||
mi[k] = v
|
||||
}
|
||||
for k := range useNullMaps[f.Name] {
|
||||
mi[k] = nil
|
||||
}
|
||||
m[tag.apiName] = mi
|
||||
continue
|
||||
}
|
||||
|
||||
// nil maps are treated as empty maps.
|
||||
if f.Type.Kind() == reflect.Map && v.IsNil() {
|
||||
m[tag.apiName] = map[string]string{}
|
||||
continue
|
||||
}
|
||||
|
||||
// nil slices are treated as empty slices.
|
||||
if f.Type.Kind() == reflect.Slice && v.IsNil() {
|
||||
m[tag.apiName] = []bool{}
|
||||
continue
|
||||
}
|
||||
|
||||
if tag.stringFormat {
|
||||
m[tag.apiName] = formatAsString(v, f.Type.Kind())
|
||||
} else {
|
||||
m[tag.apiName] = v.Interface()
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// initMapSlow uses reflection to build up a map object. This is slower than
|
||||
// the default behavior so it should be used only as a fallback.
|
||||
func initMapSlow(rv reflect.Value, fieldName string, useNullMaps map[string]map[string]bool) (map[string]interface{}, error) {
|
||||
mi := map[string]interface{}{}
|
||||
iter := rv.MapRange()
|
||||
for iter.Next() {
|
||||
k, ok := iter.Key().Interface().(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("field %q has keys in NullFields but is not a map[string]any", fieldName)
|
||||
}
|
||||
v := iter.Value().Interface()
|
||||
mi[k] = v
|
||||
}
|
||||
for k := range useNullMaps[fieldName] {
|
||||
mi[k] = nil
|
||||
}
|
||||
return mi, nil
|
||||
}
|
||||
|
||||
// formatAsString returns a string representation of v, dereferencing it first if possible.
|
||||
func formatAsString(v reflect.Value, kind reflect.Kind) string {
|
||||
if kind == reflect.Ptr && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", v.Interface())
|
||||
}
|
||||
|
||||
// jsonTag represents a restricted version of the struct tag format used by encoding/json.
|
||||
// It is used to describe the JSON encoding of fields in a Schema struct.
|
||||
type jsonTag struct {
|
||||
apiName string
|
||||
stringFormat bool
|
||||
ignore bool
|
||||
}
|
||||
|
||||
// parseJSONTag parses a restricted version of the struct tag format used by encoding/json.
|
||||
// The format of the tag must match that generated by the Schema.writeSchemaStruct method
|
||||
// in the api generator.
|
||||
func parseJSONTag(val string) (jsonTag, error) {
|
||||
if val == "-" {
|
||||
return jsonTag{ignore: true}, nil
|
||||
}
|
||||
|
||||
var tag jsonTag
|
||||
|
||||
i := strings.Index(val, ",")
|
||||
if i == -1 || val[:i] == "" {
|
||||
return tag, fmt.Errorf("malformed json tag: %s", val)
|
||||
}
|
||||
|
||||
tag = jsonTag{
|
||||
apiName: val[:i],
|
||||
}
|
||||
|
||||
switch val[i+1:] {
|
||||
case "omitempty":
|
||||
case "omitempty,string":
|
||||
tag.stringFormat = true
|
||||
default:
|
||||
return tag, fmt.Errorf("malformed json tag: %s", val)
|
||||
}
|
||||
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
// Reports whether the struct field "f" with value "v" should be included in JSON output.
|
||||
func includeField(v reflect.Value, f reflect.StructField, mustInclude map[string]bool) bool {
|
||||
// The regular JSON encoding of a nil pointer is "null", which means "delete this field".
|
||||
// Therefore, we could enable field deletion by honoring pointer fields' presence in the mustInclude set.
|
||||
// However, many fields are not pointers, so there would be no way to delete these fields.
|
||||
// Rather than partially supporting field deletion, we ignore mustInclude for nil pointer fields.
|
||||
// Deletion will be handled by a separate mechanism.
|
||||
if f.Type.Kind() == reflect.Ptr && v.IsNil() {
|
||||
return false
|
||||
}
|
||||
|
||||
// The "any" type is represented as an interface{}. If this interface
|
||||
// is nil, there is no reasonable representation to send. We ignore
|
||||
// these fields, for the same reasons as given above for pointers.
|
||||
if f.Type.Kind() == reflect.Interface && v.IsNil() {
|
||||
return false
|
||||
}
|
||||
|
||||
return mustInclude[f.Name] || !isEmptyValue(v)
|
||||
}
|
||||
|
||||
// isEmptyValue reports whether v is the empty value for its type. This
|
||||
// implementation is based on that of the encoding/json package, but its
|
||||
// correctness does not depend on it being identical. What's important is that
|
||||
// this function return false in situations where v should not be sent as part
|
||||
// of a PATCH operation.
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
57
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/jsonfloat.go
generated
vendored
Normal file
57
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/jsonfloat.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gensupport
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// JSONFloat64 is a float64 that supports proper unmarshaling of special float
|
||||
// values in JSON, according to
|
||||
// https://developers.google.com/protocol-buffers/docs/proto3#json. Although
|
||||
// that is a proto-to-JSON spec, it applies to all Google APIs.
|
||||
//
|
||||
// The jsonpb package
|
||||
// (https://github.com/golang/protobuf/blob/master/jsonpb/jsonpb.go) has
|
||||
// similar functionality, but only for direct translation from proto messages
|
||||
// to JSON.
|
||||
type JSONFloat64 float64
|
||||
|
||||
func (f *JSONFloat64) UnmarshalJSON(data []byte) error {
|
||||
var ff float64
|
||||
if err := json.Unmarshal(data, &ff); err == nil {
|
||||
*f = JSONFloat64(ff)
|
||||
return nil
|
||||
}
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err == nil {
|
||||
switch s {
|
||||
case "NaN":
|
||||
ff = math.NaN()
|
||||
case "Infinity":
|
||||
ff = math.Inf(1)
|
||||
case "-Infinity":
|
||||
ff = math.Inf(-1)
|
||||
default:
|
||||
return fmt.Errorf("google.golang.org/api/internal: bad float string %q", s)
|
||||
}
|
||||
*f = JSONFloat64(ff)
|
||||
return nil
|
||||
}
|
||||
return errors.New("google.golang.org/api/internal: data not float or string")
|
||||
}
|
||||
320
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/media.go
generated
vendored
Normal file
320
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/media.go
generated
vendored
Normal file
@@ -0,0 +1,320 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gensupport
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
type typeReader struct {
|
||||
io.Reader
|
||||
typ string
|
||||
}
|
||||
|
||||
// multipartReader combines the contents of multiple readers to create a multipart/related HTTP body.
|
||||
// Close must be called if reads from the multipartReader are abandoned before reaching EOF.
|
||||
type multipartReader struct {
|
||||
pr *io.PipeReader
|
||||
ctype string
|
||||
mu sync.Mutex
|
||||
pipeOpen bool
|
||||
}
|
||||
|
||||
// boundary optionally specifies the MIME boundary
|
||||
func newMultipartReader(parts []typeReader, boundary string) *multipartReader {
|
||||
mp := &multipartReader{pipeOpen: true}
|
||||
var pw *io.PipeWriter
|
||||
mp.pr, pw = io.Pipe()
|
||||
mpw := multipart.NewWriter(pw)
|
||||
if boundary != "" {
|
||||
mpw.SetBoundary(boundary)
|
||||
}
|
||||
mp.ctype = "multipart/related; boundary=" + mpw.Boundary()
|
||||
go func() {
|
||||
for _, part := range parts {
|
||||
w, err := mpw.CreatePart(typeHeader(part.typ))
|
||||
if err != nil {
|
||||
mpw.Close()
|
||||
pw.CloseWithError(fmt.Errorf("googleapi: CreatePart failed: %v", err))
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(w, part.Reader)
|
||||
if err != nil {
|
||||
mpw.Close()
|
||||
pw.CloseWithError(fmt.Errorf("googleapi: Copy failed: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mpw.Close()
|
||||
pw.Close()
|
||||
}()
|
||||
return mp
|
||||
}
|
||||
|
||||
func (mp *multipartReader) Read(data []byte) (n int, err error) {
|
||||
return mp.pr.Read(data)
|
||||
}
|
||||
|
||||
func (mp *multipartReader) Close() error {
|
||||
mp.mu.Lock()
|
||||
if !mp.pipeOpen {
|
||||
mp.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
mp.pipeOpen = false
|
||||
mp.mu.Unlock()
|
||||
return mp.pr.Close()
|
||||
}
|
||||
|
||||
// CombineBodyMedia combines a json body with media content to create a multipart/related HTTP body.
|
||||
// It returns a ReadCloser containing the combined body, and the overall "multipart/related" content type, with random boundary.
|
||||
//
|
||||
// The caller must call Close on the returned ReadCloser if reads are abandoned before reaching EOF.
|
||||
func CombineBodyMedia(body io.Reader, bodyContentType string, media io.Reader, mediaContentType string) (io.ReadCloser, string) {
|
||||
return combineBodyMedia(body, bodyContentType, media, mediaContentType, "")
|
||||
}
|
||||
|
||||
// combineBodyMedia is CombineBodyMedia but with an optional mimeBoundary field.
|
||||
func combineBodyMedia(body io.Reader, bodyContentType string, media io.Reader, mediaContentType, mimeBoundary string) (io.ReadCloser, string) {
|
||||
mp := newMultipartReader([]typeReader{
|
||||
{body, bodyContentType},
|
||||
{media, mediaContentType},
|
||||
}, mimeBoundary)
|
||||
return mp, mp.ctype
|
||||
}
|
||||
|
||||
func typeHeader(contentType string) textproto.MIMEHeader {
|
||||
h := make(textproto.MIMEHeader)
|
||||
if contentType != "" {
|
||||
h.Set("Content-Type", contentType)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// PrepareUpload determines whether the data in the supplied reader should be
|
||||
// uploaded in a single request, or in sequential chunks.
|
||||
// chunkSize is the size of the chunk that media should be split into.
|
||||
//
|
||||
// If chunkSize is zero, media is returned as the first value, and the other
|
||||
// two return values are nil, true.
|
||||
//
|
||||
// Otherwise, a MediaBuffer is returned, along with a bool indicating whether the
|
||||
// contents of media fit in a single chunk.
|
||||
//
|
||||
// After PrepareUpload has been called, media should no longer be used: the
|
||||
// media content should be accessed via one of the return values.
|
||||
func PrepareUpload(media io.Reader, chunkSize int) (r io.Reader, mb *MediaBuffer, singleChunk bool) {
|
||||
if chunkSize == 0 { // do not chunk
|
||||
return media, nil, true
|
||||
}
|
||||
mb = NewMediaBuffer(media, chunkSize)
|
||||
_, _, _, err := mb.Chunk()
|
||||
// If err is io.EOF, we can upload this in a single request. Otherwise, err is
|
||||
// either nil or a non-EOF error. If it is the latter, then the next call to
|
||||
// mb.Chunk will return the same error. Returning a MediaBuffer ensures that this
|
||||
// error will be handled at some point.
|
||||
return nil, mb, err == io.EOF
|
||||
}
|
||||
|
||||
// MediaInfo holds information for media uploads. It is intended for use by generated
|
||||
// code only.
|
||||
type MediaInfo struct {
|
||||
// At most one of Media and MediaBuffer will be set.
|
||||
media io.Reader
|
||||
buffer *MediaBuffer
|
||||
singleChunk bool
|
||||
mType string
|
||||
size int64 // mediaSize, if known. Used only for calls to progressUpdater_.
|
||||
progressUpdater googleapi.ProgressUpdater
|
||||
chunkRetryDeadline time.Duration
|
||||
}
|
||||
|
||||
// NewInfoFromMedia should be invoked from the Media method of a call. It returns a
|
||||
// MediaInfo populated with chunk size and content type, and a reader or MediaBuffer
|
||||
// if needed.
|
||||
func NewInfoFromMedia(r io.Reader, options []googleapi.MediaOption) *MediaInfo {
|
||||
mi := &MediaInfo{}
|
||||
opts := googleapi.ProcessMediaOptions(options)
|
||||
if !opts.ForceEmptyContentType {
|
||||
mi.mType = opts.ContentType
|
||||
if mi.mType == "" {
|
||||
r, mi.mType = gax.DetermineContentType(r)
|
||||
}
|
||||
}
|
||||
mi.chunkRetryDeadline = opts.ChunkRetryDeadline
|
||||
mi.media, mi.buffer, mi.singleChunk = PrepareUpload(r, opts.ChunkSize)
|
||||
return mi
|
||||
}
|
||||
|
||||
// NewInfoFromResumableMedia should be invoked from the ResumableMedia method of a
|
||||
// call. It returns a MediaInfo using the given reader, size and media type.
|
||||
func NewInfoFromResumableMedia(r io.ReaderAt, size int64, mediaType string) *MediaInfo {
|
||||
rdr := ReaderAtToReader(r, size)
|
||||
mType := mediaType
|
||||
if mType == "" {
|
||||
rdr, mType = gax.DetermineContentType(rdr)
|
||||
}
|
||||
|
||||
return &MediaInfo{
|
||||
size: size,
|
||||
mType: mType,
|
||||
buffer: NewMediaBuffer(rdr, googleapi.DefaultUploadChunkSize),
|
||||
media: nil,
|
||||
singleChunk: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SetProgressUpdater sets the progress updater for the media info.
|
||||
func (mi *MediaInfo) SetProgressUpdater(pu googleapi.ProgressUpdater) {
|
||||
if mi != nil {
|
||||
mi.progressUpdater = pu
|
||||
}
|
||||
}
|
||||
|
||||
// UploadType determines the type of upload: a single request, or a resumable
|
||||
// series of requests.
|
||||
func (mi *MediaInfo) UploadType() string {
|
||||
if mi.singleChunk {
|
||||
return "multipart"
|
||||
}
|
||||
return "resumable"
|
||||
}
|
||||
|
||||
// UploadRequest sets up an HTTP request for media upload. It adds headers
|
||||
// as necessary, and returns a replacement for the body and a function for http.Request.GetBody.
|
||||
func (mi *MediaInfo) UploadRequest(reqHeaders http.Header, body io.Reader) (newBody io.Reader, getBody func() (io.ReadCloser, error), cleanup func()) {
|
||||
cleanup = func() {}
|
||||
if mi == nil {
|
||||
return body, nil, cleanup
|
||||
}
|
||||
var media io.Reader
|
||||
if mi.media != nil {
|
||||
// This only happens when the caller has turned off chunking. In that
|
||||
// case, we write all of media in a single non-retryable request.
|
||||
media = mi.media
|
||||
} else if mi.singleChunk {
|
||||
// The data fits in a single chunk, which has now been read into the MediaBuffer.
|
||||
// We obtain that chunk so we can write it in a single request. The request can
|
||||
// be retried because the data is stored in the MediaBuffer.
|
||||
media, _, _, _ = mi.buffer.Chunk()
|
||||
}
|
||||
toCleanup := []io.Closer{}
|
||||
if media != nil {
|
||||
fb := readerFunc(body)
|
||||
fm := readerFunc(media)
|
||||
combined, ctype := CombineBodyMedia(body, "application/json", media, mi.mType)
|
||||
toCleanup = append(toCleanup, combined)
|
||||
if fb != nil && fm != nil {
|
||||
getBody = func() (io.ReadCloser, error) {
|
||||
rb := io.NopCloser(fb())
|
||||
rm := io.NopCloser(fm())
|
||||
var mimeBoundary string
|
||||
if _, params, err := mime.ParseMediaType(ctype); err == nil {
|
||||
mimeBoundary = params["boundary"]
|
||||
}
|
||||
r, _ := combineBodyMedia(rb, "application/json", rm, mi.mType, mimeBoundary)
|
||||
toCleanup = append(toCleanup, r)
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
reqHeaders.Set("Content-Type", ctype)
|
||||
body = combined
|
||||
}
|
||||
if mi.buffer != nil && mi.mType != "" && !mi.singleChunk {
|
||||
// This happens when initiating a resumable upload session.
|
||||
// The initial request contains a JSON body rather than media.
|
||||
// It can be retried with a getBody function that re-creates the request body.
|
||||
fb := readerFunc(body)
|
||||
if fb != nil {
|
||||
getBody = func() (io.ReadCloser, error) {
|
||||
rb := io.NopCloser(fb())
|
||||
toCleanup = append(toCleanup, rb)
|
||||
return rb, nil
|
||||
}
|
||||
}
|
||||
reqHeaders.Set("X-Upload-Content-Type", mi.mType)
|
||||
}
|
||||
// Ensure that any bodies created in getBody are cleaned up.
|
||||
cleanup = func() {
|
||||
for _, closer := range toCleanup {
|
||||
_ = closer.Close()
|
||||
}
|
||||
}
|
||||
return body, getBody, cleanup
|
||||
}
|
||||
|
||||
// readerFunc returns a function that always returns an io.Reader that has the same
|
||||
// contents as r, provided that can be done without consuming r. Otherwise, it
|
||||
// returns nil.
|
||||
// See http.NewRequest (in net/http/request.go).
|
||||
func readerFunc(r io.Reader) func() io.Reader {
|
||||
switch r := r.(type) {
|
||||
case *bytes.Buffer:
|
||||
buf := r.Bytes()
|
||||
return func() io.Reader { return bytes.NewReader(buf) }
|
||||
case *bytes.Reader:
|
||||
snapshot := *r
|
||||
return func() io.Reader { r := snapshot; return &r }
|
||||
case *strings.Reader:
|
||||
snapshot := *r
|
||||
return func() io.Reader { r := snapshot; return &r }
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ResumableUpload returns an appropriately configured ResumableUpload value if the
|
||||
// upload is resumable, or nil otherwise.
|
||||
func (mi *MediaInfo) ResumableUpload(locURI string) *ResumableUpload {
|
||||
if mi == nil || mi.singleChunk {
|
||||
return nil
|
||||
}
|
||||
return &ResumableUpload{
|
||||
URI: locURI,
|
||||
Media: mi.buffer,
|
||||
MediaType: mi.mType,
|
||||
Callback: func(curr int64) {
|
||||
if mi.progressUpdater != nil {
|
||||
mi.progressUpdater(curr, mi.size)
|
||||
}
|
||||
},
|
||||
ChunkRetryDeadline: mi.chunkRetryDeadline,
|
||||
}
|
||||
}
|
||||
|
||||
// SetGetBody sets the GetBody field of req to f. This was once needed
|
||||
// to gracefully support Go 1.7 and earlier which didn't have that
|
||||
// field.
|
||||
//
|
||||
// Deprecated: the code generator no longer uses this as of
|
||||
// 2019-02-19. Nothing else should be calling this anyway, but we
|
||||
// won't delete this immediately; it will be deleted in as early as 6
|
||||
// months.
|
||||
func SetGetBody(req *http.Request, f func() (io.ReadCloser, error)) {
|
||||
req.GetBody = f
|
||||
}
|
||||
87
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/params.go
generated
vendored
Normal file
87
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/params.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gensupport
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
// URLParams is a simplified replacement for url.Values
|
||||
// that safely builds up URL parameters for encoding.
|
||||
type URLParams map[string][]string
|
||||
|
||||
// Get returns the first value for the given key, or "".
|
||||
func (u URLParams) Get(key string) string {
|
||||
vs := u[key]
|
||||
if len(vs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return vs[0]
|
||||
}
|
||||
|
||||
// Set sets the key to value.
|
||||
// It replaces any existing values.
|
||||
func (u URLParams) Set(key, value string) {
|
||||
u[key] = []string{value}
|
||||
}
|
||||
|
||||
// SetMulti sets the key to an array of values.
|
||||
// It replaces any existing values.
|
||||
// Note that values must not be modified after calling SetMulti
|
||||
// so the caller is responsible for making a copy if necessary.
|
||||
func (u URLParams) SetMulti(key string, values []string) {
|
||||
u[key] = values
|
||||
}
|
||||
|
||||
// Encode encodes the values into “URL encoded” form
|
||||
// ("bar=baz&foo=quux") sorted by key.
|
||||
func (u URLParams) Encode() string {
|
||||
return url.Values(u).Encode()
|
||||
}
|
||||
|
||||
// SetOptions sets the URL params and any additional `CallOption` or
|
||||
// `MultiCallOption` passed in.
|
||||
func SetOptions(u URLParams, opts ...googleapi.CallOption) {
|
||||
for _, o := range opts {
|
||||
m, ok := o.(googleapi.MultiCallOption)
|
||||
if ok {
|
||||
u.SetMulti(m.GetMulti())
|
||||
continue
|
||||
}
|
||||
u.Set(o.Get())
|
||||
}
|
||||
}
|
||||
|
||||
// SetHeaders sets common headers for all requests. The keyvals header pairs
|
||||
// should have a corresponding value for every key provided. If there is an odd
|
||||
// number of keyvals this method will panic.
|
||||
func SetHeaders(userAgent, contentType string, userHeaders http.Header, keyvals ...string) http.Header {
|
||||
reqHeaders := make(http.Header)
|
||||
reqHeaders.Set("x-goog-api-client", "gl-go/"+GoVersion()+" gdcl/"+"0.179.0")
|
||||
for i := 0; i < len(keyvals); i = i + 2 {
|
||||
reqHeaders.Set(keyvals[i], keyvals[i+1])
|
||||
}
|
||||
reqHeaders.Set("User-Agent", userAgent)
|
||||
if contentType != "" {
|
||||
reqHeaders.Set("Content-Type", contentType)
|
||||
}
|
||||
for k, v := range userHeaders {
|
||||
reqHeaders[k] = v
|
||||
}
|
||||
return reqHeaders
|
||||
}
|
||||
277
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/resumable.go
generated
vendored
Normal file
277
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/resumable.go
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gensupport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ResumableUpload is used by the generated APIs to provide resumable uploads.
|
||||
// It is not used by developers directly.
|
||||
type ResumableUpload struct {
|
||||
Client *http.Client
|
||||
// URI is the resumable resource destination provided by the server after specifying "&uploadType=resumable".
|
||||
URI string
|
||||
UserAgent string // User-Agent for header of the request
|
||||
// Media is the object being uploaded.
|
||||
Media *MediaBuffer
|
||||
// MediaType defines the media type, e.g. "image/jpeg".
|
||||
MediaType string
|
||||
|
||||
mu sync.Mutex // guards progress
|
||||
progress int64 // number of bytes uploaded so far
|
||||
|
||||
// Callback is an optional function that will be periodically called with the cumulative number of bytes uploaded.
|
||||
Callback func(int64)
|
||||
|
||||
// Retry optionally configures retries for requests made against the upload.
|
||||
Retry *RetryConfig
|
||||
|
||||
// ChunkRetryDeadline configures the per-chunk deadline after which no further
|
||||
// retries should happen.
|
||||
ChunkRetryDeadline time.Duration
|
||||
|
||||
// Track current request invocation ID and attempt count for retry metrics
|
||||
// and idempotency headers.
|
||||
invocationID string
|
||||
attempts int
|
||||
}
|
||||
|
||||
// Progress returns the number of bytes uploaded at this point.
|
||||
func (rx *ResumableUpload) Progress() int64 {
|
||||
rx.mu.Lock()
|
||||
defer rx.mu.Unlock()
|
||||
return rx.progress
|
||||
}
|
||||
|
||||
// doUploadRequest performs a single HTTP request to upload data.
|
||||
// off specifies the offset in rx.Media from which data is drawn.
|
||||
// size is the number of bytes in data.
|
||||
// final specifies whether data is the final chunk to be uploaded.
|
||||
func (rx *ResumableUpload) doUploadRequest(ctx context.Context, data io.Reader, off, size int64, final bool) (*http.Response, error) {
|
||||
req, err := http.NewRequest("POST", rx.URI, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.ContentLength = size
|
||||
var contentRange string
|
||||
if final {
|
||||
if size == 0 {
|
||||
contentRange = fmt.Sprintf("bytes */%v", off)
|
||||
} else {
|
||||
contentRange = fmt.Sprintf("bytes %v-%v/%v", off, off+size-1, off+size)
|
||||
}
|
||||
} else {
|
||||
contentRange = fmt.Sprintf("bytes %v-%v/*", off, off+size-1)
|
||||
}
|
||||
req.Header.Set("Content-Range", contentRange)
|
||||
req.Header.Set("Content-Type", rx.MediaType)
|
||||
req.Header.Set("User-Agent", rx.UserAgent)
|
||||
|
||||
// TODO(b/274504690): Consider dropping gccl-invocation-id key since it
|
||||
// duplicates the X-Goog-Gcs-Idempotency-Token header (added in v0.115.0).
|
||||
baseXGoogHeader := "gl-go/" + GoVersion() + " gdcl/" + "0.179.0"
|
||||
invocationHeader := fmt.Sprintf("gccl-invocation-id/%s gccl-attempt-count/%d", rx.invocationID, rx.attempts)
|
||||
req.Header.Set("X-Goog-Api-Client", strings.Join([]string{baseXGoogHeader, invocationHeader}, " "))
|
||||
|
||||
// Set idempotency token header which is used by GCS uploads.
|
||||
req.Header.Set("X-Goog-Gcs-Idempotency-Token", rx.invocationID)
|
||||
|
||||
// Google's upload endpoint uses status code 308 for a
|
||||
// different purpose than the "308 Permanent Redirect"
|
||||
// since-standardized in RFC 7238. Because of the conflict in
|
||||
// semantics, Google added this new request header which
|
||||
// causes it to not use "308" and instead reply with 200 OK
|
||||
// and sets the upload-specific "X-HTTP-Status-Code-Override:
|
||||
// 308" response header.
|
||||
req.Header.Set("X-GUploader-No-308", "yes")
|
||||
|
||||
return SendRequest(ctx, rx.Client, req)
|
||||
}
|
||||
|
||||
func statusResumeIncomplete(resp *http.Response) bool {
|
||||
// This is how the server signals "status resume incomplete"
|
||||
// when X-GUploader-No-308 is set to "yes":
|
||||
return resp != nil && resp.Header.Get("X-Http-Status-Code-Override") == "308"
|
||||
}
|
||||
|
||||
// reportProgress calls a user-supplied callback to report upload progress.
|
||||
// If old==updated, the callback is not called.
|
||||
func (rx *ResumableUpload) reportProgress(old, updated int64) {
|
||||
if updated-old == 0 {
|
||||
return
|
||||
}
|
||||
rx.mu.Lock()
|
||||
rx.progress = updated
|
||||
rx.mu.Unlock()
|
||||
if rx.Callback != nil {
|
||||
rx.Callback(updated)
|
||||
}
|
||||
}
|
||||
|
||||
// transferChunk performs a single HTTP request to upload a single chunk from rx.Media.
|
||||
func (rx *ResumableUpload) transferChunk(ctx context.Context) (*http.Response, error) {
|
||||
chunk, off, size, err := rx.Media.Chunk()
|
||||
|
||||
done := err == io.EOF
|
||||
if !done && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := rx.doUploadRequest(ctx, chunk, off, int64(size), done)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// We sent "X-GUploader-No-308: yes" (see comment elsewhere in
|
||||
// this file), so we don't expect to get a 308.
|
||||
if res.StatusCode == 308 {
|
||||
return nil, errors.New("unexpected 308 response status code")
|
||||
}
|
||||
|
||||
if res.StatusCode == http.StatusOK {
|
||||
rx.reportProgress(off, off+int64(size))
|
||||
}
|
||||
|
||||
if statusResumeIncomplete(res) {
|
||||
rx.Media.Next()
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Upload starts the process of a resumable upload with a cancellable context.
|
||||
// It retries using the provided back off strategy until cancelled or the
|
||||
// strategy indicates to stop retrying.
|
||||
// It is called from the auto-generated API code and is not visible to the user.
|
||||
// Before sending an HTTP request, Upload calls any registered hook functions,
|
||||
// and calls the returned functions after the request returns (see send.go).
|
||||
// rx is private to the auto-generated API code.
|
||||
// Exactly one of resp or err will be nil. If resp is non-nil, the caller must call resp.Body.Close.
|
||||
func (rx *ResumableUpload) Upload(ctx context.Context) (resp *http.Response, err error) {
|
||||
// There are a couple of cases where it's possible for err and resp to both
|
||||
// be non-nil. However, we expose a simpler contract to our callers: exactly
|
||||
// one of resp and err will be non-nil. This means that any response body
|
||||
// must be closed here before returning a non-nil error.
|
||||
prepareReturn := func(resp *http.Response, err error) (*http.Response, error) {
|
||||
if err != nil {
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// This case is very unlikely but possible only if rx.ChunkRetryDeadline is
|
||||
// set to a very small value, in which case no requests will be sent before
|
||||
// the deadline. Return an error to avoid causing a panic.
|
||||
if resp == nil {
|
||||
return nil, fmt.Errorf("upload request to %v not sent, choose larger value for ChunkRetryDeadline", rx.URI)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
// Configure retryable error criteria.
|
||||
errorFunc := rx.Retry.errorFunc()
|
||||
|
||||
// Configure per-chunk retry deadline.
|
||||
var retryDeadline time.Duration
|
||||
if rx.ChunkRetryDeadline != 0 {
|
||||
retryDeadline = rx.ChunkRetryDeadline
|
||||
} else {
|
||||
retryDeadline = defaultRetryDeadline
|
||||
}
|
||||
|
||||
// Send all chunks.
|
||||
for {
|
||||
var pause time.Duration
|
||||
|
||||
// Each chunk gets its own initialized-at-zero backoff and invocation ID.
|
||||
bo := rx.Retry.backoff()
|
||||
quitAfterTimer := time.NewTimer(retryDeadline)
|
||||
rx.attempts = 1
|
||||
rx.invocationID = uuid.New().String()
|
||||
|
||||
// Retry loop for a single chunk.
|
||||
for {
|
||||
pauseTimer := time.NewTimer(pause)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
quitAfterTimer.Stop()
|
||||
pauseTimer.Stop()
|
||||
if err == nil {
|
||||
err = ctx.Err()
|
||||
}
|
||||
return prepareReturn(resp, err)
|
||||
case <-pauseTimer.C:
|
||||
case <-quitAfterTimer.C:
|
||||
pauseTimer.Stop()
|
||||
return prepareReturn(resp, err)
|
||||
}
|
||||
pauseTimer.Stop()
|
||||
|
||||
// Check for context cancellation or timeout once more. If more than one
|
||||
// case in the select statement above was satisfied at the same time, Go
|
||||
// will choose one arbitrarily.
|
||||
// That can cause an operation to go through even if the context was
|
||||
// canceled before or the timeout was reached.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
quitAfterTimer.Stop()
|
||||
if err == nil {
|
||||
err = ctx.Err()
|
||||
}
|
||||
return prepareReturn(resp, err)
|
||||
case <-quitAfterTimer.C:
|
||||
return prepareReturn(resp, err)
|
||||
default:
|
||||
}
|
||||
|
||||
resp, err = rx.transferChunk(ctx)
|
||||
|
||||
var status int
|
||||
if resp != nil {
|
||||
status = resp.StatusCode
|
||||
}
|
||||
|
||||
// Check if we should retry the request.
|
||||
if !errorFunc(status, err) {
|
||||
quitAfterTimer.Stop()
|
||||
break
|
||||
}
|
||||
|
||||
rx.attempts++
|
||||
pause = bo.Pause()
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// If the chunk was uploaded successfully, but there's still
|
||||
// more to go, upload the next chunk without any delay.
|
||||
if statusResumeIncomplete(resp) {
|
||||
resp.Body.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
return prepareReturn(resp, err)
|
||||
}
|
||||
}
|
||||
131
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/retry.go
generated
vendored
Normal file
131
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/retry.go
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gensupport
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
// Backoff is an interface around gax.Backoff's Pause method, allowing tests to provide their
|
||||
// own implementation.
|
||||
type Backoff interface {
|
||||
Pause() time.Duration
|
||||
}
|
||||
|
||||
// These are declared as global variables so that tests can overwrite them.
|
||||
var (
|
||||
// Default per-chunk deadline for resumable uploads.
|
||||
defaultRetryDeadline = 32 * time.Second
|
||||
// Default backoff timer.
|
||||
backoff = func() Backoff {
|
||||
return &gax.Backoff{Initial: 100 * time.Millisecond}
|
||||
}
|
||||
// syscallRetryable is a platform-specific hook, specified in retryable_linux.go
|
||||
syscallRetryable func(error) bool = func(err error) bool { return false }
|
||||
)
|
||||
|
||||
const (
|
||||
// statusTooManyRequests is returned by the storage API if the
|
||||
// per-project limits have been temporarily exceeded. The request
|
||||
// should be retried.
|
||||
// https://cloud.google.com/storage/docs/json_api/v1/status-codes#standardcodes
|
||||
statusTooManyRequests = 429
|
||||
|
||||
// statusRequestTimeout is returned by the storage API if the
|
||||
// upload connection was broken. The request should be retried.
|
||||
statusRequestTimeout = 408
|
||||
)
|
||||
|
||||
// shouldRetry indicates whether an error is retryable for the purposes of this
|
||||
// package, unless a ShouldRetry func is specified by the RetryConfig instead.
|
||||
// It follows guidance from
|
||||
// https://cloud.google.com/storage/docs/exponential-backoff .
|
||||
func shouldRetry(status int, err error) bool {
|
||||
if 500 <= status && status <= 599 {
|
||||
return true
|
||||
}
|
||||
if status == statusTooManyRequests || status == statusRequestTimeout {
|
||||
return true
|
||||
}
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
return true
|
||||
}
|
||||
// Transient network errors should be retried.
|
||||
if syscallRetryable(err) {
|
||||
return true
|
||||
}
|
||||
if err, ok := err.(interface{ Temporary() bool }); ok {
|
||||
if err.Temporary() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
var opErr *net.OpError
|
||||
if errors.As(err, &opErr) {
|
||||
if strings.Contains(opErr.Error(), "use of closed network connection") {
|
||||
// TODO: check against net.ErrClosed (go 1.16+) instead of string
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// If Go 1.13 error unwrapping is available, use this to examine wrapped
|
||||
// errors.
|
||||
if err, ok := err.(interface{ Unwrap() error }); ok {
|
||||
return shouldRetry(status, err.Unwrap())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RetryConfig allows configuration of backoff timing and retryable errors.
|
||||
type RetryConfig struct {
|
||||
Backoff *gax.Backoff
|
||||
ShouldRetry func(err error) bool
|
||||
}
|
||||
|
||||
// Get a new backoff object based on the configured values.
|
||||
func (r *RetryConfig) backoff() Backoff {
|
||||
if r == nil || r.Backoff == nil {
|
||||
return backoff()
|
||||
}
|
||||
return &gax.Backoff{
|
||||
Initial: r.Backoff.Initial,
|
||||
Max: r.Backoff.Max,
|
||||
Multiplier: r.Backoff.Multiplier,
|
||||
}
|
||||
}
|
||||
|
||||
// This is kind of hacky; it is necessary because ShouldRetry expects to
|
||||
// handle HTTP errors via googleapi.Error, but the error has not yet been
|
||||
// wrapped with a googleapi.Error at this layer, and the ErrorFunc type
|
||||
// in the manual layer does not pass in a status explicitly as it does
|
||||
// here. So, we must wrap error status codes in a googleapi.Error so that
|
||||
// ShouldRetry can parse this correctly.
|
||||
func (r *RetryConfig) errorFunc() func(status int, err error) bool {
|
||||
if r == nil || r.ShouldRetry == nil {
|
||||
return shouldRetry
|
||||
}
|
||||
return func(status int, err error) bool {
|
||||
if status >= 400 {
|
||||
return r.ShouldRetry(&googleapi.Error{Code: status})
|
||||
}
|
||||
return r.ShouldRetry(err)
|
||||
}
|
||||
}
|
||||
26
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/retryable_linux.go
generated
vendored
Normal file
26
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/retryable_linux.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package gensupport
|
||||
|
||||
import "syscall"
|
||||
|
||||
func init() {
|
||||
// Initialize syscallRetryable to return true on transient socket-level
|
||||
// errors. These errors are specific to Linux.
|
||||
syscallRetryable = func(err error) bool { return err == syscall.ECONNRESET || err == syscall.ECONNREFUSED }
|
||||
}
|
||||
216
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/send.go
generated
vendored
Normal file
216
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/send.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gensupport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
"github.com/googleapis/gax-go/v2/callctx"
|
||||
)
|
||||
|
||||
// Use this error type to return an error which allows introspection of both
|
||||
// the context error and the error from the service.
|
||||
type wrappedCallErr struct {
|
||||
ctxErr error
|
||||
wrappedErr error
|
||||
}
|
||||
|
||||
func (e wrappedCallErr) Error() string {
|
||||
return fmt.Sprintf("retry failed with %v; last error: %v", e.ctxErr, e.wrappedErr)
|
||||
}
|
||||
|
||||
func (e wrappedCallErr) Unwrap() error {
|
||||
return e.wrappedErr
|
||||
}
|
||||
|
||||
// Is allows errors.Is to match the error from the call as well as context
|
||||
// sentinel errors.
|
||||
func (e wrappedCallErr) Is(target error) bool {
|
||||
return errors.Is(e.ctxErr, target) || errors.Is(e.wrappedErr, target)
|
||||
}
|
||||
|
||||
// SendRequest sends a single HTTP request using the given client.
|
||||
// If ctx is non-nil, it calls all hooks, then sends the request with
|
||||
// req.WithContext, then calls any functions returned by the hooks in
|
||||
// reverse order.
|
||||
func SendRequest(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
|
||||
// Add headers set in context metadata.
|
||||
if ctx != nil {
|
||||
headers := callctx.HeadersFromContext(ctx)
|
||||
for k, vals := range headers {
|
||||
for _, v := range vals {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disallow Accept-Encoding because it interferes with the automatic gzip handling
|
||||
// done by the default http.Transport. See https://github.com/google/google-api-go-client/issues/219.
|
||||
if _, ok := req.Header["Accept-Encoding"]; ok {
|
||||
return nil, errors.New("google api: custom Accept-Encoding headers not allowed")
|
||||
}
|
||||
if ctx == nil {
|
||||
return client.Do(req)
|
||||
}
|
||||
return send(ctx, client, req)
|
||||
}
|
||||
|
||||
func send(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
resp, err := client.Do(req.WithContext(ctx))
|
||||
// If we got an error, and the context has been canceled,
|
||||
// the context's error is probably more useful.
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
default:
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// SendRequestWithRetry sends a single HTTP request using the given client,
|
||||
// with retries if a retryable error is returned.
|
||||
// If ctx is non-nil, it calls all hooks, then sends the request with
|
||||
// req.WithContext, then calls any functions returned by the hooks in
|
||||
// reverse order.
|
||||
func SendRequestWithRetry(ctx context.Context, client *http.Client, req *http.Request, retry *RetryConfig) (*http.Response, error) {
|
||||
// Add headers set in context metadata.
|
||||
if ctx != nil {
|
||||
headers := callctx.HeadersFromContext(ctx)
|
||||
for k, vals := range headers {
|
||||
for _, v := range vals {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disallow Accept-Encoding because it interferes with the automatic gzip handling
|
||||
// done by the default http.Transport. See https://github.com/google/google-api-go-client/issues/219.
|
||||
if _, ok := req.Header["Accept-Encoding"]; ok {
|
||||
return nil, errors.New("google api: custom Accept-Encoding headers not allowed")
|
||||
}
|
||||
if ctx == nil {
|
||||
return client.Do(req)
|
||||
}
|
||||
return sendAndRetry(ctx, client, req, retry)
|
||||
}
|
||||
|
||||
func sendAndRetry(ctx context.Context, client *http.Client, req *http.Request, retry *RetryConfig) (*http.Response, error) {
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
attempts := 1
|
||||
invocationID := uuid.New().String()
|
||||
baseXGoogHeader := req.Header.Get("X-Goog-Api-Client")
|
||||
|
||||
// Loop to retry the request, up to the context deadline.
|
||||
var pause time.Duration
|
||||
var bo Backoff
|
||||
if retry != nil && retry.Backoff != nil {
|
||||
bo = &gax.Backoff{
|
||||
Initial: retry.Backoff.Initial,
|
||||
Max: retry.Backoff.Max,
|
||||
Multiplier: retry.Backoff.Multiplier,
|
||||
}
|
||||
} else {
|
||||
bo = backoff()
|
||||
}
|
||||
|
||||
errorFunc := retry.errorFunc()
|
||||
|
||||
for {
|
||||
t := time.NewTimer(pause)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
// If we got an error and the context has been canceled, return an error acknowledging
|
||||
// both the context cancelation and the service error.
|
||||
if err != nil {
|
||||
return resp, wrappedCallErr{ctx.Err(), err}
|
||||
}
|
||||
return resp, ctx.Err()
|
||||
case <-t.C:
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
// Check for context cancellation once more. If more than one case in a
|
||||
// select is satisfied at the same time, Go will choose one arbitrarily.
|
||||
// That can cause an operation to go through even if the context was
|
||||
// canceled before.
|
||||
if err != nil {
|
||||
return resp, wrappedCallErr{ctx.Err(), err}
|
||||
}
|
||||
return resp, ctx.Err()
|
||||
}
|
||||
|
||||
// Set retry metrics and idempotency headers for GCS.
|
||||
// TODO(b/274504690): Consider dropping gccl-invocation-id key since it
|
||||
// duplicates the X-Goog-Gcs-Idempotency-Token header (added in v0.115.0).
|
||||
invocationHeader := fmt.Sprintf("gccl-invocation-id/%s gccl-attempt-count/%d", invocationID, attempts)
|
||||
xGoogHeader := strings.Join([]string{invocationHeader, baseXGoogHeader}, " ")
|
||||
req.Header.Set("X-Goog-Api-Client", xGoogHeader)
|
||||
req.Header.Set("X-Goog-Gcs-Idempotency-Token", invocationID)
|
||||
|
||||
resp, err = client.Do(req.WithContext(ctx))
|
||||
|
||||
var status int
|
||||
if resp != nil {
|
||||
status = resp.StatusCode
|
||||
}
|
||||
|
||||
// Check if we can retry the request. A retry can only be done if the error
|
||||
// is retryable and the request body can be re-created using GetBody (this
|
||||
// will not be possible if the body was unbuffered).
|
||||
if req.GetBody == nil || !errorFunc(status, err) {
|
||||
break
|
||||
}
|
||||
attempts++
|
||||
var errBody error
|
||||
req.Body, errBody = req.GetBody()
|
||||
if errBody != nil {
|
||||
break
|
||||
}
|
||||
|
||||
pause = bo.Pause()
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// DecodeResponse decodes the body of res into target. If there is no body,
|
||||
// target is unchanged.
|
||||
func DecodeResponse(target interface{}, res *http.Response) error {
|
||||
if res.StatusCode == http.StatusNoContent {
|
||||
return nil
|
||||
}
|
||||
return json.NewDecoder(res.Body).Decode(target)
|
||||
}
|
||||
63
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/version.go
generated
vendored
Normal file
63
vendor/github.com/google/generative-ai-go/genai/internal/gensupport/version.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gensupport
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// GoVersion returns the Go runtime version. The returned string
|
||||
// has no whitespace.
|
||||
func GoVersion() string {
|
||||
return goVersion
|
||||
}
|
||||
|
||||
var goVersion = goVer(runtime.Version())
|
||||
|
||||
const develPrefix = "devel +"
|
||||
|
||||
func goVer(s string) string {
|
||||
if strings.HasPrefix(s, develPrefix) {
|
||||
s = s[len(develPrefix):]
|
||||
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "go1") {
|
||||
s = s[2:]
|
||||
var prerelease string
|
||||
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
|
||||
s, prerelease = s[:p], s[p:]
|
||||
}
|
||||
if strings.HasSuffix(s, ".") {
|
||||
s += "0"
|
||||
} else if strings.Count(s, ".") < 2 {
|
||||
s += ".0"
|
||||
}
|
||||
if prerelease != "" {
|
||||
s += "-" + prerelease
|
||||
}
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func notSemverRune(r rune) bool {
|
||||
return !strings.ContainsRune("0123456789.", r)
|
||||
}
|
||||
18
vendor/github.com/google/generative-ai-go/genai/internal/version.go
generated
vendored
Normal file
18
vendor/github.com/google/generative-ai-go/genai/internal/version.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
// Version is the current tagged release of the library.
|
||||
const Version = "0.19.0"
|
||||
14
vendor/github.com/google/generative-ai-go/genai/license.txt
generated
vendored
Normal file
14
vendor/github.com/google/generative-ai-go/genai/license.txt
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
50
vendor/github.com/google/generative-ai-go/genai/list_models.go
generated
vendored
Normal file
50
vendor/github.com/google/generative-ai-go/genai/list_models.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package genai
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gl "cloud.google.com/go/ai/generativelanguage/apiv1beta"
|
||||
pb "cloud.google.com/go/ai/generativelanguage/apiv1beta/generativelanguagepb"
|
||||
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
func (c *Client) ListModels(ctx context.Context) *ModelInfoIterator {
|
||||
return &ModelInfoIterator{
|
||||
it: c.mc.ListModels(ctx, &pb.ListModelsRequest{}),
|
||||
}
|
||||
}
|
||||
|
||||
// A ModelInfoIterator iterates over Models.
|
||||
type ModelInfoIterator struct {
|
||||
it *gl.ModelIterator
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *ModelInfoIterator) Next() (*ModelInfo, error) {
|
||||
m, err := it.it.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (ModelInfo{}).fromProto(m), nil
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *ModelInfoIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.it.PageInfo()
|
||||
}
|
||||
44
vendor/github.com/google/generative-ai-go/genai/option.go
generated
vendored
Normal file
44
vendor/github.com/google/generative-ai-go/genai/option.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package genai
|
||||
|
||||
import (
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/option/internaloption"
|
||||
)
|
||||
|
||||
// WithClientInfo sets request information identifying the
|
||||
// product that is calling this client.
|
||||
func WithClientInfo(key, value string) option.ClientOption {
|
||||
return &clientInfo{key: key, value: value}
|
||||
}
|
||||
|
||||
type clientInfo struct {
|
||||
internaloption.EmbeddableAdapter
|
||||
key, value string
|
||||
}
|
||||
|
||||
// optionOfType returns the first value of opts that has type T,
|
||||
// along with true. If there is no option of that type, it returns
|
||||
// the zero value for T and false.
|
||||
func optionOfType[T option.ClientOption](opts []option.ClientOption) (T, bool) {
|
||||
for _, opt := range opts {
|
||||
if opt, ok := opt.(T); ok {
|
||||
return opt, true
|
||||
}
|
||||
}
|
||||
var z T
|
||||
return z, false
|
||||
}
|
||||
Reference in New Issue
Block a user