add initial impl. of scheduler

This commit is contained in:
Cheikh Seck committed 2026-04-18 17:35:03 +00:00
1 parent 1a7b16d97f
commit 6e1398fd8f
5 files changed
+825 -5

No files matched your search

+36 -2
View File
@@ -35,6 +35,7 @@ import (
"github.com/cheikh2shift/godex/internal/mcp"
"github.com/cheikh2shift/godex/internal/ml"
"github.com/cheikh2shift/godex/internal/providers"
"github.com/cheikh2shift/godex/internal/scheduler"
"github.com/cheikh2shift/godex/internal/wizard"
"github.com/cheikh2shift/godex/modelquery"
)
@@ -62,7 +63,7 @@ type MCPServer interface {
Close() error
}
var slashCommands = []string{"/add-path ", "/remove-path ", "/paths", "/tools", "/clear-context", "/commit ", "/commit-pull ", "/commit-merge ", "/commit-search ", "/exit", "/quit", "/q", "/save", "/save-exit", "/kill ", "/killbg", "/bg", "/clear", "/help", "/model", "/model-persist"}
var slashCommands = []string{"/add-path ", "/remove-path ", "/paths", "/tools", "/clear-context", "/commit ", "/commit-pull ", "/commit-merge ", "/commit-search ", "/exit", "/quit", "/q", "/save", "/save-exit", "/kill ", "/killbg", "/bg", "/clear", "/help", "/model", "/model-persist", "/schedule"}
var (
greenOrb = lipgloss.NewStyle().Foreground(lipgloss.Color("82")).Render("●")
@@ -73,6 +74,7 @@ var (
pathPromptMu sync.Mutex
hivePendingStop chan struct{}
summarisingStart chan struct{}
sched *scheduler.Scheduler
)
var thinkingStyle = lipgloss.NewStyle().
@@ -319,10 +321,34 @@ func main() {
setter.SetStatusChannel(statusCh)
}
sched, err = scheduler.NewDefault()
if err != nil {
log.Printf("[Scheduler] Failed to initialize: %v", err)
} else {
sched.SetProviderGetter(&schedulerProviderGetter{provider: provider})
schedulerServer := mcp.NewSchedulerServer(sched, &schedulerProviderGetter{provider: provider})
servers = append(servers, schedulerServer)
for _, tool := range schedulerServer.Tools() {
var inputSchema map[string]interface{}
if err := json.Unmarshal(tool.InputSchema, &inputSchema); err != nil {
inputSchema = map[string]interface{}{}
}
providerTools = append(providerTools, providers.Tool{
Name: tool.Name,
Description: tool.Description,
InputSchema: inputSchema,
})
}
agent.SetProviderTools(provider, providerTools)
if debugMode {
fmt.Println(muted.Render("[Scheduler] Initialized"))
}
}
fmt.Println()
if prompt != "" {
err := runSinglePrompt(ctx, provider, prompt, autoConfirm, debugMode, servers)
err = runSinglePrompt(ctx, provider, prompt, autoConfirm, debugMode, servers)
cleanup(servers)
if err != nil {
log.Fatalf("prompt failed: %v", err)
@@ -852,6 +878,10 @@ promptLoop:
}
continue
}
if strings.HasPrefix(input, "/schedule") {
handleSchedule(input)
continue
}
nativeToolCalls := llmProvider.SupportsNativeToolCalls()
var toolsSection string
@@ -1234,6 +1264,9 @@ promptLoop:
if historyDB != nil {
historyDB.Close()
}
if sched != nil {
sched.Close()
}
fmt.Println("Goodbye!")
}
@@ -1955,6 +1988,7 @@ Commands:
/commit-merge <ref> - Merge committed history into current state
/commit-search <query> - Search commits by ref or message
/clear-context - Reset context counter and LLM client
/schedule - List scheduled tasks (see /schedule help for more)
/save, /save-exit - Save session and exit
/kill <pid> - Kill a background process by PID
/killbg - Kill all background processes
+148
View File
@@ -0,0 +1,148 @@
package main
import (
"fmt"
"strings"
"github.com/cheikh2shift/godex/internal/agent"
"github.com/cheikh2shift/godex/internal/config"
"github.com/cheikh2shift/godex/internal/scheduler"
)
type schedulerProviderGetter struct {
provider *config.Provider
}
func (s *schedulerProviderGetter) GetProvider(cfg interface{}) (interface{}, error) {
cfgMap, ok := cfg.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid config type")
}
providerType, _ := cfgMap["type"].(string)
providerName, _ := cfgMap["name"].(string)
providerModel, _ := cfgMap["model"].(string)
if providerType == "unknown" && s.provider != nil {
providerType = s.provider.Type
providerName = s.provider.Name
providerModel = s.provider.Model
}
return agent.GetProviderFromConfig(&agent.ProviderConfig{
Type: providerType,
Name: providerName,
Model: providerModel,
})
}
func handleSchedule(input string) {
if sched == nil {
fmt.Println("[Scheduler] Not initialized")
return
}
parts := strings.Fields(input)
if len(parts) == 1 {
listScheduledTasks()
return
}
if len(parts) >= 2 && (parts[1] == "help" || parts[1] == "-h" || parts[1] == "--help") {
fmt.Println("Scheduler - Schedule prompts to run at specific times or intervals")
fmt.Println("")
fmt.Println("Usage:")
fmt.Println(" /schedule - List all scheduled tasks")
fmt.Println(" /schedule stop <id> - Stop a running task by 4-char ID")
fmt.Println(" /schedule remove <id> - Remove a task from the schedule by 4-char ID")
fmt.Println(" /schedule help - Show this help message")
fmt.Println("")
fmt.Println("LLM Tool (schedule_task):")
fmt.Println(" prompt: The prompt to execute")
fmt.Println(" interval_sec: Repeating interval in seconds (e.g., 60, 3600)")
fmt.Println(" run_at: Time in HH:MM format (e.g., '14:30') or 'now' for immediate execution")
fmt.Println("")
fmt.Println("Examples:")
fmt.Println(" schedule_task(prompt='Summarize my emails', interval_sec=3600)")
fmt.Println(" schedule_task(prompt='Check system status', run_at='09:00')")
fmt.Println(" schedule_task(prompt='Generate report', run_at='now')")
return
}
if len(parts) >= 3 && parts[1] == "stop" {
id := strings.ToUpper(parts[2])
if len(id) != 4 {
fmt.Println("Invalid task ID. Use 4-character identifier (e.g., ABC1)")
return
}
if sched.StopTask(id) {
fmt.Printf("Task %s stopped\n", id)
} else {
fmt.Printf("Task %s not found\n", id)
}
return
}
if len(parts) >= 3 && parts[1] == "remove" {
id := strings.ToUpper(parts[2])
if len(id) != 4 {
fmt.Println("Invalid task ID. Use 4-character identifier (e.g., ABC1)")
return
}
if sched.RemoveTask(id) {
fmt.Printf("Task %s removed\n", id)
} else {
fmt.Printf("Task %s not found\n", id)
}
return
}
fmt.Println("Usage:")
fmt.Println(" /schedule - List all scheduled tasks")
fmt.Println(" /schedule stop <id> - Stop a task by 4-char ID")
fmt.Println(" /schedule remove <id> - Remove a task by 4-char ID")
}
func listScheduledTasks() {
tasks := sched.ListTasks()
if len(tasks) == 0 {
fmt.Println("No scheduled tasks")
return
}
fmt.Printf("\n%-6s %-12s %-10s %-8s %-8s %s\n", "ID", "Created", "Interval", "Runs", "Last Run", "Status")
fmt.Println(strings.Repeat("-", 80))
for _, t := range tasks {
task, ok := t.(*scheduler.ScheduledTask)
if !ok {
continue
}
interval := fmt.Sprintf("%ds", task.IntervalSec)
if task.RunAt != "" {
interval = task.RunAt
}
lastRun := "Never"
if !task.LastRun.IsZero() {
lastRun = task.LastRun.Format("2006-01-02 15:04")
}
status := "OK"
if task.LastError != "" {
status = truncate(task.LastError, 30)
}
fmt.Printf("%-6s %-12s %-10s %-8d %-8s %s\n",
task.ID,
task.CreatedAt.Format("2006-01-02"),
interval,
task.RunCount,
lastRun,
status,
)
}
fmt.Println()
}
+18 -3
View File
@@ -48,7 +48,6 @@ func SendPrompt(ctx context.Context, provider *config.Provider, prompt string) (
return strings.TrimSpace(resp), nil
}
func CancelPrompt(provider *config.Provider) {
if provider == nil {
return
@@ -71,7 +70,6 @@ func CancelPrompt(provider *config.Provider) {
mcpExecutorsMu.Unlock()
}
func SetProviderTools(provider *config.Provider, tools []providers.Tool) {
p, err := GetProvider(provider)
if err != nil {
@@ -82,7 +80,6 @@ func SetProviderTools(provider *config.Provider, tools []providers.Tool) {
}
}
func RegisterMCPExecutor(provider *config.Provider, executor *mcp.MCPToolExecutor) {
if provider == nil || executor == nil {
return
@@ -147,3 +144,21 @@ func GetProvider(cfg *config.Provider) (providers.Provider, error) {
func cacheKey(cfg *config.Provider) string {
return fmt.Sprintf("%s|%s|%s|%s", cfg.Type, cfg.Name, cfg.Model, cfg.Endpoint)
}
type ProviderConfig struct {
Type string
Name string
Model string
}
func GetProviderFromConfig(cfg *ProviderConfig) (providers.Provider, error) {
if cfg == nil {
return nil, fmt.Errorf("nil config")
}
provider := &config.Provider{
Type: cfg.Type,
Name: cfg.Name,
Model: cfg.Model,
}
return GetProvider(provider)
}
+175
View File
@@ -0,0 +1,175 @@
package mcp
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
)
type SchedulerServer struct {
scheduler *schedulerExt
}
type schedulerExt struct {
scheduler interface {
AddTask(prompt string, intervalSec int, runAt string, providerType, providerName, providerModel string) (interface{}, error)
RunNow(prompt string, providerType, providerName, providerModel string) (string, error)
StopTask(id string) bool
RemoveTask(id string) bool
ListTasks() []interface{}
GetTask(id string) interface{}
}
mu interface {
Lock()
Unlock()
}
provider interface {
GetProvider(cfg interface{}) (interface{}, error)
}
providerMu interface {
Lock()
Unlock()
}
}
type TaskInfo struct {
ID string `json:"id"`
Prompt string `json:"prompt"`
IntervalSec int `json:"interval_sec"`
RunAt string `json:"run_at"`
IsRepeating bool `json:"is_repeating"`
CreatedAt time.Time `json:"created_at"`
LastRun time.Time `json:"last_run"`
RunCount int `json:"run_count"`
LastError string `json:"last_error"`
}
func NewSchedulerServer(scheduler interface {
AddTask(prompt string, intervalSec int, runAt string, providerType, providerName, providerModel string) (interface{}, error)
RunNow(prompt string, providerType, providerName, providerModel string) (string, error)
StopTask(id string) bool
RemoveTask(id string) bool
ListTasks() []interface{}
GetTask(id string) interface{}
}, providerGetter interface {
GetProvider(cfg interface{}) (interface{}, error)
}) *SchedulerServer {
return &SchedulerServer{
scheduler: &schedulerExt{
scheduler: scheduler,
},
}
}
func (s *SchedulerServer) Name() string {
return "scheduler"
}
func (s *SchedulerServer) Tools() []Tool {
currentTime := time.Now().Format("2006-01-02 15:04:05")
return []Tool{
{
Name: "schedule_task",
Description: fmt.Sprintf("Schedule a task to run a prompt at a specific time or at regular intervals. Current time: %s. Use interval_sec for repeating tasks (e.g., 60 for every minute, 3600 for every hour), or run_at for specific times (e.g., \"14:30\" for 2:30 PM daily, or \"now\" to execute immediately).", currentTime),
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "The prompt to execute when the task runs"
},
"interval_sec": {
"type": "integer",
"description": "Interval in seconds for repeating tasks (e.g., 60, 300, 3600). Use 0 or omit for one-time task."
},
"run_at": {
"type": "string",
"description": "Specific time to run in HH:MM format (e.g., '14:30' for 2:30 PM), or 'now' to execute immediately."
}
},
"required": ["prompt"]
}`),
},
}
}
func (s *SchedulerServer) CallTool(ctx context.Context, name string, args map[string]interface{}) (string, error) {
if name != "schedule_task" {
return "", fmt.Errorf("unknown tool: %s", name)
}
prompt, ok := args["prompt"].(string)
if !ok || strings.TrimSpace(prompt) == "" {
return "", fmt.Errorf("prompt is required")
}
intervalSec := 0
if iv, ok := args["interval_sec"].(float64); ok {
intervalSec = int(iv)
}
runAt := ""
if rt, ok := args["run_at"].(string); ok {
runAt = strings.TrimSpace(rt)
}
isNow := strings.ToLower(runAt) == "now"
if intervalSec <= 0 && runAt == "" {
return "", fmt.Errorf("must specify either interval_sec or run_at")
}
if isNow {
result, err := s.scheduler.scheduler.RunNow(prompt, "unknown", "unknown", "unknown")
if err != nil {
return fmt.Sprintf("Error running task: %v", err), nil
}
return fmt.Sprintf("Task executed immediately.\n\nResult:\n%s", result), nil
}
task, err := s.scheduler.scheduler.AddTask(prompt, intervalSec, runAt, "unknown", "unknown", "unknown")
if err != nil {
return fmt.Sprintf("Error scheduling task: %v", err), nil
}
if task == nil {
return "Error: task was not created", nil
}
taskInfo, ok := task.(TaskInfo)
if !ok {
return fmt.Sprintf("Task scheduled successfully but could not get ID"), nil
}
return fmt.Sprintf("Task scheduled successfully with ID: %s\nPrompt: %s\nInterval: %d seconds\nRun at: %s",
taskInfo.ID, taskInfo.Prompt, taskInfo.IntervalSec, taskInfo.RunAt), nil
}
func (s *SchedulerServer) AllowedPaths() []string {
return nil
}
func (s *SchedulerServer) AddPath(ctx context.Context, path string) error {
return fmt.Errorf("scheduler server does not support paths")
}
func (s *SchedulerServer) TempAddPath(path string) {
}
func (s *SchedulerServer) AddURL(ctx context.Context, url string) error {
return fmt.Errorf("scheduler server does not support URLs")
}
func (s *SchedulerServer) RemovePath(ctx context.Context, path string) error {
return fmt.Errorf("scheduler server does not support paths")
}
func (s *SchedulerServer) RemoveURL(ctx context.Context, url string) error {
return fmt.Errorf("scheduler server does not support URLs")
}
func (s *SchedulerServer) Close() error {
return nil
}
+448
View File
@@ -0,0 +1,448 @@
package scheduler
import (
"context"
"crypto/rand"
"database/sql"
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"sync"
"time"
_ "modernc.org/sqlite"
)
type ScheduledTask struct {
ID string `json:"id"`
Prompt string `json:"prompt"`
IntervalSec int `json:"interval_sec"`
RunAt string `json:"run_at"`
IsRepeating bool `json:"is_repeating"`
CreatedAt time.Time `json:"created_at"`
LastRun time.Time `json:"last_run"`
RunCount int `json:"run_count"`
LastError string `json:"last_error"`
ProviderType string `json:"provider_type"`
ProviderName string `json:"provider_name"`
ProviderModel string `json:"provider_model"`
}
type Scheduler struct {
db *sql.DB
tasks map[string]*ScheduledTask
taskMu sync.RWMutex
running map[string]context.CancelFunc
runMu sync.RWMutex
stopCh chan struct{}
wg sync.WaitGroup
provider ProviderGetter
}
type ProviderGetter interface {
GetProvider(cfg interface{}) (interface{}, error)
}
func getDefaultDBPath() string {
if runtime.GOOS == "windows" {
return filepath.Join(os.Getenv("LOCALAPPDATA"), "godex", "scheduler.db")
}
homeDir, err := os.UserHomeDir()
if err != nil || homeDir == "" {
tmpDir := os.TempDir()
return filepath.Join(tmpDir, "godex-scheduler.db")
}
return filepath.Join(homeDir, ".godex", "scheduler.db")
}
func New(dbPath string) (*Scheduler, error) {
if dbPath == "" {
dbPath = getDefaultDBPath()
}
dir := filepath.Dir(dbPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, err
}
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return nil, err
}
if err := db.Ping(); err != nil {
return nil, err
}
s := &Scheduler{
db: db,
tasks: make(map[string]*ScheduledTask),
running: make(map[string]context.CancelFunc),
stopCh: make(chan struct{}),
}
if err := s.initSchema(); err != nil {
db.Close()
return nil, err
}
if err := s.loadTasks(); err != nil {
db.Close()
return nil, err
}
return s, nil
}
func NewDefault() (*Scheduler, error) {
return New("")
}
func (s *Scheduler) initSchema() error {
schema := `
CREATE TABLE IF NOT EXISTS scheduled_tasks (
id TEXT PRIMARY KEY,
prompt TEXT NOT NULL,
interval_sec INTEGER NOT NULL,
run_at TEXT NOT NULL,
is_repeating INTEGER NOT NULL,
created_at TEXT NOT NULL,
last_run TEXT,
run_count INTEGER DEFAULT 0,
last_error TEXT,
provider_type TEXT NOT NULL,
provider_name TEXT NOT NULL,
provider_model TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_scheduled_tasks_created_at ON scheduled_tasks(created_at);
`
_, err := s.db.Exec(schema)
return err
}
func (s *Scheduler) loadTasks() error {
rows, err := s.db.Query(`
SELECT id, prompt, interval_sec, run_at, is_repeating, created_at,
last_run, run_count, last_error, provider_type, provider_name, provider_model
FROM scheduled_tasks
`)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var task ScheduledTask
var lastRun, createdAt, runAt sql.NullString
err := rows.Scan(
&task.ID, &task.Prompt, &task.IntervalSec, &runAt, &task.IsRepeating,
&createdAt, &lastRun, &task.RunCount, &task.LastError,
&task.ProviderType, &task.ProviderName, &task.ProviderModel,
)
if err != nil {
return err
}
if createdAt.Valid {
task.CreatedAt, _ = time.Parse(time.RFC3339, createdAt.String)
}
if lastRun.Valid {
task.LastRun, _ = time.Parse(time.RFC3339, lastRun.String)
}
if runAt.Valid {
task.RunAt = runAt.String
}
s.tasks[task.ID] = &task
}
return rows.Err()
}
func (s *Scheduler) SetProviderGetter(pg ProviderGetter) {
s.provider = pg
}
func (s *Scheduler) generateID() (string, error) {
charset := "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
bytes := make([]byte, 4)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
result := make([]byte, 4)
for i, b := range bytes {
result[i] = charset[int(b)%len(charset)]
}
return string(result), nil
}
type SchedulerInterface interface {
AddTask(prompt string, intervalSec int, runAt string, providerType, providerName, providerModel string) (interface{}, error)
RunNow(prompt string, providerType, providerName, providerModel string) (string, error)
StopTask(id string) bool
RemoveTask(id string) bool
ListTasks() []interface{}
GetTask(id string) interface{}
}
func (s *Scheduler) AddTask(prompt string, intervalSec int, runAt string, providerType, providerName, providerModel string) (interface{}, error) {
if intervalSec <= 0 && runAt == "" {
return nil, fmt.Errorf("must specify either interval or run_at time")
}
id, err := s.generateID()
if err != nil {
return nil, err
}
task := &ScheduledTask{
ID: id,
Prompt: prompt,
IntervalSec: intervalSec,
RunAt: runAt,
IsRepeating: intervalSec > 0,
CreatedAt: time.Now(),
RunCount: 0,
ProviderType: providerType,
ProviderName: providerName,
ProviderModel: providerModel,
}
s.taskMu.Lock()
s.tasks[id] = task
s.taskMu.Unlock()
if err := s.saveTask(task); err != nil {
return nil, err
}
s.startTask(task)
return task, nil
}
func (s *Scheduler) RunNow(prompt string, providerType, providerName, providerModel string) (string, error) {
if s.provider == nil {
return "", fmt.Errorf("no provider getter set")
}
cfg := map[string]interface{}{
"type": providerType,
"name": providerName,
"model": providerModel,
}
prov, err := s.provider.GetProvider(cfg)
if err != nil {
return "", fmt.Errorf("failed to get provider: %v", err)
}
provInterface, ok := prov.(interface {
Send(ctx context.Context, prompt string) (string, error)
})
if !ok {
return "", fmt.Errorf("provider does not support Send method")
}
taskCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
result, err := provInterface.Send(taskCtx, prompt)
if err != nil {
return "", fmt.Errorf("execution error: %v", err)
}
return result, nil
}
func (s *Scheduler) saveTask(task *ScheduledTask) error {
_, err := s.db.Exec(`
INSERT OR REPLACE INTO scheduled_tasks
(id, prompt, interval_sec, run_at, is_repeating, created_at, last_run, run_count, last_error, provider_type, provider_name, provider_model)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, task.ID, task.Prompt, task.IntervalSec, task.RunAt, task.IsRepeating,
task.CreatedAt.Format(time.RFC3339), task.LastRun.Format(time.RFC3339),
task.RunCount, task.LastError, task.ProviderType, task.ProviderName, task.ProviderModel)
return err
}
func (s *Scheduler) startTask(task *ScheduledTask) {
s.runMu.Lock()
if _, exists := s.running[task.ID]; exists {
s.runMu.Unlock()
return
}
ctx, cancel := context.WithCancel(context.Background())
s.running[task.ID] = cancel
s.runMu.Unlock()
s.wg.Add(1)
go s.runLoop(ctx, task)
}
func (s *Scheduler) runLoop(ctx context.Context, task *ScheduledTask) {
defer s.wg.Done()
sleepDuration := func() time.Duration {
if task.IntervalSec > 0 {
return time.Duration(task.IntervalSec) * time.Second
}
if task.RunAt != "" {
now := time.Now()
target, err := time.Parse("15:04", task.RunAt)
if err != nil {
return 24 * time.Hour
}
target = time.Date(now.Year(), now.Month(), now.Day(), target.Hour(), target.Minute(), 0, 0, now.Location())
if target.Before(now) {
target = target.Add(24 * time.Hour)
}
return target.Sub(now)
}
return 24 * time.Hour
}
for {
select {
case <-ctx.Done():
return
case <-time.After(sleepDuration()):
s.executeTask(task)
if !task.IsRepeating {
return
}
}
}
}
func (s *Scheduler) executeTask(task *ScheduledTask) {
if s.provider == nil {
task.LastError = "no provider getter set"
return
}
cfg := &struct {
Type string `json:"type"`
Name string `json:"name"`
Model string `json:"model"`
}{
Type: task.ProviderType,
Name: task.ProviderName,
Model: task.ProviderModel,
}
prov, err := s.provider.GetProvider(cfg)
if err != nil {
task.LastError = fmt.Sprintf("failed to get provider: %v", err)
task.RunCount++
s.updateTask(task)
return
}
provInterface, ok := prov.(interface {
Send(ctx context.Context, prompt string) (string, error)
})
if !ok {
task.LastError = "provider does not support Send method"
task.RunCount++
s.updateTask(task)
return
}
taskCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
result, err := provInterface.Send(taskCtx, task.Prompt)
if err != nil {
task.LastError = fmt.Sprintf("execution error: %v", err)
} else {
task.LastError = ""
fmt.Printf("[Scheduler %s] Result: %s\n", task.ID, truncate(result, 200))
}
task.LastRun = time.Now()
task.RunCount++
s.updateTask(task)
}
func truncate(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen] + "..."
}
func (s *Scheduler) updateTask(task *ScheduledTask) {
s.taskMu.Lock()
s.tasks[task.ID] = task
s.taskMu.Unlock()
s.saveTask(task)
}
func (s *Scheduler) StopTask(id string) bool {
s.runMu.Lock()
if cancel, exists := s.running[id]; exists {
cancel()
delete(s.running, id)
s.runMu.Unlock()
return true
}
s.runMu.Unlock()
return false
}
func (s *Scheduler) RemoveTask(id string) bool {
s.StopTask(id)
s.taskMu.Lock()
if _, exists := s.tasks[id]; exists {
delete(s.tasks, id)
s.taskMu.Unlock()
_, err := s.db.Exec("DELETE FROM scheduled_tasks WHERE id = ?", id)
return err == nil
}
s.taskMu.Unlock()
return false
}
func (s *Scheduler) ListTasks() []interface{} {
s.taskMu.RLock()
defer s.taskMu.RUnlock()
tasks := make([]interface{}, 0, len(s.tasks))
for _, task := range s.tasks {
tasks = append(tasks, task)
}
return tasks
}
func (s *Scheduler) GetTask(id string) interface{} {
s.taskMu.RLock()
defer s.taskMu.RUnlock()
return s.tasks[id]
}
func (s *Scheduler) Close() {
close(s.stopCh)
s.runMu.Lock()
for _, cancel := range s.running {
cancel()
}
s.runMu.Unlock()
s.wg.Wait()
s.db.Close()
}
type providerConfig struct {
Type string `json:"type"`
Name string `json:"name"`
Model string `json:"model"`
}
func GetProviderFromConfig(cfg *providerConfig) (interface{}, error) {
if cfg == nil {
return nil, fmt.Errorf("nil config")
}
cfgJSON, _ := json.Marshal(cfg)
return string(cfgJSON), nil
}