Skip to content

feat: build credential helpers just in time #561

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions pkg/cli/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/gptscript-ai/gptscript/pkg/cache"
"github.com/gptscript-ai/gptscript/pkg/config"
"github.com/gptscript-ai/gptscript/pkg/credentials"
"github.com/gptscript-ai/gptscript/pkg/repos/runtimes"
"github.com/gptscript-ai/gptscript/pkg/runner"
"github.com/spf13/cobra"
)

Expand All @@ -35,7 +37,7 @@ func (c *Credential) Customize(cmd *cobra.Command) {
cmd.AddCommand(cmd2.Command(&Show{root: c.root}))
}

func (c *Credential) Run(_ *cobra.Command, _ []string) error {
func (c *Credential) Run(cmd *cobra.Command, _ []string) error {
cfg, err := config.ReadCLIConfig(c.root.ConfigFile)
if err != nil {
return fmt.Errorf("failed to read CLI config: %w", err)
Expand All @@ -51,14 +53,22 @@ func (c *Credential) Run(_ *cobra.Command, _ []string) error {
return err
}
opts.Cache = cache.Complete(opts.Cache)
opts.Runner = runner.Complete(opts.Runner)
if opts.Runner.RuntimeManager == nil {
opts.Runner.RuntimeManager = runtimes.Default(opts.Cache.CacheDir)
}

if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg, opts.Env); err != nil {
return err
}

// Initialize the credential store and get all the credentials.
store, err := credentials.NewStore(cfg, ctx, opts.Cache.CacheDir)
store, err := credentials.NewStore(cfg, opts.Runner.RuntimeManager, ctx, opts.Cache.CacheDir)
if err != nil {
return fmt.Errorf("failed to get credentials store: %w", err)
}

creds, err := store.List()
creds, err := store.List(cmd.Context())
if err != nil {
return fmt.Errorf("failed to list credentials: %w", err)
}
Expand Down
19 changes: 15 additions & 4 deletions pkg/cli/credential_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/gptscript-ai/gptscript/pkg/cache"
"github.com/gptscript-ai/gptscript/pkg/config"
"github.com/gptscript-ai/gptscript/pkg/credentials"
"github.com/gptscript-ai/gptscript/pkg/repos/runtimes"
"github.com/gptscript-ai/gptscript/pkg/runner"
"github.com/spf13/cobra"
)

Expand All @@ -21,24 +23,33 @@ func (c *Delete) Customize(cmd *cobra.Command) {
cmd.Args = cobra.ExactArgs(1)
}

func (c *Delete) Run(_ *cobra.Command, args []string) error {
func (c *Delete) Run(cmd *cobra.Command, args []string) error {
opts, err := c.root.NewGPTScriptOpts()
if err != nil {
return err
}
opts.Cache = cache.Complete(opts.Cache)

cfg, err := config.ReadCLIConfig(c.root.ConfigFile)
if err != nil {
return fmt.Errorf("failed to read CLI config: %w", err)
}

store, err := credentials.NewStore(cfg, c.root.CredentialContext, opts.Cache.CacheDir)
opts.Cache = cache.Complete(opts.Cache)
opts.Runner = runner.Complete(opts.Runner)
if opts.Runner.RuntimeManager == nil {
opts.Runner.RuntimeManager = runtimes.Default(opts.Cache.CacheDir)
}

if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg, opts.Env); err != nil {
return err
}

store, err := credentials.NewStore(cfg, opts.Runner.RuntimeManager, c.root.CredentialContext, opts.Cache.CacheDir)
if err != nil {
return fmt.Errorf("failed to get credentials store: %w", err)
}

if err = store.Remove(args[0]); err != nil {
if err = store.Remove(cmd.Context(), args[0]); err != nil {
return fmt.Errorf("failed to remove credential: %w", err)
}
return nil
Expand Down
19 changes: 15 additions & 4 deletions pkg/cli/credential_show.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/gptscript-ai/gptscript/pkg/cache"
"github.com/gptscript-ai/gptscript/pkg/config"
"github.com/gptscript-ai/gptscript/pkg/credentials"
"github.com/gptscript-ai/gptscript/pkg/repos/runtimes"
"github.com/gptscript-ai/gptscript/pkg/runner"
"github.com/spf13/cobra"
)

Expand All @@ -23,24 +25,33 @@ func (c *Show) Customize(cmd *cobra.Command) {
cmd.Args = cobra.ExactArgs(1)
}

func (c *Show) Run(_ *cobra.Command, args []string) error {
func (c *Show) Run(cmd *cobra.Command, args []string) error {
opts, err := c.root.NewGPTScriptOpts()
if err != nil {
return err
}
opts.Cache = cache.Complete(opts.Cache)

cfg, err := config.ReadCLIConfig(c.root.ConfigFile)
if err != nil {
return fmt.Errorf("failed to read CLI config: %w", err)
}

store, err := credentials.NewStore(cfg, c.root.CredentialContext, opts.Cache.CacheDir)
opts.Cache = cache.Complete(opts.Cache)
opts.Runner = runner.Complete(opts.Runner)
if opts.Runner.RuntimeManager == nil {
opts.Runner.RuntimeManager = runtimes.Default(opts.Cache.CacheDir)
}

if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg, opts.Env); err != nil {
return err
}

store, err := credentials.NewStore(cfg, opts.Runner.RuntimeManager, c.root.CredentialContext, opts.Cache.CacheDir)
if err != nil {
return fmt.Errorf("failed to get credentials store: %w", err)
}

cred, exists, err := store.Get(args[0])
cred, exists, err := store.Get(cmd.Context(), args[0])
if err != nil {
return fmt.Errorf("failed to get credential: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (e *Eval) Run(cmd *cobra.Command, args []string) error {
return err
}

runner, err := gptscript.New(opts)
runner, err := gptscript.New(cmd.Context(), opts)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/gptscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) {

ctx := cmd.Context()

gptScript, err := gptscript.New(gptOpt)
gptScript, err := gptscript.New(ctx, gptOpt)
if err != nil {
return err
}
Expand Down
10 changes: 6 additions & 4 deletions pkg/credentials/noop.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package credentials

import "context"

type NoopStore struct{}

func (s NoopStore) Get(_ string) (*Credential, bool, error) {
func (s NoopStore) Get(context.Context, string) (*Credential, bool, error) {
return nil, false, nil
}

func (s NoopStore) Add(_ Credential) error {
func (s NoopStore) Add(context.Context, Credential) error {
return nil
}

func (s NoopStore) Remove(_ string) error {
func (s NoopStore) Remove(context.Context, string) error {
return nil
}

func (s NoopStore) List() ([]Credential, error) {
func (s NoopStore) List(context.Context) ([]Credential, error) {
return nil, nil
}
43 changes: 27 additions & 16 deletions pkg/credentials/store.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package credentials

import (
"context"
"fmt"
"path/filepath"
"regexp"
Expand All @@ -10,32 +11,38 @@ import (
"github.com/gptscript-ai/gptscript/pkg/config"
)

type CredentialBuilder interface {
EnsureCredentialHelpers(ctx context.Context) error
}

type CredentialStore interface {
Get(toolName string) (*Credential, bool, error)
Add(cred Credential) error
Remove(toolName string) error
List() ([]Credential, error)
Get(ctx context.Context, toolName string) (*Credential, bool, error)
Add(ctx context.Context, cred Credential) error
Remove(ctx context.Context, toolName string) error
List(ctx context.Context) ([]Credential, error)
}

type Store struct {
credCtx string
credBuilder CredentialBuilder
credHelperDirs CredentialHelperDirs
cfg *config.CLIConfig
}

func NewStore(cfg *config.CLIConfig, credCtx, cacheDir string) (CredentialStore, error) {
func NewStore(cfg *config.CLIConfig, credentialBuilder CredentialBuilder, credCtx, cacheDir string) (CredentialStore, error) {
if err := validateCredentialCtx(credCtx); err != nil {
return nil, err
}
return Store{
credCtx: credCtx,
credBuilder: credentialBuilder,
credHelperDirs: GetCredentialHelperDirs(cacheDir),
cfg: cfg,
}, nil
}

func (s Store) Get(toolName string) (*Credential, bool, error) {
store, err := s.getStore()
func (s Store) Get(ctx context.Context, toolName string) (*Credential, bool, error) {
store, err := s.getStore(ctx)
if err != nil {
return nil, false, err
}
Expand All @@ -57,9 +64,9 @@ func (s Store) Get(toolName string) (*Credential, bool, error) {
return &cred, true, nil
}

func (s Store) Add(cred Credential) error {
func (s Store) Add(ctx context.Context, cred Credential) error {
cred.Context = s.credCtx
store, err := s.getStore()
store, err := s.getStore(ctx)
if err != nil {
return err
}
Expand All @@ -70,16 +77,16 @@ func (s Store) Add(cred Credential) error {
return store.Store(auth)
}

func (s Store) Remove(toolName string) error {
store, err := s.getStore()
func (s Store) Remove(ctx context.Context, toolName string) error {
store, err := s.getStore(ctx)
if err != nil {
return err
}
return store.Erase(toolNameWithCtx(toolName, s.credCtx))
}

func (s Store) List() ([]Credential, error) {
store, err := s.getStore()
func (s Store) List(ctx context.Context) ([]Credential, error) {
store, err := s.getStore(ctx)
if err != nil {
return nil, err
}
Expand All @@ -106,17 +113,21 @@ func (s Store) List() ([]Credential, error) {
return creds, nil
}

func (s *Store) getStore() (credentials.Store, error) {
return s.getStoreByHelper(config.GPTScriptHelperPrefix + s.cfg.CredentialsStore)
func (s *Store) getStore(ctx context.Context) (credentials.Store, error) {
return s.getStoreByHelper(ctx, config.GPTScriptHelperPrefix+s.cfg.CredentialsStore)
}

func (s *Store) getStoreByHelper(helper string) (credentials.Store, error) {
func (s *Store) getStoreByHelper(ctx context.Context, helper string) (credentials.Store, error) {
if helper == "" || helper == config.GPTScriptHelperPrefix+"file" {
return credentials.NewFileStore(s.cfg), nil
}

// If the helper is referencing one of the credential helper programs, then reference the full path.
if strings.HasPrefix(helper, "gptscript-credential-") {
if err := s.credBuilder.EnsureCredentialHelpers(ctx); err != nil {
return nil, err
}

helper = filepath.Join(s.credHelperDirs.BinDir, helper)
}

Expand Down
13 changes: 5 additions & 8 deletions pkg/gptscript/gptscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func complete(opts ...Options) Options {
return result
}

func New(o ...Options) (*GPTScript, error) {
func New(ctx context.Context, o ...Options) (*GPTScript, error) {
opts := complete(o...)
registry := llm.NewRegistry()

Expand All @@ -91,23 +91,20 @@ func New(o ...Options) (*GPTScript, error) {
return nil, err
}

credStore, err := credentials.NewStore(cliCfg, opts.CredentialContext, cacheClient.CacheDir())
if err != nil {
return nil, err
}

if opts.Runner.RuntimeManager == nil {
opts.Runner.RuntimeManager = runtimes.Default(cacheClient.CacheDir())
}

if err := opts.Runner.RuntimeManager.SetUpCredentialHelpers(context.Background(), cliCfg, opts.Env); err != nil {
return nil, err
}
if err := opts.Runner.RuntimeManager.EnsureCredentialHelpers(context.Background()); err != nil {

credStore, err := credentials.NewStore(cliCfg, opts.Runner.RuntimeManager, opts.CredentialContext, cacheClient.CacheDir())
if err != nil {
return nil, err
}

oaiClient, err := openai.NewClient(credStore, opts.OpenAI, openai.Options{
oaiClient, err := openai.NewClient(ctx, credStore, opts.OpenAI, openai.Options{
Cache: cacheClient,
SetSeed: true,
})
Expand Down
4 changes: 2 additions & 2 deletions pkg/openai/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,15 @@ func complete(opts ...Options) (Options, error) {
return result, err
}

func NewClient(credStore credentials.CredentialStore, opts ...Options) (*Client, error) {
func NewClient(ctx context.Context, credStore credentials.CredentialStore, opts ...Options) (*Client, error) {
opt, err := complete(opts...)
if err != nil {
return nil, err
}

// If the API key is not set, try to get it from the cred store
if opt.APIKey == "" && opt.BaseURL == "" {
cred, exists, err := credStore.Get(BuiltinCredName)
cred, exists, err := credStore.Get(ctx, BuiltinCredName)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/prompt/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func GetModelProviderCredential(ctx context.Context, credStore credentials.CredentialStore, credName, env, message string, envs []string) (string, error) {
cred, exists, err := credStore.Get(credName)
cred, exists, err := credStore.Get(ctx, credName)
if err != nil {
return "", err
}
Expand All @@ -25,7 +25,7 @@ func GetModelProviderCredential(ctx context.Context, credStore credentials.Crede
}

k = gjson.Get(result, "key").String()
if err := credStore.Add(credentials.Credential{
if err := credStore.Add(ctx, credentials.Credential{
ToolName: credName,
Type: credentials.CredentialTypeModelProvider,
Env: map[string]string{
Expand Down
4 changes: 2 additions & 2 deletions pkg/remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (c *Client) clientFromURL(ctx context.Context, apiURL string) (*openai.Clie
}
}

return openai.NewClient(c.credStore, openai.Options{
return openai.NewClient(ctx, c.credStore, openai.Options{
BaseURL: apiURL,
Cache: c.cache,
APIKey: key,
Expand Down Expand Up @@ -163,7 +163,7 @@ func (c *Client) load(ctx context.Context, toolName string) (*openai.Client, err
url += "/v1"
}

client, err = openai.NewClient(c.credStore, openai.Options{
client, err = openai.NewClient(ctx, c.credStore, openai.Options{
BaseURL: url,
Cache: c.cache,
CacheKey: prg.EntryToolID,
Expand Down
Loading
Loading