Changeset 0.22.1 (#95)

This commit is contained in:
2026-03-02 09:58:38 +00:00
parent 06c4e2a5a1
commit cae6fd9f93
16 changed files with 1030 additions and 40 deletions

View File

@@ -113,6 +113,7 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
// ── Preset unwrap: preset overrides defaults, explicit request fields win ──
var presetSystemPrompt string
var personaID string // tracks active persona for KB scoping
var presetThinkingBudget *int // persona-level thinking budget for hook injection (v0.22.1)
if req.PresetID != "" {
preset := ResolvePreset(h.stores, req.PresetID, userID)
if preset == nil {
@@ -136,6 +137,7 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
if preset.SystemPrompt != "" {
presetSystemPrompt = preset.SystemPrompt
}
presetThinkingBudget = preset.ThinkingBudget
}
// ── Project persona fallback (v0.19.2): if no explicit preset, check project ──
@@ -161,6 +163,7 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
if preset.SystemPrompt != "" {
presetSystemPrompt = preset.SystemPrompt
}
presetThinkingBudget = preset.ThinkingBudget
}
}
}
@@ -174,6 +177,17 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
return
}
// ── Inject persona-level thinking budget into provider settings (v0.22.1) ──
// When a persona specifies a thinking budget, promote it into provider
// settings so hooks can activate extended thinking automatically.
if presetThinkingBudget != nil && *presetThinkingBudget > 0 {
if providerCfg.Settings == nil {
providerCfg.Settings = make(map[string]interface{})
}
providerCfg.Settings["extended_thinking"] = true
providerCfg.Settings["thinking_budget"] = *presetThinkingBudget
}
// ── Team policy: require_private_providers ──
if err := enforcePrivateProviderPolicy(userID, configID); err != nil {
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
@@ -284,9 +298,9 @@ func (h *CompletionHandler) Complete(c *gin.Context) {
}
if stream {
h.streamCompletion(c, provider, providerCfg, provReq, channelID, userID, model, configID, providerScope, personaID, workspaceID)
h.streamCompletion(c, provider, providerCfg, provReq, channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID)
} else {
h.syncCompletion(c, provider, providerCfg, provReq, channelID, userID, model, configID, providerScope, personaID, workspaceID)
h.syncCompletion(c, provider, providerCfg, provReq, channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID)
}
}
@@ -385,8 +399,13 @@ func (h *CompletionHandler) multiModelStream(
provReq.Tools = h.buildToolDefs(c.Request.Context(), userID, hasBrowserTools, req.DisabledTools, workspaceID)
}
// Apply provider-specific request hooks (v0.22.1)
if hooks := providers.GetHooks(providerID); hooks != nil {
hooks.PreRequest(providerCfg, &provReq)
}
// Stream this model's response using the shared streaming core
result := streamModelResponse(c, provider, providerCfg, &provReq, model, target.DisplayName, userID, channelID, personaID, workspaceID, configID, h.hub, h.health)
result := streamModelResponse(c, provider, providerCfg, &provReq, model, providerID, target.DisplayName, userID, channelID, personaID, workspaceID, configID, h.hub, h.health)
// Persist assistant message with model attribution
if result.Content != "" {
@@ -549,9 +568,14 @@ func (h *CompletionHandler) streamCompletion(
provider providers.Provider,
cfg providers.ProviderConfig,
req providers.CompletionRequest,
channelID, userID, model, configID, providerScope, personaID, workspaceID string,
channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID string,
) {
result := streamWithToolLoop(c, provider, cfg, &req, model, userID, channelID, personaID, workspaceID, configID, h.hub, h.health)
// Apply provider-specific request hooks (v0.22.1)
if hooks := providers.GetHooks(providerID); hooks != nil {
hooks.PreRequest(cfg, &req)
}
result := streamWithToolLoop(c, provider, cfg, &req, model, providerID, userID, channelID, personaID, workspaceID, configID, h.hub, h.health)
// Persist assistant response
if result.Content != "" {
@@ -573,8 +597,13 @@ func (h *CompletionHandler) syncCompletion(
provider providers.Provider,
cfg providers.ProviderConfig,
req providers.CompletionRequest,
channelID, userID, model, configID, providerScope, personaID, workspaceID string,
channelID, userID, model, providerID, configID, providerScope, personaID, workspaceID string,
) {
// Apply provider-specific request hooks (v0.22.1)
if hooks := providers.GetHooks(providerID); hooks != nil {
hooks.PreRequest(cfg, &req)
}
var totalInput, totalOutput int
var totalCacheCreation, totalCacheRead int
var finalContent string

View File

@@ -9,6 +9,7 @@ import (
capspkg "git.gobha.me/xcaliber/chat-switchboard/capabilities"
"git.gobha.me/xcaliber/chat-switchboard/health"
"git.gobha.me/xcaliber/chat-switchboard/models"
"git.gobha.me/xcaliber/chat-switchboard/providers"
"git.gobha.me/xcaliber/chat-switchboard/store"
)
@@ -278,3 +279,13 @@ func buildSourceAnnotations(catalog *models.ModelCapabilities, heuristic *models
}
return sources
}
// ── Provider Types Endpoint ─────────────────
// GetProviderTypes returns metadata and profile schemas for all registered
// provider types. Used by the admin UI to render provider creation forms
// and show available settings.
func GetProviderTypes(c *gin.Context) {
types := providers.ListTypes()
c.JSON(http.StatusOK, gin.H{"data": types})
}

View File

@@ -516,7 +516,12 @@ func (h *MessageHandler) Regenerate(c *gin.Context) {
// ── Stream the response (shared loop handles tools, reasoning, SSE) ──
result := streamWithToolLoop(c, provider, providerCfg, &provReq, model, userID, channelID, personaID, workspaceID, configID, h.hub, comp.health)
// Apply provider-specific request hooks (v0.22.1)
if hooks := providers.GetHooks(providerID); hooks != nil {
hooks.PreRequest(providerCfg, &provReq)
}
result := streamWithToolLoop(c, provider, providerCfg, &provReq, model, providerID, userID, channelID, personaID, workspaceID, configID, h.hub, comp.health)
// Persist as sibling (regen) with tool activity
if result.Content != "" {

View File

@@ -57,7 +57,7 @@ func streamWithToolLoop(
provider providers.Provider,
cfg providers.ProviderConfig,
req *providers.CompletionRequest,
model, userID, channelID, personaID, workspaceID, configID string,
model, providerType, userID, channelID, personaID, workspaceID, configID string,
hub *events.Hub,
health HealthRecorder,
) streamResult {
@@ -102,6 +102,11 @@ func streamWithToolLoop(
var toolCalls []providers.ToolCall
for event := range ch {
// Apply provider-specific post-processing hooks (v0.22.1)
if hooks := providers.GetHooks(providerType); hooks != nil {
hooks.PostStreamEvent(cfg, &event)
}
if event.Error != nil {
recordHealthFn(health, configID, callStart, event.Error)
sendSSE(fmt.Sprintf(`{"error":"%s"}`, escapeJSON(event.Error.Error())))
@@ -287,7 +292,7 @@ func streamModelResponse(
provider providers.Provider,
cfg providers.ProviderConfig,
req *providers.CompletionRequest,
model, displayName, userID, channelID, personaID, workspaceID, configID string,
model, providerType, displayName, userID, channelID, personaID, workspaceID, configID string,
hub *events.Hub,
health HealthRecorder,
) streamResult {
@@ -322,6 +327,11 @@ func streamModelResponse(
var toolCalls []providers.ToolCall
for event := range ch {
// Apply provider-specific post-processing hooks (v0.22.1)
if hooks := providers.GetHooks(providerType); hooks != nil {
hooks.PostStreamEvent(cfg, &event)
}
if event.Error != nil {
recordHealthFn(health, configID, callStart, event.Error)
sendSSE(fmt.Sprintf(`{"error":"%s"}`, escapeJSON(event.Error.Error())))