feat(tools): implement CRUD operations for thoughts and projects
* Add tools for creating, retrieving, updating, and deleting thoughts. * Implement project management tools for creating and listing projects. * Introduce linking functionality between thoughts. * Add search and recall capabilities for thoughts based on semantic queries. * Implement statistics and summarization tools for thought analysis. * Create database migrations for thoughts, projects, and links. * Add helper functions for UUID parsing and project resolution.
This commit is contained in:
95
internal/tools/capture.go
Normal file
95
internal/tools/capture.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"git.warky.dev/wdevs/amcs/internal/ai"
|
||||
"git.warky.dev/wdevs/amcs/internal/config"
|
||||
"git.warky.dev/wdevs/amcs/internal/metadata"
|
||||
"git.warky.dev/wdevs/amcs/internal/session"
|
||||
"git.warky.dev/wdevs/amcs/internal/store"
|
||||
thoughttypes "git.warky.dev/wdevs/amcs/internal/types"
|
||||
)
|
||||
|
||||
type CaptureTool struct {
|
||||
store *store.DB
|
||||
provider ai.Provider
|
||||
capture config.CaptureConfig
|
||||
sessions *session.ActiveProjects
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
type CaptureInput struct {
|
||||
Content string `json:"content" jsonschema:"the thought or note to capture"`
|
||||
Project string `json:"project,omitempty" jsonschema:"optional project name or id to associate with the thought"`
|
||||
}
|
||||
|
||||
type CaptureOutput struct {
|
||||
Thought thoughttypes.Thought `json:"thought"`
|
||||
}
|
||||
|
||||
func NewCaptureTool(db *store.DB, provider ai.Provider, capture config.CaptureConfig, sessions *session.ActiveProjects, log *slog.Logger) *CaptureTool {
|
||||
return &CaptureTool{store: db, provider: provider, capture: capture, sessions: sessions, log: log}
|
||||
}
|
||||
|
||||
func (t *CaptureTool) Handle(ctx context.Context, req *mcp.CallToolRequest, in CaptureInput) (*mcp.CallToolResult, CaptureOutput, error) {
|
||||
content := strings.TrimSpace(in.Content)
|
||||
if content == "" {
|
||||
return nil, CaptureOutput{}, errInvalidInput("content is required")
|
||||
}
|
||||
|
||||
project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, false)
|
||||
if err != nil {
|
||||
return nil, CaptureOutput{}, err
|
||||
}
|
||||
|
||||
var embedding []float32
|
||||
rawMetadata := metadata.Fallback(t.capture)
|
||||
|
||||
group, groupCtx := errgroup.WithContext(ctx)
|
||||
group.Go(func() error {
|
||||
vector, err := t.provider.Embed(groupCtx, content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
embedding = vector
|
||||
return nil
|
||||
})
|
||||
group.Go(func() error {
|
||||
extracted, err := t.provider.ExtractMetadata(groupCtx, content)
|
||||
if err != nil {
|
||||
t.log.Warn("metadata extraction failed, using fallback", slog.String("provider", t.provider.Name()), slog.String("error", err.Error()))
|
||||
return nil
|
||||
}
|
||||
rawMetadata = extracted
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := group.Wait(); err != nil {
|
||||
return nil, CaptureOutput{}, err
|
||||
}
|
||||
|
||||
thought := thoughttypes.Thought{
|
||||
Content: content,
|
||||
Embedding: embedding,
|
||||
Metadata: metadata.Normalize(rawMetadata, t.capture),
|
||||
}
|
||||
if project != nil {
|
||||
thought.ProjectID = &project.ID
|
||||
}
|
||||
|
||||
created, err := t.store.InsertThought(ctx, thought)
|
||||
if err != nil {
|
||||
return nil, CaptureOutput{}, err
|
||||
}
|
||||
if project != nil {
|
||||
_ = t.store.TouchProject(ctx, project.ID)
|
||||
}
|
||||
|
||||
return nil, CaptureOutput{Thought: created}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user