Compare commits
11 Commits
feat/brows
...
chore/para
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47492f890f | ||
|
|
ddac344147 | ||
|
|
25bb3fb123 | ||
|
|
9e52438877 | ||
|
|
c4618896cf | ||
|
|
ee1667d51a | ||
|
|
bafd26e92c | ||
|
|
8ecc18f76f | ||
|
|
985f07a529 | ||
|
|
8b2900c6d8 | ||
|
|
50e56fe22f |
2
Makefile
2
Makefile
@@ -9,7 +9,7 @@ cleanup-tests:
|
||||
docker compose down
|
||||
|
||||
tests: prepare-tests
|
||||
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 ./...
|
||||
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 ./...
|
||||
|
||||
run-nokb:
|
||||
$(MAKE) run KBDISABLEINDEX=true
|
||||
|
||||
@@ -114,7 +114,7 @@ LocalAGI supports multiple hardware configurations through Docker Compose profil
|
||||
- Supports text, multimodal, and image generation models
|
||||
- Run with: `docker compose -f docker-compose.nvidia.yaml up`
|
||||
- Default models:
|
||||
- Text: `arcee-agent`
|
||||
- Text: `gemma-3-12b-it-qat`
|
||||
- Multimodal: `minicpm-v-2_6`
|
||||
- Image: `sd-1.5-ggml`
|
||||
- Environment variables:
|
||||
@@ -130,7 +130,7 @@ LocalAGI supports multiple hardware configurations through Docker Compose profil
|
||||
- Supports text, multimodal, and image generation models
|
||||
- Run with: `docker compose -f docker-compose.intel.yaml up`
|
||||
- Default models:
|
||||
- Text: `arcee-agent`
|
||||
- Text: `gemma-3-12b-it-qat`
|
||||
- Multimodal: `minicpm-v-2_6`
|
||||
- Image: `sd-1.5-ggml`
|
||||
- Environment variables:
|
||||
@@ -161,7 +161,7 @@ docker compose -f docker-compose.intel.yaml up
|
||||
```
|
||||
|
||||
If no models are specified, it will use the defaults:
|
||||
- Text model: `arcee-agent`
|
||||
- Text model: `gemma-3-12b-it-qat`
|
||||
- Multimodal model: `minicpm-v-2_6`
|
||||
- Image model: `sd-1.5-ggml`
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
@@ -989,7 +990,6 @@ func (a *Agent) periodicallyRun(timer *time.Timer) {
|
||||
}
|
||||
|
||||
func (a *Agent) Run() error {
|
||||
|
||||
a.startNewConversationsConsumer()
|
||||
xlog.Debug("Agent is now running", "agent", a.Character.Name)
|
||||
// The agent run does two things:
|
||||
@@ -1004,36 +1004,68 @@ func (a *Agent) Run() error {
|
||||
|
||||
// Expose a REST API to interact with the agent to ask it things
|
||||
|
||||
//todoTimer := time.NewTicker(a.options.periodicRuns)
|
||||
timer := time.NewTimer(a.options.periodicRuns)
|
||||
|
||||
// we fire the periodicalRunner only once.
|
||||
go a.periodicalRunRunner(timer)
|
||||
var errs []error
|
||||
var muErr sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
|
||||
parallelJobs := a.options.parallelJobs
|
||||
if a.options.parallelJobs == 0 {
|
||||
parallelJobs = 1
|
||||
}
|
||||
|
||||
for i := 0; i < parallelJobs; i++ {
|
||||
xlog.Debug("Starting agent worker", "worker", i)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
e := a.run(timer)
|
||||
muErr.Lock()
|
||||
errs = append(errs, e)
|
||||
muErr.Unlock()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (a *Agent) run(timer *time.Timer) error {
|
||||
for {
|
||||
xlog.Debug("Agent is now waiting for a new job", "agent", a.Character.Name)
|
||||
select {
|
||||
case job := <-a.jobQueue:
|
||||
a.loop(timer, job)
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
xlog.Debug("Agent is consuming a job", "agent", a.Character.Name, "job", job)
|
||||
a.consumeJob(job, UserRole)
|
||||
timer.Reset(a.options.periodicRuns)
|
||||
case <-a.context.Done():
|
||||
// Agent has been canceled, return error
|
||||
xlog.Warn("Agent has been canceled", "agent", a.Character.Name)
|
||||
return ErrContextCanceled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) periodicalRunRunner(timer *time.Timer) {
|
||||
for {
|
||||
select {
|
||||
case <-a.context.Done():
|
||||
// Agent has been canceled, return error
|
||||
xlog.Warn("periodicalRunner has been canceled", "agent", a.Character.Name)
|
||||
return
|
||||
case <-timer.C:
|
||||
a.periodicallyRun(timer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) loop(timer *time.Timer, job *types.Job) {
|
||||
// Remember always to reset the timer - if we don't the agent will stop..
|
||||
defer timer.Reset(a.options.periodicRuns)
|
||||
// Consume the job and generate a response
|
||||
// TODO: Give a short-term memory to the agent
|
||||
// stop and drain the timer
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
xlog.Debug("Agent is consuming a job", "agent", a.Character.Name, "job", job)
|
||||
a.consumeJob(job, UserRole)
|
||||
}
|
||||
|
||||
func (a *Agent) Observer() Observer {
|
||||
return a.observer
|
||||
}
|
||||
|
||||
@@ -226,7 +226,10 @@ var _ = Describe("Agent test", func() {
|
||||
WithLLMAPIKey(apiKeyURL),
|
||||
WithTimeout("10m"),
|
||||
WithActions(
|
||||
actions.NewSearch(map[string]string{}),
|
||||
&TestAction{response: map[string]string{
|
||||
"boston": testActionResult,
|
||||
"milan": testActionResult2,
|
||||
}},
|
||||
),
|
||||
EnablePlanning,
|
||||
EnableForceReasoning,
|
||||
@@ -238,18 +241,21 @@ var _ = Describe("Agent test", func() {
|
||||
defer agent.Stop()
|
||||
|
||||
result := agent.Ask(
|
||||
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."),
|
||||
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"),
|
||||
)
|
||||
Expect(len(result.State)).To(BeNumerically(">", 1))
|
||||
|
||||
actionsExecuted := []string{}
|
||||
actionResults := []string{}
|
||||
for _, r := range result.State {
|
||||
xlog.Info(r.Result)
|
||||
actionsExecuted = append(actionsExecuted, r.Action.Definition().Name.String())
|
||||
actionResults = append(actionResults, r.ActionResult.Result)
|
||||
}
|
||||
Expect(actionsExecuted).To(ContainElement("search_internet"), fmt.Sprint(result))
|
||||
Expect(actionsExecuted).To(ContainElement("get_weather"), 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() {
|
||||
|
||||
@@ -55,6 +55,7 @@ type options struct {
|
||||
newConversationsSubscribers []func(openai.ChatCompletionMessage)
|
||||
|
||||
observer Observer
|
||||
parallelJobs int
|
||||
}
|
||||
|
||||
func (o *options) SeparatedMultimodalModel() bool {
|
||||
@@ -63,6 +64,7 @@ func (o *options) SeparatedMultimodalModel() bool {
|
||||
|
||||
func defaultOptions() *options {
|
||||
return &options{
|
||||
parallelJobs: 1,
|
||||
periodicRuns: 15 * time.Minute,
|
||||
LLMAPI: llmOptions{
|
||||
APIURL: "http://localhost:8080",
|
||||
@@ -138,6 +140,13 @@ func EnableKnowledgeBaseWithResults(results int) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithParallelJobs(jobs int) Option {
|
||||
return func(o *options) error {
|
||||
o.parallelJobs = jobs
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithNewConversationSubscriber(sub func(openai.ChatCompletionMessage)) Option {
|
||||
return func(o *options) error {
|
||||
o.newConversationsSubscribers = append(o.newConversationsSubscribers, sub)
|
||||
|
||||
@@ -25,6 +25,7 @@ var _ = Describe("Agent test", func() {
|
||||
agent, err = New(
|
||||
WithLLMAPIURL(apiURL),
|
||||
WithModel(testModel),
|
||||
WithTimeout("10m"),
|
||||
WithRandomIdentity(),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
@@ -61,6 +61,7 @@ type AgentConfig struct {
|
||||
SystemPrompt string `json:"system_prompt" form:"system_prompt"`
|
||||
LongTermMemory bool `json:"long_term_memory" form:"long_term_memory"`
|
||||
SummaryLongTermMemory bool `json:"summary_long_term_memory" form:"summary_long_term_memory"`
|
||||
ParallelJobs int `json:"parallel_jobs" form:"parallel_jobs"`
|
||||
}
|
||||
|
||||
type AgentConfigMeta struct {
|
||||
@@ -260,6 +261,16 @@ func NewAgentConfigMeta(
|
||||
Step: 1,
|
||||
Tags: config.Tags{Section: "AdvancedSettings"},
|
||||
},
|
||||
{
|
||||
Name: "parallel_jobs",
|
||||
Label: "Parallel Jobs",
|
||||
Type: "number",
|
||||
DefaultValue: 5,
|
||||
Min: 1,
|
||||
Step: 1,
|
||||
HelpText: "Number of concurrent tasks that can run in parallel",
|
||||
Tags: config.Tags{Section: "AdvancedSettings"},
|
||||
},
|
||||
},
|
||||
MCPServers: []config.Field{
|
||||
{
|
||||
|
||||
@@ -466,6 +466,10 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
|
||||
opts = append(opts, WithLoopDetectionSteps(config.LoopDetectionSteps))
|
||||
}
|
||||
|
||||
if config.ParallelJobs > 0 {
|
||||
opts = append(opts, WithParallelJobs(config.ParallelJobs))
|
||||
}
|
||||
|
||||
xlog.Info("Starting agent", "name", name, "config", config)
|
||||
|
||||
agent, err := New(opts...)
|
||||
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
# Image list (dockerhub): https://hub.docker.com/r/localai/localai
|
||||
image: localai/localai:master-ffmpeg-core
|
||||
command:
|
||||
- ${MODEL_NAME:-arcee-agent}
|
||||
- ${MODEL_NAME:-gemma-3-12b-it-qat}
|
||||
- ${MULTIMODAL_MODEL:-minicpm-v-2_6}
|
||||
- ${IMAGE_MODEL:-sd-1.5-ggml}
|
||||
- granite-embedding-107m-multilingual
|
||||
@@ -59,7 +59,7 @@ services:
|
||||
- 8080:3000
|
||||
#image: quay.io/mudler/localagi:master
|
||||
environment:
|
||||
- LOCALAGI_MODEL=${MODEL_NAME:-arcee-agent}
|
||||
- LOCALAGI_MODEL=${MODEL_NAME:-gemma-3-12b-it-qat}
|
||||
- LOCALAGI_MULTIMODAL_MODEL=${MULTIMODAL_MODEL:-minicpm-v-2_6}
|
||||
- LOCALAGI_IMAGE_MODEL=${IMAGE_MODEL:-sd-1.5-ggml}
|
||||
- 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/google/go-github/v69 v69.2.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/metoro-io/mcp-golang v0.9.0
|
||||
github.com/metoro-io/mcp-golang v0.11.0
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/philippgille/chromem-go v0.7.0
|
||||
github.com/sashabaranov/go-openai v1.38.1
|
||||
github.com/sashabaranov/go-openai v1.38.2
|
||||
github.com/slack-go/slack v0.16.0
|
||||
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64
|
||||
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.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/metoro-io/mcp-golang v0.9.0 h1:GpFENjieZ/KosTu7CE7tyGI/a2FhiG0nandR0d8B3rE=
|
||||
github.com/metoro-io/mcp-golang v0.9.0/go.mod h1:ifLP9ZzKpN1UqFWNTpAHOqSvNkMK6b7d1FSZ5Lu0lN0=
|
||||
github.com/metoro-io/mcp-golang v0.11.0 h1:1k+VSE9QaeMTLn0gJ3FgE/DcjsCBsLFnz5eSFbgXUiI=
|
||||
github.com/metoro-io/mcp-golang v0.11.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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
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/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/sashabaranov/go-openai v1.38.1 h1:TtZabbFQZa1nEni/IhVtDF/WQjVqDgd+cWR5OeddzF8=
|
||||
github.com/sashabaranov/go-openai v1.38.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.38.2 h1:akrssjj+6DY3lWuDwHv6cBvJ8Z+FZDM9XEaaYFt0Auo=
|
||||
github.com/sashabaranov/go-openai v1.38.2/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/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||
|
||||
5
main.go
5
main.go
@@ -22,6 +22,7 @@ var withLogs = os.Getenv("LOCALAGI_ENABLE_CONVERSATIONS_LOGGING") == "true"
|
||||
var apiKeysEnv = os.Getenv("LOCALAGI_API_KEYS")
|
||||
var imageModel = os.Getenv("LOCALAGI_IMAGE_MODEL")
|
||||
var conversationDuration = os.Getenv("LOCALAGI_CONVERSATION_DURATION")
|
||||
var localOperatorBaseURL = os.Getenv("LOCALOPERATOR_BASE_URL")
|
||||
|
||||
func init() {
|
||||
if baseModel == "" {
|
||||
@@ -61,7 +62,9 @@ func main() {
|
||||
apiKey,
|
||||
stateDir,
|
||||
localRAG,
|
||||
services.Actions,
|
||||
services.Actions(map[string]string{
|
||||
"browser-agent-runner-base-url": localOperatorBaseURL,
|
||||
}),
|
||||
services.Connectors,
|
||||
services.DynamicPrompts,
|
||||
timeout,
|
||||
|
||||
72
pkg/localoperator/client.go
Normal file
72
pkg/localoperator/client.go
Normal file
@@ -0,0 +1,72 @@
|
||||
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,6 +18,7 @@ const (
|
||||
// Actions
|
||||
ActionSearch = "search"
|
||||
ActionCustom = "custom"
|
||||
ActionBrowserAgentRunner = "browser-agent-runner"
|
||||
ActionGithubIssueLabeler = "github-issue-labeler"
|
||||
ActionGithubIssueOpener = "github-issue-opener"
|
||||
ActionGithubIssueCloser = "github-issue-closer"
|
||||
@@ -52,6 +53,7 @@ var AvailableActions = []string{
|
||||
ActionGithubIssueSearcher,
|
||||
ActionGithubRepositoryGet,
|
||||
ActionGithubGetAllContent,
|
||||
ActionBrowserAgentRunner,
|
||||
ActionGithubRepositoryCreateOrUpdate,
|
||||
ActionGithubIssueReader,
|
||||
ActionGithubIssueCommenter,
|
||||
@@ -71,7 +73,8 @@ var AvailableActions = []string{
|
||||
ActionShellcommand,
|
||||
}
|
||||
|
||||
func Actions(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
||||
func Actions(actionsConfigs map[string]string) func(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 {
|
||||
allActions := []types.Action{}
|
||||
|
||||
@@ -84,7 +87,7 @@ func Actions(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPo
|
||||
continue
|
||||
}
|
||||
|
||||
a, err := Action(a.Name, agentName, config, pool)
|
||||
a, err := Action(a.Name, agentName, config, pool, actionsConfigs)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -95,7 +98,9 @@ func Actions(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPo
|
||||
}
|
||||
}
|
||||
|
||||
func Action(name, agentName string, config map[string]string, pool *state.AgentPool) (types.Action, error) {
|
||||
}
|
||||
|
||||
func Action(name, agentName string, config map[string]string, pool *state.AgentPool, actionsConfigs map[string]string) (types.Action, error) {
|
||||
var a types.Action
|
||||
var err error
|
||||
|
||||
@@ -114,6 +119,8 @@ func Action(name, agentName string, config map[string]string, pool *state.AgentP
|
||||
a = actions.NewGithubIssueCloser(config)
|
||||
case ActionGithubIssueSearcher:
|
||||
a = actions.NewGithubIssueSearch(config)
|
||||
case ActionBrowserAgentRunner:
|
||||
a = actions.NewBrowserAgentRunner(config, actionsConfigs["browser-agent-runner-base-url"])
|
||||
case ActionGithubIssueReader:
|
||||
a = actions.NewGithubIssueReader(config)
|
||||
case ActionGithubPRReader:
|
||||
@@ -169,6 +176,11 @@ func ActionsConfigMeta() []config.FieldGroup {
|
||||
Label: "Search",
|
||||
Fields: actions.SearchConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "browser-agent-runner",
|
||||
Label: "Browser Agent Runner",
|
||||
Fields: actions.BrowserAgentRunnerConfigMeta(),
|
||||
},
|
||||
{
|
||||
Name: "generate_image",
|
||||
Label: "Generate Image",
|
||||
|
||||
117
services/actions/browseragentrunner.go
Normal file
117
services/actions/browseragentrunner.go
Normal file
@@ -0,0 +1,117 @@
|
||||
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",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -444,7 +444,7 @@ func (a *App) ExecuteAction(pool *state.AgentPool) func(c *fiber.Ctx) error {
|
||||
actionName := c.Params("name")
|
||||
|
||||
xlog.Debug("Executing action", "action", actionName, "config", payload.Config, "params", payload.Params)
|
||||
a, err := services.Action(actionName, "", payload.Config, pool)
|
||||
a, err := services.Action(actionName, "", payload.Config, pool, map[string]string{})
|
||||
if err != nil {
|
||||
xlog.Error("Error creating action", "error", err)
|
||||
return errorJSONMessage(c, err.Error())
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@vitejs/plugin-react": "^4.4.0",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-hooks": "^6.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^16.0.0",
|
||||
"react-router-dom": "^7.5.0",
|
||||
"vite": "^6.3.1",
|
||||
"react-router-dom": "^7.5.1",
|
||||
"vite": "^6.3.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -33,14 +33,26 @@
|
||||
|
||||
"@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-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-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-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-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
||||
@@ -51,6 +63,8 @@
|
||||
|
||||
"@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-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=="],
|
||||
@@ -195,8 +209,6 @@
|
||||
|
||||
"@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/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
@@ -257,7 +269,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-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-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-refresh": ["eslint-plugin-react-refresh@0.4.19", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ=="],
|
||||
|
||||
@@ -301,6 +313,10 @@
|
||||
|
||||
"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=="],
|
||||
@@ -377,9 +393,9 @@
|
||||
|
||||
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
||||
|
||||
"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": ["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-dom": ["react-router-dom@7.5.0", "", { "dependencies": { "react-router": "7.5.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-fFhGFCULy4vIseTtH5PNcY/VvDJK5gvOWcwJVHQp8JQcWVr85ENhJ3UpuF/zP1tQOIFYNRJHzXtyhU1Bdgw0RA=="],
|
||||
"react-router-dom": ["react-router-dom@7.5.1", "", { "dependencies": { "react-router": "7.5.1" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-5DPSPc7ENrt2tlKPq0FtpG80ZbqA9aIKEyqX6hSNJDlol/tr6iqCK4crqdsusmOSSotq6zDsn0y3urX9TuTNmA=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
@@ -411,7 +427,7 @@
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"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=="],
|
||||
"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=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
@@ -421,6 +437,10 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@vitejs/plugin-react": "^4.4.0",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-hooks": "^6.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^16.0.0",
|
||||
"react-router-dom": "^7.5.0",
|
||||
"vite": "^6.3.1"
|
||||
"react-router-dom": "^7.5.1",
|
||||
"vite": "^6.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user