From 4db35110cffc2c2ee0a91289ece8f8c4c1726d83 Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Mon, 20 Jan 2025 10:26:35 -0700 Subject: [PATCH] chore: enhancements for otto8 --- apiclient/types/thread.go | 1 + go.mod | 3 +- go.sum | 7 +- pkg/api/authz/assistant.go | 6 + pkg/api/handlers/threads.go | 1 + pkg/api/handlers/tools.go | 69 +++++++----- .../workflowexecution/workflowexecution.go | 10 ++ .../handlers/workspace/workspace.go | 2 +- pkg/invoke/system.go | 11 +- pkg/render/tool.go | 2 +- .../openapi/generated/openapi_generated.go | 6 + ui/user/src/app.css | 4 + ui/user/src/lib/components/Thread.svelte | 36 +++--- .../src/lib/components/messages/Input.svelte | 6 +- .../lib/components/messages/Message.svelte | 4 +- .../src/lib/components/navbar/Tools.svelte | 17 +-- ui/user/src/lib/components/tasks/Files.svelte | 5 + .../lib/components/terminal/Terminal.svelte | 2 +- ui/user/src/lib/components/tool/Params.svelte | 4 +- ui/user/src/lib/components/tool/Tool.svelte | 103 ++++++++++++------ ui/user/src/routes/+page.svelte | 6 - 21 files changed, 190 insertions(+), 115 deletions(-) diff --git a/apiclient/types/thread.go b/apiclient/types/thread.go index f9bba3ba0..49f58035b 100644 --- a/apiclient/types/thread.go +++ b/apiclient/types/thread.go @@ -33,6 +33,7 @@ type Thread struct { UserID string `json:"userID,omitempty"` AgentAlias string `json:"agentAlias,omitempty"` Abort bool `json:"abort,omitempty"` + SystemTask bool `json:"systemTask,omitempty"` Env []string `json:"env,omitempty"` } diff --git a/go.mod b/go.mod index d08b46793..60ae9ec8d 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/gptscript-ai/chat-completion-client v0.0.0-20241219123536-85c44096bc10 github.com/gptscript-ai/cmd v0.0.0-20240907001148-ffd49061124a github.com/gptscript-ai/go-gptscript v0.9.6-0.20241216211344-79a66826cf82 - github.com/gptscript-ai/gptscript v0.9.6-0.20250114054318-73a9ffeb1cc8 + github.com/gptscript-ai/gptscript v0.9.6-0.20250120172457-3f876b2ef42b github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de github.com/mhale/smtpd v0.8.3 github.com/oauth2-proxy/oauth2-proxy/v7 v7.0.0-00010101000000-000000000000 @@ -232,6 +232,7 @@ require ( github.com/spf13/viper v1.19.0 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/therootcompany/xz v1.0.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect diff --git a/go.sum b/go.sum index 2a6154fbc..4c7647a79 100644 --- a/go.sum +++ b/go.sum @@ -353,8 +353,8 @@ github.com/gptscript-ai/cmd v0.0.0-20240907001148-ffd49061124a h1:LX7AOcbBoTnUk/ github.com/gptscript-ai/cmd v0.0.0-20240907001148-ffd49061124a/go.mod h1:DJAo1xTht1LDkNYFNydVjTHd576TC7MlpsVRl3oloVw= github.com/gptscript-ai/go-gptscript v0.9.6-0.20241216211344-79a66826cf82 h1:BEN268Z92gqeDc51XVvWdJWdQ47BuuWH3MUysHzilfI= github.com/gptscript-ai/go-gptscript v0.9.6-0.20241216211344-79a66826cf82/go.mod h1:/FVuLwhz+sIfsWUgUHWKi32qT0i6+IXlUlzs70KKt/Q= -github.com/gptscript-ai/gptscript v0.9.6-0.20250114054318-73a9ffeb1cc8 h1:UNtUJkRTKj35jRFK59ndd3O9NFPfk5OzrYl2boUqFqM= -github.com/gptscript-ai/gptscript v0.9.6-0.20250114054318-73a9ffeb1cc8/go.mod h1:eBrKu1mmZ4tLPoHJJD1xT/Ogm5K7Oue14xk54e+yEZw= +github.com/gptscript-ai/gptscript v0.9.6-0.20250120172457-3f876b2ef42b h1:xOnA8rGg3iGjTpvWUtuw8IzPFey5gsivi1CUNtZp4Gc= +github.com/gptscript-ai/gptscript v0.9.6-0.20250120172457-3f876b2ef42b/go.mod h1:eBrKu1mmZ4tLPoHJJD1xT/Ogm5K7Oue14xk54e+yEZw= github.com/gptscript-ai/tui v0.0.0-20240923192013-172e51ccf1d6 h1:vkgNZVWQgbE33VD3z9WKDwuu7B/eJVVMMPM62ixfCR8= github.com/gptscript-ai/tui v0.0.0-20240923192013-172e51ccf1d6/go.mod h1:frrl/B+ZH3VSs3Tqk2qxEIIWTONExX3tuUa4JsVnqx4= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= @@ -633,8 +633,9 @@ github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ai github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/pkg/api/authz/assistant.go b/pkg/api/authz/assistant.go index beb8a5d95..9170270e9 100644 --- a/pkg/api/authz/assistant.go +++ b/pkg/api/authz/assistant.go @@ -2,6 +2,7 @@ package authz import ( "net/http" + "slices" "strings" "github.com/obot-platform/obot/pkg/alias" @@ -20,6 +21,11 @@ func (a *Authorizer) authorizeAssistant(req *http.Request, user user.Info) bool return false } + // Must be authenticated + if !slices.Contains(user.GetGroups(), AuthenticatedGroup) { + return false + } + var ( agentID = paths[3] keys = make([]string, 0, 3) diff --git a/pkg/api/handlers/threads.go b/pkg/api/handlers/threads.go index 17d874ba0..1a5c319fb 100644 --- a/pkg/api/handlers/threads.go +++ b/pkg/api/handlers/threads.go @@ -56,6 +56,7 @@ func convertThread(thread v1.Thread) types.Thread { AgentAlias: thread.Spec.AgentAlias, UserID: thread.Spec.UserUID, Abort: thread.Spec.Abort, + SystemTask: thread.Spec.SystemTask, Env: thread.Spec.Env, } } diff --git a/pkg/api/handlers/tools.go b/pkg/api/handlers/tools.go index c754d1db1..fce7fa21c 100644 --- a/pkg/api/handlers/tools.go +++ b/pkg/api/handlers/tools.go @@ -1,10 +1,12 @@ package handlers import ( + "context" "errors" "maps" "regexp" "slices" + "time" "github.com/gptscript-ai/go-gptscript" "github.com/obot-platform/obot/apiclient/types" @@ -142,36 +144,36 @@ type TestInput struct { } func (t *ToolHandler) Test(req api.Context) error { - toolID := req.PathValue("tool_id") + var ( + toolID = req.PathValue("tool_id") + agent v1.Agent + envs []string + envNameList []string + testID = system.ToolPrefix + "-test-cred" + ) thread, err := getThreadForScope(req) if err != nil { return err } - var tool v1.Tool - if err := req.Get(&tool, toolID); err != nil { + if err := req.Get(&agent, thread.Spec.AgentName); err != nil { return err } - if tool.Spec.ThreadName != thread.Name { - return types.NewErrNotFound("tool %s not found", toolID) + for _, env := range agent.Spec.Manifest.Env { + if env.Name != "" && env.Value != "" { + envs = append(envs, env.Name+"="+env.Value) + } } - env, err := getEnvMap(req, t.gptScript, thread.Name, tool.Name) - if err != nil { + var tool v1.Tool + if err := req.Get(&tool, toolID); err != nil { return err } - var ( - envNameList []string - envNameSeen = map[string]struct{}{} - ) - var envList []string - for k, v := range env { - envNameList = append(envNameList, k) - envNameSeen[k] = struct{}{} - envList = append(envList, k+"="+v) + if tool.Spec.ThreadName != thread.Name { + return types.NewErrNotFound("tool %s not found", toolID) } var input TestInput @@ -179,22 +181,32 @@ func (t *ToolHandler) Test(req api.Context) error { return err } - for k, v := range input.Env { - if _, ok := envNameSeen[k]; !ok { - envNameList = append(envNameList, k) - envNameSeen[k] = struct{}{} + if len(input.Env) > 0 { + for envName := range input.Env { + if invalidEnv.MatchString(envName) { + return types.NewErrBadRequest("invalid env key %s", envName) + } + envNameList = append(envNameList, envName) + } + err := t.gptScript.CreateCredential(req.Context(), gptscript.Credential{ + Context: thread.Name, + ToolName: testID, + Type: gptscript.CredentialTypeTool, + Env: input.Env, + }) + if err != nil { + return err } - envList = append(envList, k+"="+v) + defer func() { + _ = t.gptScript.DeleteCredential(req.Context(), thread.Name, testID) + }() } if input.Tool != nil { tool.Spec.Manifest = input.Tool.ToolManifest } - if tool.Spec.Manifest.Name == "" { - tool.Spec.Manifest.Name = "test" - } - + tool.Spec.Manifest.Name = testID tool.Spec.Envs = envNameList tools, err := render.CustomTool(req.Context(), req.Storage, tool) @@ -202,8 +214,11 @@ func (t *ToolHandler) Test(req api.Context) error { return err } - result, err := t.invoke.EphemeralThreadTask(req.Context(), thread, tools, input.Input, invoke.SystemTaskOptions{ - Env: envList, + timeoutCtx, cancel := context.WithTimeout(req.Context(), 1*time.Minute) + defer cancel() + + result, err := t.invoke.EphemeralThreadTask(timeoutCtx, thread, tools, input.Input, invoke.SystemTaskOptions{ + Env: envs, }) if err != nil { return err diff --git a/pkg/controller/handlers/workflowexecution/workflowexecution.go b/pkg/controller/handlers/workflowexecution/workflowexecution.go index a0c19055a..9da959605 100644 --- a/pkg/controller/handlers/workflowexecution/workflowexecution.go +++ b/pkg/controller/handlers/workflowexecution/workflowexecution.go @@ -121,6 +121,16 @@ func (h *Handler) loadManifest(req router.Request, we *v1.WorkflowExecution) err func (h *Handler) newThread(ctx context.Context, c kclient.Client, wf *v1.Workflow, we *v1.WorkflowExecution) (*v1.Thread, error) { workspaceName := we.Spec.WorkspaceName + if workspaceName == "" && wf.Spec.ThreadName != "" { + var thread v1.Thread + if err := c.Get(ctx, kclient.ObjectKey{Namespace: we.Namespace, Name: wf.Spec.ThreadName}, &thread); err != nil { + return nil, err + } + if thread.Status.WorkspaceName != "" { + workspaceName = thread.Status.WorkspaceName + } + } + if workspaceName == "" { workspaceName = wf.Status.WorkspaceName } diff --git a/pkg/controller/handlers/workspace/workspace.go b/pkg/controller/handlers/workspace/workspace.go index 474868f02..60356a396 100644 --- a/pkg/controller/handlers/workspace/workspace.go +++ b/pkg/controller/handlers/workspace/workspace.go @@ -30,7 +30,7 @@ func getWorkspaceIDs(ctx context.Context, c kclient.WithWatch, ws *v1.Workspace) if err := c.Get(ctx, router.Key(ws.Namespace, wsName), &dependentWS); err != nil || dependentWS.Status.WorkspaceID == "" { return nil, false, err } - wsIDs = append(wsIDs, ws.Status.WorkspaceID) + wsIDs = append(wsIDs, dependentWS.Status.WorkspaceID) } return wsIDs, true, nil diff --git a/pkg/invoke/system.go b/pkg/invoke/system.go index 6e493054f..69ef419d2 100644 --- a/pkg/invoke/system.go +++ b/pkg/invoke/system.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/obot-platform/nah/pkg/router" "github.com/obot-platform/obot/apiclient/types" "github.com/obot-platform/obot/pkg/render" v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" @@ -45,6 +46,13 @@ func (i *Invoker) EphemeralThreadTask(ctx context.Context, thread *v1.Thread, to return "", err } + var agent v1.Agent + if thread.Spec.AgentName != "" { + if err := i.uncached.Get(ctx, router.Key(thread.Namespace, thread.Spec.AgentName), &agent); err != nil { + return "", err + } + } + tool, extraEnv, err = render.Agent(ctx, i.uncached, &v1.Agent{ ObjectMeta: metav1.ObjectMeta{ Namespace: thread.Namespace, @@ -53,6 +61,7 @@ func (i *Invoker) EphemeralThreadTask(ctx context.Context, thread *v1.Thread, to Manifest: types.AgentManifest{ Prompt: "#!sys.call " + toolRef, Tools: []string{toolRef}, + Env: agent.Spec.Manifest.Env, }, }, }, i.serverURL, render.AgentOptions{ @@ -77,7 +86,7 @@ func (i *Invoker) EphemeralThreadTask(ctx context.Context, thread *v1.Thread, to resp, err := i.createRun(ctx, i.uncached, thread, tool, inputString, runOptions{ Ephemeral: true, Env: append(opt.Env, extraEnv...), - CredentialContextIDs: append(credContexts, opt.CredentialContextIDs...), + CredentialContextIDs: append(opt.CredentialContextIDs, credContexts...), Synchronous: true, Timeout: opt.Timeout, }) diff --git a/pkg/render/tool.go b/pkg/render/tool.go index 8a95ba9b5..d6fa680be 100644 --- a/pkg/render/tool.go +++ b/pkg/render/tool.go @@ -19,7 +19,7 @@ func CustomTool(ctx context.Context, c client.Client, tool v1.Tool) (toolDefs [] return nil, nil } - if tool.Spec.Manifest.ToolType != "" && tool.Spec.Manifest.ToolType != "docker" && tool.Spec.Manifest.Instructions == "" { + if tool.Spec.Manifest.ToolType != "" && tool.Spec.Manifest.ToolType != "container" && tool.Spec.Manifest.Instructions == "" { return nil, fmt.Errorf("instructions are required for custom tools") } diff --git a/pkg/storage/openapi/generated/openapi_generated.go b/pkg/storage/openapi/generated/openapi_generated.go index 6e65838ea..24327505f 100644 --- a/pkg/storage/openapi/generated/openapi_generated.go +++ b/pkg/storage/openapi/generated/openapi_generated.go @@ -3489,6 +3489,12 @@ func schema_obot_platform_obot_apiclient_types_Thread(ref common.ReferenceCallba Format: "", }, }, + "systemTask": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, "env": { SchemaProps: spec.SchemaProps{ Type: []string{"array"}, diff --git a/ui/user/src/app.css b/ui/user/src/app.css index 4fe128692..de061a8c7 100644 --- a/ui/user/src/app.css +++ b/ui/user/src/app.css @@ -13,3 +13,7 @@ body { @apply bg-white text-black dark:bg-black dark:text-gray-50; } + +.text-input { + @apply w-full rounded-lg bg-gray-100 p-2 outline-none focus:ring-2 focus:ring-blue dark:bg-gray-900; +} diff --git a/ui/user/src/lib/components/Thread.svelte b/ui/user/src/lib/components/Thread.svelte index 807a165b1..4eaa51557 100644 --- a/ui/user/src/lib/components/Thread.svelte +++ b/ui/user/src/lib/components/Thread.svelte @@ -54,25 +54,23 @@ >
- {#if messages.messages.length < 7} -
- {#if assistant?.introductionMessage} - {@html toHTMLFromMarkdown(assistant.introductionMessage)} - {/if} -
-
- {#each assistant?.starterMessages ?? [] as msg} - - {/each} -
- {/if} +
+ {#if assistant?.introductionMessage} + {@html toHTMLFromMarkdown(assistant.introductionMessage)} + {/if} +
+
+ {#each assistant?.starterMessages ?? [] as msg} + + {/each} +
{#each messages.messages as msg} {/each} diff --git a/ui/user/src/lib/components/messages/Input.svelte b/ui/user/src/lib/components/messages/Input.svelte index 82aaabbd0..2206f616d 100644 --- a/ui/user/src/lib/components/messages/Input.svelte +++ b/ui/user/src/lib/components/messages/Input.svelte @@ -98,9 +98,9 @@ class="peer !ml-4 !mr-2 - scrollbar-none - w-full resize-none - bg-gray-70 !p-2.5 outline-none dark:bg-gray-950" + w-full + resize-none bg-gray-70 + !p-2.5 outline-none scrollbar-none dark:bg-gray-950" {placeholder} >
+

+ Files are private to the task execution. On start of the task a copy of the global workspace + files is made, but no changes are persisted back to the global workspace. +