From c7d8d7f46c2bbefcf9ca7920676a19f8207c047a Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 12 Sep 2024 17:32:08 -0400 Subject: [PATCH 01/15] feat: add credential management Signed-off-by: Grant Linville --- credentials.go | 27 ++++++++++++++++++++++++ go.mod | 7 +++++- gptscript.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++ gptscript_test.go | 37 ++++++++++++++++++++++++++++++++ run.go | 9 ++++++++ 5 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 credentials.go diff --git a/credentials.go b/credentials.go new file mode 100644 index 0000000..c6603b1 --- /dev/null +++ b/credentials.go @@ -0,0 +1,27 @@ +package gptscript + +import "time" + +type CredentialType string + +const ( + CredentialTypeTool CredentialType = "tool" + CredentialTypeModelProvider CredentialType = "modelProvider" +) + +type Credential struct { + Context string `json:"context"` + ToolName string `json:"toolName"` + Type CredentialType `json:"type"` + Env map[string]string `json:"env"` + Ephemeral bool `json:"ephemeral,omitempty"` + ExpiresAt *time.Time `json:"expiresAt"` + RefreshToken string `json:"refreshToken"` +} + +type CredentialRequest struct { + Content string `json:"content"` + AllContexts bool `json:"allContexts"` + Context string `json:"context"` + Name string `json:"name"` +} diff --git a/go.mod b/go.mod index 7094ab6..283f8d6 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,13 @@ module github.com/gptscript-ai/go-gptscript go 1.23.0 -require github.com/getkin/kin-openapi v0.124.0 +require ( + github.com/getkin/kin-openapi v0.124.0 + github.com/stretchr/testify v1.8.4 +) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/swag v0.22.8 // indirect github.com/invopop/yaml v0.2.0 // indirect @@ -12,5 +16,6 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/gptscript.go b/gptscript.go index 1968144..019e694 100644 --- a/gptscript.go +++ b/gptscript.go @@ -307,6 +307,60 @@ func (g *GPTScript) PromptResponse(ctx context.Context, resp PromptResponse) err return err } +func (g *GPTScript) ListCredentials(ctx context.Context, credCtx string, allContexts bool) ([]Credential, error) { + req := CredentialRequest{} + if allContexts { + req.AllContexts = true + } else { + req.Context = credCtx + } + + out, err := g.runBasicCommand(ctx, "credentials", req) + if err != nil { + return nil, err + } + + var creds []Credential + if err = json.Unmarshal([]byte(out), &creds); err != nil { + return nil, err + } + return creds, nil +} + +func (g *GPTScript) CreateCredential(ctx context.Context, cred Credential) error { + credJson, err := json.Marshal(cred) + if err != nil { + return fmt.Errorf("failed to marshal credential: %w", err) + } + + _, err = g.runBasicCommand(ctx, "credentials/create", CredentialRequest{Content: string(credJson)}) + return err +} + +func (g *GPTScript) RevealCredential(ctx context.Context, credCtx, name string) (Credential, error) { + out, err := g.runBasicCommand(ctx, "credentials/reveal", CredentialRequest{ + Context: credCtx, + Name: name, + }) + if err != nil { + return Credential{}, err + } + + var cred Credential + if err = json.Unmarshal([]byte(out), &cred); err != nil { + return Credential{}, err + } + return cred, nil +} + +func (g *GPTScript) DeleteCredential(ctx context.Context, credCtx, name string) error { + _, err := g.runBasicCommand(ctx, "credentials/delete", CredentialRequest{ + Context: credCtx, + Name: name, + }) + return err +} + func (g *GPTScript) runBasicCommand(ctx context.Context, requestPath string, body any) (string, error) { run := &Run{ url: g.url, diff --git a/gptscript_test.go b/gptscript_test.go index 6d43e4f..4596a00 100644 --- a/gptscript_test.go +++ b/gptscript_test.go @@ -3,13 +3,16 @@ package gptscript import ( "context" "fmt" + "math/rand" "os" "path/filepath" "runtime" + "strconv" "strings" "testing" "github.com/getkin/kin-openapi/openapi3" + "github.com/stretchr/testify/assert" ) var g *GPTScript @@ -1448,3 +1451,37 @@ func TestLoadTools(t *testing.T) { t.Errorf("Unexpected name: %s", prg.Name) } } + +func TestCredentials(t *testing.T) { + // We will test in the following order of create, list, reveal, delete. + name := "test-" + strconv.Itoa(rand.Int()) + if len(name) > 20 { + name = name[:20] + } + + // Create + err := g.CreateCredential(context.Background(), Credential{ + Context: "testing", + ToolName: name, + Type: CredentialTypeTool, + Env: map[string]string{"ENV": "testing"}, + RefreshToken: "my-refresh-token", + }) + assert.NoError(t, err) + + // List + creds, err := g.ListCredentials(context.Background(), "testing", false) + assert.NoError(t, err) + assert.GreaterOrEqual(t, len(creds), 1) + + // Reveal + cred, err := g.RevealCredential(context.Background(), "testing", name) + assert.NoError(t, err) + assert.Contains(t, cred.Env, "ENV") + assert.Equal(t, cred.Env["ENV"], "testing") + assert.Equal(t, cred.RefreshToken, "my-refresh-token") + + // Delete + err = g.DeleteCredential(context.Background(), "testing", name) + assert.NoError(t, err) +} diff --git a/run.go b/run.go index 5b716aa..9e037a4 100644 --- a/run.go +++ b/run.go @@ -325,6 +325,15 @@ func (r *Run) request(ctx context.Context, payload any) (err error) { done, _ = out["done"].(bool) r.rawOutput = out + case []any: + b, err := json.Marshal(out) + if err != nil { + r.state = Error + r.err = fmt.Errorf("failed to process stdout: %w", err) + return + } + + r.output = string(b) default: r.state = Error r.err = fmt.Errorf("failed to process stdout, invalid type: %T", out) From a28b5798ad81580b568add979a1fba31db6735a5 Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 12 Sep 2024 17:33:57 -0400 Subject: [PATCH 02/15] fix linter error Signed-off-by: Grant Linville --- gptscript.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gptscript.go b/gptscript.go index 019e694..dc8ee7c 100644 --- a/gptscript.go +++ b/gptscript.go @@ -328,12 +328,12 @@ func (g *GPTScript) ListCredentials(ctx context.Context, credCtx string, allCont } func (g *GPTScript) CreateCredential(ctx context.Context, cred Credential) error { - credJson, err := json.Marshal(cred) + credJSON, err := json.Marshal(cred) if err != nil { return fmt.Errorf("failed to marshal credential: %w", err) } - _, err = g.runBasicCommand(ctx, "credentials/create", CredentialRequest{Content: string(credJson)}) + _, err = g.runBasicCommand(ctx, "credentials/create", CredentialRequest{Content: string(credJSON)}) return err } From ebc128c069d30c13ad6cce74369b42df605e3a30 Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Wed, 18 Sep 2024 10:04:31 -0400 Subject: [PATCH 03/15] update for stacked contexts Signed-off-by: Grant Linville --- credentials.go | 8 ++++---- gptscript.go | 10 +++++----- gptscript_test.go | 22 +++++++++++----------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/credentials.go b/credentials.go index c6603b1..4c7c11f 100644 --- a/credentials.go +++ b/credentials.go @@ -20,8 +20,8 @@ type Credential struct { } type CredentialRequest struct { - Content string `json:"content"` - AllContexts bool `json:"allContexts"` - Context string `json:"context"` - Name string `json:"name"` + Content string `json:"content"` + AllContexts bool `json:"allContexts"` + Context []string `json:"context"` + Name string `json:"name"` } diff --git a/gptscript.go b/gptscript.go index dc8ee7c..5fa1146 100644 --- a/gptscript.go +++ b/gptscript.go @@ -307,12 +307,12 @@ func (g *GPTScript) PromptResponse(ctx context.Context, resp PromptResponse) err return err } -func (g *GPTScript) ListCredentials(ctx context.Context, credCtx string, allContexts bool) ([]Credential, error) { +func (g *GPTScript) ListCredentials(ctx context.Context, credCtxs []string, allContexts bool) ([]Credential, error) { req := CredentialRequest{} if allContexts { req.AllContexts = true } else { - req.Context = credCtx + req.Context = credCtxs } out, err := g.runBasicCommand(ctx, "credentials", req) @@ -337,9 +337,9 @@ func (g *GPTScript) CreateCredential(ctx context.Context, cred Credential) error return err } -func (g *GPTScript) RevealCredential(ctx context.Context, credCtx, name string) (Credential, error) { +func (g *GPTScript) RevealCredential(ctx context.Context, credCtxs []string, name string) (Credential, error) { out, err := g.runBasicCommand(ctx, "credentials/reveal", CredentialRequest{ - Context: credCtx, + Context: credCtxs, Name: name, }) if err != nil { @@ -355,7 +355,7 @@ func (g *GPTScript) RevealCredential(ctx context.Context, credCtx, name string) func (g *GPTScript) DeleteCredential(ctx context.Context, credCtx, name string) error { _, err := g.runBasicCommand(ctx, "credentials/delete", CredentialRequest{ - Context: credCtx, + Context: []string{credCtx}, // Only one context can be specified for delete operations Name: name, }) return err diff --git a/gptscript_test.go b/gptscript_test.go index 4596a00..585fa93 100644 --- a/gptscript_test.go +++ b/gptscript_test.go @@ -12,7 +12,7 @@ import ( "testing" "github.com/getkin/kin-openapi/openapi3" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var g *GPTScript @@ -1467,21 +1467,21 @@ func TestCredentials(t *testing.T) { Env: map[string]string{"ENV": "testing"}, RefreshToken: "my-refresh-token", }) - assert.NoError(t, err) + require.NoError(t, err) // List - creds, err := g.ListCredentials(context.Background(), "testing", false) - assert.NoError(t, err) - assert.GreaterOrEqual(t, len(creds), 1) + creds, err := g.ListCredentials(context.Background(), []string{"testing"}, false) + require.NoError(t, err) + require.GreaterOrEqual(t, len(creds), 1) // Reveal - cred, err := g.RevealCredential(context.Background(), "testing", name) - assert.NoError(t, err) - assert.Contains(t, cred.Env, "ENV") - assert.Equal(t, cred.Env["ENV"], "testing") - assert.Equal(t, cred.RefreshToken, "my-refresh-token") + cred, err := g.RevealCredential(context.Background(), []string{"testing"}, name) + require.NoError(t, err) + require.Contains(t, cred.Env, "ENV") + require.Equal(t, cred.Env["ENV"], "testing") + require.Equal(t, cred.RefreshToken, "my-refresh-token") // Delete err = g.DeleteCredential(context.Background(), "testing", name) - assert.NoError(t, err) + require.NoError(t, err) } From 256098258b6641de429859359c63044340138416 Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 10:23:26 -0400 Subject: [PATCH 04/15] in deletecredential, return whether the credential was found Signed-off-by: Grant Linville --- gptscript.go | 48 ++++++++++++++++++++++++++++------------------- gptscript_test.go | 3 ++- run.go | 2 ++ 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/gptscript.go b/gptscript.go index 5fa1146..c36c1ea 100644 --- a/gptscript.go +++ b/gptscript.go @@ -161,7 +161,7 @@ func (g *GPTScript) Parse(ctx context.Context, fileName string, opts ...ParseOpt disableCache = disableCache || opt.DisableCache } - out, err := g.runBasicCommand(ctx, "parse", map[string]any{"file": fileName, "disableCache": disableCache}) + out, _, err := g.runBasicCommand(ctx, "parse", map[string]any{"file": fileName, "disableCache": disableCache}) if err != nil { return nil, err } @@ -180,7 +180,7 @@ func (g *GPTScript) Parse(ctx context.Context, fileName string, opts ...ParseOpt // ParseContent will parse the given string into a tool. func (g *GPTScript) ParseContent(ctx context.Context, toolDef string) ([]Node, error) { - out, err := g.runBasicCommand(ctx, "parse", map[string]any{"content": toolDef}) + out, _, err := g.runBasicCommand(ctx, "parse", map[string]any{"content": toolDef}) if err != nil { return nil, err } @@ -203,7 +203,7 @@ func (g *GPTScript) Fmt(ctx context.Context, nodes []Node) (string, error) { node.TextNode.combine() } - out, err := g.runBasicCommand(ctx, "fmt", Document{Nodes: nodes}) + out, _, err := g.runBasicCommand(ctx, "fmt", Document{Nodes: nodes}) if err != nil { return "", err } @@ -241,7 +241,7 @@ func (g *GPTScript) load(ctx context.Context, payload map[string]any, opts ...Lo } } - out, err := g.runBasicCommand(ctx, "load", payload) + out, _, err := g.runBasicCommand(ctx, "load", payload) if err != nil { return nil, err } @@ -260,7 +260,7 @@ func (g *GPTScript) load(ctx context.Context, payload map[string]any, opts ...Lo // Version will return the output of `gptscript --version` func (g *GPTScript) Version(ctx context.Context) (string, error) { - out, err := g.runBasicCommand(ctx, "version", nil) + out, _, err := g.runBasicCommand(ctx, "version", nil) if err != nil { return "", err } @@ -285,7 +285,7 @@ func (g *GPTScript) ListModels(ctx context.Context, opts ...ListModelsOptions) ( o.Providers = append(o.Providers, g.globalOpts.DefaultModelProvider) } - out, err := g.runBasicCommand(ctx, "list-models", map[string]any{ + out, _, err := g.runBasicCommand(ctx, "list-models", map[string]any{ "providers": o.Providers, "env": g.globalOpts.Env, "credentialOverrides": o.CredentialOverrides, @@ -298,12 +298,12 @@ func (g *GPTScript) ListModels(ctx context.Context, opts ...ListModelsOptions) ( } func (g *GPTScript) Confirm(ctx context.Context, resp AuthResponse) error { - _, err := g.runBasicCommand(ctx, "confirm/"+resp.ID, resp) + _, _, err := g.runBasicCommand(ctx, "confirm/"+resp.ID, resp) return err } func (g *GPTScript) PromptResponse(ctx context.Context, resp PromptResponse) error { - _, err := g.runBasicCommand(ctx, "prompt-response/"+resp.ID, resp.Responses) + _, _, err := g.runBasicCommand(ctx, "prompt-response/"+resp.ID, resp.Responses) return err } @@ -315,7 +315,7 @@ func (g *GPTScript) ListCredentials(ctx context.Context, credCtxs []string, allC req.Context = credCtxs } - out, err := g.runBasicCommand(ctx, "credentials", req) + out, _, err := g.runBasicCommand(ctx, "credentials", req) if err != nil { return nil, err } @@ -333,12 +333,12 @@ func (g *GPTScript) CreateCredential(ctx context.Context, cred Credential) error return fmt.Errorf("failed to marshal credential: %w", err) } - _, err = g.runBasicCommand(ctx, "credentials/create", CredentialRequest{Content: string(credJSON)}) + _, _, err = g.runBasicCommand(ctx, "credentials/create", CredentialRequest{Content: string(credJSON)}) return err } func (g *GPTScript) RevealCredential(ctx context.Context, credCtxs []string, name string) (Credential, error) { - out, err := g.runBasicCommand(ctx, "credentials/reveal", CredentialRequest{ + out, _, err := g.runBasicCommand(ctx, "credentials/reveal", CredentialRequest{ Context: credCtxs, Name: name, }) @@ -353,15 +353,25 @@ func (g *GPTScript) RevealCredential(ctx context.Context, credCtxs []string, nam return cred, nil } -func (g *GPTScript) DeleteCredential(ctx context.Context, credCtx, name string) error { - _, err := g.runBasicCommand(ctx, "credentials/delete", CredentialRequest{ +// DeleteCredential will delete the credential with the given name in the given context. +// A return value of false, nil indicates that the credential was not found. +// false, non-nil error indicates a different error when trying to delete. +// true, nil indicates a successful deletion. +func (g *GPTScript) DeleteCredential(ctx context.Context, credCtx, name string) (bool, error) { + _, code, err := g.runBasicCommand(ctx, "credentials/delete", CredentialRequest{ Context: []string{credCtx}, // Only one context can be specified for delete operations Name: name, }) - return err + if err != nil { + if code == 404 { + return false, nil + } + return false, err + } + return true, nil } -func (g *GPTScript) runBasicCommand(ctx context.Context, requestPath string, body any) (string, error) { +func (g *GPTScript) runBasicCommand(ctx context.Context, requestPath string, body any) (string, int, error) { run := &Run{ url: g.url, requestPath: requestPath, @@ -370,18 +380,18 @@ func (g *GPTScript) runBasicCommand(ctx context.Context, requestPath string, bod } if err := run.request(ctx, body); err != nil { - return "", err + return "", run.responseCode, err } out, err := run.Text() if err != nil { - return "", err + return "", run.responseCode, err } if run.err != nil { - return run.ErrorOutput(), run.err + return run.ErrorOutput(), run.responseCode, run.err } - return out, nil + return out, run.responseCode, nil } func getCommand() string { diff --git a/gptscript_test.go b/gptscript_test.go index 585fa93..c92638d 100644 --- a/gptscript_test.go +++ b/gptscript_test.go @@ -1482,6 +1482,7 @@ func TestCredentials(t *testing.T) { require.Equal(t, cred.RefreshToken, "my-refresh-token") // Delete - err = g.DeleteCredential(context.Background(), "testing", name) + found, err := g.DeleteCredential(context.Background(), "testing", name) require.NoError(t, err) + require.True(t, found) } diff --git a/run.go b/run.go index 9e037a4..f63ab0b 100644 --- a/run.go +++ b/run.go @@ -36,6 +36,7 @@ type Run struct { output, errput string events chan Frame lock sync.Mutex + responseCode int } // Text returns the text output of the gptscript. It blocks until the output is ready. @@ -235,6 +236,7 @@ func (r *Run) request(ctx context.Context, payload any) (err error) { return r.err } + r.responseCode = resp.StatusCode if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest { r.state = Error r.err = fmt.Errorf("run encountered an error") From 3e9d2ba6f7e4f558c6492c24d1c342e53e8f0e1a Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 10:53:28 -0400 Subject: [PATCH 05/15] PR feedback Signed-off-by: Grant Linville --- gptscript.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gptscript.go b/gptscript.go index c36c1ea..15a06d0 100644 --- a/gptscript.go +++ b/gptscript.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "log/slog" + "net/http" "os" "os/exec" "path/filepath" @@ -363,7 +364,7 @@ func (g *GPTScript) DeleteCredential(ctx context.Context, credCtx, name string) Name: name, }) if err != nil { - if code == 404 { + if code == http.StatusNotFound { return false, nil } return false, err From 5960c54077b107d9ff59e537e30015f6a5163942 Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 11:14:44 -0400 Subject: [PATCH 06/15] add credentialcontext to run options Signed-off-by: Grant Linville --- opts.go | 1 + 1 file changed, 1 insertion(+) diff --git a/opts.go b/opts.go index 191a8d0..f4c777c 100644 --- a/opts.go +++ b/opts.go @@ -66,6 +66,7 @@ type Options struct { IncludeEvents bool `json:"includeEvents"` Prompt bool `json:"prompt"` CredentialOverrides []string `json:"credentialOverrides"` + CredentialContext []string `json:"credentialContext"` Location string `json:"location"` ForceSequential bool `json:"forceSequential"` } From 6ed61eee03e4e5c7ee95ca437569b0c226c0b086 Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 11:20:24 -0400 Subject: [PATCH 07/15] add an options struct for ListCredentials Signed-off-by: Grant Linville --- gptscript.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/gptscript.go b/gptscript.go index 15a06d0..b8784f9 100644 --- a/gptscript.go +++ b/gptscript.go @@ -308,12 +308,19 @@ func (g *GPTScript) PromptResponse(ctx context.Context, resp PromptResponse) err return err } -func (g *GPTScript) ListCredentials(ctx context.Context, credCtxs []string, allContexts bool) ([]Credential, error) { +type ListCredentialsOptions struct { + credCtxs []string + allContexts bool +} + +func (g *GPTScript) ListCredentials(ctx context.Context, opts ListCredentialsOptions) ([]Credential, error) { req := CredentialRequest{} - if allContexts { + if opts.allContexts { req.AllContexts = true + } else if opts.credCtxs != nil && len(opts.credCtxs) > 0 { + req.Context = opts.credCtxs } else { - req.Context = credCtxs + req.Context = []string{"default"} } out, _, err := g.runBasicCommand(ctx, "credentials", req) From a7786932f22d523101e831005ca76e58d16c28bb Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 11:21:02 -0400 Subject: [PATCH 08/15] remove knowledge from global tools test Signed-off-by: Grant Linville --- test/global-tools.gpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/global-tools.gpt b/test/global-tools.gpt index 4671fee..7e975be 100644 --- a/test/global-tools.gpt +++ b/test/global-tools.gpt @@ -4,7 +4,7 @@ Runbook 3 --- Name: tool_1 -Global Tools: github.com/gptscript-ai/knowledge, github.com/drpebcak/duckdb, github.com/gptscript-ai/browser, github.com/gptscript-ai/browser-search/google, github.com/gptscript-ai/browser-search/google-question-answerer +Global Tools: github.com/drpebcak/duckdb, github.com/gptscript-ai/browser, github.com/gptscript-ai/browser-search/google, github.com/gptscript-ai/browser-search/google-question-answerer Say "Hello!" From 354587aedf7ac48a9d98e99d17809cad3b3a2347 Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 11:33:50 -0400 Subject: [PATCH 09/15] fix test Signed-off-by: Grant Linville --- gptscript_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gptscript_test.go b/gptscript_test.go index c92638d..700aa78 100644 --- a/gptscript_test.go +++ b/gptscript_test.go @@ -1470,7 +1470,9 @@ func TestCredentials(t *testing.T) { require.NoError(t, err) // List - creds, err := g.ListCredentials(context.Background(), []string{"testing"}, false) + creds, err := g.ListCredentials(context.Background(), ListCredentialsOptions{ + credCtxs: []string{"testing"}, + }) require.NoError(t, err) require.GreaterOrEqual(t, len(creds), 1) From 8d1d053cda5c4cd761b8a050050d9ec77bad2da6 Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 11:37:11 -0400 Subject: [PATCH 10/15] linter Signed-off-by: Grant Linville --- gptscript.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gptscript.go b/gptscript.go index b8784f9..154c649 100644 --- a/gptscript.go +++ b/gptscript.go @@ -317,7 +317,7 @@ func (g *GPTScript) ListCredentials(ctx context.Context, opts ListCredentialsOpt req := CredentialRequest{} if opts.allContexts { req.AllContexts = true - } else if opts.credCtxs != nil && len(opts.credCtxs) > 0 { + } else if len(opts.credCtxs) > 0 { req.Context = opts.credCtxs } else { req.Context = []string{"default"} From accfe59b9dc19d690e67130b5d88c4933aa6ecfc Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 13:35:15 -0400 Subject: [PATCH 11/15] implement a custom type for ErrNotFound Signed-off-by: Grant Linville --- gptscript.go | 49 ++++++++++++++++++----------------------------- gptscript_test.go | 9 +++++++-- run.go | 13 +++++++++++++ 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/gptscript.go b/gptscript.go index 154c649..3ccdcff 100644 --- a/gptscript.go +++ b/gptscript.go @@ -10,7 +10,6 @@ import ( "fmt" "io" "log/slog" - "net/http" "os" "os/exec" "path/filepath" @@ -162,7 +161,7 @@ func (g *GPTScript) Parse(ctx context.Context, fileName string, opts ...ParseOpt disableCache = disableCache || opt.DisableCache } - out, _, err := g.runBasicCommand(ctx, "parse", map[string]any{"file": fileName, "disableCache": disableCache}) + out, err := g.runBasicCommand(ctx, "parse", map[string]any{"file": fileName, "disableCache": disableCache}) if err != nil { return nil, err } @@ -181,7 +180,7 @@ func (g *GPTScript) Parse(ctx context.Context, fileName string, opts ...ParseOpt // ParseContent will parse the given string into a tool. func (g *GPTScript) ParseContent(ctx context.Context, toolDef string) ([]Node, error) { - out, _, err := g.runBasicCommand(ctx, "parse", map[string]any{"content": toolDef}) + out, err := g.runBasicCommand(ctx, "parse", map[string]any{"content": toolDef}) if err != nil { return nil, err } @@ -204,7 +203,7 @@ func (g *GPTScript) Fmt(ctx context.Context, nodes []Node) (string, error) { node.TextNode.combine() } - out, _, err := g.runBasicCommand(ctx, "fmt", Document{Nodes: nodes}) + out, err := g.runBasicCommand(ctx, "fmt", Document{Nodes: nodes}) if err != nil { return "", err } @@ -242,7 +241,7 @@ func (g *GPTScript) load(ctx context.Context, payload map[string]any, opts ...Lo } } - out, _, err := g.runBasicCommand(ctx, "load", payload) + out, err := g.runBasicCommand(ctx, "load", payload) if err != nil { return nil, err } @@ -261,7 +260,7 @@ func (g *GPTScript) load(ctx context.Context, payload map[string]any, opts ...Lo // Version will return the output of `gptscript --version` func (g *GPTScript) Version(ctx context.Context) (string, error) { - out, _, err := g.runBasicCommand(ctx, "version", nil) + out, err := g.runBasicCommand(ctx, "version", nil) if err != nil { return "", err } @@ -286,7 +285,7 @@ func (g *GPTScript) ListModels(ctx context.Context, opts ...ListModelsOptions) ( o.Providers = append(o.Providers, g.globalOpts.DefaultModelProvider) } - out, _, err := g.runBasicCommand(ctx, "list-models", map[string]any{ + out, err := g.runBasicCommand(ctx, "list-models", map[string]any{ "providers": o.Providers, "env": g.globalOpts.Env, "credentialOverrides": o.CredentialOverrides, @@ -299,12 +298,12 @@ func (g *GPTScript) ListModels(ctx context.Context, opts ...ListModelsOptions) ( } func (g *GPTScript) Confirm(ctx context.Context, resp AuthResponse) error { - _, _, err := g.runBasicCommand(ctx, "confirm/"+resp.ID, resp) + _, err := g.runBasicCommand(ctx, "confirm/"+resp.ID, resp) return err } func (g *GPTScript) PromptResponse(ctx context.Context, resp PromptResponse) error { - _, _, err := g.runBasicCommand(ctx, "prompt-response/"+resp.ID, resp.Responses) + _, err := g.runBasicCommand(ctx, "prompt-response/"+resp.ID, resp.Responses) return err } @@ -323,7 +322,7 @@ func (g *GPTScript) ListCredentials(ctx context.Context, opts ListCredentialsOpt req.Context = []string{"default"} } - out, _, err := g.runBasicCommand(ctx, "credentials", req) + out, err := g.runBasicCommand(ctx, "credentials", req) if err != nil { return nil, err } @@ -341,12 +340,12 @@ func (g *GPTScript) CreateCredential(ctx context.Context, cred Credential) error return fmt.Errorf("failed to marshal credential: %w", err) } - _, _, err = g.runBasicCommand(ctx, "credentials/create", CredentialRequest{Content: string(credJSON)}) + _, err = g.runBasicCommand(ctx, "credentials/create", CredentialRequest{Content: string(credJSON)}) return err } func (g *GPTScript) RevealCredential(ctx context.Context, credCtxs []string, name string) (Credential, error) { - out, _, err := g.runBasicCommand(ctx, "credentials/reveal", CredentialRequest{ + out, err := g.runBasicCommand(ctx, "credentials/reveal", CredentialRequest{ Context: credCtxs, Name: name, }) @@ -361,25 +360,15 @@ func (g *GPTScript) RevealCredential(ctx context.Context, credCtxs []string, nam return cred, nil } -// DeleteCredential will delete the credential with the given name in the given context. -// A return value of false, nil indicates that the credential was not found. -// false, non-nil error indicates a different error when trying to delete. -// true, nil indicates a successful deletion. -func (g *GPTScript) DeleteCredential(ctx context.Context, credCtx, name string) (bool, error) { - _, code, err := g.runBasicCommand(ctx, "credentials/delete", CredentialRequest{ +func (g *GPTScript) DeleteCredential(ctx context.Context, credCtx, name string) error { + _, err := g.runBasicCommand(ctx, "credentials/delete", CredentialRequest{ Context: []string{credCtx}, // Only one context can be specified for delete operations Name: name, }) - if err != nil { - if code == http.StatusNotFound { - return false, nil - } - return false, err - } - return true, nil + return err } -func (g *GPTScript) runBasicCommand(ctx context.Context, requestPath string, body any) (string, int, error) { +func (g *GPTScript) runBasicCommand(ctx context.Context, requestPath string, body any) (string, error) { run := &Run{ url: g.url, requestPath: requestPath, @@ -388,18 +377,18 @@ func (g *GPTScript) runBasicCommand(ctx context.Context, requestPath string, bod } if err := run.request(ctx, body); err != nil { - return "", run.responseCode, err + return "", err } out, err := run.Text() if err != nil { - return "", run.responseCode, err + return "", err } if run.err != nil { - return run.ErrorOutput(), run.responseCode, run.err + return run.ErrorOutput(), run.err } - return out, run.responseCode, nil + return out, nil } func getCommand() string { diff --git a/gptscript_test.go b/gptscript_test.go index 700aa78..f7c9bb7 100644 --- a/gptscript_test.go +++ b/gptscript_test.go @@ -2,6 +2,7 @@ package gptscript import ( "context" + "errors" "fmt" "math/rand" "os" @@ -1484,7 +1485,11 @@ func TestCredentials(t *testing.T) { require.Equal(t, cred.RefreshToken, "my-refresh-token") // Delete - found, err := g.DeleteCredential(context.Background(), "testing", name) + err = g.DeleteCredential(context.Background(), "testing", name) require.NoError(t, err) - require.True(t, found) + + // Delete again and make sure we get a NotFoundError + err = g.DeleteCredential(context.Background(), "testing", name) + require.Error(t, err) + require.True(t, errors.As(err, &ErrNotFound{})) } diff --git a/run.go b/run.go index f63ab0b..1b376ab 100644 --- a/run.go +++ b/run.go @@ -17,6 +17,14 @@ import ( var errAbortRun = errors.New("run aborted") +type ErrNotFound struct { + Message string +} + +func (e ErrNotFound) Error() string { + return e.Message +} + type Run struct { url, requestPath, toolPath string tools []ToolDef @@ -61,6 +69,11 @@ func (r *Run) State() RunState { // Err returns the error that caused the gptscript to fail, if any. func (r *Run) Err() error { if r.err != nil { + if r.responseCode == http.StatusNotFound { + return ErrNotFound{ + Message: fmt.Sprintf("run encountered an error: %s", r.errput), + } + } return fmt.Errorf("run encountered an error: %w with error output: %s", r.err, r.errput) } return nil From 512ec19a0d8b57814b830f38c45a33d4ff64ee9b Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 14:07:57 -0400 Subject: [PATCH 12/15] rename field and add test Signed-off-by: Grant Linville --- opts.go | 2 +- run_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ test/credential.gpt | 13 +++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 test/credential.gpt diff --git a/opts.go b/opts.go index f4c777c..b1eae28 100644 --- a/opts.go +++ b/opts.go @@ -66,7 +66,7 @@ type Options struct { IncludeEvents bool `json:"includeEvents"` Prompt bool `json:"prompt"` CredentialOverrides []string `json:"credentialOverrides"` - CredentialContext []string `json:"credentialContext"` + CredentialContexts []string `json:"credentialContext"` // json tag is left singular to match SDKServer Location string `json:"location"` ForceSequential bool `json:"forceSequential"` } diff --git a/run_test.go b/run_test.go index f9014a1..46c91b4 100644 --- a/run_test.go +++ b/run_test.go @@ -2,8 +2,11 @@ package gptscript import ( "context" + "crypto/rand" "runtime" "testing" + + "github.com/stretchr/testify/require" ) func TestRestartingErrorRun(t *testing.T) { @@ -42,3 +45,41 @@ func TestRestartingErrorRun(t *testing.T) { t.Errorf("executing run with input of 0 should not fail: %v", err) } } + +func TestStackedContexts(t *testing.T) { + const name = "testcred" + + bytes := make([]byte, 32) + _, err := rand.Read(bytes) + require.NoError(t, err) + + context1 := string(bytes)[:16] + context2 := string(bytes)[16:] + + run, err := g.Run(context.Background(), "test/credential.gpt", Options{ + CredentialContexts: []string{context1, context2}, + }) + require.NoError(t, err) + + _, err = run.Text() + require.NoError(t, err) + + // The credential should exist in context1 now. + cred, err := g.RevealCredential(context.Background(), []string{context1, context2}, name) + require.NoError(t, err) + require.Equal(t, cred.Context, context1) + + // Now change the context order and run the script again. + run, err = g.Run(context.Background(), "test/credential.gpt", Options{ + CredentialContexts: []string{context2, context1}, + }) + require.NoError(t, err) + + _, err = run.Text() + require.NoError(t, err) + + // Now make sure the credential exists in context1 still. + cred, err = g.RevealCredential(context.Background(), []string{context2, context1}, name) + require.NoError(t, err) + require.Equal(t, cred.Context, context1) +} diff --git a/test/credential.gpt b/test/credential.gpt new file mode 100644 index 0000000..d866e51 --- /dev/null +++ b/test/credential.gpt @@ -0,0 +1,13 @@ +name: echocred +credential: mycredentialtool as testcred + +#!/usr/bin/env bash + +echo $VALUE + +--- +name: mycredentialtool + +#!sys.echo + +hello \ No newline at end of file From 19f35f509d8db4bf610d7abc20341bcd09eaf63f Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 14:14:47 -0400 Subject: [PATCH 13/15] fix test Signed-off-by: Grant Linville --- run_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/run_test.go b/run_test.go index 46c91b4..6eae036 100644 --- a/run_test.go +++ b/run_test.go @@ -3,6 +3,7 @@ package gptscript import ( "context" "crypto/rand" + "encoding/hex" "runtime" "testing" @@ -53,8 +54,8 @@ func TestStackedContexts(t *testing.T) { _, err := rand.Read(bytes) require.NoError(t, err) - context1 := string(bytes)[:16] - context2 := string(bytes)[16:] + context1 := hex.EncodeToString(bytes)[:16] + context2 := hex.EncodeToString(bytes)[16:] run, err := g.Run(context.Background(), "test/credential.gpt", Options{ CredentialContexts: []string{context1, context2}, From 1c0e3ec515052d8fe803b59a32ea3e03e852a436 Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 14:17:00 -0400 Subject: [PATCH 14/15] fix test Signed-off-by: Grant Linville --- test/credential.gpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/credential.gpt b/test/credential.gpt index d866e51..61e656f 100644 --- a/test/credential.gpt +++ b/test/credential.gpt @@ -10,4 +10,4 @@ name: mycredentialtool #!sys.echo -hello \ No newline at end of file +{"env":{"VALUE":"hello"}} \ No newline at end of file From 114eb1193df68b6b2c3ee230bc8b297dff102f98 Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Thu, 19 Sep 2024 16:28:03 -0400 Subject: [PATCH 15/15] fixes Signed-off-by: Grant Linville --- gptscript.go | 10 +++++----- gptscript_test.go | 2 +- run_test.go | 10 +++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/gptscript.go b/gptscript.go index 3ccdcff..0fc11aa 100644 --- a/gptscript.go +++ b/gptscript.go @@ -308,16 +308,16 @@ func (g *GPTScript) PromptResponse(ctx context.Context, resp PromptResponse) err } type ListCredentialsOptions struct { - credCtxs []string - allContexts bool + CredentialContexts []string + AllContexts bool } func (g *GPTScript) ListCredentials(ctx context.Context, opts ListCredentialsOptions) ([]Credential, error) { req := CredentialRequest{} - if opts.allContexts { + if opts.AllContexts { req.AllContexts = true - } else if len(opts.credCtxs) > 0 { - req.Context = opts.credCtxs + } else if len(opts.CredentialContexts) > 0 { + req.Context = opts.CredentialContexts } else { req.Context = []string{"default"} } diff --git a/gptscript_test.go b/gptscript_test.go index f7c9bb7..b1a2456 100644 --- a/gptscript_test.go +++ b/gptscript_test.go @@ -1472,7 +1472,7 @@ func TestCredentials(t *testing.T) { // List creds, err := g.ListCredentials(context.Background(), ListCredentialsOptions{ - credCtxs: []string{"testing"}, + CredentialContexts: []string{"testing"}, }) require.NoError(t, err) require.GreaterOrEqual(t, len(creds), 1) diff --git a/run_test.go b/run_test.go index 6eae036..ac1e1d8 100644 --- a/run_test.go +++ b/run_test.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "encoding/hex" + "os" "runtime" "testing" @@ -50,14 +51,17 @@ func TestRestartingErrorRun(t *testing.T) { func TestStackedContexts(t *testing.T) { const name = "testcred" + wd, err := os.Getwd() + require.NoError(t, err) + bytes := make([]byte, 32) - _, err := rand.Read(bytes) + _, err = rand.Read(bytes) require.NoError(t, err) context1 := hex.EncodeToString(bytes)[:16] context2 := hex.EncodeToString(bytes)[16:] - run, err := g.Run(context.Background(), "test/credential.gpt", Options{ + run, err := g.Run(context.Background(), wd+"/test/credential.gpt", Options{ CredentialContexts: []string{context1, context2}, }) require.NoError(t, err) @@ -71,7 +75,7 @@ func TestStackedContexts(t *testing.T) { require.Equal(t, cred.Context, context1) // Now change the context order and run the script again. - run, err = g.Run(context.Background(), "test/credential.gpt", Options{ + run, err = g.Run(context.Background(), wd+"/test/credential.gpt", Options{ CredentialContexts: []string{context2, context1}, }) require.NoError(t, err)