Changeset 0.13.1 (#65)
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/providers"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/tools/search"
|
||||
)
|
||||
|
||||
type AdminHandler struct {
|
||||
@@ -260,9 +261,32 @@ func (h *AdminHandler) UpdateGlobalSetting(c *gin.Context) {
|
||||
}
|
||||
|
||||
h.auditLog(c, "settings.update", "global_settings", key, nil)
|
||||
|
||||
// Live-apply hooks for settings that have runtime effects
|
||||
if key == "search_config" {
|
||||
applySearchConfig(jsonVal)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "setting updated"})
|
||||
}
|
||||
|
||||
// applySearchConfig updates the active search provider at runtime.
|
||||
func applySearchConfig(raw models.JSONMap) {
|
||||
b, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Failed to marshal search config: %v", err)
|
||||
return
|
||||
}
|
||||
var cfg search.Config
|
||||
if err := json.Unmarshal(b, &cfg); err != nil {
|
||||
log.Printf("⚠️ Failed to parse search config: %v", err)
|
||||
return
|
||||
}
|
||||
if err := search.ApplyConfig(cfg); err != nil {
|
||||
log.Printf("⚠️ Failed to apply search config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AdminHandler) PublicSettings(c *gin.Context) {
|
||||
// Banner config, branding, etc. — safe subset for non-admin users
|
||||
banner, _ := h.stores.GlobalConfig.Get(c.Request.Context(), "banner")
|
||||
|
||||
@@ -38,6 +38,7 @@ type completionRequest struct {
|
||||
TopP *float64 `json:"top_p,omitempty"`
|
||||
Stream *bool `json:"stream,omitempty"`
|
||||
AttachmentIDs []string `json:"attachment_ids,omitempty"` // staged attachment UUIDs to include in request
|
||||
DisabledTools []string `json:"disabled_tools,omitempty"` // tool names to exclude from this request
|
||||
}
|
||||
|
||||
// CompletionHandler proxies LLM requests through the backend.
|
||||
@@ -207,7 +208,7 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
|
||||
// Attach tool definitions if model supports tool calling and tools are available
|
||||
hasBrowserTools := h.hub != nil && h.hub.IsConnected(userID)
|
||||
if caps.ToolCalling && (tools.HasTools() || hasBrowserTools) {
|
||||
provReq.Tools = h.buildToolDefs(c.Request.Context(), userID, hasBrowserTools)
|
||||
provReq.Tools = h.buildToolDefs(c.Request.Context(), userID, hasBrowserTools, req.DisabledTools)
|
||||
}
|
||||
|
||||
// Determine streaming
|
||||
@@ -225,8 +226,15 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
|
||||
|
||||
// buildToolDefs converts registered tools to provider-format tool definitions.
|
||||
// If includeBrowser is true, also fetches tool schemas from enabled browser extensions.
|
||||
func (h *CompletionHandler) buildToolDefs(ctx context.Context, userID string, includeBrowser bool) []providers.ToolDef {
|
||||
allTools := tools.AllDefinitions()
|
||||
// Tools whose names appear in disabledTools are excluded.
|
||||
func (h *CompletionHandler) buildToolDefs(ctx context.Context, userID string, includeBrowser bool, disabledTools []string) []providers.ToolDef {
|
||||
// Build disabled set for O(1) lookup
|
||||
disabled := make(map[string]bool, len(disabledTools))
|
||||
for _, name := range disabledTools {
|
||||
disabled[name] = true
|
||||
}
|
||||
|
||||
allTools := tools.AllDefinitionsFiltered(disabled)
|
||||
defs := make([]providers.ToolDef, 0, len(allTools))
|
||||
for _, t := range allTools {
|
||||
defs = append(defs, providers.ToolDef{
|
||||
@@ -262,6 +270,9 @@ func (h *CompletionHandler) buildToolDefs(ctx context.Context, userID string, in
|
||||
continue
|
||||
}
|
||||
for _, t := range manifest.Tools {
|
||||
if disabled[t.Name] {
|
||||
continue
|
||||
}
|
||||
defs = append(defs, providers.ToolDef{
|
||||
Type: "function",
|
||||
Function: providers.FunctionDef{
|
||||
@@ -277,6 +288,65 @@ func (h *CompletionHandler) buildToolDefs(ctx context.Context, userID string, in
|
||||
return defs
|
||||
}
|
||||
|
||||
// ── List Available Tools ────────────────────
|
||||
// GET /api/v1/tools
|
||||
//
|
||||
// Returns all registered server-side tools plus browser extension tools
|
||||
// for the current user. Used by the frontend to build the tools toggle menu.
|
||||
|
||||
type toolInfo struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category"`
|
||||
}
|
||||
|
||||
func (h *CompletionHandler) ListTools(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
|
||||
// Server-side tools
|
||||
allDefs := tools.AllDefinitions()
|
||||
result := make([]toolInfo, 0, len(allDefs))
|
||||
for _, d := range allDefs {
|
||||
result = append(result, toolInfo{
|
||||
Name: d.Name,
|
||||
DisplayName: d.DisplayName,
|
||||
Description: d.Description,
|
||||
Category: d.Category,
|
||||
})
|
||||
}
|
||||
|
||||
// Browser extension tools
|
||||
if h.hub != nil && h.hub.IsConnected(userID) {
|
||||
exts, err := h.stores.Extensions.ListForUser(c.Request.Context(), userID)
|
||||
if err == nil {
|
||||
for _, ext := range exts {
|
||||
if ext.Tier != "browser" {
|
||||
continue
|
||||
}
|
||||
var manifest struct {
|
||||
Tools []struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
} `json:"tools"`
|
||||
}
|
||||
if err := json.Unmarshal(ext.Manifest, &manifest); err != nil {
|
||||
continue
|
||||
}
|
||||
for _, t := range manifest.Tools {
|
||||
result = append(result, toolInfo{
|
||||
Name: t.Name,
|
||||
Description: t.Description,
|
||||
Category: "browser",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": result})
|
||||
}
|
||||
|
||||
// ── Streaming Completion (SSE) with Tool Loop ──
|
||||
|
||||
const maxToolIterations = 10 // safety limit on tool call rounds
|
||||
|
||||
@@ -46,11 +46,12 @@ type editRequest struct {
|
||||
}
|
||||
|
||||
type regenerateRequest struct {
|
||||
Model string `json:"model,omitempty"`
|
||||
PresetID string `json:"preset_id,omitempty"`
|
||||
APIConfigID string `json:"provider_config_id,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
PresetID string `json:"preset_id,omitempty"`
|
||||
APIConfigID string `json:"provider_config_id,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
DisabledTools []string `json:"disabled_tools,omitempty"`
|
||||
}
|
||||
|
||||
type cursorRequest struct {
|
||||
@@ -455,7 +456,7 @@ func (h *MessageHandler) Regenerate(c *gin.Context) {
|
||||
// Attach tool definitions (same as normal completion)
|
||||
hasBrowserTools := h.hub != nil && h.hub.IsConnected(userID)
|
||||
if caps.ToolCalling && (tools.HasTools() || hasBrowserTools) {
|
||||
provReq.Tools = comp.buildToolDefs(c.Request.Context(), userID, hasBrowserTools)
|
||||
provReq.Tools = comp.buildToolDefs(c.Request.Context(), userID, hasBrowserTools, req.DisabledTools)
|
||||
}
|
||||
|
||||
// ── Stream the response (shared loop handles tools, reasoning, SSE) ──
|
||||
|
||||
Reference in New Issue
Block a user