test(studio): cover commands_host_exec in the add-on list and refresh swagger

Pin the field on the chat and the draft endpoint for off, ask, on, no
setting and an unknown value. Regenerate the swagger files for the new
field.

Assisted-by: Claude:claude-sonnet-5-5
This commit is contained in:
Ettore Di Giacinto committed 2026-11-04 10:20:31 +00:00
1 parent 3e84fe671f
commit a933efeb8a
4 files changed
+109 -4

No files matched your search

@@ -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"),
)
})
+15 -1
View File
@@ -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"
+15 -1
View File
@@ -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"
+17 -2
View File
@@ -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