@@ -3,6 +3,7 @@ package controller
3
3
import (
4
4
"context"
5
5
"encoding/json"
6
+ "errors"
6
7
"fmt"
7
8
"path/filepath"
8
9
"strings"
@@ -20,17 +21,18 @@ import (
20
21
"github.com/helixml/helix/api/pkg/types"
21
22
"gopkg.in/yaml.v2"
22
23
24
+ "github.com/helixml/helix/api/pkg/oauth"
23
25
"github.com/rs/zerolog/log"
24
26
openai "github.com/sashabaranov/go-openai"
25
27
)
26
28
27
29
type ChatCompletionOptions struct {
28
- AppID string
29
- AssistantID string
30
- RAGSourceID string
31
- Provider string
32
-
33
- QueryParams map [ string ]string
30
+ AppID string
31
+ AssistantID string
32
+ RAGSourceID string
33
+ Provider string
34
+ QueryParams map [ string ] string
35
+ OAuthEnvVars [ ]string // OAuth environment variables
34
36
}
35
37
36
38
// ChatCompletion is used by the OpenAI compatible API. Doesn't handle any historical sessions, etc.
@@ -87,6 +89,12 @@ func (c *Controller) ChatCompletion(ctx context.Context, user *types.User, req o
87
89
return nil , nil , fmt .Errorf ("failed to get client: %v" , err )
88
90
}
89
91
92
+ // Evaluate and add OAuth tokens
93
+ err = c .evalAndAddOAuthTokens (ctx , client , opts , user )
94
+ if err != nil {
95
+ return nil , nil , fmt .Errorf ("failed to add OAuth tokens: %w" , err )
96
+ }
97
+
90
98
resp , err := client .CreateChatCompletion (ctx , req )
91
99
if err != nil {
92
100
log .Err (err ).Msg ("error creating chat completion" )
@@ -156,6 +164,12 @@ func (c *Controller) ChatCompletionStream(ctx context.Context, user *types.User,
156
164
return nil , nil , fmt .Errorf ("failed to get client: %v" , err )
157
165
}
158
166
167
+ // Evaluate and add OAuth tokens
168
+ err = c .evalAndAddOAuthTokens (ctx , client , opts , user )
169
+ if err != nil {
170
+ return nil , nil , fmt .Errorf ("failed to add OAuth tokens: %w" , err )
171
+ }
172
+
159
173
stream , err := client .CreateChatCompletionStream (ctx , req )
160
174
if err != nil {
161
175
log .Err (err ).Msg ("error creating chat completion stream" )
@@ -919,3 +933,107 @@ func (c *Controller) UpdateSessionWithKnowledgeResults(ctx context.Context, sess
919
933
920
934
return nil
921
935
}
936
+
937
+ func (c * Controller ) getAppOAuthTokens (ctx context.Context , userID string , app * types.App ) ([]string , error ) {
938
+ // Initialize empty slice for environment variables
939
+ var envVars []string
940
+
941
+ // Only proceed if we have an OAuth manager
942
+ if c .Options .OAuthManager == nil {
943
+ log .Debug ().Msg ("No OAuth manager available" )
944
+ return nil , nil
945
+ }
946
+
947
+ // If app is nil, return empty slice
948
+ if app == nil {
949
+ return nil , nil
950
+ }
951
+
952
+ // Keep track of providers we've seen to avoid duplicates
953
+ seenProviders := make (map [types.OAuthProviderType ]bool )
954
+
955
+ // First, check the tools defined in assistants
956
+ for _ , assistant := range app .Config .Helix .Assistants {
957
+ for _ , tool := range assistant .Tools {
958
+ if tool .ToolType == types .ToolTypeAPI && tool .Config .API != nil && tool .Config .API .OAuthProvider != "" {
959
+ providerType := tool .Config .API .OAuthProvider
960
+ requiredScopes := tool .Config .API .OAuthScopes
961
+
962
+ // Skip if we've already processed this provider
963
+ if seenProviders [providerType ] {
964
+ continue
965
+ }
966
+ seenProviders [providerType ] = true
967
+
968
+ token , err := c .Options .OAuthManager .GetTokenForTool (ctx , userID , providerType , requiredScopes )
969
+ if err == nil && token != "" {
970
+ envName := fmt .Sprintf ("OAUTH_TOKEN_%s" , strings .ToUpper (string (providerType )))
971
+ envVars = append (envVars , fmt .Sprintf ("%s=%s" , envName , token ))
972
+ log .Debug ().Str ("provider" , string (providerType )).Msg ("Added OAuth token to app environment" )
973
+ } else {
974
+ var scopeErr * oauth.ScopeError
975
+ if errors .As (err , & scopeErr ) {
976
+ log .Warn ().
977
+ Str ("app_id" , app .ID ).
978
+ Str ("user_id" , userID ).
979
+ Str ("provider" , string (providerType )).
980
+ Strs ("missing_scopes" , scopeErr .Missing ).
981
+ Msg ("Missing required OAuth scopes for tool" )
982
+ } else {
983
+ log .Debug ().Err (err ).Str ("provider" , string (providerType )).Msg ("Failed to get OAuth token for tool" )
984
+ }
985
+ }
986
+ }
987
+ }
988
+ }
989
+
990
+ return envVars , nil
991
+ }
992
+
993
+ func (c * Controller ) evalAndAddOAuthTokens (ctx context.Context , client oai.Client , opts * ChatCompletionOptions , user * types.User ) error {
994
+ // If we already have OAuth tokens, use them
995
+ if len (opts .OAuthEnvVars ) > 0 {
996
+ return nil
997
+ }
998
+
999
+ // If we have an app ID, try to get OAuth tokens
1000
+ if opts .AppID != "" && c .Options .OAuthManager != nil {
1001
+ app , err := c .Options .Store .GetApp (ctx , opts .AppID )
1002
+ if err != nil {
1003
+ log .Debug ().Err (err ).Str ("app_id" , opts .AppID ).Msg ("Failed to get app for OAuth tokens" )
1004
+ return nil // Continue without OAuth tokens
1005
+ }
1006
+
1007
+ // Get OAuth tokens as environment variables
1008
+ oauthEnvVars , err := c .getAppOAuthTokens (ctx , user .ID , app )
1009
+ if err != nil {
1010
+ log .Debug ().Err (err ).Str ("app_id" , opts .AppID ).Msg ("Failed to get OAuth tokens for app" )
1011
+ return nil // Continue without OAuth tokens
1012
+ }
1013
+
1014
+ // Add OAuth tokens to the options
1015
+ opts .OAuthEnvVars = oauthEnvVars
1016
+
1017
+ // If we have tokens, add them to the client as well
1018
+ if len (oauthEnvVars ) > 0 {
1019
+ for _ , envVar := range oauthEnvVars {
1020
+ parts := strings .SplitN (envVar , "=" , 2 )
1021
+ if len (parts ) == 2 {
1022
+ envKey , envValue := parts [0 ], parts [1 ]
1023
+ // Only process OAUTH_TOKEN_ variables
1024
+ if strings .HasPrefix (envKey , "OAUTH_TOKEN_" ) {
1025
+ // Extract provider type from env var name (e.g., OAUTH_TOKEN_GITHUB -> github)
1026
+ providerType := strings .ToLower (strings .TrimPrefix (envKey , "OAUTH_TOKEN_" ))
1027
+ log .Debug ().Str ("provider" , providerType ).Msg ("Added OAuth token to API client HTTP headers" )
1028
+ // Add OAuth token to client (if supported)
1029
+ if retryableClient , ok := client .(* oai.RetryableClient ); ok {
1030
+ retryableClient .AddOAuthToken (providerType , envValue )
1031
+ }
1032
+ }
1033
+ }
1034
+ }
1035
+ }
1036
+ }
1037
+
1038
+ return nil
1039
+ }
0 commit comments