@@ -13,7 +13,6 @@ import (
1313
1414 "github.com/docker/cagent/pkg/chat"
1515 latest "github.com/docker/cagent/pkg/config/v2"
16- "github.com/docker/cagent/pkg/desktop"
1716 "github.com/docker/cagent/pkg/environment"
1817 "github.com/docker/cagent/pkg/model/provider/base"
1918 "github.com/docker/cagent/pkg/model/provider/options"
@@ -24,11 +23,7 @@ import (
2423// It implements the provider.Provider interface
2524type Client struct {
2625 base.Config
27- client * genai.Client
28- // When using the Docker AI Gateway, tokens are short-lived. We rebuild
29- // the client per request when in gateway mode.
30- useGateway bool
31- gatewayBaseURL string
26+ clientFn func (context.Context ) (* genai.Client , error )
3227}
3328
3429// NewClient creates a new Gemini client from the provided configuration
@@ -46,66 +41,63 @@ func NewClient(ctx context.Context, cfg *latest.ModelConfig, env environment.Pro
4641 opt (& modelOptions )
4742 }
4843
49- var apiKey string
50- var httpOptions genai.HTTPOptions
51- useGateway := false
52- gatewayBaseURL := ""
44+ var clientFn func (context.Context ) (* genai.Client , error )
5345 if gateway := modelOptions .Gateway (); gateway == "" {
54- apiKey = env .Get (ctx , "GOOGLE_API_KEY" )
46+ apiKey : = env .Get (ctx , "GOOGLE_API_KEY" )
5547 if apiKey == "" {
5648 return nil , errors .New ("GOOGLE_API_KEY environment variable is required" )
5749 }
50+
51+ client , err := genai .NewClient (ctx , & genai.ClientConfig {
52+ APIKey : apiKey ,
53+ Backend : genai .BackendGeminiAPI ,
54+ })
55+ if err != nil {
56+ return nil , err
57+ }
58+
59+ clientFn = func (context.Context ) (* genai.Client , error ) {
60+ return client , nil
61+ }
5862 } else {
59- // genai client requires a non-empty API key
60- apiKey = desktop . GetToken (ctx )
61- if apiKey == "" {
63+ // Fail fast if Docker Desktop's auth token isn't available
64+ if env . Get (ctx , environment . DockerDesktopTokenEnv ) == "" {
65+ slog . Error ( "Gemini client creation failed" , "error" , "failed to get Docker Desktop's authentication token" )
6266 return nil , errors .New ("sorry, you first need to sign in Docker Desktop to use the Docker AI Gateway" )
6367 }
64- httpOptions .BaseURL = gateway
65- httpOptions .Headers = make (http.Header )
66- httpOptions .Headers .Set ("Authorization" , "Bearer " + apiKey )
67- useGateway = true
68- gatewayBaseURL = gateway
69- }
7068
71- client , err := genai .NewClient (ctx , & genai.ClientConfig {
72- APIKey : apiKey ,
73- Backend : genai .BackendGeminiAPI ,
74- HTTPOptions : httpOptions ,
75- })
76- if err != nil {
77- return nil , err
69+ // When using a Gateway, tokens are short-lived.
70+ clientFn = func (ctx context.Context ) (* genai.Client , error ) {
71+ // Query a fresh auth token each time the client is used
72+ authToken := env .Get (ctx , environment .DockerDesktopTokenEnv )
73+ if authToken == "" {
74+ return nil , errors .New ("failed to get Docker Desktop token for Gateway" )
75+ }
76+
77+ return genai .NewClient (ctx , & genai.ClientConfig {
78+ APIKey : authToken ,
79+ Backend : genai .BackendGeminiAPI ,
80+ HTTPOptions : genai.HTTPOptions {
81+ BaseURL : gateway ,
82+ Headers : http.Header {
83+ "Authorization" : []string {"Bearer " + authToken },
84+ },
85+ },
86+ })
87+ }
7888 }
7989
90+ slog .Debug ("Gemini client created successfully" , "model" , cfg .Model )
91+
8092 return & Client {
8193 Config : base.Config {
8294 ModelConfig : cfg ,
8395 ModelOptions : modelOptions ,
8496 },
85- client : client ,
86- useGateway : useGateway ,
87- gatewayBaseURL : gatewayBaseURL ,
97+ clientFn : clientFn ,
8898 }, nil
8999}
90100
91- // newGatewayClient builds a new Gemini client using a fresh Docker Desktop token.
92- func (c * Client ) newGatewayClient (ctx context.Context ) (* genai.Client , error ) {
93- token := desktop .GetToken (ctx )
94- if token == "" {
95- return nil , errors .New ("failed to get Docker Desktop token for gateway" )
96- }
97- httpOptions := genai.HTTPOptions {
98- BaseURL : c .gatewayBaseURL ,
99- Headers : make (http.Header ),
100- }
101- httpOptions .Headers .Set ("Authorization" , "Bearer " + token )
102- return genai .NewClient (ctx , & genai.ClientConfig {
103- APIKey : token ,
104- Backend : genai .BackendGeminiAPI ,
105- HTTPOptions : httpOptions ,
106- })
107- }
108-
109101// convertMessagesToGemini converts chat.Messages into Gemini Contents
110102func convertMessagesToGemini (messages []chat.Message ) []* genai.Content {
111103 contents := make ([]* genai.Content , 0 , len (messages ))
@@ -338,16 +330,13 @@ func (c *Client) CreateChatCompletionStream(
338330 slog .Debug ("Message" , "index" , i , "role" , content .Role )
339331 }
340332
341- // Build a fresh client per request when using the gateway
342- var iter func (func (* genai.GenerateContentResponse , error ) bool )
343- if c .useGateway {
344- if gwClient , err := c .newGatewayClient (ctx ); err == nil {
345- iter = gwClient .Models .GenerateContentStream (ctx , c .ModelConfig .Model , contents , config )
346- } else {
347- iter = c .client .Models .GenerateContentStream (ctx , c .ModelConfig .Model , contents , config )
348- }
349- } else {
350- iter = c .client .Models .GenerateContentStream (ctx , c .ModelConfig .Model , contents , config )
333+ client , err := c .clientFn (ctx )
334+ if err != nil {
335+ slog .Error ("Failed to create Gemini client" , "error" , err )
336+ return nil , err
351337 }
338+
339+ // Build a fresh client per request when using the gateway
340+ iter := client .Models .GenerateContentStream (ctx , c .ModelConfig .Model , contents , config )
352341 return NewStreamAdapter (iter , c .ModelConfig .Model ), nil
353342}
0 commit comments