Skip to content

Commit 0be969a

Browse files
chore: add sys.call
1 parent e7b2e64 commit 0be969a

File tree

10 files changed

+245
-131
lines changed

10 files changed

+245
-131
lines changed

pkg/chat/chat.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,30 @@ func Start(ctx context.Context, prevState runner.ChatState, chatter Chatter, prg
5151
resp runner.ChatResponse
5252
)
5353

54-
prg, err := prg()
54+
prog, err := prg()
5555
if err != nil {
5656
return err
5757
}
5858

59-
prompter.SetPrompt(getPrompt(prg, prevResp))
59+
prompter.SetPrompt(getPrompt(prog, prevResp))
6060

6161
if startInput != "" {
6262
input = startInput
6363
startInput = ""
64-
} else if targetTool := prg.ToolSet[prg.EntryToolID]; !((prevState == nil || prevState == "") && targetTool.Arguments == nil && targetTool.Instructions != "") {
64+
} else if targetTool := prog.ToolSet[prog.EntryToolID]; !((prevState == nil || prevState == "") && targetTool.Arguments == nil && targetTool.Instructions != "") {
6565
// The above logic will skip prompting if this is the first loop and the chat expects no args
6666
input, ok, err = prompter.Readline()
6767
if !ok || err != nil {
6868
return err
6969
}
70+
71+
prog, err = prg()
72+
if err != nil {
73+
return err
74+
}
7075
}
7176

72-
resp, err = chatter.Chat(ctx, prevState, prg, env, input)
77+
resp, err = chatter.Chat(ctx, prevState, prog, env, input)
7378
if err != nil {
7479
return err
7580
}

pkg/cli/eval.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ func (e *Eval) Run(cmd *cobra.Command, args []string) error {
7575

7676
if e.Chat {
7777
return chat.Start(cmd.Context(), nil, runner, func() (types.Program, error) {
78-
return prg, nil
78+
return loader.ProgramFromSource(cmd.Context(), tool.String(), "", loader.Options{
79+
Cache: runner.Cache,
80+
})
7981
}, os.Environ(), toolInput, "")
8082
}
8183

pkg/credentials/credential.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,8 @@ func (c Credential) toDockerAuthConfig() (types.AuthConfig, error) {
5555
func credentialFromDockerAuthConfig(authCfg types.AuthConfig) (Credential, error) {
5656
var cred Credential
5757
if authCfg.Password != "" {
58-
if err := json.Unmarshal([]byte(authCfg.Password), &cred); err != nil || len(cred.Env) == 0 {
59-
// Legacy: try unmarshalling into just an env map
60-
var env map[string]string
61-
if err := json.Unmarshal([]byte(authCfg.Password), &env); err != nil {
62-
return Credential{}, err
63-
}
64-
cred.Env = env
58+
if err := json.Unmarshal([]byte(authCfg.Password), &cred); err != nil {
59+
return cred, fmt.Errorf("failed to unmarshal credential: %w", err)
6560
}
6661
}
6762

pkg/engine/call.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package engine
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/gptscript-ai/gptscript/pkg/types"
9+
)
10+
11+
func (e *Engine) runCall(ctx Context, tool types.Tool, input string) (*Return, error) {
12+
interpreter, body, _ := strings.Cut(tool.Instructions, "\n")
13+
14+
fields := strings.Fields(interpreter)
15+
if len(fields) < 2 {
16+
return nil, fmt.Errorf("invalid tool call, no target tool found in %s", tool.Instructions)
17+
}
18+
toolRef := strings.Join(fields[1:], " ")
19+
20+
toolName, args := types.SplitArg(toolRef)
21+
22+
toolNameParts := strings.Fields(toolName)
23+
24+
toolName = toolNameParts[0]
25+
toolNameArgs := toolNameParts[1:]
26+
27+
targetTools, ok := tool.ToolMapping[toolName]
28+
if !ok || len(targetTools) == 0 {
29+
return nil, fmt.Errorf("target tool %s not found, must reference in `tools:` fields", toolName)
30+
}
31+
32+
ref := types.ToolReference{
33+
Reference: toolName,
34+
Arg: args,
35+
ToolID: targetTools[0].ToolID,
36+
}
37+
38+
newInput, err := types.GetToolRefInput(ctx.Program, ref, input)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
newInput, err = mergeInputs(input, newInput)
44+
if err != nil {
45+
return nil, fmt.Errorf("failed to merge inputs: %w", err)
46+
}
47+
48+
newInput, err = mergeInputs(newInput, toString(map[string]string{
49+
"TOOL_CALL_ARGS": strings.Join(toolNameArgs, " "),
50+
"TOOL_CALL_BODY": body,
51+
}))
52+
53+
newCtx := ctx
54+
newCtx.Tool = ctx.Program.ToolSet[ref.ToolID]
55+
56+
return e.Start(newCtx, newInput)
57+
}
58+
59+
func toString(data map[string]string) string {
60+
out, err := json.Marshal(data)
61+
if err != nil {
62+
// this will never happen
63+
panic(err)
64+
}
65+
return string(out)
66+
}
67+
68+
func mergeInputs(base, overlay string) (string, error) {
69+
baseMap := map[string]interface{}{}
70+
overlayMap := map[string]interface{}{}
71+
72+
if overlay == "" || overlay == "{}" {
73+
return base, nil
74+
}
75+
76+
err := json.Unmarshal([]byte(base), &baseMap)
77+
if err != nil {
78+
return "", fmt.Errorf("failed to unmarshal base input: %w", err)
79+
}
80+
81+
err = json.Unmarshal([]byte(overlay), &overlayMap)
82+
if err != nil {
83+
return "", fmt.Errorf("failed to unmarshal overlay input: %w", err)
84+
}
85+
86+
for k, v := range overlayMap {
87+
baseMap[k] = v
88+
}
89+
90+
out, err := json.Marshal(baseMap)
91+
return string(out), err
92+
}

pkg/engine/engine.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@ func (e *Engine) runCommandTools(ctx Context, tool types.Tool, input string) (*R
286286
return e.runOpenAPI(tool, input)
287287
} else if tool.IsEcho() {
288288
return e.runEcho(tool)
289+
} else if tool.IsCall() {
290+
return e.runCall(ctx, tool, input)
289291
}
290292
s, err := e.runCommand(ctx, tool, input, ctx.ToolCategory)
291293
if err != nil {

pkg/runner/output.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func argsForFilters(prg *types.Program, tool types.ToolReference, startState *St
1919
startInput = *startState.StartInput
2020
}
2121

22-
parsedArgs, err := getToolRefInput(prg, tool, startInput)
22+
parsedArgs, err := types.GetToolRefInput(prg, tool, startInput)
2323
if err != nil {
2424
return "", err
2525
}

pkg/runner/runner.go

Lines changed: 13 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"errors"
77
"fmt"
88
"sort"
9-
"strings"
109
"sync"
1110
"time"
1211

@@ -245,92 +244,6 @@ var (
245244
EventTypeRunFinish EventType = "runFinish"
246245
)
247246

248-
func getToolRefInput(prg *types.Program, ref types.ToolReference, input string) (string, error) {
249-
if ref.Arg == "" {
250-
return "", nil
251-
}
252-
253-
targetArgs := prg.ToolSet[ref.ToolID].Arguments
254-
targetKeys := map[string]string{}
255-
256-
if ref.Arg == "*" {
257-
return input, nil
258-
}
259-
260-
if targetArgs == nil {
261-
return "", nil
262-
}
263-
264-
for targetKey := range targetArgs.Properties {
265-
targetKeys[strings.ToLower(targetKey)] = targetKey
266-
}
267-
268-
inputMap := map[string]interface{}{}
269-
outputMap := map[string]interface{}{}
270-
271-
_ = json.Unmarshal([]byte(input), &inputMap)
272-
for k, v := range inputMap {
273-
inputMap[strings.ToLower(k)] = v
274-
}
275-
276-
fields := strings.Fields(ref.Arg)
277-
278-
for i := 0; i < len(fields); i++ {
279-
field := fields[i]
280-
if field == "and" {
281-
continue
282-
}
283-
if field == "as" {
284-
i++
285-
continue
286-
}
287-
288-
var (
289-
keyName string
290-
val any
291-
)
292-
293-
if strings.HasPrefix(field, "$") {
294-
key := strings.TrimPrefix(field, "$")
295-
key = strings.TrimPrefix(key, "{")
296-
key = strings.TrimSuffix(key, "}")
297-
val = inputMap[strings.ToLower(key)]
298-
} else {
299-
val = field
300-
}
301-
302-
if len(fields) > i+1 && fields[i+1] == "as" {
303-
keyName = strings.ToLower(fields[i+2])
304-
}
305-
306-
if len(targetKeys) == 0 {
307-
return "", fmt.Errorf("can not assign arg to context because target tool [%s] has no defined args", ref.ToolID)
308-
}
309-
310-
if keyName == "" {
311-
if len(targetKeys) != 1 {
312-
return "", fmt.Errorf("can not assign arg to context because target tool [%s] has does not have one args. You must use \"as\" syntax to map the arg to a key %v", ref.ToolID, targetKeys)
313-
}
314-
for k := range targetKeys {
315-
keyName = k
316-
}
317-
}
318-
319-
if targetKey, ok := targetKeys[strings.ToLower(keyName)]; ok {
320-
outputMap[targetKey] = val
321-
} else {
322-
return "", fmt.Errorf("can not assign arg to context because target tool [%s] has does not args [%s]", ref.ToolID, keyName)
323-
}
324-
}
325-
326-
if len(outputMap) == 0 {
327-
return "", nil
328-
}
329-
330-
output, err := json.Marshal(outputMap)
331-
return string(output), err
332-
}
333-
334247
func (r *Runner) getContext(callCtx engine.Context, state *State, monitor Monitor, env []string, input string) (result []engine.InputContext, _ error) {
335248
toolRefs, err := callCtx.Tool.GetToolsByType(callCtx.Program, types.ToolTypeContext)
336249
if err != nil {
@@ -343,7 +256,7 @@ func (r *Runner) getContext(callCtx engine.Context, state *State, monitor Monito
343256
continue
344257
}
345258

346-
contextInput, err := getToolRefInput(callCtx.Program, toolRef, input)
259+
contextInput, err := types.GetToolRefInput(callCtx.Program, toolRef, input)
347260
if err != nil {
348261
return nil, err
349262
}
@@ -878,18 +791,9 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env
878791
refresh bool
879792
)
880793

881-
// Only try to look up the cred if the tool is on GitHub or has an alias.
882-
// If it is a GitHub tool and has an alias, the alias overrides the tool name, so we use it as the credential name.
883-
if isGitHubTool(toolName) && credentialAlias == "" {
884-
c, exists, err = r.credStore.Get(callCtx.Ctx, toolName)
885-
if err != nil {
886-
return nil, fmt.Errorf("failed to get credentials for tool %s: %w", toolName, err)
887-
}
888-
} else if credentialAlias != "" {
889-
c, exists, err = r.credStore.Get(callCtx.Ctx, credentialAlias)
890-
if err != nil {
891-
return nil, fmt.Errorf("failed to get credential %s: %w", credentialAlias, err)
892-
}
794+
c, exists, err = r.credStore.Get(callCtx.Ctx, credName)
795+
if err != nil {
796+
return nil, fmt.Errorf("failed to get credentials for tool %s: %w", toolName, err)
893797
}
894798

895799
if c == nil {
@@ -955,22 +859,17 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env
955859
}
956860

957861
if !resultCredential.Ephemeral {
958-
// Only store the credential if the tool is on GitHub or has an alias, and the credential is non-empty.
959-
if (isGitHubTool(toolName) && callCtx.Program.ToolSet[ref.ToolID].Source.Repo != nil) || credentialAlias != "" {
960-
if isEmpty {
961-
log.Warnf("Not saving empty credential for tool %s", toolName)
862+
if isEmpty {
863+
log.Warnf("Not saving empty credential for tool %s", toolName)
864+
} else {
865+
if refresh {
866+
err = r.credStore.Refresh(callCtx.Ctx, resultCredential)
962867
} else {
963-
if refresh {
964-
err = r.credStore.Refresh(callCtx.Ctx, resultCredential)
965-
} else {
966-
err = r.credStore.Add(callCtx.Ctx, resultCredential)
967-
}
968-
if err != nil {
969-
return nil, fmt.Errorf("failed to save credential for tool %s: %w", toolName, err)
970-
}
868+
err = r.credStore.Add(callCtx.Ctx, resultCredential)
869+
}
870+
if err != nil {
871+
return nil, fmt.Errorf("failed to save credential for tool %s: %w", toolName, err)
971872
}
972-
} else {
973-
log.Warnf("Not saving credential for tool %s - credentials will only be saved for tools from GitHub, or tools that use aliases.", toolName)
974873
}
975874
}
976875
} else {
@@ -992,7 +891,3 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env
992891

993892
return env, nil
994893
}
995-
996-
func isGitHubTool(toolName string) bool {
997-
return strings.HasPrefix(toolName, "github.com")
998-
}

0 commit comments

Comments
 (0)