snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
285
server/channels/app/e2e_agents_bridge.go
Normal file
285
server/channels/app/e2e_agents_bridge.go
Normal file
@@ -0,0 +1,285 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
)
|
||||
|
||||
type e2eAgentsBridge struct {
|
||||
mut sync.Mutex
|
||||
|
||||
status *model.AIBridgeTestHelperStatus
|
||||
agents *[]model.BridgeAgentInfo
|
||||
services *[]model.BridgeServiceInfo
|
||||
agentCompletions map[string][]model.AIBridgeTestHelperCompletion
|
||||
recordRequests bool
|
||||
recordedRequests []model.AIBridgeTestHelperRecordedRequest
|
||||
}
|
||||
|
||||
func newE2EAgentsBridge(config *model.AIBridgeTestHelperConfig) (*e2eAgentsBridge, error) {
|
||||
b := &e2eAgentsBridge{
|
||||
agentCompletions: make(map[string][]model.AIBridgeTestHelperCompletion),
|
||||
recordedRequests: []model.AIBridgeTestHelperRecordedRequest{},
|
||||
}
|
||||
if err := b.setConfig(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (b *e2eAgentsBridge) setConfig(config *model.AIBridgeTestHelperConfig) error {
|
||||
if config == nil {
|
||||
return fmt.Errorf("config cannot be nil")
|
||||
}
|
||||
|
||||
normalizedCompletions := make(map[string][]model.AIBridgeTestHelperCompletion, len(config.AgentCompletions))
|
||||
for operation, completions := range config.AgentCompletions {
|
||||
if operation == "" {
|
||||
return fmt.Errorf("agent completion operation key cannot be empty")
|
||||
}
|
||||
|
||||
normalizedCompletions[operation] = make([]model.AIBridgeTestHelperCompletion, 0, len(completions))
|
||||
for idx, completion := range completions {
|
||||
hasCompletion := completion.Completion != ""
|
||||
hasError := completion.Error != ""
|
||||
if hasCompletion == hasError {
|
||||
return fmt.Errorf("agent completion %q at index %d must set exactly one of completion or error", operation, idx)
|
||||
}
|
||||
if hasError && completion.StatusCode == 0 {
|
||||
completion.StatusCode = http.StatusInternalServerError
|
||||
}
|
||||
normalizedCompletions[operation] = append(normalizedCompletions[operation], completion)
|
||||
}
|
||||
}
|
||||
|
||||
for idx, agent := range config.Agents {
|
||||
if agent.ID == "" {
|
||||
return fmt.Errorf("agent at index %d is missing id", idx)
|
||||
}
|
||||
if agent.Username == "" {
|
||||
return fmt.Errorf("agent %q is missing username", agent.ID)
|
||||
}
|
||||
}
|
||||
|
||||
for idx, service := range config.Services {
|
||||
if service.ID == "" {
|
||||
return fmt.Errorf("service at index %d is missing id", idx)
|
||||
}
|
||||
if service.Name == "" {
|
||||
return fmt.Errorf("service %q is missing name", service.ID)
|
||||
}
|
||||
}
|
||||
|
||||
b.mut.Lock()
|
||||
defer b.mut.Unlock()
|
||||
|
||||
b.status = cloneAIBridgeStatus(config.Status)
|
||||
b.agents = cloneBridgeAgentsPtr(config.Agents)
|
||||
b.services = cloneBridgeServicesPtr(config.Services)
|
||||
b.agentCompletions = normalizedCompletions
|
||||
b.recordRequests = config.RecordRequests != nil && *config.RecordRequests
|
||||
b.recordedRequests = []model.AIBridgeTestHelperRecordedRequest{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *e2eAgentsBridge) GetState() *model.AIBridgeTestHelperState {
|
||||
b.mut.Lock()
|
||||
defer b.mut.Unlock()
|
||||
|
||||
return &model.AIBridgeTestHelperState{
|
||||
Status: cloneAIBridgeStatus(b.status),
|
||||
Agents: cloneBridgeAgentsValue(b.agents),
|
||||
Services: cloneBridgeServicesValue(b.services),
|
||||
AgentCompletions: cloneAIBridgeCompletions(b.agentCompletions),
|
||||
RecordRequests: b.recordRequests,
|
||||
RecordedRequests: cloneRecordedRequests(b.recordedRequests),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *e2eAgentsBridge) Status(_ request.CTX) (bool, string) {
|
||||
b.mut.Lock()
|
||||
defer b.mut.Unlock()
|
||||
|
||||
if b.status == nil {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
return b.status.Available, b.status.Reason
|
||||
}
|
||||
|
||||
func (b *e2eAgentsBridge) GetAgents(_, _ string) ([]model.BridgeAgentInfo, error) {
|
||||
b.mut.Lock()
|
||||
defer b.mut.Unlock()
|
||||
|
||||
if b.agents == nil {
|
||||
return []model.BridgeAgentInfo{}, nil
|
||||
}
|
||||
|
||||
return append([]model.BridgeAgentInfo(nil), (*b.agents)...), nil
|
||||
}
|
||||
|
||||
func (b *e2eAgentsBridge) GetServices(_, _ string) ([]model.BridgeServiceInfo, error) {
|
||||
b.mut.Lock()
|
||||
defer b.mut.Unlock()
|
||||
|
||||
if b.services == nil {
|
||||
return []model.BridgeServiceInfo{}, nil
|
||||
}
|
||||
|
||||
return append([]model.BridgeServiceInfo(nil), (*b.services)...), nil
|
||||
}
|
||||
|
||||
func (b *e2eAgentsBridge) AgentCompletion(sessionUserID, agentID string, req BridgeCompletionRequest) (string, error) {
|
||||
b.mut.Lock()
|
||||
defer b.mut.Unlock()
|
||||
|
||||
b.recordRequestLocked(sessionUserID, agentID, "", req)
|
||||
return b.getCompletionLocked(string(req.Operation))
|
||||
}
|
||||
|
||||
func (b *e2eAgentsBridge) ServiceCompletion(sessionUserID, serviceID string, req BridgeCompletionRequest) (string, error) {
|
||||
b.mut.Lock()
|
||||
defer b.mut.Unlock()
|
||||
|
||||
b.recordRequestLocked(sessionUserID, "", serviceID, req)
|
||||
return b.getCompletionLocked(string(req.Operation))
|
||||
}
|
||||
|
||||
func (b *e2eAgentsBridge) getCompletionLocked(operation string) (string, error) {
|
||||
completions, ok := b.agentCompletions[operation]
|
||||
if !ok || len(completions) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
completion := completions[0]
|
||||
b.agentCompletions[operation] = completions[1:]
|
||||
|
||||
if completion.Error != "" {
|
||||
if completion.StatusCode > 0 {
|
||||
return "", fmt.Errorf("request failed with status %d: %s", completion.StatusCode, completion.Error)
|
||||
}
|
||||
return "", fmt.Errorf("%s", completion.Error)
|
||||
}
|
||||
|
||||
return completion.Completion, nil
|
||||
}
|
||||
|
||||
func (b *e2eAgentsBridge) recordRequestLocked(sessionUserID, agentID, serviceID string, req BridgeCompletionRequest) {
|
||||
if !b.recordRequests {
|
||||
return
|
||||
}
|
||||
|
||||
recorded := model.AIBridgeTestHelperRecordedRequest{
|
||||
Operation: string(req.Operation),
|
||||
ClientOperation: req.ClientOperation,
|
||||
OperationSubType: req.OperationSubType,
|
||||
SessionUserID: sessionUserID,
|
||||
UserID: req.UserID,
|
||||
ChannelID: req.ChannelID,
|
||||
AgentID: agentID,
|
||||
ServiceID: serviceID,
|
||||
Messages: toE2ERecordedMessages(req.Messages),
|
||||
JSONOutputFormat: cloneJSONOutputFormat(req.JSONOutputFormat),
|
||||
}
|
||||
|
||||
b.recordedRequests = append(b.recordedRequests, recorded)
|
||||
}
|
||||
|
||||
func toE2ERecordedMessages(messages []BridgeMessage) []model.AIBridgeTestHelperMessage {
|
||||
recorded := make([]model.AIBridgeTestHelperMessage, 0, len(messages))
|
||||
for _, message := range messages {
|
||||
recorded = append(recorded, model.AIBridgeTestHelperMessage{
|
||||
Role: message.Role,
|
||||
Message: message.Message,
|
||||
FileIDs: append([]string(nil), message.FileIDs...),
|
||||
})
|
||||
}
|
||||
|
||||
return recorded
|
||||
}
|
||||
|
||||
func cloneAIBridgeStatus(status *model.AIBridgeTestHelperStatus) *model.AIBridgeTestHelperStatus {
|
||||
if status == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cloned := *status
|
||||
return &cloned
|
||||
}
|
||||
|
||||
func cloneBridgeAgentsPtr(agents []model.BridgeAgentInfo) *[]model.BridgeAgentInfo {
|
||||
if agents == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cloned := append([]model.BridgeAgentInfo(nil), agents...)
|
||||
return &cloned
|
||||
}
|
||||
|
||||
func cloneBridgeServicesPtr(services []model.BridgeServiceInfo) *[]model.BridgeServiceInfo {
|
||||
if services == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cloned := append([]model.BridgeServiceInfo(nil), services...)
|
||||
return &cloned
|
||||
}
|
||||
|
||||
func cloneBridgeAgentsValue(agents *[]model.BridgeAgentInfo) []model.BridgeAgentInfo {
|
||||
if agents == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return append([]model.BridgeAgentInfo(nil), (*agents)...)
|
||||
}
|
||||
|
||||
func cloneBridgeServicesValue(services *[]model.BridgeServiceInfo) []model.BridgeServiceInfo {
|
||||
if services == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return append([]model.BridgeServiceInfo(nil), (*services)...)
|
||||
}
|
||||
|
||||
func cloneAIBridgeCompletions(agentCompletions map[string][]model.AIBridgeTestHelperCompletion) map[string][]model.AIBridgeTestHelperCompletion {
|
||||
if agentCompletions == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cloned := make(map[string][]model.AIBridgeTestHelperCompletion, len(agentCompletions))
|
||||
for key, value := range agentCompletions {
|
||||
cloned[key] = append([]model.AIBridgeTestHelperCompletion(nil), value...)
|
||||
}
|
||||
|
||||
return cloned
|
||||
}
|
||||
|
||||
func cloneRecordedRequests(recordedRequests []model.AIBridgeTestHelperRecordedRequest) []model.AIBridgeTestHelperRecordedRequest {
|
||||
if recordedRequests == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cloned := make([]model.AIBridgeTestHelperRecordedRequest, 0, len(recordedRequests))
|
||||
for _, req := range recordedRequests {
|
||||
clonedReq := req
|
||||
clonedReq.Messages = append([]model.AIBridgeTestHelperMessage(nil), req.Messages...)
|
||||
if req.JSONOutputFormat != nil {
|
||||
clonedReq.JSONOutputFormat = make(map[string]any, len(req.JSONOutputFormat))
|
||||
maps.Copy(clonedReq.JSONOutputFormat, req.JSONOutputFormat)
|
||||
}
|
||||
cloned = append(cloned, clonedReq)
|
||||
}
|
||||
|
||||
return cloned
|
||||
}
|
||||
|
||||
var _ AgentsBridge = (*e2eAgentsBridge)(nil)
|
||||
Reference in New Issue
Block a user