Phase 1: go-io/go-log → core.Fs{}, core.E(), core.Error/Info/Warn
Phase 2: strings/fmt → core.Contains, core.Sprintf, core.Split etc
Phase 3: embed.FS → core.Mount/core.Embed, core.Extract
Phase 4: cmd/main.go → core.Command(), c.Cli().Run(), no cli package
All packages migrated:
- pkg/lib (Codex): core.Mount, core.Extract, Result returns, AX comments
- pkg/setup (Codex): core.Fs, core.E, fixed missing lib helpers
- pkg/brain (Codex): Core primitives, AX comments
- pkg/monitor (Codex): Core string/logging primitives
- pkg/agentic (Codex): 20 files, Core primitives throughout
- cmd/main.go: pure Core CLI, no fmt/log/filepath/strings/cli
Remaining stdlib: path/filepath (Core doesn't wrap OS paths),
fmt.Sscanf/strings.Map (no Core equivalent).
Co-Authored-By: Virgil <virgil@lethean.io>
336 lines
8.8 KiB
Go
336 lines
8.8 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package brain
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"dappco.re/go/core/api"
|
|
"dappco.re/go/core/api/pkg/provider"
|
|
"dappco.re/go/core/ws"
|
|
"forge.lthn.ai/core/mcp/pkg/mcp/ide"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// BrainProvider wraps the brain Subsystem as a service provider with REST
|
|
// endpoints. It delegates to the same IDE bridge that the MCP tools use.
|
|
type BrainProvider struct {
|
|
bridge *ide.Bridge
|
|
hub *ws.Hub
|
|
}
|
|
|
|
// compile-time interface checks
|
|
var (
|
|
_ provider.Provider = (*BrainProvider)(nil)
|
|
_ provider.Streamable = (*BrainProvider)(nil)
|
|
_ provider.Describable = (*BrainProvider)(nil)
|
|
_ provider.Renderable = (*BrainProvider)(nil)
|
|
)
|
|
|
|
// NewProvider creates a brain provider that proxies to Laravel via the IDE bridge.
|
|
// The WS hub is used to emit brain events. Pass nil for hub if not needed.
|
|
func NewProvider(bridge *ide.Bridge, hub *ws.Hub) *BrainProvider {
|
|
return &BrainProvider{
|
|
bridge: bridge,
|
|
hub: hub,
|
|
}
|
|
}
|
|
|
|
// Name implements api.RouteGroup.
|
|
func (p *BrainProvider) Name() string { return "brain" }
|
|
|
|
// BasePath implements api.RouteGroup.
|
|
func (p *BrainProvider) BasePath() string { return "/api/brain" }
|
|
|
|
// Channels implements provider.Streamable.
|
|
func (p *BrainProvider) Channels() []string {
|
|
return []string{
|
|
"brain.remember.complete",
|
|
"brain.recall.complete",
|
|
"brain.forget.complete",
|
|
}
|
|
}
|
|
|
|
// Element implements provider.Renderable.
|
|
func (p *BrainProvider) Element() provider.ElementSpec {
|
|
return provider.ElementSpec{
|
|
Tag: "core-brain-panel",
|
|
Source: "/assets/brain-panel.js",
|
|
}
|
|
}
|
|
|
|
// RegisterRoutes implements api.RouteGroup.
|
|
func (p *BrainProvider) RegisterRoutes(rg *gin.RouterGroup) {
|
|
rg.POST("/remember", p.remember)
|
|
rg.POST("/recall", p.recall)
|
|
rg.POST("/forget", p.forget)
|
|
rg.GET("/list", p.list)
|
|
rg.GET("/status", p.status)
|
|
}
|
|
|
|
// Describe implements api.DescribableGroup.
|
|
func (p *BrainProvider) Describe() []api.RouteDescription {
|
|
return []api.RouteDescription{
|
|
{
|
|
Method: "POST",
|
|
Path: "/remember",
|
|
Summary: "Store a memory",
|
|
Description: "Store a memory in the shared OpenBrain knowledge store via the Laravel backend.",
|
|
Tags: []string{"brain"},
|
|
RequestBody: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"content": map[string]any{"type": "string"},
|
|
"type": map[string]any{"type": "string"},
|
|
"tags": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
|
|
"project": map[string]any{"type": "string"},
|
|
"confidence": map[string]any{"type": "number"},
|
|
},
|
|
"required": []string{"content", "type"},
|
|
},
|
|
Response: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"success": map[string]any{"type": "boolean"},
|
|
"memoryId": map[string]any{"type": "string"},
|
|
"timestamp": map[string]any{"type": "string", "format": "date-time"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Method: "POST",
|
|
Path: "/recall",
|
|
Summary: "Semantic search memories",
|
|
Description: "Semantic search across the shared OpenBrain knowledge store.",
|
|
Tags: []string{"brain"},
|
|
RequestBody: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"query": map[string]any{"type": "string"},
|
|
"top_k": map[string]any{"type": "integer"},
|
|
"filter": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"project": map[string]any{"type": "string"},
|
|
"type": map[string]any{"type": "string"},
|
|
},
|
|
},
|
|
},
|
|
"required": []string{"query"},
|
|
},
|
|
Response: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"success": map[string]any{"type": "boolean"},
|
|
"count": map[string]any{"type": "integer"},
|
|
"memories": map[string]any{"type": "array"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Method: "POST",
|
|
Path: "/forget",
|
|
Summary: "Remove a memory",
|
|
Description: "Permanently delete a memory from the knowledge store.",
|
|
Tags: []string{"brain"},
|
|
RequestBody: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"id": map[string]any{"type": "string"},
|
|
"reason": map[string]any{"type": "string"},
|
|
},
|
|
"required": []string{"id"},
|
|
},
|
|
Response: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"success": map[string]any{"type": "boolean"},
|
|
"forgotten": map[string]any{"type": "string"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Method: "GET",
|
|
Path: "/list",
|
|
Summary: "List memories",
|
|
Description: "List memories with optional filtering by project, type, and agent.",
|
|
Tags: []string{"brain"},
|
|
Response: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"success": map[string]any{"type": "boolean"},
|
|
"count": map[string]any{"type": "integer"},
|
|
"memories": map[string]any{"type": "array"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Method: "GET",
|
|
Path: "/status",
|
|
Summary: "Brain bridge status",
|
|
Description: "Returns whether the Laravel bridge is connected.",
|
|
Tags: []string{"brain"},
|
|
Response: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"connected": map[string]any{"type": "boolean"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// -- Handlers -----------------------------------------------------------------
|
|
|
|
func (p *BrainProvider) remember(c *gin.Context) {
|
|
if p.bridge == nil {
|
|
c.JSON(http.StatusServiceUnavailable, api.Fail("bridge_unavailable", "brain bridge not available"))
|
|
return
|
|
}
|
|
|
|
var input RememberInput
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
c.JSON(http.StatusBadRequest, api.Fail("invalid_input", err.Error()))
|
|
return
|
|
}
|
|
|
|
err := p.bridge.Send(ide.BridgeMessage{
|
|
Type: "brain_remember",
|
|
Data: map[string]any{
|
|
"content": input.Content,
|
|
"type": input.Type,
|
|
"tags": input.Tags,
|
|
"project": input.Project,
|
|
"confidence": input.Confidence,
|
|
"supersedes": input.Supersedes,
|
|
"expires_in": input.ExpiresIn,
|
|
},
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Fail("bridge_error", err.Error()))
|
|
return
|
|
}
|
|
|
|
p.emitEvent("brain.remember.complete", map[string]any{
|
|
"type": input.Type,
|
|
"project": input.Project,
|
|
})
|
|
|
|
c.JSON(http.StatusOK, api.OK(map[string]any{"success": true}))
|
|
}
|
|
|
|
func (p *BrainProvider) recall(c *gin.Context) {
|
|
if p.bridge == nil {
|
|
c.JSON(http.StatusServiceUnavailable, api.Fail("bridge_unavailable", "brain bridge not available"))
|
|
return
|
|
}
|
|
|
|
var input RecallInput
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
c.JSON(http.StatusBadRequest, api.Fail("invalid_input", err.Error()))
|
|
return
|
|
}
|
|
|
|
err := p.bridge.Send(ide.BridgeMessage{
|
|
Type: "brain_recall",
|
|
Data: map[string]any{
|
|
"query": input.Query,
|
|
"top_k": input.TopK,
|
|
"filter": input.Filter,
|
|
},
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Fail("bridge_error", err.Error()))
|
|
return
|
|
}
|
|
|
|
p.emitEvent("brain.recall.complete", map[string]any{
|
|
"query": input.Query,
|
|
})
|
|
|
|
c.JSON(http.StatusOK, api.OK(RecallOutput{
|
|
Success: true,
|
|
Memories: []Memory{},
|
|
}))
|
|
}
|
|
|
|
func (p *BrainProvider) forget(c *gin.Context) {
|
|
if p.bridge == nil {
|
|
c.JSON(http.StatusServiceUnavailable, api.Fail("bridge_unavailable", "brain bridge not available"))
|
|
return
|
|
}
|
|
|
|
var input ForgetInput
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
c.JSON(http.StatusBadRequest, api.Fail("invalid_input", err.Error()))
|
|
return
|
|
}
|
|
|
|
err := p.bridge.Send(ide.BridgeMessage{
|
|
Type: "brain_forget",
|
|
Data: map[string]any{
|
|
"id": input.ID,
|
|
"reason": input.Reason,
|
|
},
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Fail("bridge_error", err.Error()))
|
|
return
|
|
}
|
|
|
|
p.emitEvent("brain.forget.complete", map[string]any{
|
|
"id": input.ID,
|
|
})
|
|
|
|
c.JSON(http.StatusOK, api.OK(map[string]any{
|
|
"success": true,
|
|
"forgotten": input.ID,
|
|
}))
|
|
}
|
|
|
|
func (p *BrainProvider) list(c *gin.Context) {
|
|
if p.bridge == nil {
|
|
c.JSON(http.StatusServiceUnavailable, api.Fail("bridge_unavailable", "brain bridge not available"))
|
|
return
|
|
}
|
|
|
|
err := p.bridge.Send(ide.BridgeMessage{
|
|
Type: "brain_list",
|
|
Data: map[string]any{
|
|
"project": c.Query("project"),
|
|
"type": c.Query("type"),
|
|
"agent_id": c.Query("agent_id"),
|
|
"limit": c.Query("limit"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Fail("bridge_error", err.Error()))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, api.OK(ListOutput{
|
|
Success: true,
|
|
Memories: []Memory{},
|
|
}))
|
|
}
|
|
|
|
func (p *BrainProvider) status(c *gin.Context) {
|
|
connected := false
|
|
if p.bridge != nil {
|
|
connected = p.bridge.Connected()
|
|
}
|
|
c.JSON(http.StatusOK, api.OK(map[string]any{
|
|
"connected": connected,
|
|
}))
|
|
}
|
|
|
|
// emitEvent sends a WS event if the hub is available.
|
|
func (p *BrainProvider) emitEvent(channel string, data any) {
|
|
if p.hub == nil {
|
|
return
|
|
}
|
|
_ = p.hub.SendToChannel(channel, ws.Message{
|
|
Type: ws.TypeEvent,
|
|
Data: data,
|
|
})
|
|
}
|