diff --git a/docs/docs/03-tools/04-credential-tools.md b/docs/docs/03-tools/04-credential-tools.md index dfddfebb..7178542d 100644 --- a/docs/docs/03-tools/04-credential-tools.md +++ b/docs/docs/03-tools/04-credential-tools.md @@ -159,6 +159,13 @@ need to be aware of which environment variables the credential tool sets. You ca ### Format +:::info +In the examples that follow, `toolA`, `toolB`, etc. are the names of credentials. +By default, a credential has the same name as the tool that created it. +This can be overridden with a credential alias, i.e. `credential: my-cred-tool.gpt as myAlias`. +If the credential has an alias, use it instead of the tool name when you specify an override. +::: + The `--credential-override` argument must be formatted in one of the following three ways: #### 1. Key-Value Pairs diff --git a/docs/docs/04-command-line-reference/gptscript.md b/docs/docs/04-command-line-reference/gptscript.md index 694a563e..8c5d1459 100644 --- a/docs/docs/04-command-line-reference/gptscript.md +++ b/docs/docs/04-command-line-reference/gptscript.md @@ -33,7 +33,6 @@ gptscript [flags] PROGRAM_FILE [INPUT...] -f, --input string Read input from a file ("-" for stdin) ($GPTSCRIPT_INPUT) --list-models List the models available and exit ($GPTSCRIPT_LIST_MODELS) --list-tools List built-in tools and exit ($GPTSCRIPT_LIST_TOOLS) - --listen-address string Server listen address ($GPTSCRIPT_LISTEN_ADDRESS) (default "127.0.0.1:0") --no-trunc Do not truncate long log messages ($GPTSCRIPT_NO_TRUNC) --openai-api-key string OpenAI API KEY ($OPENAI_API_KEY) --openai-base-url string OpenAI base URL ($OPENAI_BASE_URL) @@ -41,7 +40,6 @@ gptscript [flags] PROGRAM_FILE [INPUT...] -o, --output string Save output to a file, or - for stdout ($GPTSCRIPT_OUTPUT) -q, --quiet No output logging (set --quiet=false to force on even when there is no TTY) ($GPTSCRIPT_QUIET) --save-chat-state-file string A file to save the chat state to so that a conversation can be resumed with --chat-state ($GPTSCRIPT_SAVE_CHAT_STATE_FILE) - --server Start server ($GPTSCRIPT_SERVER) --sub-tool string Use tool of this name, not the first tool in file ($GPTSCRIPT_SUB_TOOL) --ui Launch the UI ($GPTSCRIPT_UI) --workspace string Directory to use for the workspace, if specified it will not be deleted on exit ($GPTSCRIPT_WORKSPACE) diff --git a/docs/docs/04-command-line-reference/gptscript_credential.md b/docs/docs/04-command-line-reference/gptscript_credential.md index 4362680a..435ba6e5 100644 --- a/docs/docs/04-command-line-reference/gptscript_credential.md +++ b/docs/docs/04-command-line-reference/gptscript_credential.md @@ -27,4 +27,5 @@ gptscript credential [flags] * [gptscript](gptscript.md) - * [gptscript credential delete](gptscript_credential_delete.md) - Delete a stored credential +* [gptscript credential show](gptscript_credential_show.md) - Show the secret value of a stored credential diff --git a/docs/docs/04-command-line-reference/gptscript_credential_delete.md b/docs/docs/04-command-line-reference/gptscript_credential_delete.md index 29a29b4b..c2f78e88 100644 --- a/docs/docs/04-command-line-reference/gptscript_credential_delete.md +++ b/docs/docs/04-command-line-reference/gptscript_credential_delete.md @@ -6,7 +6,7 @@ title: "gptscript credential delete" Delete a stored credential ``` -gptscript credential delete [flags] +gptscript credential delete [flags] ``` ### Options diff --git a/docs/docs/04-command-line-reference/gptscript_credential_show.md b/docs/docs/04-command-line-reference/gptscript_credential_show.md new file mode 100644 index 00000000..f5fb11af --- /dev/null +++ b/docs/docs/04-command-line-reference/gptscript_credential_show.md @@ -0,0 +1,27 @@ +--- +title: "gptscript credential show" +--- +## gptscript credential show + +Show the secret value of a stored credential + +``` +gptscript credential show [flags] +``` + +### Options + +``` + -h, --help help for show +``` + +### Options inherited from parent commands + +``` + --credential-context string Context name in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) (default "default") +``` + +### SEE ALSO + +* [gptscript credential](gptscript_credential.md) - List stored credentials + diff --git a/pkg/cli/gptscript.go b/pkg/cli/gptscript.go index f758105c..d17afe45 100644 --- a/pkg/cli/gptscript.go +++ b/pkg/cli/gptscript.go @@ -329,7 +329,7 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { // If the user is trying to launch the chat-builder UI, then set up the tool and options here. if r.UI { - args = append([]string{env.VarOrDefault("GPTSCRIPT_CHAT_UI_TOOL", "github.com/gptscript-ai/ui@v0.8.2")}, args...) + args = append([]string{env.VarOrDefault("GPTSCRIPT_CHAT_UI_TOOL", "github.com/gptscript-ai/ui@v0.8.3")}, args...) // If args has more than one element, then the user has provided a file. if len(args) > 1 { @@ -453,7 +453,7 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { } if prg.IsChat() || r.ForceChat { - if !r.DisableTUI && !r.Debug && !r.DebugMessages { + if !r.DisableTUI && !r.Debug && !r.DebugMessages && !r.NoTrunc { // Don't use cmd.Context() because then sigint will cancel everything return tui.Run(context.Background(), args[0], tui.RunOptions{ OpenAIAPIKey: r.OpenAIOptions.APIKey, diff --git a/pkg/loader/loader.go b/pkg/loader/loader.go index 1b119067..c12f976f 100644 --- a/pkg/loader/loader.go +++ b/pkg/loader/loader.go @@ -296,8 +296,13 @@ func readTool(ctx context.Context, cache *cache.Client, prg *types.Program, base } func linkAll(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, tools []types.Tool, localTools types.ToolSet) (result []types.Tool, _ error) { + localToolsMapping := make(map[string]string, len(tools)) + for _, localTool := range localTools { + localToolsMapping[strings.ToLower(localTool.Parameters.Name)] = localTool.ID + } + for _, tool := range tools { - tool, err := link(ctx, cache, prg, base, tool, localTools) + tool, err := link(ctx, cache, prg, base, tool, localTools, localToolsMapping) if err != nil { return nil, err } @@ -306,7 +311,7 @@ func linkAll(ctx context.Context, cache *cache.Client, prg *types.Program, base return } -func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, tool types.Tool, localTools types.ToolSet) (types.Tool, error) { +func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, tool types.Tool, localTools types.ToolSet, localToolsMapping map[string]string) (types.Tool, error) { if existing, ok := prg.ToolSet[tool.ID]; ok { return existing, nil } @@ -331,7 +336,7 @@ func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *so linkedTool = existing } else { var err error - linkedTool, err = link(ctx, cache, prg, base, localTool, localTools) + linkedTool, err = link(ctx, cache, prg, base, localTool, localTools, localToolsMapping) if err != nil { return types.Tool{}, fmt.Errorf("failed linking %s at %s: %w", targetToolName, base, err) } @@ -351,9 +356,7 @@ func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *so } } - for _, localTool := range localTools { - tool.LocalTools[strings.ToLower(localTool.Parameters.Name)] = localTool.ID - } + tool.LocalTools = localToolsMapping tool = builtin.SetDefaults(tool) prg.ToolSet[tool.ID] = tool @@ -438,7 +441,16 @@ func resolve(ctx context.Context, cache *cache.Client, prg *types.Program, base return nil, err } - return readTool(ctx, cache, prg, s, subTool) + result, err := readTool(ctx, cache, prg, s, subTool) + if err != nil { + return nil, err + } + + if len(result) == 0 { + return nil, types.NewErrToolNotFound(types.ToToolName(name, subTool)) + } + + return result, nil } func input(ctx context.Context, cache *cache.Client, base *source, name string) (*source, error) { diff --git a/pkg/loader/url.go b/pkg/loader/url.go index 983112c3..95249478 100644 --- a/pkg/loader/url.go +++ b/pkg/loader/url.go @@ -105,20 +105,13 @@ func loadURL(ctx context.Context, cache *cache.Client, base *source, name string return nil, false, err } - resp, err := http.DefaultClient.Do(req) + data, err := getWithDefaults(req) if err != nil { - return nil, false, err - } else if resp.StatusCode != http.StatusOK { - return nil, false, fmt.Errorf("error loading %s: %s", url, resp.Status) + return nil, false, fmt.Errorf("error loading %s: %v", url, err) } log.Debugf("opened %s", url) - data, err := io.ReadAll(resp.Body) - if err != nil { - return nil, false, fmt.Errorf("error loading %s: %v", url, err) - } - result := &source{ Content: data, Remote: true, @@ -138,6 +131,33 @@ func loadURL(ctx context.Context, cache *cache.Client, base *source, name string return result, true, nil } +func getWithDefaults(req *http.Request) ([]byte, error) { + originalPath := req.URL.Path + for i, def := range types.DefaultFiles { + base := path.Base(originalPath) + if !strings.Contains(base, ".") { + req.URL.Path = path.Join(originalPath, def) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound && i != len(types.DefaultFiles)-1 { + continue + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("error loading %s: %s", req.URL.String(), resp.Status) + } + + return io.ReadAll(resp.Body) + } + panic("unreachable") +} + func ContentFromURL(url string) (string, error) { cache, err := cache.New() if err != nil { diff --git a/pkg/runner/credentials.go b/pkg/runner/credentials.go index 1cc403c0..c547e832 100644 --- a/pkg/runner/credentials.go +++ b/pkg/runner/credentials.go @@ -8,18 +8,18 @@ import ( // parseCredentialOverrides parses a string of credential overrides that the user provided as a command line arg. // The format of credential overrides can be one of three things: -// tool1:ENV1,ENV2;tool2:ENV1,ENV2 (direct mapping of environment variables) -// tool1:ENV1=VALUE1,ENV2=VALUE2;tool2:ENV1=VALUE1,ENV2=VALUE2 (key-value pairs) -// tool1:ENV1->OTHER_ENV1,ENV2->OTHER_ENV2;tool2:ENV1->OTHER_ENV1,ENV2->OTHER_ENV2 (mapping to other environment variables) +// cred1:ENV1,ENV2;cred2:ENV1,ENV2 (direct mapping of environment variables) +// cred1:ENV1=VALUE1,ENV2=VALUE2;cred2:ENV1=VALUE1,ENV2=VALUE2 (key-value pairs) +// cred1:ENV1->OTHER_ENV1,ENV2->OTHER_ENV2;cred2:ENV1->OTHER_ENV1,ENV2->OTHER_ENV2 (mapping to other environment variables) // // This function turns it into a map[string]map[string]string like this: // // { -// "tool1": { +// "cred1": { // "ENV1": "VALUE1", // "ENV2": "VALUE2", // }, -// "tool2": { +// "cred2": { // "ENV1": "VALUE1", // "ENV2": "VALUE2", // }, @@ -28,7 +28,7 @@ func parseCredentialOverrides(override string) (map[string]map[string]string, er credentialOverrides := make(map[string]map[string]string) for _, o := range strings.Split(override, ";") { - toolName, envs, found := strings.Cut(o, ":") + credName, envs, found := strings.Cut(o, ":") if !found { return nil, fmt.Errorf("invalid credential override: %s", o) } @@ -48,7 +48,7 @@ func parseCredentialOverrides(override string) (map[string]map[string]string, er } envMap[key] = value } - credentialOverrides[toolName] = envMap + credentialOverrides[credName] = envMap } return credentialOverrides, nil diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index d3ef3f95..06ccf941 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -813,19 +813,24 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env } for _, credToolName := range callCtx.Tool.Credentials { + toolName, credentialAlias, args, err := types.ParseCredentialArgs(credToolName, callCtx.Input) + if err != nil { + return nil, fmt.Errorf("failed to parse credential tool %q: %w", credToolName, err) + } + + credName := toolName + if credentialAlias != "" { + credName = credentialAlias + } + // Check whether the credential was overridden before we attempt to find it in the store or run the tool. - if override, exists := credOverrides[credToolName]; exists { + if override, exists := credOverrides[credName]; exists { for k, v := range override { env = append(env, fmt.Sprintf("%s=%s", k, v)) } continue } - toolName, credentialAlias, args, err := types.ParseCredentialArgs(credToolName, callCtx.Input) - if err != nil { - return nil, fmt.Errorf("failed to parse credential tool %q: %w", credToolName, err) - } - var ( cred *credentials.Credential exists bool @@ -884,13 +889,9 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env } cred = &credentials.Credential{ - Type: credentials.CredentialTypeTool, - Env: envMap.Env, - } - if credentialAlias != "" { - cred.ToolName = credentialAlias - } else { - cred.ToolName = toolName + Type: credentials.CredentialTypeTool, + Env: envMap.Env, + ToolName: credName, } isEmpty := true diff --git a/pkg/types/tool.go b/pkg/types/tool.go index 9ae7c07f..fe137e6a 100644 --- a/pkg/types/tool.go +++ b/pkg/types/tool.go @@ -30,6 +30,13 @@ type ErrToolNotFound struct { ToolName string } +func ToToolName(toolName, subTool string) string { + if subTool == "" { + return toolName + } + return fmt.Sprintf("%s from %s", subTool, toolName) +} + func NewErrToolNotFound(toolName string) *ErrToolNotFound { return &ErrToolNotFound{ ToolName: toolName,