@@ -2,9 +2,12 @@ package openai
22
33import (
44 "context"
5+ "crypto/tls"
6+ "crypto/x509"
57 "errors"
68 "io"
79 "log/slog"
10+ "net/http"
811 "os"
912 "slices"
1013 "sort"
@@ -13,6 +16,7 @@ import (
1316
1417 openai "github.com/gptscript-ai/chat-completion-client"
1518 "github.com/gptscript-ai/gptscript/pkg/cache"
19+ "github.com/gptscript-ai/gptscript/pkg/certs"
1620 "github.com/gptscript-ai/gptscript/pkg/counter"
1721 "github.com/gptscript-ai/gptscript/pkg/credentials"
1822 "github.com/gptscript-ai/gptscript/pkg/hash"
@@ -51,13 +55,15 @@ type Client struct {
5155}
5256
5357type Options struct {
54- BaseURL string `usage:"OpenAI base URL" name:"openai-base-url" env:"OPENAI_BASE_URL"`
55- APIKey string `usage:"OpenAI API KEY" name:"openai-api-key" env:"OPENAI_API_KEY"`
56- OrgID string `usage:"OpenAI organization ID" name:"openai-org-id" env:"OPENAI_ORG_ID"`
57- DefaultModel string `usage:"Default LLM model to use" default:"gpt-4o"`
58- ConfigFile string `usage:"Path to GPTScript config file" name:"config"`
59- SetSeed bool `usage:"-"`
60- CacheKey string `usage:"-"`
58+ BaseURL string `usage:"OpenAI base URL" name:"openai-base-url" env:"OPENAI_BASE_URL"`
59+ APIKey string `usage:"OpenAI API KEY" name:"openai-api-key" env:"OPENAI_API_KEY"`
60+ OrgID string `usage:"OpenAI organization ID" name:"openai-org-id" env:"OPENAI_ORG_ID"`
61+ DefaultModel string `usage:"Default LLM model to use" default:"gpt-4o"`
62+ ConfigFile string `usage:"Path to GPTScript config file" name:"config"`
63+ SetSeed bool `usage:"-"`
64+ CacheKey string `usage:"-"`
65+ ClientCert certs.CertAndKey `usage:"-"`
66+ ServerCert []byte `usage:"-"`
6167 Cache * cache.Client
6268}
6369
@@ -70,6 +76,14 @@ func Complete(opts ...Options) (result Options) {
7076 result .DefaultModel = types .FirstSet (opt .DefaultModel , result .DefaultModel )
7177 result .SetSeed = types .FirstSet (opt .SetSeed , result .SetSeed )
7278 result .CacheKey = types .FirstSet (opt .CacheKey , result .CacheKey )
79+
80+ if len (opt .ClientCert .Cert ) > 0 {
81+ result .ClientCert = opt .ClientCert
82+ }
83+
84+ if len (opt .ServerCert ) > 0 {
85+ result .ServerCert = opt .ServerCert
86+ }
7387 }
7488
7589 return result
@@ -116,6 +130,29 @@ func NewClient(ctx context.Context, credStore credentials.CredentialStore, opts
116130 cfg .BaseURL = types .FirstSet (opt .BaseURL , cfg .BaseURL )
117131 cfg .OrgID = types .FirstSet (opt .OrgID , cfg .OrgID )
118132
133+ // Set up for mTLS, if configured.
134+ if opt .ServerCert != nil && len (opt .ClientCert .Cert ) > 0 {
135+ pool := x509 .NewCertPool ()
136+ if ! pool .AppendCertsFromPEM (opt .ServerCert ) {
137+ return nil , errors .New ("failed to append server cert to pool" )
138+ }
139+
140+ clientCert , err := tls .X509KeyPair (opt .ClientCert .Cert , opt .ClientCert .Key )
141+ if err != nil {
142+ return nil , err
143+ }
144+
145+ cfg .HTTPClient = & http.Client {
146+ Transport : & http.Transport {
147+ TLSClientConfig : & tls.Config {
148+ Certificates : []tls.Certificate {clientCert },
149+ RootCAs : pool ,
150+ InsecureSkipVerify : false ,
151+ },
152+ },
153+ }
154+ }
155+
119156 cacheKeyBase := opt .CacheKey
120157 if cacheKeyBase == "" {
121158 cacheKeyBase = hash .ID (opt .APIKey , opt .BaseURL )
0 commit comments