diff --git a/core/http/endpoints/studio/addonhostexec_test.go b/core/http/endpoints/studio/addonhostexec_test.go new file mode 100644 index 0000000000..53bd634b5f --- /dev/null +++ b/core/http/endpoints/studio/addonhostexec_test.go @@ -0,0 +1,62 @@ +package studio + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + + "github.com/labstack/echo/v4" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mudler/LocalAI/core/schema" + studiosvc "github.com/mudler/LocalAI/core/services/studio" + "github.com/mudler/LocalAI/core/services/studio/provider" + "github.com/mudler/LocalAI/core/services/studio/store" + "github.com/mudler/LocalAI/core/services/studio/studiotest" + "github.com/mudler/LocalAI/core/services/studio/workspace" +) + +// The add-on list carries commands_host_exec, a read-only copy of the +// chats_host_exec mode, on the chat endpoint and on the draft endpoint. +var _ = Describe("commands_host_exec in the add-on list", func() { + rigWith := func(mode func() string) (*studiosvc.Service, *echo.Echo) { + h := &studiotest.ToolHarness{Script: func(workspace.Actor, string) []studiotest.ToolStep { + return []studiotest.ToolStep{{Say: "ok"}} + }} + st, err := store.NewJSON("") + Expect(err).NotTo(HaveOccurred()) + svc, err := studiosvc.New(studiosvc.Deps{Store: st, Provider: provider.New(), StateDir: GinkgoT().TempDir(), + Harness: h, HostExecMode: mode}) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { _ = svc.Close(context.Background()) }) + hs := New(svc, nil) + e := echo.New() + e.GET("/api/chats/:id/addons", hs.ListChatAddons) + e.GET("/api/chats/draft/addons", hs.DraftAddons) + return svc, e + } + get := func(e *echo.Echo, path string) schema.ChatsAddonList { + rec := httptest.NewRecorder() + e.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, path, nil)) + Expect(rec.Code).To(Equal(http.StatusOK), rec.Body.String()) + var out schema.ChatsAddonList + Expect(json.Unmarshal(rec.Body.Bytes(), &out)).To(Succeed()) + return out + } + DescribeTable("reports the mode on both endpoints", + func(mode func() string, want string) { + svc, e := rigWith(mode) + rec, err := svc.Create(context.Background(), "", studiosvc.CreateInput{Model: "m"}) + Expect(err).NotTo(HaveOccurred()) + Expect(get(e, "/api/chats/"+rec.ID+"/addons").CommandsHostExec).To(Equal(want)) + Expect(get(e, "/api/chats/draft/addons?model=m").CommandsHostExec).To(Equal(want)) + }, + Entry("off", func() string { return "off" }, "off"), + Entry("ask", func() string { return "ask" }, "ask"), + Entry("on", func() string { return "on" }, "on"), + Entry("no setting reads as off", nil, "off"), + Entry("an unknown value reads as off", func() string { return "maybe" }, "off"), + ) +}) diff --git a/swagger/docs.go b/swagger/docs.go index 75df19ee01..f4eae1d247 100644 --- a/swagger/docs.go +++ b/swagger/docs.go @@ -9040,7 +9040,7 @@ const docTemplate = `{ }, "/api/chats/{id}/addons": { "get": { - "description": "The add-ons whose tools the signed-in user may use now, each with the chat's switch, and whether the chat can run commands at its next turn (commands: available, off, unavailable, none, or switched_off when the chat switched the built-in group shell off). When commands is available, commands_provider says who serves bash (builtin: directly on this machine with no isolation and with its network, or the add-on's ID) and commands_isolation says isolated or host; commands_consent is ask when each command waits for a yes in the chat, and commands_fallback names an add-on that would serve but does not (commands_fallback_reason: off, or why it cannot run). Without the add-on host the list is empty.", + "description": "The add-ons whose tools the signed-in user may use now, each with the chat's switch, and whether the chat can run commands at its next turn (commands: available, off, unavailable, none, or switched_off when the chat switched the built-in group shell off). When commands is available, commands_provider says who serves bash (builtin: directly on this machine with no isolation and with its network, or the add-on's ID) and commands_isolation says isolated or host; commands_consent is ask when each command waits for a yes in the chat, and commands_fallback names an add-on that would serve but does not (commands_fallback_reason: off, or why it cannot run). commands_host_exec is the mode of chats_host_exec now (off, ask or on), so a client can say that the built-in bash is off when commands is none. Without the add-on host the list is empty.", "tags": [ "chats" ], @@ -18234,6 +18234,12 @@ const docTemplate = `{ "installed": { "type": "boolean" }, + "locked": { + "type": "boolean" + }, + "lockedReason": { + "type": "string" + }, "nonce": { "type": "string" }, @@ -18295,6 +18301,10 @@ const docTemplate = `{ "locked": { "type": "boolean" }, + "lockedReason": { + "description": "LockedReason is LockedByDeployment or LockedNoAuth when Locked.", + "type": "string" + }, "revision": { "type": "integer" } @@ -20120,6 +20130,10 @@ const docTemplate = `{ "commands_fallback_reason": { "type": "string" }, + "commands_host_exec": { + "description": "CommandsHostExec is the mode of the setting chats_host_exec as it applies\nnow (off, ask or on), so a client can say that the built-in bash is off\nwhen no add-on runs commands. Every signed-in user may read it.", + "type": "string" + }, "commands_isolation": { "description": "CommandsIsolation is isolated (a sandbox) or host (directly on this\nmachine, with its files and network), when Commands is available.", "type": "string" diff --git a/swagger/swagger.json b/swagger/swagger.json index 7292eddb1c..ee533e2341 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -9037,7 +9037,7 @@ }, "/api/chats/{id}/addons": { "get": { - "description": "The add-ons whose tools the signed-in user may use now, each with the chat's switch, and whether the chat can run commands at its next turn (commands: available, off, unavailable, none, or switched_off when the chat switched the built-in group shell off). When commands is available, commands_provider says who serves bash (builtin: directly on this machine with no isolation and with its network, or the add-on's ID) and commands_isolation says isolated or host; commands_consent is ask when each command waits for a yes in the chat, and commands_fallback names an add-on that would serve but does not (commands_fallback_reason: off, or why it cannot run). Without the add-on host the list is empty.", + "description": "The add-ons whose tools the signed-in user may use now, each with the chat's switch, and whether the chat can run commands at its next turn (commands: available, off, unavailable, none, or switched_off when the chat switched the built-in group shell off). When commands is available, commands_provider says who serves bash (builtin: directly on this machine with no isolation and with its network, or the add-on's ID) and commands_isolation says isolated or host; commands_consent is ask when each command waits for a yes in the chat, and commands_fallback names an add-on that would serve but does not (commands_fallback_reason: off, or why it cannot run). commands_host_exec is the mode of chats_host_exec now (off, ask or on), so a client can say that the built-in bash is off when commands is none. Without the add-on host the list is empty.", "tags": [ "chats" ], @@ -18231,6 +18231,12 @@ "installed": { "type": "boolean" }, + "locked": { + "type": "boolean" + }, + "lockedReason": { + "type": "string" + }, "nonce": { "type": "string" }, @@ -18292,6 +18298,10 @@ "locked": { "type": "boolean" }, + "lockedReason": { + "description": "LockedReason is LockedByDeployment or LockedNoAuth when Locked.", + "type": "string" + }, "revision": { "type": "integer" } @@ -20117,6 +20127,10 @@ "commands_fallback_reason": { "type": "string" }, + "commands_host_exec": { + "description": "CommandsHostExec is the mode of the setting chats_host_exec as it applies\nnow (off, ask or on), so a client can say that the built-in bash is off\nwhen no add-on runs commands. Every signed-in user may read it.", + "type": "string" + }, "commands_isolation": { "description": "CommandsIsolation is isolated (a sandbox) or host (directly on this\nmachine, with its files and network), when Commands is available.", "type": "string" diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml index 4f122b6179..78a9748ec2 100644 --- a/swagger/swagger.yaml +++ b/swagger/swagger.yaml @@ -2106,6 +2106,10 @@ definitions: type: string installed: type: boolean + locked: + type: boolean + lockedReason: + type: string nonce: type: string nonceExpiresAt: @@ -2146,6 +2150,9 @@ definitions: $ref: '#/definitions/gateway.HostGrantsFloor' locked: type: boolean + lockedReason: + description: LockedReason is LockedByDeployment or LockedNoAuth when Locked. + type: string revision: type: integer type: object @@ -3510,6 +3517,12 @@ definitions: type: string commands_fallback_reason: type: string + commands_host_exec: + description: |- + CommandsHostExec is the mode of the setting chats_host_exec as it applies + now (off, ask or on), so a client can say that the built-in bash is off + when no add-on runs commands. Every signed-in user may read it. + type: string commands_isolation: description: |- CommandsIsolation is isolated (a sandbox) or host (directly on this @@ -14463,8 +14476,10 @@ paths: and with its network, or the add-on''s ID) and commands_isolation says isolated or host; commands_consent is ask when each command waits for a yes in the chat, and commands_fallback names an add-on that would serve but does not - (commands_fallback_reason: off, or why it cannot run). Without the add-on - host the list is empty.' + (commands_fallback_reason: off, or why it cannot run). commands_host_exec + is the mode of chats_host_exec now (off, ask or on), so a client can say that + the built-in bash is off when commands is none. Without the add-on host the + list is empty.' parameters: - description: conversation id in: path