V0.38.4 full composition (#237)

Co-authored-by: gobha <jasafpro@gmail.com>
Co-committed-by: gobha <jasafpro@gmail.com>
This commit is contained in:
2026-03-25 17:29:08 +00:00
committed by xcaliber
parent fe5894b6e2
commit 495bcc94f4
17 changed files with 943 additions and 39 deletions

View File

@@ -0,0 +1,103 @@
package handlers
// v0.38.4: Connection type discovery endpoint.
// Returns merged connection types from all active packages.
// Library declarations take precedence over non-library declarations.
import (
"encoding/json"
"net/http"
"github.com/gin-gonic/gin"
"chat-switchboard/store"
)
// ConnectionTypeEntry is a single connection type in the API response.
type ConnectionTypeEntry struct {
Type string `json:"type"`
Label string `json:"label"`
PackageID string `json:"package_id"`
PackageTitle string `json:"package_title"`
Fields map[string]map[string]string `json:"fields"`
Scopes []string `json:"scopes,omitempty"`
}
// ConnectionTypeHandler serves GET /api/v1/connection-types.
type ConnectionTypeHandler struct {
stores store.Stores
}
func NewConnectionTypeHandler(s store.Stores) *ConnectionTypeHandler {
return &ConnectionTypeHandler{stores: s}
}
// ListConnectionTypes returns all connection types declared by active packages.
// Library packages take precedence when multiple packages declare the same type.
func (h *ConnectionTypeHandler) ListConnectionTypes(c *gin.Context) {
ctx := c.Request.Context()
pkgs, err := h.stores.Packages.List(ctx)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list packages"})
return
}
type entry struct {
ct ConnectionTypeEntry
isLibrary bool
}
seen := map[string]*entry{}
for _, pkg := range pkgs {
if pkg.Status != "active" || !pkg.Enabled {
continue
}
connsRaw, ok := pkg.Manifest["connections"]
if !ok {
continue
}
connsJSON, _ := json.Marshal(connsRaw)
var conns []struct {
Type string `json:"type"`
Label string `json:"label"`
Fields map[string]map[string]string `json:"fields"`
Scopes []string `json:"scopes"`
}
if err := json.Unmarshal(connsJSON, &conns); err != nil {
continue
}
isLib := pkg.Type == "library"
for _, cd := range conns {
if cd.Type == "" {
continue
}
existing, exists := seen[cd.Type]
// Library declarations take precedence
if exists && !isLib && existing.isLibrary {
continue
}
label := cd.Label
if label == "" {
label = cd.Type
}
seen[cd.Type] = &entry{
ct: ConnectionTypeEntry{
Type: cd.Type,
Label: label,
PackageID: pkg.ID,
PackageTitle: pkg.Title,
Fields: cd.Fields,
Scopes: cd.Scopes,
},
isLibrary: isLib,
}
}
}
result := make([]ConnectionTypeEntry, 0, len(seen))
for _, e := range seen {
result = append(result, e.ct)
}
c.JSON(http.StatusOK, gin.H{"data": result})
}

View File

@@ -814,6 +814,10 @@ func main() {
protected.GET("/api-configs/:id/models", provCfg.ListModels)
protected.POST("/api-configs/:id/models/fetch", provCfg.FetchModels)
// Connection Type Discovery (v0.38.4)
connTypeH := handlers.NewConnectionTypeHandler(stores)
protected.GET("/connection-types", connTypeH.ListConnectionTypes)
// Extension Connections (personal scope — v0.38.1)
connH := handlers.NewConnectionHandler(stores, keyResolver)
protected.GET("/connections", connH.ListConnections)

View File

@@ -8888,6 +8888,31 @@ paths:
$ref: '#/components/schemas/ExtDependency'
'401':
$ref: '#/components/responses/Unauthorized'
# ── Connection Type Discovery (v0.38.4) ─────────────────────────────
/api/v1/connection-types:
get:
tags: [Extensions]
summary: List available connection types
description: |
Returns merged connection types from all active packages.
Library declarations take precedence over non-library declarations.
Any authenticated user can call this endpoint.
security:
- bearerAuth: []
responses:
'200':
description: Connection type list
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/ConnectionType'
'401':
$ref: '#/components/responses/Unauthorized'
# ── Extension Connections (v0.38.1) ─────────────────────────────────
/api/v1/connections:
get:
@@ -12334,6 +12359,39 @@ components:
updated_at:
type: string
format: date-time
ConnectionType:
type: object
description: "v0.38.4: Available connection type from an installed package"
properties:
type:
type: string
description: Connection type identifier (e.g. gitea, slack)
label:
type: string
description: Human-readable label
package_id:
type: string
description: Package that declares this connection type
package_title:
type: string
description: Title of the declaring package
fields:
type: object
description: Field definitions (name → {type, required, label})
additionalProperties:
type: object
properties:
type:
type: string
required:
type: string
label:
type: string
scopes:
type: array
items:
type: string
description: Allowed scopes (global, team, personal)
ExtDependency:
type: object
description: "v0.38.2: Package dependency record (consumer → library)"