This commit is contained in:
despiegk 2025-05-23 09:33:05 +04:00
parent a16ac8f627
commit 79d66e4b6b
34 changed files with 603 additions and 608 deletions

View File

@ -25,9 +25,6 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"github.com/freeflowuniverse/herolauncher/pkg/herolauncher"
_ "github.com/freeflowuniverse/herolauncher/pkg/herolauncher/docs" // Import generated swagger docs
) )
func main() { func main() {

2
go.mod
View File

@ -71,7 +71,7 @@ require (
github.com/metoro-io/mcp-golang v0.8.0 // indirect github.com/metoro-io/mcp-golang v0.8.0 // indirect
github.com/mholt/archiver/v3 v3.5.1 // indirect github.com/mholt/archiver/v3 v3.5.1 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect github.com/nwaples/rardecode v1.1.0 // indirect
github.com/openai/openai-go v0.1.0-beta.9 // indirect github.com/openaiproxy/openaiproxy-go v0.1.0-beta.9 // indirect
github.com/pb33f/libopenapi v0.21.8 // indirect github.com/pb33f/libopenapi v0.21.8 // indirect
github.com/pierrec/lz4/v4 v4.1.2 // indirect github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect

4
go.sum
View File

@ -160,8 +160,8 @@ github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssn
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/openai/openai-go v0.1.0-beta.9 h1:ABpubc5yU/3ejee2GgRrbFta81SG/d7bQbB8mIdP0Xo= github.com/openaiproxy/openaiproxy-go v0.1.0-beta.9 h1:ABpubc5yU/3ejee2GgRrbFta81SG/d7bQbB8mIdP0Xo=
github.com/openai/openai-go v0.1.0-beta.9/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= github.com/openaiproxy/openaiproxy-go v0.1.0-beta.9/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/pb33f/libopenapi v0.21.8 h1:Fi2dAogMwC6av/5n3YIo7aMOGBZH/fBMO4OnzFB3dQA= github.com/pb33f/libopenapi v0.21.8 h1:Fi2dAogMwC6av/5n3YIo7aMOGBZH/fBMO4OnzFB3dQA=
github.com/pb33f/libopenapi v0.21.8/go.mod h1:Gc8oQkjr2InxwumK0zOBtKN9gIlv9L2VmSVIUk2YxcU= github.com/pb33f/libopenapi v0.21.8/go.mod h1:Gc8oQkjr2InxwumK0zOBtKN9gIlv9L2VmSVIUk2YxcU=
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM= github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=

View File

@ -34,7 +34,7 @@ Jobs are stored in both Redis and OurDB:
- Handles all queue operations (adding/removing jobs) - Handles all queue operations (adding/removing jobs)
- Stores all running jobs for fast access - Stores all running jobs for fast access
- Used for real-time operations and status updates - Used for real-time operations and status updates
- **Job Storage**: `herojobs:<circleID>:<topic>:<jobID>` or legacy `jobsmanager:<jobID>` - **Job Storage**: `herojobs:<circleID>:<topic>:<jobID>` or legacy `herojobs:<jobID>`
- **Queue**: `heroqueue:<circleID>:<topic>` - **Queue**: `heroqueue:<circleID>:<topic>`
#### OurDB #### OurDB
@ -85,14 +85,14 @@ The watchdog uses Go's concurrency primitives to safely manage multiple jobs:
```go ```go
// Initialize Redis client // Initialize Redis client
redisClient, err := jobsmanager.NewRedisClient("localhost:6379", false) redisClient, err := herojobs.NewRedisClient("localhost:6379", false)
if err != nil { if err != nil {
log.Fatalf("Failed to connect to Redis: %v", err) log.Fatalf("Failed to connect to Redis: %v", err)
} }
defer redisClient.Close() defer redisClient.Close()
// Create and start watchdog // Create and start watchdog
watchdog := jobsmanager.NewWatchDog(redisClient) watchdog := herojobs.NewWatchDog(redisClient)
watchdog.Start() watchdog.Start()
// Handle shutdown // Handle shutdown
@ -103,14 +103,14 @@ defer watchdog.Stop()
```go ```go
// Create a new job // Create a new job
job := jobsmanager.NewJob() job := herojobs.NewJob()
job.CircleID = "myCircle" job.CircleID = "myCircle"
job.Topic = "myTopic" job.Topic = "myTopic"
job.Params = ` job.Params = `
!!fake.return_success !!fake.return_success
message: "This is a test job" message: "This is a test job"
` `
job.ParamsType = jobsmanager.ParamsTypeHeroScript job.ParamsType = herojobs.ParamsTypeHeroScript
job.Timeout = 30 // 30 seconds timeout job.Timeout = 30 // 30 seconds timeout
// Save the job to OurDB to get an ID // Save the job to OurDB to get an ID
@ -140,7 +140,7 @@ jobID := job.JobID
job, err := redisClient.GetJob(jobID) job, err := redisClient.GetJob(jobID)
if err != nil { if err != nil {
// If not found in Redis, try OurDB for historical jobs // If not found in Redis, try OurDB for historical jobs
job = &jobsmanager.Job{JobID: jobID} job = &herojobs.Job{JobID: jobID}
if err := job.Load(); err != nil { if err := job.Load(); err != nil {
log.Printf("Failed to load job: %v", err) log.Printf("Failed to load job: %v", err)
return return
@ -149,13 +149,13 @@ if err != nil {
// Check job status // Check job status
switch job.Status { switch job.Status {
case jobsmanager.JobStatusNew: case herojobs.JobStatusNew:
fmt.Println("Job is waiting to be processed") fmt.Println("Job is waiting to be processed")
case jobsmanager.JobStatusActive: case herojobs.JobStatusActive:
fmt.Println("Job is currently being processed") fmt.Println("Job is currently being processed")
case jobsmanager.JobStatusDone: case herojobs.JobStatusDone:
fmt.Printf("Job completed successfully: %s\n", job.Result) fmt.Printf("Job completed successfully: %s\n", job.Result)
case jobsmanager.JobStatusError: case herojobs.JobStatusError:
fmt.Printf("Job failed: %s\n", job.Error) fmt.Printf("Job failed: %s\n", job.Error)
} }
``` ```

View File

@ -1,4 +1,4 @@
package jobsmanager package herojobs
import ( import (
"encoding/json" "encoding/json"

View File

@ -1,4 +1,4 @@
package jobsmanager package herojobs
import ( import (
"context" "context"

View File

@ -1,4 +1,4 @@
package jobsmanager package herojobs
import ( import (
"context" "context"
@ -82,7 +82,7 @@ func (r *RedisClient) GetJob(jobID interface{}) (*Job, error) {
switch id := jobID.(type) { switch id := jobID.(type) {
case uint32: case uint32:
// Legacy format for backward compatibility // Legacy format for backward compatibility
storageKey = fmt.Sprintf("jobsmanager:%d", id) storageKey = fmt.Sprintf("herojobs:%d", id)
case string: case string:
// Check if this is a composite key (circleID:topic:jobID) // Check if this is a composite key (circleID:topic:jobID)
parts := strings.Split(id, ":") parts := strings.Split(id, ":")
@ -103,10 +103,10 @@ func (r *RedisClient) GetJob(jobID interface{}) (*Job, error) {
// Try to convert string to uint32 (legacy format) // Try to convert string to uint32 (legacy format)
var numericID uint32 var numericID uint32
if _, err := fmt.Sscanf(id, "%d", &numericID); err == nil { if _, err := fmt.Sscanf(id, "%d", &numericID); err == nil {
storageKey = fmt.Sprintf("jobsmanager:%d", numericID) storageKey = fmt.Sprintf("herojobs:%d", numericID)
} else { } else {
// Legacy string ID format // Legacy string ID format
storageKey = fmt.Sprintf("jobsmanager:%s", id) storageKey = fmt.Sprintf("herojobs:%s", id)
} }
} }
default: default:
@ -139,7 +139,7 @@ func (r *RedisClient) DeleteJob(jobID interface{}) error {
switch id := jobID.(type) { switch id := jobID.(type) {
case uint32: case uint32:
// Legacy format for backward compatibility // Legacy format for backward compatibility
storageKey = fmt.Sprintf("jobsmanager:%d", id) storageKey = fmt.Sprintf("herojobs:%d", id)
case string: case string:
// Check if this is a composite key (circleID:topic:jobID) // Check if this is a composite key (circleID:topic:jobID)
parts := strings.Split(id, ":") parts := strings.Split(id, ":")
@ -160,10 +160,10 @@ func (r *RedisClient) DeleteJob(jobID interface{}) error {
// Try to convert string to uint32 (legacy format) // Try to convert string to uint32 (legacy format)
var numericID uint32 var numericID uint32
if _, err := fmt.Sscanf(id, "%d", &numericID); err == nil { if _, err := fmt.Sscanf(id, "%d", &numericID); err == nil {
storageKey = fmt.Sprintf("jobsmanager:%d", numericID) storageKey = fmt.Sprintf("herojobs:%d", numericID)
} else { } else {
// Legacy string ID format // Legacy string ID format
storageKey = fmt.Sprintf("jobsmanager:%s", id) storageKey = fmt.Sprintf("herojobs:%s", id)
} }
} }
default: default:
@ -238,7 +238,7 @@ func (r *RedisClient) QueueEmpty(circleID, topic string) error {
} }
} else { } else {
// Handle legacy string IDs // Handle legacy string IDs
storageKey := fmt.Sprintf("jobsmanager:%s", jobIDStr) storageKey := fmt.Sprintf("herojobs:%s", jobIDStr)
err := r.client.Del(r.ctx, storageKey).Err() err := r.client.Del(r.ctx, storageKey).Err()
if err != nil { if err != nil {
return fmt.Errorf("failed to delete job %s: %w", jobIDStr, err) return fmt.Errorf("failed to delete job %s: %w", jobIDStr, err)

View File

@ -1,4 +1,4 @@
package jobsmanager package herojobs
import ( import (
"context" "context"

View File

@ -4,7 +4,7 @@ import (
"log" "log"
"sync" "sync"
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory/herohandler" "github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/herohandler"
) )
func main() { func main() {

View File

@ -2,8 +2,6 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook" "github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
) )
@ -15,26 +13,26 @@ func main() {
pb := playbook.New() pb := playbook.New()
// Start a simple process // Start a simple process
startAction := pb.NewAction(1, "start", "process", 0, playbook.ActionTypeUnknown) startAction := pb.NewAction("1", "start", "process", 0, playbook.ActionTypeUnknown)
startAction.Params.Set("name", "example_process") startAction.Params.Set("name", "example_process")
startAction.Params.Set("command", "ping -c 60 localhost") startAction.Params.Set("command", "ping -c 60 localhost")
startAction.Params.Set("log", "true") startAction.Params.Set("log", "true")
// List all processes // List all processes
listAction := pb.NewAction(2, "list", "process", 0, playbook.ActionTypeUnknown) listAction := pb.NewAction("2", "list", "process", 0, playbook.ActionTypeUnknown)
listAction.Params.Set("format", "table") listAction.Params.Set("format", "table")
// Get status of a specific process // Get status of a specific process
statusAction := pb.NewAction(3, "status", "process", 0, playbook.ActionTypeUnknown) statusAction := pb.NewAction("3", "status", "process", 0, playbook.ActionTypeUnknown)
statusAction.Params.Set("name", "example_process") statusAction.Params.Set("name", "example_process")
// Get logs of a specific process // Get logs of a specific process
logsAction := pb.NewAction(4, "logs", "process", 0, playbook.ActionTypeUnknown) logsAction := pb.NewAction("4", "logs", "process", 0, playbook.ActionTypeUnknown)
logsAction.Params.Set("name", "example_process") logsAction.Params.Set("name", "example_process")
logsAction.Params.Set("lines", "10") logsAction.Params.Set("lines", "10")
// Stop a process // Stop a process
stopAction := pb.NewAction(5, "stop", "process", 0, playbook.ActionTypeUnknown) stopAction := pb.NewAction("5", "stop", "process", 0, playbook.ActionTypeUnknown)
stopAction.Params.Set("name", "example_process") stopAction.Params.Set("name", "example_process")
// Generate the heroscript // Generate the heroscript

View File

@ -9,7 +9,7 @@ import (
"syscall" "syscall"
"time" "time"
proxy "github.com/freeflowuniverse/heroagent/pkg/proxies/openai" openaiproxy "github.com/freeflowuniverse/heroagent/pkg/heroservices/openaiproxy"
"github.com/openai/openai-go" "github.com/openai/openai-go"
"github.com/openai/openai-go/option" "github.com/openai/openai-go/option"
) )
@ -37,15 +37,15 @@ func testProxyWithClient() {
// Create a client that points to our proxy // Create a client that points to our proxy
// Note: The server is using "/ai" as the prefix for all routes // Note: The server is using "/ai" as the prefix for all routes
client := openai.NewClient( client := openaiproxy.NewClient(
option.WithAPIKey("test-key"), // This is our test key, not a real OpenAI key option.WithAPIKey("test-key"), // This is our test key, not a real OpenAI key
option.WithBaseURL("http://localhost:8080/ai"), // Use the /ai prefix to match the server routes option.WithBaseURL("http://localhost:8080/ai"), // Use the /ai prefix to match the server routes
) )
// Create a completion request // Create a completion request
chatCompletion, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ chatCompletion, err := client.Chat.Completions.New(context.Background(), openaiproxy.ChatCompletionNewParams{
Messages: []openai.ChatCompletionMessageParamUnion{ Messages: []openaiproxy.ChatCompletionMessageParamUnion{
openai.UserMessage("Say this is a test"), openaiproxy.UserMessage("Say this is a test"),
}, },
Model: "gpt-3.5-turbo", // Use a model that our proxy supports Model: "gpt-3.5-turbo", // Use a model that our proxy supports
}) })
@ -70,9 +70,9 @@ func runServerMode() {
// Create a proxy configuration // Create a proxy configuration
config := proxy.ProxyConfig{ config := proxy.ProxyConfig{
Port: 8080, // Use a non-privileged port for testing Port: 8080, // Use a non-privileged port for testing
OpenAIBaseURL: "https://api.openai.com", // Default OpenAI API URL OpenAIBaseURL: "https://api.openaiproxy.com", // Default OpenAI API URL
DefaultOpenAIKey: openaiKey, // Fallback API key if user doesn't have one DefaultOpenAIKey: openaiKey, // Fallback API key if user doesn't have one
} }
// Create a new factory with the configuration // Create a new factory with the configuration

View File

@ -119,7 +119,7 @@ func createJob(c *fiber.Ctx, apiKey string, endpoint string, requestBody interfa
// Create a new job // Create a new job
job := jobsmanager.NewJob() job := jobsmanager.NewJob()
job.ParamsType = jobsmanager.ParamsTypeAI job.ParamsType = jobsmanager.ParamsTypeAI
job.Topic = "openai-proxy" job.Topic = "openaiproxy-proxy"
job.CircleID = "ai" job.CircleID = "ai"
// Serialize request body to JSON // Serialize request body to JSON
@ -183,7 +183,7 @@ func (s *Server) handleModels(c *fiber.Ctx) error {
// Forward request to OpenAI // Forward request to OpenAI
url := s.Factory.Config.OpenAIBaseURL + "/v1/models" url := s.Factory.Config.OpenAIBaseURL + "/v1/models"
if url == "/v1/models" { if url == "/v1/models" {
url = "https://api.openai.com/v1/models" url = "https://api.openaiproxy.com/v1/models"
} }
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
@ -250,7 +250,7 @@ func (s *Server) handleGetModel(c *fiber.Ctx) error {
// Forward request to OpenAI // Forward request to OpenAI
url := s.Factory.Config.OpenAIBaseURL + "/v1/models/" + modelID url := s.Factory.Config.OpenAIBaseURL + "/v1/models/" + modelID
if strings.HasPrefix(url, "/v1/models/") { if strings.HasPrefix(url, "/v1/models/") {
url = "https://api.openai.com/v1/models/" + modelID url = "https://api.openaiproxy.com/v1/models/" + modelID
} }
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
@ -337,7 +337,7 @@ func (s *Server) handleChatCompletions(c *fiber.Ctx) error {
// Forward request to OpenAI // Forward request to OpenAI
url := s.Factory.Config.OpenAIBaseURL + "/v1/chat/completions" url := s.Factory.Config.OpenAIBaseURL + "/v1/chat/completions"
if url == "/v1/chat/completions" { if url == "/v1/chat/completions" {
url = "https://api.openai.com/v1/chat/completions" url = "https://api.openaiproxy.com/v1/chat/completions"
} }
// Convert the request body back to JSON // Convert the request body back to JSON
@ -461,7 +461,7 @@ func (s *Server) handleCompletions(c *fiber.Ctx) error {
// Forward request to OpenAI // Forward request to OpenAI
url := s.Factory.Config.OpenAIBaseURL + "/v1/completions" url := s.Factory.Config.OpenAIBaseURL + "/v1/completions"
if url == "/v1/completions" { if url == "/v1/completions" {
url = "https://api.openai.com/v1/completions" url = "https://api.openaiproxy.com/v1/completions"
} }
// Convert the request body back to JSON // Convert the request body back to JSON
@ -585,7 +585,7 @@ func (s *Server) handleEmbeddings(c *fiber.Ctx) error {
// Forward request to OpenAI // Forward request to OpenAI
url := s.Factory.Config.OpenAIBaseURL + "/v1/embeddings" url := s.Factory.Config.OpenAIBaseURL + "/v1/embeddings"
if url == "/v1/embeddings" { if url == "/v1/embeddings" {
url = "https://api.openai.com/v1/embeddings" url = "https://api.openaiproxy.com/v1/embeddings"
} }
// Convert the request body back to JSON // Convert the request body back to JSON
@ -724,7 +724,7 @@ func (s *Server) handleImagesGenerations(c *fiber.Ctx) error {
// Forward request to OpenAI // Forward request to OpenAI
url := s.Factory.Config.OpenAIBaseURL + "/v1/images/generations" url := s.Factory.Config.OpenAIBaseURL + "/v1/images/generations"
if url == "/v1/images/generations" { if url == "/v1/images/generations" {
url = "https://api.openai.com/v1/images/generations" url = "https://api.openaiproxy.com/v1/images/generations"
} }
// Convert the request body back to JSON // Convert the request body back to JSON
@ -919,7 +919,7 @@ func (s *Server) handleListFiles(c *fiber.Ctx) error {
// Forward request to OpenAI // Forward request to OpenAI
url := s.Factory.Config.OpenAIBaseURL + "/v1/files" url := s.Factory.Config.OpenAIBaseURL + "/v1/files"
if url == "/v1/files" { if url == "/v1/files" {
url = "https://api.openai.com/v1/files" url = "https://api.openaiproxy.com/v1/files"
} }
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
@ -1017,7 +1017,7 @@ func (s *Server) handleGetFile(c *fiber.Ctx) error {
// Forward request to OpenAI // Forward request to OpenAI
url := s.Factory.Config.OpenAIBaseURL + "/v1/files/" + fileID url := s.Factory.Config.OpenAIBaseURL + "/v1/files/" + fileID
if strings.HasPrefix(url, "/v1/files/") { if strings.HasPrefix(url, "/v1/files/") {
url = "https://api.openai.com/v1/files/" + fileID url = "https://api.openaiproxy.com/v1/files/" + fileID
} }
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
@ -1084,7 +1084,7 @@ func (s *Server) handleDeleteFile(c *fiber.Ctx) error {
// Forward request to OpenAI // Forward request to OpenAI
url := s.Factory.Config.OpenAIBaseURL + "/v1/files/" + fileID url := s.Factory.Config.OpenAIBaseURL + "/v1/files/" + fileID
if strings.HasPrefix(url, "/v1/files/") { if strings.HasPrefix(url, "/v1/files/") {
url = "https://api.openai.com/v1/files/" + fileID url = "https://api.openaiproxy.com/v1/files/" + fileID
} }
req, err := http.NewRequest("DELETE", url, nil) req, err := http.NewRequest("DELETE", url, nil)

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"net" "net"
"github.com/freeflowuniverse/herolauncher/pkg/openrpcmanager" "github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
) )
// Common errors // Common errors

View File

@ -7,7 +7,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/freeflowuniverse/herolauncher/pkg/openrpcmanager" "github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
) )
// MockClient implements the Client interface for testing // MockClient implements the Client interface for testing

View File

@ -9,7 +9,7 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/freeflowuniverse/herolauncher/pkg/openrpcmanager" "github.com/freeflowuniverse/heroagent/pkg/openrpcmanager"
) )
func main() { func main() {