Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd83253f1c |
2
Makefile
2
Makefile
@@ -9,7 +9,7 @@ cleanup-tests:
|
|||||||
docker compose down
|
docker compose down
|
||||||
|
|
||||||
tests: prepare-tests
|
tests: prepare-tests
|
||||||
LOCALAGI_MODEL="gemma-3-12b-it-qat" LOCALAI_API_URL="http://localhost:8081" LOCALAGI_API_URL="http://localhost:8080" $(GOCMD) run github.com/onsi/ginkgo/v2/ginkgo --fail-fast -v -r ./...
|
LOCALAGI_MODEL="arcee-agent" LOCALAI_API_URL="http://localhost:8081" LOCALAGI_API_URL="http://localhost:8080" $(GOCMD) run github.com/onsi/ginkgo/v2/ginkgo --fail-fast -v -r ./...
|
||||||
|
|
||||||
run-nokb:
|
run-nokb:
|
||||||
$(MAKE) run KBDISABLEINDEX=true
|
$(MAKE) run KBDISABLEINDEX=true
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -60,7 +60,7 @@ MODEL_NAME=gemma-3-12b-it docker compose up
|
|||||||
# NVIDIA GPU setup with custom multimodal and image models
|
# NVIDIA GPU setup with custom multimodal and image models
|
||||||
MODEL_NAME=gemma-3-12b-it \
|
MODEL_NAME=gemma-3-12b-it \
|
||||||
MULTIMODAL_MODEL=minicpm-v-2_6 \
|
MULTIMODAL_MODEL=minicpm-v-2_6 \
|
||||||
IMAGE_MODEL=flux.1-dev-ggml \
|
IMAGE_MODEL=flux.1-dev \
|
||||||
docker compose -f docker-compose.nvidia.yaml up
|
docker compose -f docker-compose.nvidia.yaml up
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -114,9 +114,9 @@ LocalAGI supports multiple hardware configurations through Docker Compose profil
|
|||||||
- Supports text, multimodal, and image generation models
|
- Supports text, multimodal, and image generation models
|
||||||
- Run with: `docker compose -f docker-compose.nvidia.yaml up`
|
- Run with: `docker compose -f docker-compose.nvidia.yaml up`
|
||||||
- Default models:
|
- Default models:
|
||||||
- Text: `gemma-3-12b-it-qat`
|
- Text: `arcee-agent`
|
||||||
- Multimodal: `minicpm-v-2_6`
|
- Multimodal: `minicpm-v-2_6`
|
||||||
- Image: `sd-1.5-ggml`
|
- Image: `flux.1-dev`
|
||||||
- Environment variables:
|
- Environment variables:
|
||||||
- `MODEL_NAME`: Text model to use
|
- `MODEL_NAME`: Text model to use
|
||||||
- `MULTIMODAL_MODEL`: Multimodal model to use
|
- `MULTIMODAL_MODEL`: Multimodal model to use
|
||||||
@@ -130,7 +130,7 @@ LocalAGI supports multiple hardware configurations through Docker Compose profil
|
|||||||
- Supports text, multimodal, and image generation models
|
- Supports text, multimodal, and image generation models
|
||||||
- Run with: `docker compose -f docker-compose.intel.yaml up`
|
- Run with: `docker compose -f docker-compose.intel.yaml up`
|
||||||
- Default models:
|
- Default models:
|
||||||
- Text: `gemma-3-12b-it-qat`
|
- Text: `arcee-agent`
|
||||||
- Multimodal: `minicpm-v-2_6`
|
- Multimodal: `minicpm-v-2_6`
|
||||||
- Image: `sd-1.5-ggml`
|
- Image: `sd-1.5-ggml`
|
||||||
- Environment variables:
|
- Environment variables:
|
||||||
@@ -150,7 +150,7 @@ MODEL_NAME=gemma-3-12b-it docker compose up
|
|||||||
# NVIDIA GPU with custom models
|
# NVIDIA GPU with custom models
|
||||||
MODEL_NAME=gemma-3-12b-it \
|
MODEL_NAME=gemma-3-12b-it \
|
||||||
MULTIMODAL_MODEL=minicpm-v-2_6 \
|
MULTIMODAL_MODEL=minicpm-v-2_6 \
|
||||||
IMAGE_MODEL=flux.1-dev-ggml \
|
IMAGE_MODEL=flux.1-dev \
|
||||||
docker compose -f docker-compose.nvidia.yaml up
|
docker compose -f docker-compose.nvidia.yaml up
|
||||||
|
|
||||||
# Intel GPU with custom models
|
# Intel GPU with custom models
|
||||||
@@ -161,9 +161,9 @@ docker compose -f docker-compose.intel.yaml up
|
|||||||
```
|
```
|
||||||
|
|
||||||
If no models are specified, it will use the defaults:
|
If no models are specified, it will use the defaults:
|
||||||
- Text model: `gemma-3-12b-it-qat`
|
- Text model: `arcee-agent`
|
||||||
- Multimodal model: `minicpm-v-2_6`
|
- Multimodal model: `minicpm-v-2_6`
|
||||||
- Image model: `sd-1.5-ggml`
|
- Image model: `flux.1-dev` (NVIDIA) or `sd-1.5-ggml` (Intel)
|
||||||
|
|
||||||
Good (relatively small) models that have been tested are:
|
Good (relatively small) models that have been tested are:
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package action
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/mudler/LocalAGI/core/types"
|
"github.com/mudler/LocalAGI/core/types"
|
||||||
"github.com/sashabaranov/go-openai/jsonschema"
|
"github.com/sashabaranov/go-openai/jsonschema"
|
||||||
@@ -15,6 +16,24 @@ func NewState() *StateAction {
|
|||||||
|
|
||||||
type StateAction struct{}
|
type StateAction struct{}
|
||||||
|
|
||||||
|
// State is the structure
|
||||||
|
// that is used to keep track of the current state
|
||||||
|
// and the Agent's short memory that it can update
|
||||||
|
// Besides a long term memory that is accessible by the agent (With vector database),
|
||||||
|
// And a context memory (that is always powered by a vector database),
|
||||||
|
// this memory is the shorter one that the LLM keeps across conversation and across its
|
||||||
|
// reasoning process's and life time.
|
||||||
|
// TODO: A special action is then used to let the LLM itself update its memory
|
||||||
|
// periodically during self-processing, and the same action is ALSO exposed
|
||||||
|
// during the conversation to let the user put for example, a new goal to the agent.
|
||||||
|
type AgentInternalState struct {
|
||||||
|
NowDoing string `json:"doing_now"`
|
||||||
|
DoingNext string `json:"doing_next"`
|
||||||
|
DoneHistory []string `json:"done_history"`
|
||||||
|
Memories []string `json:"memories"`
|
||||||
|
Goal string `json:"goal"`
|
||||||
|
}
|
||||||
|
|
||||||
func (a *StateAction) Run(context.Context, types.ActionParams) (types.ActionResult, error) {
|
func (a *StateAction) Run(context.Context, types.ActionParams) (types.ActionResult, error) {
|
||||||
return types.ActionResult{Result: "internal state has been updated"}, nil
|
return types.ActionResult{Result: "internal state has been updated"}, nil
|
||||||
}
|
}
|
||||||
@@ -57,3 +76,23 @@ func (a *StateAction) Definition() types.ActionDefinition {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fmtT = `=====================
|
||||||
|
NowDoing: %s
|
||||||
|
DoingNext: %s
|
||||||
|
Your current goal is: %s
|
||||||
|
You have done: %+v
|
||||||
|
You have a short memory with: %+v
|
||||||
|
=====================
|
||||||
|
`
|
||||||
|
|
||||||
|
func (c AgentInternalState) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
fmtT,
|
||||||
|
c.NowDoing,
|
||||||
|
c.DoingNext,
|
||||||
|
c.Goal,
|
||||||
|
c.DoneHistory,
|
||||||
|
c.Memories,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type decisionResult struct {
|
|||||||
|
|
||||||
// decision forces the agent to take one of the available actions
|
// decision forces the agent to take one of the available actions
|
||||||
func (a *Agent) decision(
|
func (a *Agent) decision(
|
||||||
job *types.Job,
|
ctx context.Context,
|
||||||
conversation []openai.ChatCompletionMessage,
|
conversation []openai.ChatCompletionMessage,
|
||||||
tools []openai.Tool, toolchoice string, maxRetries int) (*decisionResult, error) {
|
tools []openai.Tool, toolchoice string, maxRetries int) (*decisionResult, error) {
|
||||||
|
|
||||||
@@ -35,63 +35,31 @@ func (a *Agent) decision(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decision := openai.ChatCompletionRequest{
|
|
||||||
Model: a.options.LLMAPI.Model,
|
|
||||||
Messages: conversation,
|
|
||||||
Tools: tools,
|
|
||||||
}
|
|
||||||
|
|
||||||
if choice != nil {
|
|
||||||
decision.ToolChoice = *choice
|
|
||||||
}
|
|
||||||
|
|
||||||
var obs *types.Observable
|
|
||||||
if job.Obs != nil {
|
|
||||||
obs = a.observer.NewObservable()
|
|
||||||
obs.Name = "decision"
|
|
||||||
obs.ParentID = job.Obs.ID
|
|
||||||
obs.Icon = "brain"
|
|
||||||
obs.Creation = &types.Creation{
|
|
||||||
ChatCompletionRequest: &decision,
|
|
||||||
}
|
|
||||||
a.observer.Update(*obs)
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for attempts := 0; attempts < maxRetries; attempts++ {
|
for attempts := 0; attempts < maxRetries; attempts++ {
|
||||||
resp, err := a.client.CreateChatCompletion(job.GetContext(), decision)
|
decision := openai.ChatCompletionRequest{
|
||||||
|
Model: a.options.LLMAPI.Model,
|
||||||
|
Messages: conversation,
|
||||||
|
Tools: tools,
|
||||||
|
}
|
||||||
|
|
||||||
|
if choice != nil {
|
||||||
|
decision.ToolChoice = *choice
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.client.CreateChatCompletion(ctx, decision)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
xlog.Warn("Attempt to make a decision failed", "attempt", attempts+1, "error", err)
|
xlog.Warn("Attempt to make a decision failed", "attempt", attempts+1, "error", err)
|
||||||
|
|
||||||
if obs != nil {
|
|
||||||
obs.Progress = append(obs.Progress, types.Progress{
|
|
||||||
Error: err.Error(),
|
|
||||||
})
|
|
||||||
a.observer.Update(*obs)
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonResp, _ := json.Marshal(resp)
|
jsonResp, _ := json.Marshal(resp)
|
||||||
xlog.Debug("Decision response", "response", string(jsonResp))
|
xlog.Debug("Decision response", "response", string(jsonResp))
|
||||||
|
|
||||||
if obs != nil {
|
|
||||||
obs.AddProgress(types.Progress{
|
|
||||||
ChatCompletionResponse: &resp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resp.Choices) != 1 {
|
if len(resp.Choices) != 1 {
|
||||||
lastErr = fmt.Errorf("no choices: %d", len(resp.Choices))
|
lastErr = fmt.Errorf("no choices: %d", len(resp.Choices))
|
||||||
xlog.Warn("Attempt to make a decision failed", "attempt", attempts+1, "error", lastErr)
|
xlog.Warn("Attempt to make a decision failed", "attempt", attempts+1, "error", lastErr)
|
||||||
|
|
||||||
if obs != nil {
|
|
||||||
obs.Progress[len(obs.Progress)-1].Error = lastErr.Error()
|
|
||||||
a.observer.Update(*obs)
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,12 +68,6 @@ func (a *Agent) decision(
|
|||||||
if err := a.saveConversation(append(conversation, msg), "decision"); err != nil {
|
if err := a.saveConversation(append(conversation, msg), "decision"); err != nil {
|
||||||
xlog.Error("Error saving conversation", "error", err)
|
xlog.Error("Error saving conversation", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if obs != nil {
|
|
||||||
obs.MakeLastProgressCompletion()
|
|
||||||
a.observer.Update(*obs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &decisionResult{message: msg.Content}, nil
|
return &decisionResult{message: msg.Content}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,12 +75,6 @@ func (a *Agent) decision(
|
|||||||
if err := params.Read(msg.ToolCalls[0].Function.Arguments); err != nil {
|
if err := params.Read(msg.ToolCalls[0].Function.Arguments); err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
xlog.Warn("Attempt to parse action parameters failed", "attempt", attempts+1, "error", err)
|
xlog.Warn("Attempt to parse action parameters failed", "attempt", attempts+1, "error", err)
|
||||||
|
|
||||||
if obs != nil {
|
|
||||||
obs.Progress[len(obs.Progress)-1].Error = lastErr.Error()
|
|
||||||
a.observer.Update(*obs)
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,11 +82,6 @@ func (a *Agent) decision(
|
|||||||
xlog.Error("Error saving conversation", "error", err)
|
xlog.Error("Error saving conversation", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if obs != nil {
|
|
||||||
obs.MakeLastProgressCompletion()
|
|
||||||
a.observer.Update(*obs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &decisionResult{actionParams: params, actioName: msg.ToolCalls[0].Function.Name, message: msg.Content}, nil
|
return &decisionResult{actionParams: params, actioName: msg.ToolCalls[0].Function.Name, message: msg.Content}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +173,7 @@ func (m Messages) IsLastMessageFromRole(role string) bool {
|
|||||||
return m[len(m)-1].Role == role
|
return m[len(m)-1].Role == role
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) generateParameters(job *types.Job, pickTemplate string, act types.Action, c []openai.ChatCompletionMessage, reasoning string, maxAttempts int) (*decisionResult, error) {
|
func (a *Agent) generateParameters(ctx context.Context, pickTemplate string, act types.Action, c []openai.ChatCompletionMessage, reasoning string, maxAttempts int) (*decisionResult, error) {
|
||||||
stateHUD, err := renderTemplate(pickTemplate, a.prepareHUD(), a.availableActions(), reasoning)
|
stateHUD, err := renderTemplate(pickTemplate, a.prepareHUD(), a.availableActions(), reasoning)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -250,7 +201,7 @@ func (a *Agent) generateParameters(job *types.Job, pickTemplate string, act type
|
|||||||
var attemptErr error
|
var attemptErr error
|
||||||
|
|
||||||
for attempts := 0; attempts < maxAttempts; attempts++ {
|
for attempts := 0; attempts < maxAttempts; attempts++ {
|
||||||
result, attemptErr = a.decision(job,
|
result, attemptErr = a.decision(ctx,
|
||||||
cc,
|
cc,
|
||||||
a.availableActions().ToTools(),
|
a.availableActions().ToTools(),
|
||||||
act.Definition().Name.String(),
|
act.Definition().Name.String(),
|
||||||
@@ -312,7 +263,7 @@ func (a *Agent) handlePlanning(ctx context.Context, job *types.Job, chosenAction
|
|||||||
subTaskAction := a.availableActions().Find(subtask.Action)
|
subTaskAction := a.availableActions().Find(subtask.Action)
|
||||||
subTaskReasoning := fmt.Sprintf("%s Overall goal is: %s", subtask.Reasoning, planResult.Goal)
|
subTaskReasoning := fmt.Sprintf("%s Overall goal is: %s", subtask.Reasoning, planResult.Goal)
|
||||||
|
|
||||||
params, err := a.generateParameters(job, pickTemplate, subTaskAction, conv, subTaskReasoning, maxRetries)
|
params, err := a.generateParameters(ctx, pickTemplate, subTaskAction, conv, subTaskReasoning, maxRetries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xlog.Error("error generating action's parameters", "error", err)
|
xlog.Error("error generating action's parameters", "error", err)
|
||||||
return conv, fmt.Errorf("error generating action's parameters: %w", err)
|
return conv, fmt.Errorf("error generating action's parameters: %w", err)
|
||||||
@@ -342,7 +293,7 @@ func (a *Agent) handlePlanning(ctx context.Context, job *types.Job, chosenAction
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := a.runAction(job, subTaskAction, actionParams)
|
result, err := a.runAction(ctx, subTaskAction, actionParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xlog.Error("error running action", "error", err)
|
xlog.Error("error running action", "error", err)
|
||||||
return conv, fmt.Errorf("error running action: %w", err)
|
return conv, fmt.Errorf("error running action: %w", err)
|
||||||
@@ -427,7 +378,7 @@ func (a *Agent) prepareHUD() (promptHUD *PromptHUD) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pickAction picks an action based on the conversation
|
// pickAction picks an action based on the conversation
|
||||||
func (a *Agent) pickAction(job *types.Job, templ string, messages []openai.ChatCompletionMessage, maxRetries int) (types.Action, types.ActionParams, string, error) {
|
func (a *Agent) pickAction(ctx context.Context, templ string, messages []openai.ChatCompletionMessage, maxRetries int) (types.Action, types.ActionParams, string, error) {
|
||||||
c := messages
|
c := messages
|
||||||
|
|
||||||
xlog.Debug("[pickAction] picking action starts", "messages", messages)
|
xlog.Debug("[pickAction] picking action starts", "messages", messages)
|
||||||
@@ -438,7 +389,7 @@ func (a *Agent) pickAction(job *types.Job, templ string, messages []openai.ChatC
|
|||||||
xlog.Debug("not forcing reasoning")
|
xlog.Debug("not forcing reasoning")
|
||||||
// We also could avoid to use functions here and get just a reply from the LLM
|
// We also could avoid to use functions here and get just a reply from the LLM
|
||||||
// and then use the reply to get the action
|
// and then use the reply to get the action
|
||||||
thought, err := a.decision(job,
|
thought, err := a.decision(ctx,
|
||||||
messages,
|
messages,
|
||||||
a.availableActions().ToTools(),
|
a.availableActions().ToTools(),
|
||||||
"",
|
"",
|
||||||
@@ -480,7 +431,7 @@ func (a *Agent) pickAction(job *types.Job, templ string, messages []openai.ChatC
|
|||||||
}, c...)
|
}, c...)
|
||||||
}
|
}
|
||||||
|
|
||||||
thought, err := a.decision(job,
|
thought, err := a.decision(ctx,
|
||||||
c,
|
c,
|
||||||
types.Actions{action.NewReasoning()}.ToTools(),
|
types.Actions{action.NewReasoning()}.ToTools(),
|
||||||
action.NewReasoning().Definition().Name.String(), maxRetries)
|
action.NewReasoning().Definition().Name.String(), maxRetries)
|
||||||
@@ -516,7 +467,7 @@ func (a *Agent) pickAction(job *types.Job, templ string, messages []openai.ChatC
|
|||||||
// to avoid hallucinations
|
// to avoid hallucinations
|
||||||
|
|
||||||
// Extract an action
|
// Extract an action
|
||||||
params, err := a.decision(job,
|
params, err := a.decision(ctx,
|
||||||
append(c, openai.ChatCompletionMessage{
|
append(c, openai.ChatCompletionMessage{
|
||||||
Role: "system",
|
Role: "system",
|
||||||
Content: "Pick the relevant action given the following reasoning: " + originalReasoning,
|
Content: "Pick the relevant action given the following reasoning: " + originalReasoning,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type Agent struct {
|
|||||||
jobQueue chan *types.Job
|
jobQueue chan *types.Job
|
||||||
context *types.ActionContext
|
context *types.ActionContext
|
||||||
|
|
||||||
currentState *types.AgentInternalState
|
currentState *action.AgentInternalState
|
||||||
|
|
||||||
selfEvaluationInProgress bool
|
selfEvaluationInProgress bool
|
||||||
pause bool
|
pause bool
|
||||||
@@ -41,8 +41,6 @@ type Agent struct {
|
|||||||
|
|
||||||
subscriberMutex sync.Mutex
|
subscriberMutex sync.Mutex
|
||||||
newMessagesSubscribers []func(openai.ChatCompletionMessage)
|
newMessagesSubscribers []func(openai.ChatCompletionMessage)
|
||||||
|
|
||||||
observer Observer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RAGDB interface {
|
type RAGDB interface {
|
||||||
@@ -71,17 +69,12 @@ func New(opts ...Option) (*Agent, error) {
|
|||||||
options: options,
|
options: options,
|
||||||
client: client,
|
client: client,
|
||||||
Character: options.character,
|
Character: options.character,
|
||||||
currentState: &types.AgentInternalState{},
|
currentState: &action.AgentInternalState{},
|
||||||
context: types.NewActionContext(ctx, cancel),
|
context: types.NewActionContext(ctx, cancel),
|
||||||
newConversations: make(chan openai.ChatCompletionMessage),
|
newConversations: make(chan openai.ChatCompletionMessage),
|
||||||
newMessagesSubscribers: options.newConversationsSubscribers,
|
newMessagesSubscribers: options.newConversationsSubscribers,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize observer if provided
|
|
||||||
if options.observer != nil {
|
|
||||||
a.observer = options.observer
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.options.statefile != "" {
|
if a.options.statefile != "" {
|
||||||
if _, err := os.Stat(a.options.statefile); err == nil {
|
if _, err := os.Stat(a.options.statefile); err == nil {
|
||||||
if err = a.LoadState(a.options.statefile); err != nil {
|
if err = a.LoadState(a.options.statefile); err != nil {
|
||||||
@@ -153,14 +146,6 @@ func (a *Agent) Ask(opts ...types.JobOption) *types.JobResult {
|
|||||||
xlog.Debug("Agent has finished being asked", "agent", a.Character.Name)
|
xlog.Debug("Agent has finished being asked", "agent", a.Character.Name)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if a.observer != nil {
|
|
||||||
obs := a.observer.NewObservable()
|
|
||||||
obs.Name = "job"
|
|
||||||
obs.Icon = "plug"
|
|
||||||
a.observer.Update(*obs)
|
|
||||||
opts = append(opts, types.WithObservable(obs))
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.Execute(types.NewJob(
|
return a.Execute(types.NewJob(
|
||||||
append(
|
append(
|
||||||
opts,
|
opts,
|
||||||
@@ -178,20 +163,6 @@ func (a *Agent) Execute(j *types.Job) *types.JobResult {
|
|||||||
xlog.Debug("Agent has finished", "agent", a.Character.Name)
|
xlog.Debug("Agent has finished", "agent", a.Character.Name)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if j.Obs != nil {
|
|
||||||
j.Result.AddFinalizer(func(ccm []openai.ChatCompletionMessage) {
|
|
||||||
j.Obs.Completion = &types.Completion{
|
|
||||||
Conversation: ccm,
|
|
||||||
}
|
|
||||||
|
|
||||||
if j.Result.Error != nil {
|
|
||||||
j.Obs.Completion.Error = j.Result.Error.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
a.observer.Update(*j.Obs)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Enqueue(j)
|
a.Enqueue(j)
|
||||||
return j.Result.WaitResult()
|
return j.Result.WaitResult()
|
||||||
}
|
}
|
||||||
@@ -266,78 +237,34 @@ func (a *Agent) Memory() RAGDB {
|
|||||||
return a.options.ragdb
|
return a.options.ragdb
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) runAction(job *types.Job, chosenAction types.Action, params types.ActionParams) (result types.ActionResult, err error) {
|
func (a *Agent) runAction(ctx context.Context, chosenAction types.Action, params types.ActionParams) (result types.ActionResult, err error) {
|
||||||
var obs *types.Observable
|
|
||||||
if job.Obs != nil {
|
|
||||||
obs = a.observer.NewObservable()
|
|
||||||
obs.Name = "action"
|
|
||||||
obs.Icon = "bolt"
|
|
||||||
obs.ParentID = job.Obs.ID
|
|
||||||
obs.Creation = &types.Creation{
|
|
||||||
FunctionDefinition: chosenAction.Definition().ToFunctionDefinition(),
|
|
||||||
FunctionParams: params,
|
|
||||||
}
|
|
||||||
a.observer.Update(*obs)
|
|
||||||
}
|
|
||||||
|
|
||||||
xlog.Info("[runAction] Running action", "action", chosenAction.Definition().Name, "agent", a.Character.Name, "params", params.String())
|
|
||||||
|
|
||||||
for _, act := range a.availableActions() {
|
for _, act := range a.availableActions() {
|
||||||
if act.Definition().Name == chosenAction.Definition().Name {
|
if act.Definition().Name == chosenAction.Definition().Name {
|
||||||
res, err := act.Run(job.GetContext(), params)
|
res, err := act.Run(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if obs != nil {
|
|
||||||
obs.Completion = &types.Completion{
|
|
||||||
Error: err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.ActionResult{}, fmt.Errorf("error running action: %w", err)
|
return types.ActionResult{}, fmt.Errorf("error running action: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if obs != nil {
|
|
||||||
obs.Progress = append(obs.Progress, types.Progress{
|
|
||||||
ActionResult: res.Result,
|
|
||||||
})
|
|
||||||
a.observer.Update(*obs)
|
|
||||||
}
|
|
||||||
|
|
||||||
result = res
|
result = res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xlog.Info("[runAction] Running action", "action", chosenAction.Definition().Name, "agent", a.Character.Name, "params", params.String())
|
||||||
|
|
||||||
if chosenAction.Definition().Name.Is(action.StateActionName) {
|
if chosenAction.Definition().Name.Is(action.StateActionName) {
|
||||||
// We need to store the result in the state
|
// We need to store the result in the state
|
||||||
state := types.AgentInternalState{}
|
state := action.AgentInternalState{}
|
||||||
|
|
||||||
err = params.Unmarshal(&state)
|
err = params.Unmarshal(&state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
werr := fmt.Errorf("error unmarshalling state of the agent: %w", err)
|
return types.ActionResult{}, fmt.Errorf("error unmarshalling state of the agent: %w", err)
|
||||||
if obs != nil {
|
|
||||||
obs.Completion = &types.Completion{
|
|
||||||
Error: werr.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return types.ActionResult{}, werr
|
|
||||||
}
|
}
|
||||||
// update the current state with the one we just got from the action
|
// update the current state with the one we just got from the action
|
||||||
a.currentState = &state
|
a.currentState = &state
|
||||||
if obs != nil {
|
|
||||||
obs.Progress = append(obs.Progress, types.Progress{
|
|
||||||
AgentState: &state,
|
|
||||||
})
|
|
||||||
a.observer.Update(*obs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the state file
|
// update the state file
|
||||||
if a.options.statefile != "" {
|
if a.options.statefile != "" {
|
||||||
if err := a.SaveState(a.options.statefile); err != nil {
|
if err := a.SaveState(a.options.statefile); err != nil {
|
||||||
if obs != nil {
|
|
||||||
obs.Completion = &types.Completion{
|
|
||||||
Error: err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.ActionResult{}, err
|
return types.ActionResult{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,11 +272,6 @@ func (a *Agent) runAction(job *types.Job, chosenAction types.Action, params type
|
|||||||
|
|
||||||
xlog.Debug("[runAction] Action result", "action", chosenAction.Definition().Name, "params", params.String(), "result", result.Result)
|
xlog.Debug("[runAction] Action result", "action", chosenAction.Definition().Name, "params", params.String(), "result", result.Result)
|
||||||
|
|
||||||
if obs != nil {
|
|
||||||
obs.MakeLastProgressCompletion()
|
|
||||||
a.observer.Update(*obs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,7 +468,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
|||||||
chosenAction = *action
|
chosenAction = *action
|
||||||
reasoning = reason
|
reasoning = reason
|
||||||
if params == nil {
|
if params == nil {
|
||||||
p, err := a.generateParameters(job, pickTemplate, chosenAction, conv, reasoning, maxRetries)
|
p, err := a.generateParameters(job.GetContext(), pickTemplate, chosenAction, conv, reasoning, maxRetries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xlog.Error("Error generating parameters, trying again", "error", err)
|
xlog.Error("Error generating parameters, trying again", "error", err)
|
||||||
// try again
|
// try again
|
||||||
@@ -561,7 +483,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
|||||||
job.ResetNextAction()
|
job.ResetNextAction()
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
chosenAction, actionParams, reasoning, err = a.pickAction(job, pickTemplate, conv, maxRetries)
|
chosenAction, actionParams, reasoning, err = a.pickAction(job.GetContext(), pickTemplate, conv, maxRetries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xlog.Error("Error picking action", "error", err)
|
xlog.Error("Error picking action", "error", err)
|
||||||
job.Result.Finish(err)
|
job.Result.Finish(err)
|
||||||
@@ -635,7 +557,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
|||||||
"reasoning", reasoning,
|
"reasoning", reasoning,
|
||||||
)
|
)
|
||||||
|
|
||||||
params, err := a.generateParameters(job, pickTemplate, chosenAction, conv, reasoning, maxRetries)
|
params, err := a.generateParameters(job.GetContext(), pickTemplate, chosenAction, conv, reasoning, maxRetries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xlog.Error("Error generating parameters, trying again", "error", err)
|
xlog.Error("Error generating parameters, trying again", "error", err)
|
||||||
// try again
|
// try again
|
||||||
@@ -730,7 +652,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !chosenAction.Definition().Name.Is(action.PlanActionName) {
|
if !chosenAction.Definition().Name.Is(action.PlanActionName) {
|
||||||
result, err := a.runAction(job, chosenAction, actionParams)
|
result, err := a.runAction(job.GetContext(), chosenAction, actionParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//job.Result.Finish(fmt.Errorf("error running action: %w", err))
|
//job.Result.Finish(fmt.Errorf("error running action: %w", err))
|
||||||
//return
|
//return
|
||||||
@@ -755,7 +677,7 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// given the result, we can now re-evaluate the conversation
|
// given the result, we can now re-evaluate the conversation
|
||||||
followingAction, followingParams, reasoning, err := a.pickAction(job, reEvaluationTemplate, conv, maxRetries)
|
followingAction, followingParams, reasoning, err := a.pickAction(job.GetContext(), reEvaluationTemplate, conv, maxRetries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
job.Result.Conversation = conv
|
job.Result.Conversation = conv
|
||||||
job.Result.Finish(fmt.Errorf("error picking action: %w", err))
|
job.Result.Finish(fmt.Errorf("error picking action: %w", err))
|
||||||
@@ -1033,7 +955,3 @@ func (a *Agent) loop(timer *time.Timer, job *types.Job) {
|
|||||||
xlog.Debug("Agent is consuming a job", "agent", a.Character.Name, "job", job)
|
xlog.Debug("Agent is consuming a job", "agent", a.Character.Name, "job", job)
|
||||||
a.consumeJob(job, UserRole)
|
a.consumeJob(job, UserRole)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) Observer() Observer {
|
|
||||||
return a.observer
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -226,10 +226,7 @@ var _ = Describe("Agent test", func() {
|
|||||||
WithLLMAPIKey(apiKeyURL),
|
WithLLMAPIKey(apiKeyURL),
|
||||||
WithTimeout("10m"),
|
WithTimeout("10m"),
|
||||||
WithActions(
|
WithActions(
|
||||||
&TestAction{response: map[string]string{
|
actions.NewSearch(map[string]string{}),
|
||||||
"boston": testActionResult,
|
|
||||||
"milan": testActionResult2,
|
|
||||||
}},
|
|
||||||
),
|
),
|
||||||
EnablePlanning,
|
EnablePlanning,
|
||||||
EnableForceReasoning,
|
EnableForceReasoning,
|
||||||
@@ -241,21 +238,18 @@ var _ = Describe("Agent test", func() {
|
|||||||
defer agent.Stop()
|
defer agent.Stop()
|
||||||
|
|
||||||
result := agent.Ask(
|
result := agent.Ask(
|
||||||
types.WithText("Use the plan tool to do two actions in sequence: search for the weather in boston and search for the weather in milan"),
|
types.WithText("Thoroughly plan a trip to San Francisco from Venice, Italy; check flight times, visa requirements and whether electrical items are allowed in cabin luggage."),
|
||||||
)
|
)
|
||||||
Expect(len(result.State)).To(BeNumerically(">", 1))
|
Expect(len(result.State)).To(BeNumerically(">", 1))
|
||||||
|
|
||||||
actionsExecuted := []string{}
|
actionsExecuted := []string{}
|
||||||
actionResults := []string{}
|
|
||||||
for _, r := range result.State {
|
for _, r := range result.State {
|
||||||
xlog.Info(r.Result)
|
xlog.Info(r.Result)
|
||||||
actionsExecuted = append(actionsExecuted, r.Action.Definition().Name.String())
|
actionsExecuted = append(actionsExecuted, r.Action.Definition().Name.String())
|
||||||
actionResults = append(actionResults, r.ActionResult.Result)
|
|
||||||
}
|
}
|
||||||
Expect(actionsExecuted).To(ContainElement("get_weather"), fmt.Sprint(result))
|
Expect(actionsExecuted).To(ContainElement("search_internet"), fmt.Sprint(result))
|
||||||
Expect(actionsExecuted).To(ContainElement("plan"), fmt.Sprint(result))
|
Expect(actionsExecuted).To(ContainElement("plan"), fmt.Sprint(result))
|
||||||
Expect(actionResults).To(ContainElement(testActionResult), fmt.Sprint(result))
|
|
||||||
Expect(actionResults).To(ContainElement(testActionResult2), fmt.Sprint(result))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Can initiate conversations", func() {
|
It("Can initiate conversations", func() {
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/mudler/LocalAGI/core/sse"
|
|
||||||
"github.com/mudler/LocalAGI/core/types"
|
|
||||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Observer interface {
|
|
||||||
NewObservable() *types.Observable
|
|
||||||
Update(types.Observable)
|
|
||||||
History() []types.Observable
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSEObserver struct {
|
|
||||||
agent string
|
|
||||||
maxID int32
|
|
||||||
manager sse.Manager
|
|
||||||
|
|
||||||
mutex sync.Mutex
|
|
||||||
history []types.Observable
|
|
||||||
historyLast int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSSEObserver(agent string, manager sse.Manager) *SSEObserver {
|
|
||||||
return &SSEObserver{
|
|
||||||
agent: agent,
|
|
||||||
maxID: 1,
|
|
||||||
manager: manager,
|
|
||||||
history: make([]types.Observable, 100),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSEObserver) NewObservable() *types.Observable {
|
|
||||||
id := atomic.AddInt32(&s.maxID, 1)
|
|
||||||
|
|
||||||
return &types.Observable{
|
|
||||||
ID: id - 1,
|
|
||||||
Agent: s.agent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSEObserver) Update(obs types.Observable) {
|
|
||||||
data, err := json.Marshal(obs)
|
|
||||||
if err != nil {
|
|
||||||
xlog.Error("Error marshaling observable", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
msg := sse.NewMessage(string(data)).WithEvent("observable_update")
|
|
||||||
s.manager.Send(msg)
|
|
||||||
|
|
||||||
s.mutex.Lock()
|
|
||||||
defer s.mutex.Unlock()
|
|
||||||
|
|
||||||
for i, o := range s.history {
|
|
||||||
if o.ID == obs.ID {
|
|
||||||
s.history[i] = obs
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.history[s.historyLast] = obs
|
|
||||||
s.historyLast += 1
|
|
||||||
if s.historyLast >= len(s.history) {
|
|
||||||
s.historyLast = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SSEObserver) History() []types.Observable {
|
|
||||||
h := make([]types.Observable, 0, 20)
|
|
||||||
|
|
||||||
s.mutex.Lock()
|
|
||||||
defer s.mutex.Unlock()
|
|
||||||
|
|
||||||
for _, obs := range s.history {
|
|
||||||
if obs.ID == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
h = append(h, obs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
@@ -53,8 +53,6 @@ type options struct {
|
|||||||
mcpServers []MCPServer
|
mcpServers []MCPServer
|
||||||
|
|
||||||
newConversationsSubscribers []func(openai.ChatCompletionMessage)
|
newConversationsSubscribers []func(openai.ChatCompletionMessage)
|
||||||
|
|
||||||
observer Observer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *options) SeparatedMultimodalModel() bool {
|
func (o *options) SeparatedMultimodalModel() bool {
|
||||||
@@ -338,10 +336,3 @@ func WithActions(actions ...types.Action) Option {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithObserver(observer Observer) Option {
|
|
||||||
return func(o *options) error {
|
|
||||||
o.observer = observer
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/mudler/LocalAGI/core/types"
|
"github.com/mudler/LocalAGI/core/action"
|
||||||
"github.com/sashabaranov/go-openai/jsonschema"
|
"github.com/sashabaranov/go-openai/jsonschema"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
// in the prompts
|
// in the prompts
|
||||||
type PromptHUD struct {
|
type PromptHUD struct {
|
||||||
Character Character `json:"character"`
|
Character Character `json:"character"`
|
||||||
CurrentState types.AgentInternalState `json:"current_state"`
|
CurrentState action.AgentInternalState `json:"current_state"`
|
||||||
PermanentGoal string `json:"permanent_goal"`
|
PermanentGoal string `json:"permanent_goal"`
|
||||||
ShowCharacter bool `json:"show_character"`
|
ShowCharacter bool `json:"show_character"`
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ func Load(path string) (*Character, error) {
|
|||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) State() types.AgentInternalState {
|
func (a *Agent) State() action.AgentInternalState {
|
||||||
return *a.currentState
|
return *a.currentState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -166,56 +166,7 @@ func (a *AgentPool) CreateAgent(name string, agentConfig *AgentConfig) error {
|
|||||||
}
|
}
|
||||||
}(a.pool[name])
|
}(a.pool[name])
|
||||||
|
|
||||||
return a.startAgentWithConfig(name, agentConfig, nil)
|
return a.startAgentWithConfig(name, agentConfig)
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AgentPool) RecreateAgent(name string, agentConfig *AgentConfig) error {
|
|
||||||
a.Lock()
|
|
||||||
defer a.Unlock()
|
|
||||||
|
|
||||||
oldAgent := a.agents[name]
|
|
||||||
var o *types.Observable
|
|
||||||
obs := oldAgent.Observer()
|
|
||||||
if obs != nil {
|
|
||||||
o = obs.NewObservable()
|
|
||||||
o.Name = "Restarting Agent"
|
|
||||||
o.Icon = "sync"
|
|
||||||
o.Creation = &types.Creation{}
|
|
||||||
obs.Update(*o)
|
|
||||||
}
|
|
||||||
|
|
||||||
stateFile, characterFile := a.stateFiles(name)
|
|
||||||
|
|
||||||
os.Remove(stateFile)
|
|
||||||
os.Remove(characterFile)
|
|
||||||
|
|
||||||
oldAgent.Stop()
|
|
||||||
|
|
||||||
a.pool[name] = *agentConfig
|
|
||||||
delete(a.agents, name)
|
|
||||||
|
|
||||||
if err := a.save(); err != nil {
|
|
||||||
if obs != nil {
|
|
||||||
o.Completion = &types.Completion{Error: err.Error()}
|
|
||||||
obs.Update(*o)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.startAgentWithConfig(name, agentConfig, obs); err != nil {
|
|
||||||
if obs != nil {
|
|
||||||
o.Completion = &types.Completion{Error: err.Error()}
|
|
||||||
obs.Update(*o)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if obs != nil {
|
|
||||||
o.Completion = &types.Completion{}
|
|
||||||
obs.Update(*o)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createAgentAvatar(APIURL, APIKey, model, imageModel, avatarDir string, agent AgentConfig) error {
|
func createAgentAvatar(APIURL, APIKey, model, imageModel, avatarDir string, agent AgentConfig) error {
|
||||||
@@ -317,13 +268,8 @@ func (a *AgentPool) GetStatusHistory(name string) *Status {
|
|||||||
return a.agentStatus[name]
|
return a.agentStatus[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs Observer) error {
|
func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error {
|
||||||
var manager sse.Manager
|
manager := sse.NewManager(5)
|
||||||
if m, ok := a.managers[name]; ok {
|
|
||||||
manager = m
|
|
||||||
} else {
|
|
||||||
manager = sse.NewManager(5)
|
|
||||||
}
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
model := a.defaultModel
|
model := a.defaultModel
|
||||||
multimodalModel := a.defaultMultimodalModel
|
multimodalModel := a.defaultMultimodalModel
|
||||||
@@ -385,10 +331,6 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs O
|
|||||||
// dynamicPrompts = append(dynamicPrompts, p.ToMap())
|
// dynamicPrompts = append(dynamicPrompts, p.ToMap())
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if obs == nil {
|
|
||||||
obs = NewSSEObserver(name, manager)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := []Option{
|
opts := []Option{
|
||||||
WithModel(model),
|
WithModel(model),
|
||||||
WithLLMAPIURL(a.apiURL),
|
WithLLMAPIURL(a.apiURL),
|
||||||
@@ -465,7 +407,6 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs O
|
|||||||
c.AgentResultCallback()(state)
|
c.AgentResultCallback()(state)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
WithObserver(obs),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.HUD {
|
if config.HUD {
|
||||||
@@ -568,7 +509,7 @@ func (a *AgentPool) StartAll() error {
|
|||||||
if a.agents[name] != nil { // Agent already started
|
if a.agents[name] != nil { // Agent already started
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := a.startAgentWithConfig(name, &config, nil); err != nil {
|
if err := a.startAgentWithConfig(name, &config); err != nil {
|
||||||
xlog.Error("Failed to start agent", "name", name, "error", err)
|
xlog.Error("Failed to start agent", "name", name, "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -606,7 +547,7 @@ func (a *AgentPool) Start(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if config, ok := a.pool[name]; ok {
|
if config, ok := a.pool[name]; ok {
|
||||||
return a.startAgentWithConfig(name, &config, nil)
|
return a.startAgentWithConfig(name, &config)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("agent %s not found", name)
|
return fmt.Errorf("agent %s not found", name)
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ type Job struct {
|
|||||||
|
|
||||||
context context.Context
|
context context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
Obs *Observable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionRequest struct {
|
type ActionRequest struct {
|
||||||
@@ -200,9 +198,3 @@ func (j *Job) Cancel() {
|
|||||||
func (j *Job) GetContext() context.Context {
|
func (j *Job) GetContext() context.Context {
|
||||||
return j.context
|
return j.context
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithObservable(obs *Observable) JobOption {
|
|
||||||
return func(j *Job) {
|
|
||||||
j.Obs = obs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mudler/LocalAGI/pkg/xlog"
|
|
||||||
"github.com/sashabaranov/go-openai"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Creation struct {
|
|
||||||
ChatCompletionRequest *openai.ChatCompletionRequest `json:"chat_completion_request,omitempty"`
|
|
||||||
FunctionDefinition *openai.FunctionDefinition `json:"function_definition,omitempty"`
|
|
||||||
FunctionParams ActionParams `json:"function_params,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Progress struct {
|
|
||||||
Error string `json:"error,omitempty"`
|
|
||||||
ChatCompletionResponse *openai.ChatCompletionResponse `json:"chat_completion_response,omitempty"`
|
|
||||||
ActionResult string `json:"action_result,omitempty"`
|
|
||||||
AgentState *AgentInternalState `json:"agent_state"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Completion struct {
|
|
||||||
Error string `json:"error,omitempty"`
|
|
||||||
ChatCompletionResponse *openai.ChatCompletionResponse `json:"chat_completion_response,omitempty"`
|
|
||||||
Conversation []openai.ChatCompletionMessage `json:"conversation,omitempty"`
|
|
||||||
ActionResult string `json:"action_result,omitempty"`
|
|
||||||
AgentState *AgentInternalState `json:"agent_state"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Observable struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
ParentID int32 `json:"parent_id,omitempty"`
|
|
||||||
Agent string `json:"agent"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Icon string `json:"icon"`
|
|
||||||
|
|
||||||
Creation *Creation `json:"creation,omitempty"`
|
|
||||||
Progress []Progress `json:"progress,omitempty"`
|
|
||||||
Completion *Completion `json:"completion,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Observable) AddProgress(p Progress) {
|
|
||||||
if o.Progress == nil {
|
|
||||||
o.Progress = make([]Progress, 0)
|
|
||||||
}
|
|
||||||
o.Progress = append(o.Progress, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Observable) MakeLastProgressCompletion() {
|
|
||||||
if len(o.Progress) == 0 {
|
|
||||||
xlog.Error("Observable completed without any progress", "id", o.ID, "name", o.Name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p := o.Progress[len(o.Progress)-1]
|
|
||||||
o.Progress = o.Progress[:len(o.Progress)-1]
|
|
||||||
o.Completion = &Completion{
|
|
||||||
Error: p.Error,
|
|
||||||
ChatCompletionResponse: p.ChatCompletionResponse,
|
|
||||||
ActionResult: p.ActionResult,
|
|
||||||
AgentState: p.AgentState,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// State is the structure
|
|
||||||
// that is used to keep track of the current state
|
|
||||||
// and the Agent's short memory that it can update
|
|
||||||
// Besides a long term memory that is accessible by the agent (With vector database),
|
|
||||||
// And a context memory (that is always powered by a vector database),
|
|
||||||
// this memory is the shorter one that the LLM keeps across conversation and across its
|
|
||||||
// reasoning process's and life time.
|
|
||||||
// TODO: A special action is then used to let the LLM itself update its memory
|
|
||||||
// periodically during self-processing, and the same action is ALSO exposed
|
|
||||||
// during the conversation to let the user put for example, a new goal to the agent.
|
|
||||||
type AgentInternalState struct {
|
|
||||||
NowDoing string `json:"doing_now"`
|
|
||||||
DoingNext string `json:"doing_next"`
|
|
||||||
DoneHistory []string `json:"done_history"`
|
|
||||||
Memories []string `json:"memories"`
|
|
||||||
Goal string `json:"goal"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const fmtT = `=====================
|
|
||||||
NowDoing: %s
|
|
||||||
DoingNext: %s
|
|
||||||
Your current goal is: %s
|
|
||||||
You have done: %+v
|
|
||||||
You have a short memory with: %+v
|
|
||||||
=====================
|
|
||||||
`
|
|
||||||
|
|
||||||
func (c AgentInternalState) String() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
fmtT,
|
|
||||||
c.NowDoing,
|
|
||||||
c.DoingNext,
|
|
||||||
c.Goal,
|
|
||||||
c.DoneHistory,
|
|
||||||
c.Memories,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -11,6 +11,11 @@ services:
|
|||||||
# On a system with integrated GPU and an Arc 770, this is the Arc 770
|
# On a system with integrated GPU and an Arc 770, this is the Arc 770
|
||||||
- /dev/dri/card1
|
- /dev/dri/card1
|
||||||
- /dev/dri/renderD129
|
- /dev/dri/renderD129
|
||||||
|
command:
|
||||||
|
- ${MODEL_NAME:-arcee-agent}
|
||||||
|
- ${MULTIMODAL_MODEL:-minicpm-v-2_6}
|
||||||
|
- ${IMAGE_MODEL:-sd-1.5-ggml}
|
||||||
|
- granite-embedding-107m-multilingual
|
||||||
|
|
||||||
localrecall:
|
localrecall:
|
||||||
extends:
|
extends:
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- LOCALAI_SINGLE_ACTIVE_BACKEND=true
|
- LOCALAI_SINGLE_ACTIVE_BACKEND=true
|
||||||
- DEBUG=true
|
- DEBUG=true
|
||||||
image: localai/localai:master-cublas-cuda12-ffmpeg-core
|
image: localai/localai:master-sycl-f32-ffmpeg-core
|
||||||
# For images with python backends, use:
|
|
||||||
# image: localai/localai:master-cublas-cuda12-ffmpeg
|
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
reservations:
|
reservations:
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ services:
|
|||||||
# Image list (dockerhub): https://hub.docker.com/r/localai/localai
|
# Image list (dockerhub): https://hub.docker.com/r/localai/localai
|
||||||
image: localai/localai:master-ffmpeg-core
|
image: localai/localai:master-ffmpeg-core
|
||||||
command:
|
command:
|
||||||
- ${MODEL_NAME:-gemma-3-12b-it-qat}
|
- ${MODEL_NAME:-arcee-agent}
|
||||||
- ${MULTIMODAL_MODEL:-minicpm-v-2_6}
|
- ${MULTIMODAL_MODEL:-minicpm-v-2_6}
|
||||||
- ${IMAGE_MODEL:-sd-1.5-ggml}
|
- ${IMAGE_MODEL:-flux.1-dev}
|
||||||
- granite-embedding-107m-multilingual
|
- granite-embedding-107m-multilingual
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8080/readyz"]
|
test: ["CMD", "curl", "-f", "http://localhost:8080/readyz"]
|
||||||
@@ -59,7 +59,7 @@ services:
|
|||||||
- 8080:3000
|
- 8080:3000
|
||||||
#image: quay.io/mudler/localagi:master
|
#image: quay.io/mudler/localagi:master
|
||||||
environment:
|
environment:
|
||||||
- LOCALAGI_MODEL=${MODEL_NAME:-gemma-3-12b-it-qat}
|
- LOCALAGI_MODEL=${MODEL_NAME:-arcee-agent}
|
||||||
- LOCALAGI_MULTIMODAL_MODEL=${MULTIMODAL_MODEL:-minicpm-v-2_6}
|
- LOCALAGI_MULTIMODAL_MODEL=${MULTIMODAL_MODEL:-minicpm-v-2_6}
|
||||||
- LOCALAGI_IMAGE_MODEL=${IMAGE_MODEL:-sd-1.5-ggml}
|
- LOCALAGI_IMAGE_MODEL=${IMAGE_MODEL:-sd-1.5-ggml}
|
||||||
- LOCALAGI_LLM_API_URL=http://localai:8080
|
- LOCALAGI_LLM_API_URL=http://localai:8080
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -15,11 +15,11 @@ require (
|
|||||||
github.com/gofiber/template/html/v2 v2.1.3
|
github.com/gofiber/template/html/v2 v2.1.3
|
||||||
github.com/google/go-github/v69 v69.2.0
|
github.com/google/go-github/v69 v69.2.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/metoro-io/mcp-golang v0.11.0
|
github.com/metoro-io/mcp-golang v0.9.0
|
||||||
github.com/onsi/ginkgo/v2 v2.23.4
|
github.com/onsi/ginkgo/v2 v2.23.4
|
||||||
github.com/onsi/gomega v1.37.0
|
github.com/onsi/gomega v1.37.0
|
||||||
github.com/philippgille/chromem-go v0.7.0
|
github.com/philippgille/chromem-go v0.7.0
|
||||||
github.com/sashabaranov/go-openai v1.38.2
|
github.com/sashabaranov/go-openai v1.38.1
|
||||||
github.com/slack-go/slack v0.16.0
|
github.com/slack-go/slack v0.16.0
|
||||||
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64
|
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64
|
||||||
github.com/tmc/langchaingo v0.1.13
|
github.com/tmc/langchaingo v0.1.13
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -144,8 +144,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/metoro-io/mcp-golang v0.11.0 h1:1k+VSE9QaeMTLn0gJ3FgE/DcjsCBsLFnz5eSFbgXUiI=
|
github.com/metoro-io/mcp-golang v0.9.0 h1:GpFENjieZ/KosTu7CE7tyGI/a2FhiG0nandR0d8B3rE=
|
||||||
github.com/metoro-io/mcp-golang v0.11.0/go.mod h1:ifLP9ZzKpN1UqFWNTpAHOqSvNkMK6b7d1FSZ5Lu0lN0=
|
github.com/metoro-io/mcp-golang v0.9.0/go.mod h1:ifLP9ZzKpN1UqFWNTpAHOqSvNkMK6b7d1FSZ5Lu0lN0=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -179,8 +179,8 @@ github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGK
|
|||||||
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY=
|
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY=
|
||||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
github.com/sashabaranov/go-openai v1.38.2 h1:akrssjj+6DY3lWuDwHv6cBvJ8Z+FZDM9XEaaYFt0Auo=
|
github.com/sashabaranov/go-openai v1.38.1 h1:TtZabbFQZa1nEni/IhVtDF/WQjVqDgd+cWR5OeddzF8=
|
||||||
github.com/sashabaranov/go-openai v1.38.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
github.com/sashabaranov/go-openai v1.38.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||||
github.com/slack-go/slack v0.16.0 h1:khp/WCFv+Hb/B/AJaAwvcxKun0hM6grN0bUZ8xG60P8=
|
github.com/slack-go/slack v0.16.0 h1:khp/WCFv+Hb/B/AJaAwvcxKun0hM6grN0bUZ8xG60P8=
|
||||||
github.com/slack-go/slack v0.16.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
github.com/slack-go/slack v0.16.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||||
|
|||||||
5
main.go
5
main.go
@@ -22,7 +22,6 @@ var withLogs = os.Getenv("LOCALAGI_ENABLE_CONVERSATIONS_LOGGING") == "true"
|
|||||||
var apiKeysEnv = os.Getenv("LOCALAGI_API_KEYS")
|
var apiKeysEnv = os.Getenv("LOCALAGI_API_KEYS")
|
||||||
var imageModel = os.Getenv("LOCALAGI_IMAGE_MODEL")
|
var imageModel = os.Getenv("LOCALAGI_IMAGE_MODEL")
|
||||||
var conversationDuration = os.Getenv("LOCALAGI_CONVERSATION_DURATION")
|
var conversationDuration = os.Getenv("LOCALAGI_CONVERSATION_DURATION")
|
||||||
var localOperatorBaseURL = os.Getenv("LOCALOPERATOR_BASE_URL")
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if baseModel == "" {
|
if baseModel == "" {
|
||||||
@@ -62,9 +61,7 @@ func main() {
|
|||||||
apiKey,
|
apiKey,
|
||||||
stateDir,
|
stateDir,
|
||||||
localRAG,
|
localRAG,
|
||||||
services.Actions(map[string]string{
|
services.Actions,
|
||||||
"browser-agent-runner-base-url": localOperatorBaseURL,
|
|
||||||
}),
|
|
||||||
services.Connectors,
|
services.Connectors,
|
||||||
services.DynamicPrompts,
|
services.DynamicPrompts,
|
||||||
timeout,
|
timeout,
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client represents a client for interacting with the LocalOperator API
|
|
||||||
type Client struct {
|
|
||||||
baseURL string
|
|
||||||
httpClient *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates a new API client
|
|
||||||
func NewClient(baseURL string) *Client {
|
|
||||||
return &Client{
|
|
||||||
baseURL: baseURL,
|
|
||||||
httpClient: &http.Client{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AgentRequest represents the request body for running an agent
|
|
||||||
type AgentRequest struct {
|
|
||||||
Goal string `json:"goal"`
|
|
||||||
MaxAttempts int `json:"max_attempts,omitempty"`
|
|
||||||
MaxNoActionAttempts int `json:"max_no_action_attempts,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateDescription represents a single state in the agent's history
|
|
||||||
type StateDescription struct {
|
|
||||||
CurrentURL string `json:"current_url"`
|
|
||||||
PageTitle string `json:"page_title"`
|
|
||||||
PageContentDescription string `json:"page_content_description"`
|
|
||||||
Screenshot string `json:"screenshot"`
|
|
||||||
ScreenshotMimeType string `json:"screenshot_mime_type"` // MIME type of the screenshot (e.g., "image/png")
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateHistory represents the complete history of states during agent execution
|
|
||||||
type StateHistory struct {
|
|
||||||
States []StateDescription `json:"states"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunAgent sends a request to run an agent with the given goal
|
|
||||||
func (c *Client) RunBrowserAgent(req AgentRequest) (*StateHistory, error) {
|
|
||||||
body, err := json.Marshal(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.httpClient.Post(
|
|
||||||
fmt.Sprintf("%s/api/browser/run", c.baseURL),
|
|
||||||
"application/json",
|
|
||||||
bytes.NewBuffer(body),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
var state StateHistory
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&state); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &state, nil
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ const (
|
|||||||
// Actions
|
// Actions
|
||||||
ActionSearch = "search"
|
ActionSearch = "search"
|
||||||
ActionCustom = "custom"
|
ActionCustom = "custom"
|
||||||
ActionBrowserAgentRunner = "browser-agent-runner"
|
|
||||||
ActionGithubIssueLabeler = "github-issue-labeler"
|
ActionGithubIssueLabeler = "github-issue-labeler"
|
||||||
ActionGithubIssueOpener = "github-issue-opener"
|
ActionGithubIssueOpener = "github-issue-opener"
|
||||||
ActionGithubIssueCloser = "github-issue-closer"
|
ActionGithubIssueCloser = "github-issue-closer"
|
||||||
@@ -53,7 +52,6 @@ var AvailableActions = []string{
|
|||||||
ActionGithubIssueSearcher,
|
ActionGithubIssueSearcher,
|
||||||
ActionGithubRepositoryGet,
|
ActionGithubRepositoryGet,
|
||||||
ActionGithubGetAllContent,
|
ActionGithubGetAllContent,
|
||||||
ActionBrowserAgentRunner,
|
|
||||||
ActionGithubRepositoryCreateOrUpdate,
|
ActionGithubRepositoryCreateOrUpdate,
|
||||||
ActionGithubIssueReader,
|
ActionGithubIssueReader,
|
||||||
ActionGithubIssueCommenter,
|
ActionGithubIssueCommenter,
|
||||||
@@ -73,34 +71,31 @@ var AvailableActions = []string{
|
|||||||
ActionShellcommand,
|
ActionShellcommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
func Actions(actionsConfigs map[string]string) func(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
func Actions(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
||||||
return func(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
return func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
||||||
return func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
allActions := []types.Action{}
|
||||||
allActions := []types.Action{}
|
|
||||||
|
|
||||||
agentName := a.Name
|
agentName := a.Name
|
||||||
|
|
||||||
for _, a := range a.Actions {
|
for _, a := range a.Actions {
|
||||||
var config map[string]string
|
var config map[string]string
|
||||||
if err := json.Unmarshal([]byte(a.Config), &config); err != nil {
|
if err := json.Unmarshal([]byte(a.Config), &config); err != nil {
|
||||||
xlog.Error("Error unmarshalling action config", "error", err)
|
xlog.Error("Error unmarshalling action config", "error", err)
|
||||||
continue
|
continue
|
||||||
}
|
|
||||||
|
|
||||||
a, err := Action(a.Name, agentName, config, pool, actionsConfigs)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
allActions = append(allActions, a)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return allActions
|
a, err := Action(a.Name, agentName, config, pool)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
allActions = append(allActions, a)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
return allActions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Action(name, agentName string, config map[string]string, pool *state.AgentPool, actionsConfigs map[string]string) (types.Action, error) {
|
func Action(name, agentName string, config map[string]string, pool *state.AgentPool) (types.Action, error) {
|
||||||
var a types.Action
|
var a types.Action
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -119,8 +114,6 @@ func Action(name, agentName string, config map[string]string, pool *state.AgentP
|
|||||||
a = actions.NewGithubIssueCloser(config)
|
a = actions.NewGithubIssueCloser(config)
|
||||||
case ActionGithubIssueSearcher:
|
case ActionGithubIssueSearcher:
|
||||||
a = actions.NewGithubIssueSearch(config)
|
a = actions.NewGithubIssueSearch(config)
|
||||||
case ActionBrowserAgentRunner:
|
|
||||||
a = actions.NewBrowserAgentRunner(config, actionsConfigs["browser-agent-runner-base-url"])
|
|
||||||
case ActionGithubIssueReader:
|
case ActionGithubIssueReader:
|
||||||
a = actions.NewGithubIssueReader(config)
|
a = actions.NewGithubIssueReader(config)
|
||||||
case ActionGithubPRReader:
|
case ActionGithubPRReader:
|
||||||
@@ -176,11 +169,6 @@ func ActionsConfigMeta() []config.FieldGroup {
|
|||||||
Label: "Search",
|
Label: "Search",
|
||||||
Fields: actions.SearchConfigMeta(),
|
Fields: actions.SearchConfigMeta(),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "browser-agent-runner",
|
|
||||||
Label: "Browser Agent Runner",
|
|
||||||
Fields: actions.BrowserAgentRunnerConfigMeta(),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "generate_image",
|
Name: "generate_image",
|
||||||
Label: "Generate Image",
|
Label: "Generate Image",
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/mudler/LocalAGI/core/types"
|
|
||||||
"github.com/mudler/LocalAGI/pkg/config"
|
|
||||||
api "github.com/mudler/LocalAGI/pkg/localoperator"
|
|
||||||
"github.com/sashabaranov/go-openai/jsonschema"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BrowserAgentRunner struct {
|
|
||||||
baseURL, customActionName string
|
|
||||||
client *api.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBrowserAgentRunner(config map[string]string, defaultURL string) *BrowserAgentRunner {
|
|
||||||
if config["baseURL"] == "" {
|
|
||||||
config["baseURL"] = defaultURL
|
|
||||||
}
|
|
||||||
|
|
||||||
client := api.NewClient(config["baseURL"])
|
|
||||||
|
|
||||||
return &BrowserAgentRunner{
|
|
||||||
client: client,
|
|
||||||
baseURL: config["baseURL"],
|
|
||||||
customActionName: config["customActionName"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BrowserAgentRunner) Run(ctx context.Context, params types.ActionParams) (types.ActionResult, error) {
|
|
||||||
result := api.AgentRequest{}
|
|
||||||
err := params.Unmarshal(&result)
|
|
||||||
if err != nil {
|
|
||||||
return types.ActionResult{}, fmt.Errorf("failed to unmarshal params: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req := api.AgentRequest{
|
|
||||||
Goal: result.Goal,
|
|
||||||
MaxAttempts: result.MaxAttempts,
|
|
||||||
MaxNoActionAttempts: result.MaxNoActionAttempts,
|
|
||||||
}
|
|
||||||
|
|
||||||
stateHistory, err := b.client.RunBrowserAgent(req)
|
|
||||||
if err != nil {
|
|
||||||
return types.ActionResult{}, fmt.Errorf("failed to run browser agent: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the state history into a readable string
|
|
||||||
var historyStr string
|
|
||||||
// for i, state := range stateHistory.States {
|
|
||||||
// historyStr += fmt.Sprintf("State %d:\n", i+1)
|
|
||||||
// historyStr += fmt.Sprintf(" URL: %s\n", state.CurrentURL)
|
|
||||||
// historyStr += fmt.Sprintf(" Title: %s\n", state.PageTitle)
|
|
||||||
// historyStr += fmt.Sprintf(" Description: %s\n\n", state.PageContentDescription)
|
|
||||||
// }
|
|
||||||
|
|
||||||
historyStr += fmt.Sprintf(" URL: %s\n", stateHistory.States[len(stateHistory.States)-1].CurrentURL)
|
|
||||||
historyStr += fmt.Sprintf(" Title: %s\n", stateHistory.States[len(stateHistory.States)-1].PageTitle)
|
|
||||||
historyStr += fmt.Sprintf(" Description: %s\n\n", stateHistory.States[len(stateHistory.States)-1].PageContentDescription)
|
|
||||||
|
|
||||||
return types.ActionResult{
|
|
||||||
Result: fmt.Sprintf("Browser agent completed successfully. History:\n%s", historyStr),
|
|
||||||
Metadata: map[string]interface{}{"browser_agent_history": stateHistory},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BrowserAgentRunner) Definition() types.ActionDefinition {
|
|
||||||
actionName := "run_browser_agent"
|
|
||||||
if b.customActionName != "" {
|
|
||||||
actionName = b.customActionName
|
|
||||||
}
|
|
||||||
description := "Run a browser agent to achieve a specific goal, for example: 'Go to https://www.google.com and search for 'LocalAI', and tell me what's on the first page'"
|
|
||||||
return types.ActionDefinition{
|
|
||||||
Name: types.ActionDefinitionName(actionName),
|
|
||||||
Description: description,
|
|
||||||
Properties: map[string]jsonschema.Definition{
|
|
||||||
"goal": {
|
|
||||||
Type: jsonschema.String,
|
|
||||||
Description: "The goal for the browser agent to achieve",
|
|
||||||
},
|
|
||||||
"max_attempts": {
|
|
||||||
Type: jsonschema.Number,
|
|
||||||
Description: "Maximum number of attempts the agent can make (optional)",
|
|
||||||
},
|
|
||||||
"max_no_action_attempts": {
|
|
||||||
Type: jsonschema.Number,
|
|
||||||
Description: "Maximum number of attempts without taking an action (optional)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Required: []string{"goal"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *BrowserAgentRunner) Plannable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// BrowserAgentRunnerConfigMeta returns the metadata for Browser Agent Runner action configuration fields
|
|
||||||
func BrowserAgentRunnerConfigMeta() []config.Field {
|
|
||||||
return []config.Field{
|
|
||||||
{
|
|
||||||
Name: "baseURL",
|
|
||||||
Label: "Base URL",
|
|
||||||
Type: config.FieldTypeText,
|
|
||||||
Required: false,
|
|
||||||
HelpText: "Base URL of the LocalOperator API",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "customActionName",
|
|
||||||
Label: "Custom Action Name",
|
|
||||||
Type: config.FieldTypeText,
|
|
||||||
HelpText: "Custom name for this action",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -49,8 +49,7 @@ func (g *GithubIssuesReader) Run(ctx context.Context, params types.ActionParams)
|
|||||||
return types.ActionResult{
|
return types.ActionResult{
|
||||||
Result: fmt.Sprintf(
|
Result: fmt.Sprintf(
|
||||||
"Issue %d Repository: %s\nTitle: %s\nBody: %s",
|
"Issue %d Repository: %s\nTitle: %s\nBody: %s",
|
||||||
issue.GetNumber(), issue.GetRepository().GetFullName(), issue.GetTitle(), issue.GetBody()),
|
*issue.Number, *issue.Repository.FullName, *issue.Title, *issue.Body)}, nil
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ActionResult{Result: fmt.Sprintf("Error fetching issue: %s", err.Error())}, err
|
return types.ActionResult{Result: fmt.Sprintf("Error fetching issue: %s", err.Error())}, err
|
||||||
|
|||||||
@@ -18,49 +18,38 @@ type GithubPRCreator struct {
|
|||||||
func NewGithubPRCreator(config map[string]string) *GithubPRCreator {
|
func NewGithubPRCreator(config map[string]string) *GithubPRCreator {
|
||||||
client := github.NewClient(nil).WithAuthToken(config["token"])
|
client := github.NewClient(nil).WithAuthToken(config["token"])
|
||||||
|
|
||||||
defaultBranch := config["defaultBranch"]
|
|
||||||
if defaultBranch == "" {
|
|
||||||
defaultBranch = "main" // Default to "main" if not specified
|
|
||||||
}
|
|
||||||
|
|
||||||
return &GithubPRCreator{
|
return &GithubPRCreator{
|
||||||
client: client,
|
client: client,
|
||||||
token: config["token"],
|
token: config["token"],
|
||||||
repository: config["repository"],
|
repository: config["repository"],
|
||||||
owner: config["owner"],
|
owner: config["owner"],
|
||||||
customActionName: config["customActionName"],
|
customActionName: config["customActionName"],
|
||||||
defaultBranch: defaultBranch,
|
defaultBranch: config["defaultBranch"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GithubPRCreator) createOrUpdateBranch(ctx context.Context, branchName string, owner string, repository string) error {
|
func (g *GithubPRCreator) createOrUpdateBranch(ctx context.Context, branchName string) error {
|
||||||
// Get the latest commit SHA from the default branch
|
// Get the latest commit SHA from the default branch
|
||||||
ref, _, err := g.client.Git.GetRef(ctx, owner, repository, "refs/heads/"+g.defaultBranch)
|
ref, _, err := g.client.Git.GetRef(ctx, g.owner, g.repository, "refs/heads/"+g.defaultBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get reference for default branch %s: %w", g.defaultBranch, err)
|
return fmt.Errorf("failed to get reference: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get the branch if it exists
|
// Try to get the branch if it exists
|
||||||
_, resp, err := g.client.Git.GetRef(ctx, owner, repository, "refs/heads/"+branchName)
|
_, resp, err := g.client.Git.GetRef(ctx, g.owner, g.repository, "refs/heads/"+branchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if resp == nil {
|
// If branch doesn't exist, create it
|
||||||
return fmt.Errorf("failed to check branch existence: %w", err)
|
if resp != nil && resp.StatusCode == 404 {
|
||||||
}
|
|
||||||
|
|
||||||
// If branch doesn't exist (404), create it
|
|
||||||
if resp.StatusCode == 404 {
|
|
||||||
newRef := &github.Reference{
|
newRef := &github.Reference{
|
||||||
Ref: github.String("refs/heads/" + branchName),
|
Ref: github.String("refs/heads/" + branchName),
|
||||||
Object: &github.GitObject{SHA: ref.Object.SHA},
|
Object: &github.GitObject{SHA: ref.Object.SHA},
|
||||||
}
|
}
|
||||||
_, _, err = g.client.Git.CreateRef(ctx, owner, repository, newRef)
|
_, _, err = g.client.Git.CreateRef(ctx, g.owner, g.repository, newRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create branch: %w", err)
|
return fmt.Errorf("failed to create branch: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// For other errors, return the error
|
|
||||||
return fmt.Errorf("failed to check branch existence: %w", err)
|
return fmt.Errorf("failed to check branch existence: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +58,7 @@ func (g *GithubPRCreator) createOrUpdateBranch(ctx context.Context, branchName s
|
|||||||
Ref: github.String("refs/heads/" + branchName),
|
Ref: github.String("refs/heads/" + branchName),
|
||||||
Object: &github.GitObject{SHA: ref.Object.SHA},
|
Object: &github.GitObject{SHA: ref.Object.SHA},
|
||||||
}
|
}
|
||||||
_, _, err = g.client.Git.UpdateRef(ctx, owner, repository, updateRef, true)
|
_, _, err = g.client.Git.UpdateRef(ctx, g.owner, g.repository, updateRef, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update branch: %w", err)
|
return fmt.Errorf("failed to update branch: %w", err)
|
||||||
}
|
}
|
||||||
@@ -77,10 +66,10 @@ func (g *GithubPRCreator) createOrUpdateBranch(ctx context.Context, branchName s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GithubPRCreator) createOrUpdateFile(ctx context.Context, branch string, filePath string, content string, message string, owner string, repository string) error {
|
func (g *GithubPRCreator) createOrUpdateFile(ctx context.Context, branch string, filePath string, content string, message string) error {
|
||||||
// Get the current file content if it exists
|
// Get the current file content if it exists
|
||||||
var sha *string
|
var sha *string
|
||||||
fileContent, _, _, err := g.client.Repositories.GetContents(ctx, owner, repository, filePath, &github.RepositoryContentGetOptions{
|
fileContent, _, _, err := g.client.Repositories.GetContents(ctx, g.owner, g.repository, filePath, &github.RepositoryContentGetOptions{
|
||||||
Ref: branch,
|
Ref: branch,
|
||||||
})
|
})
|
||||||
if err == nil && fileContent != nil {
|
if err == nil && fileContent != nil {
|
||||||
@@ -88,7 +77,7 @@ func (g *GithubPRCreator) createOrUpdateFile(ctx context.Context, branch string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create or update the file
|
// Create or update the file
|
||||||
_, _, err = g.client.Repositories.CreateFile(ctx, owner, repository, filePath, &github.RepositoryContentFileOptions{
|
_, _, err = g.client.Repositories.CreateFile(ctx, g.owner, g.repository, filePath, &github.RepositoryContentFileOptions{
|
||||||
Message: &message,
|
Message: &message,
|
||||||
Content: []byte(content),
|
Content: []byte(content),
|
||||||
Branch: &branch,
|
Branch: &branch,
|
||||||
@@ -129,14 +118,14 @@ func (g *GithubPRCreator) Run(ctx context.Context, params types.ActionParams) (t
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create or update branch
|
// Create or update branch
|
||||||
err = g.createOrUpdateBranch(ctx, result.Branch, result.Owner, result.Repository)
|
err = g.createOrUpdateBranch(ctx, result.Branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ActionResult{}, fmt.Errorf("failed to create/update branch: %w", err)
|
return types.ActionResult{}, fmt.Errorf("failed to create/update branch: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create or update files
|
// Create or update files
|
||||||
for _, file := range result.Files {
|
for _, file := range result.Files {
|
||||||
err = g.createOrUpdateFile(ctx, result.Branch, file.Path, file.Content, fmt.Sprintf("Update %s", file.Path), result.Owner, result.Repository)
|
err = g.createOrUpdateFile(ctx, result.Branch, file.Path, file.Content, fmt.Sprintf("Update %s", file.Path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ActionResult{}, fmt.Errorf("failed to update file %s: %w", file.Path, err)
|
return types.ActionResult{}, fmt.Errorf("failed to update file %s: %w", file.Path, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ func NewGithubRepositoryGetAllContent(config map[string]string) *GithubRepositor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GithubRepositoryGetAllContent) getContentRecursively(ctx context.Context, path string, owner string, repository string) (string, error) {
|
func (g *GithubRepositoryGetAllContent) getContentRecursively(ctx context.Context, path string) (string, error) {
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
|
|
||||||
// Get content at the current path
|
// Get content at the current path
|
||||||
_, directoryContent, _, err := g.client.Repositories.GetContents(ctx, owner, repository, path, nil)
|
_, directoryContent, _, err := g.client.Repositories.GetContents(ctx, g.owner, g.repository, path, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error getting content at path %s: %w", path, err)
|
return "", fmt.Errorf("error getting content at path %s: %w", path, err)
|
||||||
}
|
}
|
||||||
@@ -41,14 +41,14 @@ func (g *GithubRepositoryGetAllContent) getContentRecursively(ctx context.Contex
|
|||||||
for _, item := range directoryContent {
|
for _, item := range directoryContent {
|
||||||
if item.GetType() == "dir" {
|
if item.GetType() == "dir" {
|
||||||
// Recursively get content for subdirectories
|
// Recursively get content for subdirectories
|
||||||
subContent, err := g.getContentRecursively(ctx, item.GetPath(), owner, repository)
|
subContent, err := g.getContentRecursively(ctx, item.GetPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
result.WriteString(subContent)
|
result.WriteString(subContent)
|
||||||
} else if item.GetType() == "file" {
|
} else if item.GetType() == "file" {
|
||||||
// Get file content
|
// Get file content
|
||||||
fileContent, _, _, err := g.client.Repositories.GetContents(ctx, owner, repository, item.GetPath(), nil)
|
fileContent, _, _, err := g.client.Repositories.GetContents(ctx, g.owner, g.repository, item.GetPath(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error getting file content for %s: %w", item.GetPath(), err)
|
return "", fmt.Errorf("error getting file content for %s: %w", item.GetPath(), err)
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ func (g *GithubRepositoryGetAllContent) Run(ctx context.Context, params types.Ac
|
|||||||
result.Path = "."
|
result.Path = "."
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := g.getContentRecursively(ctx, result.Path, result.Owner, result.Repository)
|
content, err := g.getContentRecursively(ctx, result.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ActionResult{}, err
|
return types.ActionResult{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,9 +77,8 @@ func (i *IRC) Start(a *agent.Agent) {
|
|||||||
}
|
}
|
||||||
i.conn.UseTLS = false
|
i.conn.UseTLS = false
|
||||||
i.conn.AddCallback("001", func(e *irc.Event) {
|
i.conn.AddCallback("001", func(e *irc.Event) {
|
||||||
xlog.Info("Connected to IRC server", "server", i.server, "arguments", e.Arguments)
|
xlog.Info("Connected to IRC server", "server", i.server)
|
||||||
i.conn.Join(i.channel)
|
i.conn.Join(i.channel)
|
||||||
i.nickname = e.Arguments[0]
|
|
||||||
xlog.Info("Joined channel", "channel", i.channel)
|
xlog.Info("Joined channel", "channel", i.channel)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -208,13 +207,6 @@ func (i *IRC) Start(a *agent.Agent) {
|
|||||||
|
|
||||||
// Start the IRC client in a goroutine
|
// Start the IRC client in a goroutine
|
||||||
go i.conn.Loop()
|
go i.conn.Loop()
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-a.Context().Done():
|
|
||||||
i.conn.Quit()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IRCConfigMeta returns the metadata for IRC connector configuration fields
|
// IRCConfigMeta returns the metadata for IRC connector configuration fields
|
||||||
|
|||||||
18
webui/app.go
18
webui/app.go
@@ -176,7 +176,17 @@ func (a *App) UpdateAgentConfig(pool *state.AgentPool) func(c *fiber.Ctx) error
|
|||||||
return errorJSONMessage(c, err.Error())
|
return errorJSONMessage(c, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pool.RecreateAgent(agentName, &newConfig); err != nil {
|
// Remove the agent first
|
||||||
|
if err := pool.Remove(agentName); err != nil {
|
||||||
|
return errorJSONMessage(c, "Error removing agent: "+err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create agent with new config
|
||||||
|
if err := pool.CreateAgent(agentName, &newConfig); err != nil {
|
||||||
|
// Try to restore the old configuration if update fails
|
||||||
|
if restoreErr := pool.CreateAgent(agentName, oldConfig); restoreErr != nil {
|
||||||
|
return errorJSONMessage(c, fmt.Sprintf("Failed to update agent and restore failed: %v, %v", err, restoreErr))
|
||||||
|
}
|
||||||
return errorJSONMessage(c, "Error updating agent: "+err.Error())
|
return errorJSONMessage(c, "Error updating agent: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,7 +370,7 @@ func (a *App) Chat(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
|||||||
xlog.Error("Error marshaling status message", "error", err)
|
xlog.Error("Error marshaling status message", "error", err)
|
||||||
} else {
|
} else {
|
||||||
manager.Send(
|
manager.Send(
|
||||||
sse.NewMessage(string(statusData)).WithEvent("json_message_status"))
|
sse.NewMessage(string(statusData)).WithEvent("json_status"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the message asynchronously
|
// Process the message asynchronously
|
||||||
@@ -407,7 +417,7 @@ func (a *App) Chat(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
|||||||
xlog.Error("Error marshaling completed status", "error", err)
|
xlog.Error("Error marshaling completed status", "error", err)
|
||||||
} else {
|
} else {
|
||||||
manager.Send(
|
manager.Send(
|
||||||
sse.NewMessage(string(completedData)).WithEvent("json_message_status"))
|
sse.NewMessage(string(completedData)).WithEvent("json_status"))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -434,7 +444,7 @@ func (a *App) ExecuteAction(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
|||||||
actionName := c.Params("name")
|
actionName := c.Params("name")
|
||||||
|
|
||||||
xlog.Debug("Executing action", "action", actionName, "config", payload.Config, "params", payload.Params)
|
xlog.Debug("Executing action", "action", actionName, "config", payload.Config, "params", payload.Params)
|
||||||
a, err := services.Action(actionName, "", payload.Config, pool, map[string]string{})
|
a, err := services.Action(actionName, "", payload.Config, pool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xlog.Error("Error creating action", "error", err)
|
xlog.Error("Error creating action", "error", err)
|
||||||
return errorJSONMessage(c, err.Error())
|
return errorJSONMessage(c, err.Error())
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "react-ui",
|
"name": "react-ui",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"highlight.js": "^11.11.1",
|
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
},
|
},
|
||||||
@@ -14,11 +13,11 @@
|
|||||||
"@types/react-dom": "^19.1.2",
|
"@types/react-dom": "^19.1.2",
|
||||||
"@vitejs/plugin-react": "^4.4.0",
|
"@vitejs/plugin-react": "^4.4.0",
|
||||||
"eslint": "^9.24.0",
|
"eslint": "^9.24.0",
|
||||||
"eslint-plugin-react-hooks": "^6.0.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^16.0.0",
|
"globals": "^15.15.0",
|
||||||
"react-router-dom": "^7.5.1",
|
"react-router-dom": "^7.5.0",
|
||||||
"vite": "^6.3.2",
|
"vite": "^6.2.6",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -33,26 +32,14 @@
|
|||||||
|
|
||||||
"@babel/generator": ["@babel/generator@7.27.0", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw=="],
|
"@babel/generator": ["@babel/generator@7.27.0", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw=="],
|
||||||
|
|
||||||
"@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.25.9", "", { "dependencies": { "@babel/types": "^7.25.9" } }, "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g=="],
|
|
||||||
|
|
||||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.0", "", { "dependencies": { "@babel/compat-data": "^7.26.8", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA=="],
|
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.0", "", { "dependencies": { "@babel/compat-data": "^7.26.8", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA=="],
|
||||||
|
|
||||||
"@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.27.0", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", "@babel/helper-replace-supers": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", "@babel/traverse": "^7.27.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg=="],
|
|
||||||
|
|
||||||
"@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ=="],
|
|
||||||
|
|
||||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw=="],
|
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw=="],
|
||||||
|
|
||||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.26.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw=="],
|
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.26.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw=="],
|
||||||
|
|
||||||
"@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.25.9", "", { "dependencies": { "@babel/types": "^7.25.9" } }, "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ=="],
|
|
||||||
|
|
||||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.26.5", "", {}, "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg=="],
|
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.26.5", "", {}, "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg=="],
|
||||||
|
|
||||||
"@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.26.5", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", "@babel/traverse": "^7.26.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg=="],
|
|
||||||
|
|
||||||
"@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA=="],
|
|
||||||
|
|
||||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
||||||
|
|
||||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||||
@@ -63,8 +50,6 @@
|
|||||||
|
|
||||||
"@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="],
|
"@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.25.9", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg=="],
|
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg=="],
|
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg=="],
|
||||||
@@ -209,6 +194,8 @@
|
|||||||
|
|
||||||
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
|
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
|
||||||
|
|
||||||
|
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
|
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
|
||||||
|
|
||||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||||
@@ -269,7 +256,7 @@
|
|||||||
|
|
||||||
"eslint": ["eslint@9.24.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.0", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.24.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ=="],
|
"eslint": ["eslint@9.24.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.0", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.24.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ=="],
|
||||||
|
|
||||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@6.0.0", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "@babel/plugin-transform-private-methods": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.22.4", "zod-validation-error": "^3.0.3" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-NyC3yIC9fazLitYiN8eHykV5wLp/SMuUZMh+sdPSHIeN4ReXIc7if40jtGjDplAgVL/4OkN1d9gneWe9lFZgag=="],
|
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="],
|
||||||
|
|
||||||
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.19", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ=="],
|
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.19", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ=="],
|
||||||
|
|
||||||
@@ -309,16 +296,10 @@
|
|||||||
|
|
||||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||||
|
|
||||||
"globals": ["globals@16.0.0", "", {}, "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A=="],
|
"globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
|
||||||
|
|
||||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||||
|
|
||||||
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
|
||||||
|
|
||||||
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
|
||||||
|
|
||||||
"highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
|
|
||||||
|
|
||||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||||
|
|
||||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||||
@@ -393,9 +374,9 @@
|
|||||||
|
|
||||||
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
||||||
|
|
||||||
"react-router": ["react-router@7.5.1", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0", "turbo-stream": "2.4.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-/jjU3fcYNd2bwz9Q0xt5TwyiyoO8XjSEFXJY4O/lMAlkGTHWuHRAbR9Etik+lSDqMC7A7mz3UlXzgYT6Vl58sA=="],
|
"react-router": ["react-router@7.5.0", "", { "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0", "turbo-stream": "2.4.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g=="],
|
||||||
|
|
||||||
"react-router-dom": ["react-router-dom@7.5.1", "", { "dependencies": { "react-router": "7.5.1" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-5DPSPc7ENrt2tlKPq0FtpG80ZbqA9aIKEyqX6hSNJDlol/tr6iqCK4crqdsusmOSSotq6zDsn0y3urX9TuTNmA=="],
|
"react-router-dom": ["react-router-dom@7.5.0", "", { "dependencies": { "react-router": "7.5.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-fFhGFCULy4vIseTtH5PNcY/VvDJK5gvOWcwJVHQp8JQcWVr85ENhJ3UpuF/zP1tQOIFYNRJHzXtyhU1Bdgw0RA=="],
|
||||||
|
|
||||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||||
|
|
||||||
@@ -427,7 +408,7 @@
|
|||||||
|
|
||||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||||
|
|
||||||
"vite": ["vite@6.3.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.3", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.12" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg=="],
|
"vite": ["vite@6.3.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.3", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.12" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-kkzzkqtMESYklo96HKKPE5KKLkC1amlsqt+RjFMlX2AvbRB/0wghap19NdBxxwGZ+h/C6DLCrcEphPIItlGrRQ=="],
|
||||||
|
|
||||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
@@ -437,10 +418,6 @@
|
|||||||
|
|
||||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
"zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="],
|
|
||||||
|
|
||||||
"zod-validation-error": ["zod-validation-error@3.4.0", "", { "peerDependencies": { "zod": "^3.18.0" } }, "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ=="],
|
|
||||||
|
|
||||||
"@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
"@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||||
|
|
||||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|||||||
@@ -11,8 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0"
|
||||||
"highlight.js": "^11.11.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.24.0",
|
"@eslint/js": "^9.24.0",
|
||||||
@@ -20,10 +19,10 @@
|
|||||||
"@types/react-dom": "^19.1.2",
|
"@types/react-dom": "^19.1.2",
|
||||||
"@vitejs/plugin-react": "^4.4.0",
|
"@vitejs/plugin-react": "^4.4.0",
|
||||||
"eslint": "^9.24.0",
|
"eslint": "^9.24.0",
|
||||||
"eslint-plugin-react-hooks": "^6.0.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^16.0.0",
|
"globals": "^15.15.0",
|
||||||
"react-router-dom": "^7.5.1",
|
"react-router-dom": "^7.5.0",
|
||||||
"vite": "^6.3.2"
|
"vite": "^6.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,4 @@
|
|||||||
/* Base styles */
|
/* Base styles */
|
||||||
pre.hljs {
|
|
||||||
background-color: var(--medium-bg);
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
code.json {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary: #00ff95;
|
--primary: #00ff95;
|
||||||
--secondary: #ff00b1;
|
--secondary: #ff00b1;
|
||||||
@@ -2007,62 +1994,16 @@ select.form-control {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-button:hover {
|
||||||
|
background: rgba(0, 255, 149, 0.8);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.file-button i {
|
.file-button i {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
|
||||||
background: var(--medium-bg);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
|
||||||
background: var(--light-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border: 2px solid var(--primary);
|
|
||||||
border-radius: 50%;
|
|
||||||
border-top-color: transparent;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand-button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--primary);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1.2em;
|
|
||||||
padding: 5px;
|
|
||||||
margin-left: 10px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand-button:hover {
|
|
||||||
color: var(--success);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand-button:focus {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 2px var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-file-info {
|
.selected-file-info {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
4
webui/react-ui/src/hooks/useSSE.js
vendored
4
webui/react-ui/src/hooks/useSSE.js
vendored
@@ -63,8 +63,8 @@ export function useSSE(agentName) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle 'json_message_status' event
|
// Handle 'json_status' event
|
||||||
eventSource.addEventListener('json_message_status', (event) => {
|
eventSource.addEventListener('json_status', (event) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
const timestamp = data.timestamp || new Date().toISOString();
|
const timestamp = data.timestamp || new Date().toISOString();
|
||||||
|
|||||||
@@ -1,22 +1,13 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useParams, Link } from 'react-router-dom';
|
import { useParams, Link } from 'react-router-dom';
|
||||||
import hljs from 'highlight.js/lib/core';
|
|
||||||
import json from 'highlight.js/lib/languages/json';
|
|
||||||
import 'highlight.js/styles/monokai.css';
|
|
||||||
|
|
||||||
hljs.registerLanguage('json', json);
|
|
||||||
|
|
||||||
function AgentStatus() {
|
function AgentStatus() {
|
||||||
const [showStatus, setShowStatus] = useState(true);
|
|
||||||
const { name } = useParams();
|
const { name } = useParams();
|
||||||
const [statusData, setStatusData] = useState(null);
|
const [statusData, setStatusData] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [_eventSource, setEventSource] = useState(null);
|
const [_eventSource, setEventSource] = useState(null);
|
||||||
// Store all observables by id
|
const [liveUpdates, setLiveUpdates] = useState([]);
|
||||||
const [observableMap, setObservableMap] = useState({});
|
|
||||||
const [observableTree, setObservableTree] = useState([]);
|
|
||||||
const [expandedCards, setExpandedCards] = useState(new Map());
|
|
||||||
|
|
||||||
// Update document title
|
// Update document title
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -48,89 +39,17 @@ function AgentStatus() {
|
|||||||
|
|
||||||
fetchStatusData();
|
fetchStatusData();
|
||||||
|
|
||||||
// Helper to build observable tree from map
|
|
||||||
function buildObservableTree(map) {
|
|
||||||
const nodes = Object.values(map);
|
|
||||||
const nodeMap = {};
|
|
||||||
nodes.forEach(node => { nodeMap[node.id] = { ...node, children: [] }; });
|
|
||||||
const roots = [];
|
|
||||||
nodes.forEach(node => {
|
|
||||||
if (!node.parent_id) {
|
|
||||||
roots.push(nodeMap[node.id]);
|
|
||||||
} else if (nodeMap[node.parent_id]) {
|
|
||||||
nodeMap[node.parent_id].children.push(nodeMap[node.id]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return roots;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch initial observable history
|
|
||||||
const fetchObservables = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/agent/${name}/observables`);
|
|
||||||
if (!response.ok) return;
|
|
||||||
const data = await response.json();
|
|
||||||
if (Array.isArray(data.History)) {
|
|
||||||
const map = {};
|
|
||||||
data.History.forEach(obs => {
|
|
||||||
map[obs.id] = obs;
|
|
||||||
});
|
|
||||||
setObservableMap(map);
|
|
||||||
setObservableTree(buildObservableTree(map));
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// Ignore errors for now
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchObservables();
|
|
||||||
|
|
||||||
// Setup SSE connection for live updates
|
// Setup SSE connection for live updates
|
||||||
const sse = new EventSource(`/sse/${name}`);
|
const sse = new EventSource(`/sse/${name}`);
|
||||||
setEventSource(sse);
|
setEventSource(sse);
|
||||||
|
|
||||||
sse.addEventListener('observable_update', (event) => {
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
console.log(data);
|
|
||||||
setObservableMap(prevMap => {
|
|
||||||
const prev = prevMap[data.id] || {};
|
|
||||||
const updated = {
|
|
||||||
...prev,
|
|
||||||
...data,
|
|
||||||
creation: data.creation,
|
|
||||||
progress: data.progress,
|
|
||||||
completion: data.completion,
|
|
||||||
};
|
|
||||||
// Events can be received out of order
|
|
||||||
if (data.creation)
|
|
||||||
updated.creation = data.creation;
|
|
||||||
if (data.completion)
|
|
||||||
updated.completion = data.completion;
|
|
||||||
if (data.parent_id && !prevMap[data.parent_id])
|
|
||||||
prevMap[data.parent_id] = {
|
|
||||||
id: data.parent_id,
|
|
||||||
name: "unknown",
|
|
||||||
};
|
|
||||||
const newMap = { ...prevMap, [data.id]: updated };
|
|
||||||
setObservableTree(buildObservableTree(newMap));
|
|
||||||
return newMap;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen for status events and append to statusData.History
|
|
||||||
sse.addEventListener('status', (event) => {
|
sse.addEventListener('status', (event) => {
|
||||||
const status = event.data;
|
try {
|
||||||
setStatusData(prev => {
|
const data = JSON.parse(event.data);
|
||||||
// If prev is null, start a new object
|
setLiveUpdates(prev => [data, ...prev.slice(0, 19)]); // Keep last 20 updates
|
||||||
if (!prev || typeof prev !== 'object') {
|
} catch (err) {
|
||||||
return { History: [status] };
|
setLiveUpdates(prev => [event.data, ...prev.slice(0, 19)]);
|
||||||
}
|
}
|
||||||
// If History not present, add it
|
|
||||||
if (!Array.isArray(prev.History)) {
|
|
||||||
return { ...prev, History: [status] };
|
|
||||||
}
|
|
||||||
// Otherwise, append
|
|
||||||
return { ...prev, History: [...prev.History, status] };
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
sse.onerror = (err) => {
|
sse.onerror = (err) => {
|
||||||
@@ -164,8 +83,8 @@ function AgentStatus() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="loading-container">
|
||||||
<div></div>
|
<div className="loader"></div>
|
||||||
<p>Loading agent status...</p>
|
<p>Loading agent status...</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -173,199 +92,56 @@ function AgentStatus() {
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="error-container">
|
||||||
<h2>Error</h2>
|
<h2>Error</h2>
|
||||||
<p>{error}</p>
|
<p>{error}</p>
|
||||||
<Link to="/agents">
|
<Link to="/agents" className="back-btn">
|
||||||
<i className="fas fa-arrow-left"></i> Back to Agents
|
<i className="fas fa-arrow-left"></i> Back to Agents
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
// Combine live updates with history
|
||||||
<div>
|
const allUpdates = [...liveUpdates, ...(statusData?.History || [])];
|
||||||
<h1>Agent Status: {name}</h1>
|
|
||||||
<div style={{ color: '#aaa', fontSize: 16, marginBottom: 18 }}>
|
|
||||||
See what the agent is doing and thinking
|
|
||||||
</div>
|
|
||||||
{error && (
|
|
||||||
<div>
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{loading && <div>Loading...</div>}
|
|
||||||
{statusData && (
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', userSelect: 'none' }}
|
|
||||||
onClick={() => setShowStatus(prev => !prev)}>
|
|
||||||
<h2 style={{ margin: 0 }}>Current Status</h2>
|
|
||||||
<i
|
|
||||||
className={`fas fa-chevron-${showStatus ? 'up' : 'down'}`}
|
|
||||||
style={{ color: 'var(--primary)', marginLeft: 12 }}
|
|
||||||
title={showStatus ? 'Collapse' : 'Expand'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style={{ color: '#aaa', fontSize: 14, margin: '5px 0 10px 2px' }}>
|
|
||||||
Summary of the agent's thoughts and actions
|
|
||||||
</div>
|
|
||||||
{showStatus && (
|
|
||||||
<div style={{ marginTop: 10 }}>
|
|
||||||
{(Array.isArray(statusData?.History) && statusData.History.length === 0) && (
|
|
||||||
<div style={{ color: '#aaa' }}>No status history available.</div>
|
|
||||||
)}
|
|
||||||
{Array.isArray(statusData?.History) && statusData.History.map((item, idx) => (
|
|
||||||
<div key={idx} style={{
|
|
||||||
background: '#222',
|
|
||||||
border: '1px solid #444',
|
|
||||||
borderRadius: 8,
|
|
||||||
padding: '12px 16px',
|
|
||||||
marginBottom: 10,
|
|
||||||
whiteSpace: 'pre-line',
|
|
||||||
fontFamily: 'inherit',
|
|
||||||
fontSize: 15,
|
|
||||||
color: '#eee',
|
|
||||||
}}>
|
|
||||||
{/* Replace <br> tags with newlines, then render as pre-line */}
|
|
||||||
{typeof item === 'string'
|
|
||||||
? item.replace(/<br\s*\/?>/gi, '\n')
|
|
||||||
: JSON.stringify(item)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{observableTree.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<h2>Observable Updates</h2>
|
|
||||||
<div style={{ color: '#aaa', fontSize: 14, margin: '5px 0 10px 2px' }}>
|
|
||||||
Drill down into what the agent is doing and thinking when activated by a connector
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{observableTree.map((container, idx) => (
|
|
||||||
<div key={container.id || idx} className='card' style={{ marginBottom: '1em' }}>
|
|
||||||
<div>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => {
|
|
||||||
const newExpanded = !expandedCards.get(container.id);
|
|
||||||
setExpandedCards(new Map(expandedCards).set(container.id, newExpanded));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
|
||||||
<i className={`fas fa-${container.icon || 'robot'}`} style={{ verticalAlign: '-0.125em' }}></i>
|
|
||||||
<span>
|
|
||||||
<span className='stat-label'>{container.name}</span>#<span className='stat-label'>{container.id}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
||||||
<i
|
|
||||||
className={`fas fa-chevron-${expandedCards.get(container.id) ? 'up' : 'down'}`}
|
|
||||||
style={{ color: 'var(--primary)' }}
|
|
||||||
title='Toggle details'
|
|
||||||
/>
|
|
||||||
{!container.completion && (
|
|
||||||
<div className='spinner' />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: expandedCards.get(container.id) ? 'block' : 'none' }}>
|
|
||||||
{container.children && container.children.length > 0 && (
|
|
||||||
|
|
||||||
<div style={{ marginLeft: '2em', marginTop: '1em' }}>
|
return (
|
||||||
<h4>Nested Observables</h4>
|
<div className="agent-status-container">
|
||||||
{container.children.map(child => {
|
<header className="page-header">
|
||||||
const childKey = `child-${child.id}`;
|
<div className="header-content">
|
||||||
const isExpanded = expandedCards.get(childKey);
|
<h1>
|
||||||
return (
|
<Link to="/agents" className="back-link">
|
||||||
<div key={`${container.id}-child-${child.id}`} className='card' style={{ background: '#222', marginBottom: '0.5em' }}>
|
<i className="fas fa-arrow-left"></i>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
</Link>
|
||||||
onClick={() => {
|
Agent Status: {name}
|
||||||
const newExpanded = !expandedCards.get(childKey);
|
</h1>
|
||||||
setExpandedCards(new Map(expandedCards).set(childKey, newExpanded));
|
</div>
|
||||||
}}
|
</header>
|
||||||
>
|
|
||||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
<div className="chat-container bg-gray-800 shadow-lg rounded-lg">
|
||||||
<i className={`fas fa-${child.icon || 'robot'}`} style={{ verticalAlign: '-0.125em' }}></i>
|
{/* Chat Messages */}
|
||||||
<span>
|
<div className="chat-messages p-4">
|
||||||
<span className='stat-label'>{child.name}</span>#<span className='stat-label'>{child.id}</span>
|
{allUpdates.length > 0 ? (
|
||||||
</span>
|
allUpdates.map((item, index) => (
|
||||||
</div>
|
<div key={index} className="status-item mb-4">
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
<div className="bg-gray-700 p-4 rounded-lg">
|
||||||
<i
|
<h2 className="text-sm font-semibold mb-2">Agent Action:</h2>
|
||||||
className={`fas fa-chevron-${isExpanded ? 'up' : 'down'}`}
|
<div className="status-details">
|
||||||
style={{ color: 'var(--primary)' }}
|
<div className="status-row">
|
||||||
title='Toggle details'
|
<span className="status-label">{index}</span>
|
||||||
/>
|
<span className="status-value">{formatValue(item)}</span>
|
||||||
{!child.completion && (
|
|
||||||
<div className='spinner' />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: isExpanded ? 'block' : 'none' }}>
|
|
||||||
{child.creation && (
|
|
||||||
<div>
|
|
||||||
<h5>Creation:</h5>
|
|
||||||
<pre className="hljs"><code>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: hljs.highlight(JSON.stringify(child.creation || {}, null, 2), { language: 'json' }).value }}></div>
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{child.progress && child.progress.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<h5>Progress:</h5>
|
|
||||||
<pre className="hljs"><code>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: hljs.highlight(JSON.stringify(child.progress || {}, null, 2), { language: 'json' }).value }}></div>
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{child.completion && (
|
|
||||||
<div>
|
|
||||||
<h5>Completion:</h5>
|
|
||||||
<pre className="hljs"><code>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: hljs.highlight(JSON.stringify(child.completion || {}, null, 2), { language: 'json' }).value }}></div>
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{container.creation && (
|
|
||||||
<div>
|
|
||||||
<h4>Creation:</h4>
|
|
||||||
<pre className="hljs"><code>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: hljs.highlight(JSON.stringify(container.creation || {}, null, 2), { language: 'json' }).value }}></div>
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{container.progress && container.progress.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<h4>Progress:</h4>
|
|
||||||
<pre className="hljs"><code>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: hljs.highlight(JSON.stringify(container.progress || {}, null, 2), { language: 'json' }).value }}></div>
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{container.completion && (
|
|
||||||
<div>
|
|
||||||
<h4>Completion:</h4>
|
|
||||||
<pre className="hljs"><code>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: hljs.highlight(JSON.stringify(container.completion || {}, null, 2), { language: 'json' }).value }}></div>
|
|
||||||
</code></pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="no-status-data">
|
||||||
|
<p>No status data available for this agent.</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,14 +241,13 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
|||||||
|
|
||||||
entries := []string{}
|
entries := []string{}
|
||||||
for _, h := range Reverse(history.Results()) {
|
for _, h := range Reverse(history.Results()) {
|
||||||
entries = append(entries, fmt.Sprintf(`Reasoning: %s
|
entries = append(entries, fmt.Sprintf(
|
||||||
Action taken: %+v
|
"Result: %v Action: %v Params: %v Reasoning: %v",
|
||||||
Parameters: %+v
|
h.Result,
|
||||||
Result: %s`,
|
h.Action.Definition().Name,
|
||||||
|
h.Params,
|
||||||
h.Reasoning,
|
h.Reasoning,
|
||||||
h.ActionCurrentState.Action.Definition().Name,
|
))
|
||||||
h.ActionCurrentState.Params,
|
|
||||||
h.Result))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
@@ -257,21 +256,6 @@ func (app *App) registerRoutes(pool *state.AgentPool, webapp *fiber.App) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
webapp.Get("/api/agent/:name/observables", func(c *fiber.Ctx) error {
|
|
||||||
name := c.Params("name")
|
|
||||||
agent := pool.GetAgent(name)
|
|
||||||
if agent == nil {
|
|
||||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
|
|
||||||
"error": "Agent not found",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(fiber.Map{
|
|
||||||
"Name": name,
|
|
||||||
"History": agent.Observer().History(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
webapp.Post("/settings/import", app.ImportAgent(pool))
|
webapp.Post("/settings/import", app.ImportAgent(pool))
|
||||||
webapp.Get("/settings/export/:name", app.ExportAgent(pool))
|
webapp.Get("/settings/export/:name", app.ExportAgent(pool))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user