diff --git a/backend/pkg/graph/generated.go b/backend/pkg/graph/generated.go index c8c1e6ef..30a0a313 100644 --- a/backend/pkg/graph/generated.go +++ b/backend/pkg/graph/generated.go @@ -536,6 +536,8 @@ type ComplexityRoot struct { AssistantUseAgents func(childComplexity int) int Debug func(childComplexity int) int DockerInside func(childComplexity int) int + IsDevelopMode func(childComplexity int) int + Version func(childComplexity int) int } Subscription struct { @@ -3540,6 +3542,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Settings.DockerInside(childComplexity), true + case "Settings.isDevelopMode": + if e.complexity.Settings.IsDevelopMode == nil { + break + } + + return e.complexity.Settings.IsDevelopMode(childComplexity), true + + case "Settings.version": + if e.complexity.Settings.Version == nil { + break + } + + return e.complexity.Settings.Version(childComplexity), true + case "Subscription.apiTokenCreated": if e.complexity.Subscription.APITokenCreated == nil { break @@ -23912,8 +23928,12 @@ func (ec *executionContext) fieldContext_Query_settings(_ context.Context, field return ec.fieldContext_Settings_debug(ctx, field) case "askUser": return ec.fieldContext_Settings_askUser(ctx, field) + case "version": + return ec.fieldContext_Settings_version(ctx, field) case "dockerInside": return ec.fieldContext_Settings_dockerInside(ctx, field) + case "isDevelopMode": + return ec.fieldContext_Settings_isDevelopMode(ctx, field) case "assistantUseAgents": return ec.fieldContext_Settings_assistantUseAgents(ctx, field) } @@ -25680,6 +25700,50 @@ func (ec *executionContext) fieldContext_Settings_askUser(_ context.Context, fie return fc, nil } +func (ec *executionContext) _Settings_version(ctx context.Context, field graphql.CollectedField, obj *model.Settings) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Settings_version(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Version, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Settings_version(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Settings", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Settings_dockerInside(ctx context.Context, field graphql.CollectedField, obj *model.Settings) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Settings_dockerInside(ctx, field) if err != nil { @@ -25724,6 +25788,50 @@ func (ec *executionContext) fieldContext_Settings_dockerInside(_ context.Context return fc, nil } +func (ec *executionContext) _Settings_isDevelopMode(ctx context.Context, field graphql.CollectedField, obj *model.Settings) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Settings_isDevelopMode(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.IsDevelopMode, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Settings_isDevelopMode(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Settings", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Settings_assistantUseAgents(ctx context.Context, field graphql.CollectedField, obj *model.Settings) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Settings_assistantUseAgents(ctx, field) if err != nil { @@ -39224,11 +39332,21 @@ func (ec *executionContext) _Settings(ctx context.Context, sel ast.SelectionSet, if out.Values[i] == graphql.Null { out.Invalids++ } + case "version": + out.Values[i] = ec._Settings_version(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "dockerInside": out.Values[i] = ec._Settings_dockerInside(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } + case "isDevelopMode": + out.Values[i] = ec._Settings_isDevelopMode(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "assistantUseAgents": out.Values[i] = ec._Settings_assistantUseAgents(ctx, field, obj) if out.Values[i] == graphql.Null { diff --git a/backend/pkg/graph/model/models_gen.go b/backend/pkg/graph/model/models_gen.go index c7314c1d..8e6fcc2e 100644 --- a/backend/pkg/graph/model/models_gen.go +++ b/backend/pkg/graph/model/models_gen.go @@ -448,10 +448,12 @@ type SearchLog struct { } type Settings struct { - Debug bool `json:"debug"` - AskUser bool `json:"askUser"` - DockerInside bool `json:"dockerInside"` - AssistantUseAgents bool `json:"assistantUseAgents"` + Debug bool `json:"debug"` + AskUser bool `json:"askUser"` + Version string `json:"version"` + DockerInside bool `json:"dockerInside"` + IsDevelopMode bool `json:"isDevelopMode"` + AssistantUseAgents bool `json:"assistantUseAgents"` } type Subscription struct { diff --git a/backend/pkg/graph/schema.graphqls b/backend/pkg/graph/schema.graphqls index 70d7e691..7d253011 100644 --- a/backend/pkg/graph/schema.graphqls +++ b/backend/pkg/graph/schema.graphqls @@ -158,7 +158,9 @@ enum VectorStoreAction { type Settings { debug: Boolean! askUser: Boolean! + version: String! dockerInside: Boolean! + isDevelopMode: Boolean! assistantUseAgents: Boolean! } diff --git a/backend/pkg/graph/schema.resolvers.go b/backend/pkg/graph/schema.resolvers.go index 334c2f58..aaeb4b38 100644 --- a/backend/pkg/graph/schema.resolvers.go +++ b/backend/pkg/graph/schema.resolvers.go @@ -29,6 +29,7 @@ import ( "pentagi/pkg/server/auth" "pentagi/pkg/templates" "pentagi/pkg/templates/validator" + "pentagi/pkg/version" "strings" "time" @@ -1960,7 +1961,9 @@ func (r *queryResolver) Settings(ctx context.Context) (*model.Settings, error) { settings := &model.Settings{ Debug: r.Config.Debug, AskUser: r.Config.AskUser, + Version: version.GetBinaryVersion(), DockerInside: r.Config.DockerInside, + IsDevelopMode: version.IsDevelopMode(), AssistantUseAgents: r.Config.AssistantUseAgents, } diff --git a/backend/pkg/server/docs/docs.go b/backend/pkg/server/docs/docs.go index f015226d..ddd07d2f 100644 --- a/backend/pkg/server/docs/docs.go +++ b/backend/pkg/server/docs/docs.go @@ -5155,7 +5155,10 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/models.ProviderInfo" + "type": "array", + "items": { + "$ref": "#/definitions/models.ProviderInfo" + } } } } @@ -6095,6 +6098,48 @@ const docTemplate = `{ } } }, + "/settings/": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Settings" + ], + "summary": "Retrieve settings", + "responses": { + "200": { + "description": "settings received successful", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Settings" + } + } + } + ] + } + }, + "403": { + "description": "getting settings not permitted", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, "/termlogs/": { "get": { "security": [ @@ -8469,6 +8514,53 @@ const docTemplate = `{ } } }, + "models.ModelInfo": { + "type": "object", + "required": [ + "agent_types", + "name" + ], + "properties": { + "agent_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "example": "gpt-4o" + }, + "price_info": { + "$ref": "#/definitions/models.ModelPriceInfo" + } + } + }, + "models.ModelPriceInfo": { + "type": "object", + "properties": { + "cache_read": { + "type": "number", + "minimum": 0, + "example": 0.1 + }, + "cache_write": { + "type": "number", + "minimum": 0, + "example": 0.3 + }, + "input": { + "type": "number", + "minimum": 0, + "example": 1.1 + }, + "output": { + "type": "number", + "minimum": 0, + "example": 3 + } + } + }, "models.ModelUsageStats": { "type": "object", "required": [ @@ -8744,10 +8836,22 @@ const docTemplate = `{ "models.ProviderInfo": { "type": "object", "required": [ + "default_model", + "models", "name", "type" ], "properties": { + "default_model": { + "type": "string", + "example": "gpt-4o" + }, + "models": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ModelInfo" + } + }, "name": { "type": "string", "example": "my openai provider" @@ -8956,6 +9060,35 @@ const docTemplate = `{ } } }, + "models.Settings": { + "type": "object", + "properties": { + "ask_user": { + "type": "boolean", + "example": false + }, + "assistant_use_agents": { + "type": "boolean", + "example": false + }, + "debug": { + "type": "boolean", + "example": false + }, + "docker_inside": { + "type": "boolean", + "example": false + }, + "is_develop_mode": { + "type": "boolean", + "example": false + }, + "version": { + "type": "string", + "example": "v1.0.0" + } + } + }, "models.Subtask": { "type": "object", "required": [ diff --git a/backend/pkg/server/docs/swagger.json b/backend/pkg/server/docs/swagger.json index 4d1f3c53..9a629ab3 100644 --- a/backend/pkg/server/docs/swagger.json +++ b/backend/pkg/server/docs/swagger.json @@ -5147,7 +5147,10 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/models.ProviderInfo" + "type": "array", + "items": { + "$ref": "#/definitions/models.ProviderInfo" + } } } } @@ -6087,6 +6090,48 @@ } } }, + "/settings/": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Settings" + ], + "summary": "Retrieve settings", + "responses": { + "200": { + "description": "settings received successful", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Settings" + } + } + } + ] + } + }, + "403": { + "description": "getting settings not permitted", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, "/termlogs/": { "get": { "security": [ @@ -8461,6 +8506,53 @@ } } }, + "models.ModelInfo": { + "type": "object", + "required": [ + "agent_types", + "name" + ], + "properties": { + "agent_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string", + "example": "gpt-4o" + }, + "price_info": { + "$ref": "#/definitions/models.ModelPriceInfo" + } + } + }, + "models.ModelPriceInfo": { + "type": "object", + "properties": { + "cache_read": { + "type": "number", + "minimum": 0, + "example": 0.1 + }, + "cache_write": { + "type": "number", + "minimum": 0, + "example": 0.3 + }, + "input": { + "type": "number", + "minimum": 0, + "example": 1.1 + }, + "output": { + "type": "number", + "minimum": 0, + "example": 3 + } + } + }, "models.ModelUsageStats": { "type": "object", "required": [ @@ -8736,10 +8828,22 @@ "models.ProviderInfo": { "type": "object", "required": [ + "default_model", + "models", "name", "type" ], "properties": { + "default_model": { + "type": "string", + "example": "gpt-4o" + }, + "models": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ModelInfo" + } + }, "name": { "type": "string", "example": "my openai provider" @@ -8948,6 +9052,35 @@ } } }, + "models.Settings": { + "type": "object", + "properties": { + "ask_user": { + "type": "boolean", + "example": false + }, + "assistant_use_agents": { + "type": "boolean", + "example": false + }, + "debug": { + "type": "boolean", + "example": false + }, + "docker_inside": { + "type": "boolean", + "example": false + }, + "is_develop_mode": { + "type": "boolean", + "example": false + }, + "version": { + "type": "string", + "example": "v1.0.0" + } + } + }, "models.Subtask": { "type": "object", "required": [ diff --git a/backend/pkg/server/docs/swagger.yaml b/backend/pkg/server/docs/swagger.yaml index 25044d39..1c151407 100644 --- a/backend/pkg/server/docs/swagger.yaml +++ b/backend/pkg/server/docs/swagger.yaml @@ -917,6 +917,40 @@ definitions: required: - path type: object + models.ModelInfo: + properties: + agent_types: + items: + type: string + type: array + name: + example: gpt-4o + type: string + price_info: + $ref: '#/definitions/models.ModelPriceInfo' + required: + - agent_types + - name + type: object + models.ModelPriceInfo: + properties: + cache_read: + example: 0.1 + minimum: 0 + type: number + cache_write: + example: 0.3 + minimum: 0 + type: number + input: + example: 1.1 + minimum: 0 + type: number + output: + example: 3 + minimum: 0 + type: number + type: object models.ModelUsageStats: properties: model: @@ -1115,6 +1149,13 @@ definitions: type: object models.ProviderInfo: properties: + default_model: + example: gpt-4o + type: string + models: + items: + $ref: '#/definitions/models.ModelInfo' + type: array name: example: my openai provider type: string @@ -1122,6 +1163,8 @@ definitions: example: openai type: string required: + - default_model + - models - name - type type: object @@ -1265,6 +1308,27 @@ definitions: - initiator - query type: object + models.Settings: + properties: + ask_user: + example: false + type: boolean + assistant_use_agents: + example: false + type: boolean + debug: + example: false + type: boolean + docker_inside: + example: false + type: boolean + is_develop_mode: + example: false + type: boolean + version: + example: v1.0.0 + type: string + type: object models.Subtask: properties: context: @@ -5330,7 +5394,9 @@ paths: - $ref: '#/definitions/SuccessResponse' - properties: data: - $ref: '#/definitions/models.ProviderInfo' + items: + $ref: '#/definitions/models.ProviderInfo' + type: array type: object "403": description: getting providers not permitted @@ -5946,6 +6012,29 @@ paths: summary: Retrieve searchlogs list tags: - Searchlogs + /settings/: + get: + produces: + - application/json + responses: + "200": + description: settings received successful + schema: + allOf: + - $ref: '#/definitions/SuccessResponse' + - properties: + data: + $ref: '#/definitions/models.Settings' + type: object + "403": + description: getting settings not permitted + schema: + $ref: '#/definitions/ErrorResponse' + security: + - BearerAuth: [] + summary: Retrieve settings + tags: + - Settings /termlogs/: get: parameters: diff --git a/backend/pkg/server/models/providers.go b/backend/pkg/server/models/providers.go index e2e90c1d..e20a46ed 100644 --- a/backend/pkg/server/models/providers.go +++ b/backend/pkg/server/models/providers.go @@ -96,11 +96,35 @@ func (pp PatchProvider) Valid() error { return validate.Struct(pp) } +// ModelPriceInfo is model to contain price information for a model +// nolint:lll +type ModelPriceInfo struct { + Input float64 `form:"input" json:"input" validate:"omitempty,numeric,min=0" example:"1.1"` + Output float64 `form:"output" json:"output" validate:"omitempty,numeric,min=0" example:"3.0"` + CacheRead float64 `form:"cache_read" json:"cache_read" validate:"omitempty,numeric,min=0" example:"0.1"` + CacheWrite float64 `form:"cache_write" json:"cache_write" validate:"omitempty,numeric,min=0" example:"0.3"` +} + +// Valid is function to control input/output data +func (mpi ModelPriceInfo) Valid() error { + return validate.Struct(mpi) +} + +// ModelInfo is model to contain model short information for display +// nolint:lll +type ModelInfo struct { + Name string `form:"name" json:"name" validate:"required" example:"gpt-4o"` + AgentTypes []string `form:"agent_types" json:"agent_types" validate:"required"` + PriceInfo *ModelPriceInfo `form:"price_info" json:"price_info" validate:"omitempty,valid"` +} + // ProviderInfo is model to contain provider short information for display // nolint:lll type ProviderInfo struct { - Name string `form:"name" json:"name" validate:"required" example:"my openai provider"` - Type ProviderType `form:"type" json:"type" validate:"valid,required" example:"openai"` + Name string `form:"name" json:"name" validate:"required" example:"my openai provider"` + Type ProviderType `form:"type" json:"type" validate:"valid,required" example:"openai"` + DefaultModel string `form:"default_model" json:"default_model" validate:"required" example:"gpt-4o"` + Models []ModelInfo `form:"models" json:"models" validate:"required"` } // Valid is function to control input/output data diff --git a/backend/pkg/server/models/providers_test.go b/backend/pkg/server/models/providers_test.go index ceea628d..42d87d15 100644 --- a/backend/pkg/server/models/providers_test.go +++ b/backend/pkg/server/models/providers_test.go @@ -141,21 +141,43 @@ func TestPatchProviderValid(t *testing.T) { func TestProviderInfoValid(t *testing.T) { t.Parallel() + validInfo := ProviderInfo{ + Name: "my-provider", + Type: ProviderType("openai"), + DefaultModel: "gpt-4o", + Models: []ModelInfo{{Name: "gpt-4o", AgentTypes: []string{"primary_agent"}}}, + } + t.Run("valid provider info", func(t *testing.T) { t.Parallel() - pi := ProviderInfo{Name: "my-provider", Type: ProviderType("openai")} - assert.NoError(t, pi.Valid()) + assert.NoError(t, validInfo.Valid()) }) t.Run("missing name", func(t *testing.T) { t.Parallel() - pi := ProviderInfo{Name: "", Type: ProviderType("openai")} + pi := validInfo + pi.Name = "" assert.Error(t, pi.Valid()) }) t.Run("invalid type", func(t *testing.T) { t.Parallel() - pi := ProviderInfo{Name: "my-provider", Type: ProviderType("invalid")} + pi := validInfo + pi.Type = ProviderType("invalid") + assert.Error(t, pi.Valid()) + }) + + t.Run("missing default model", func(t *testing.T) { + t.Parallel() + pi := validInfo + pi.DefaultModel = "" + assert.Error(t, pi.Valid()) + }) + + t.Run("missing models", func(t *testing.T) { + t.Parallel() + pi := validInfo + pi.Models = nil assert.Error(t, pi.Valid()) }) } diff --git a/backend/pkg/server/models/settings.go b/backend/pkg/server/models/settings.go new file mode 100644 index 00000000..14726a18 --- /dev/null +++ b/backend/pkg/server/models/settings.go @@ -0,0 +1,17 @@ +package models + +// Settings is model to contain application settings information +// nolint:lll +type Settings struct { + Debug bool `json:"debug" example:"false"` + AskUser bool `json:"ask_user" example:"false"` + Version string `json:"version" example:"v1.0.0"` + DockerInside bool `json:"docker_inside" example:"false"` + IsDevelopMode bool `json:"is_develop_mode" example:"false"` + AssistantUseAgents bool `json:"assistant_use_agents" example:"false"` +} + +// Valid is function to control input/output data +func (s Settings) Valid() error { + return validate.Struct(s) +} diff --git a/backend/pkg/server/router.go b/backend/pkg/server/router.go index e84c4f51..5aea4a5a 100644 --- a/backend/pkg/server/router.go +++ b/backend/pkg/server/router.go @@ -155,6 +155,7 @@ func NewRouter( userService := services.NewUserService(orm, userCache) roleService := services.NewRoleService(orm) providerService := services.NewProviderService(providers) + settingsService := services.NewSettingsService(cfg) flowService := services.NewFlowService(orm, providers, controller, subscriptions) flowFileService := services.NewFlowFileService(orm, cfg.DataDir, dockerClient, subscriptions) resourceService := services.NewResourceService(orm, cfg.DataDir, subscriptions) @@ -252,6 +253,7 @@ func NewRouter( setKnowledgeGroup(privateGroup, knowledgeService) setProvidersGroup(privateGroup, providerService) + setSettingsGroup(privateGroup, settingsService) setFlowsGroup(privateGroup, flowService) setFlowFilesGroup(privateGroup, flowFileService) setResourcesGroup(privateGroup, resourceService) @@ -358,6 +360,13 @@ func setProvidersGroup(parent *gin.RouterGroup, svc *services.ProviderService) { } } +func setSettingsGroup(parent *gin.RouterGroup, svc *services.SettingsService) { + settingsGroup := parent.Group("/settings") + { + settingsGroup.GET("/", svc.GetSettings) + } +} + func setGraphqlGroup(parent *gin.RouterGroup, svc *services.GraphqlService) { graphqlGroup := parent.Group("/") { diff --git a/backend/pkg/server/services/providers.go b/backend/pkg/server/services/providers.go index bc4224f0..516dcad1 100644 --- a/backend/pkg/server/services/providers.go +++ b/backend/pkg/server/services/providers.go @@ -5,6 +5,8 @@ import ( "slices" "pentagi/pkg/providers" + "pentagi/pkg/providers/pconfig" + "pentagi/pkg/providers/provider" "pentagi/pkg/server/logger" "pentagi/pkg/server/models" "pentagi/pkg/server/response" @@ -27,7 +29,7 @@ func NewProviderService(providers providers.ProviderController) *ProviderService // @Tags Providers // @Produce json // @Security BearerAuth -// @Success 200 {object} response.successResp{data=models.ProviderInfo} "providers list received successful" +// @Success 200 {object} response.successResp{data=[]models.ProviderInfo} "providers list received successful" // @Failure 403 {object} response.errorResp "getting providers not permitted" // @Router /providers/ [get] func (s *ProviderService) GetProviders(c *gin.Context) { @@ -47,11 +49,75 @@ func (s *ProviderService) GetProviders(c *gin.Context) { providerInfos := make([]models.ProviderInfo, len(providers)) for i, name := range providers.ListNames() { + prv := providers[name] providerInfos[i] = models.ProviderInfo{ - Name: name.String(), - Type: models.ProviderType(providers[name].Type()), + Name: name.String(), + Type: models.ProviderType(prv.Type()), + DefaultModel: prv.Model(pconfig.OptionsTypePrimaryAgent), + Models: buildModelInfos(prv), } } response.Success(c, http.StatusOK, providerInfos) } + +func buildModelInfos(prv provider.Provider) []models.ModelInfo { + modelsConfig := prv.GetModels() + + // Build lookup: model name -> price from ModelsConfig (models.yml) + modelConfigPrice := make(map[string]*pconfig.PriceInfo, len(modelsConfig)) + for _, mc := range modelsConfig { + if mc.Price != nil { + modelConfigPrice[mc.Name] = mc.Price + } + } + + // Collect unique models actually in use across all agent types. + // For price priority: AgentConfig.Price > ModelsConfig price. + type entry struct { + price *pconfig.PriceInfo + agentTypes []string + } + seen := make(map[string]*entry) + order := make([]string, 0) + + for _, optType := range pconfig.AllAgentTypes { + modelName := prv.Model(optType) + if modelName == "" { + continue + } + + e, exists := seen[modelName] + if !exists { + price := prv.GetPriceInfo(optType) + if price == nil { + price = modelConfigPrice[modelName] + } + e = &entry{price: price} + seen[modelName] = e + order = append(order, modelName) + } + + e.agentTypes = append(e.agentTypes, string(optType)) + } + + modelInfos := make([]models.ModelInfo, 0, len(order)) + for _, name := range order { + e := seen[name] + mi := models.ModelInfo{ + Name: name, + AgentTypes: e.agentTypes, + } + if e.price != nil { + mi.PriceInfo = &models.ModelPriceInfo{ + Input: e.price.Input, + Output: e.price.Output, + CacheRead: e.price.CacheRead, + CacheWrite: e.price.CacheWrite, + } + } + modelInfos = append(modelInfos, mi) + } + + return modelInfos +} diff --git a/backend/pkg/server/services/settings.go b/backend/pkg/server/services/settings.go new file mode 100644 index 00000000..9200b96a --- /dev/null +++ b/backend/pkg/server/services/settings.go @@ -0,0 +1,50 @@ +package services + +import ( + "net/http" + "slices" + + "pentagi/pkg/config" + "pentagi/pkg/server/logger" + "pentagi/pkg/server/models" + "pentagi/pkg/server/response" + "pentagi/pkg/version" + + "github.com/gin-gonic/gin" +) + +type SettingsService struct { + cfg *config.Config +} + +func NewSettingsService(cfg *config.Config) *SettingsService { + return &SettingsService{cfg: cfg} +} + +// GetSettings is a function to return settings +// @Summary Retrieve settings +// @Tags Settings +// @Produce json +// @Security BearerAuth +// @Success 200 {object} response.successResp{data=models.Settings} "settings received successful" +// @Failure 403 {object} response.errorResp "getting settings not permitted" +// @Router /settings/ [get] +func (s *SettingsService) GetSettings(c *gin.Context) { + privs := c.GetStringSlice("prm") + if !slices.Contains(privs, "settings.view") { + logger.FromContext(c).Errorf("error filtering user role permissions: permission not found") + response.Error(c, response.ErrNotPermitted, nil) + return + } + + settings := models.Settings{ + Debug: s.cfg.Debug, + AskUser: s.cfg.AskUser, + Version: version.GetBinaryVersion(), + DockerInside: s.cfg.DockerInside, + IsDevelopMode: version.IsDevelopMode(), + AssistantUseAgents: s.cfg.AssistantUseAgents, + } + + response.Success(c, http.StatusOK, settings) +} diff --git a/frontend/graphql-schema.graphql b/frontend/graphql-schema.graphql index f571e02f..5f129441 100644 --- a/frontend/graphql-schema.graphql +++ b/frontend/graphql-schema.graphql @@ -3,7 +3,9 @@ fragment settingsFragment on Settings { debug askUser + version dockerInside + isDevelopMode assistantUseAgents } diff --git a/frontend/src/graphql/types.ts b/frontend/src/graphql/types.ts index 003f1e7f..969d0385 100644 --- a/frontend/src/graphql/types.ts +++ b/frontend/src/graphql/types.ts @@ -990,6 +990,8 @@ export type Settings = { assistantUseAgents: Scalars['Boolean']['output']; debug: Scalars['Boolean']['output']; dockerInside: Scalars['Boolean']['output']; + isDevelopMode: Scalars['Boolean']['output']; + version: Scalars['String']['output']; }; export enum StatusType { @@ -1292,7 +1294,9 @@ export type VectorStoreLog = { export type SettingsFragmentFragment = { debug: boolean; askUser: boolean; + version: string; dockerInside: boolean; + isDevelopMode: boolean; assistantUseAgents: boolean; }; @@ -2371,7 +2375,9 @@ export const SettingsFragmentFragmentDoc = gql` fragment settingsFragment on Settings { debug askUser + version dockerInside + isDevelopMode assistantUseAgents } `;