Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

560 lines
18 KiB
Go

package workbenchservice
import (
"encoding/json"
"errors"
"fmt"
"math"
"sort"
"strings"
)
// NormalizeSnapshot validates and encodes a workbench snapshot in canonical form.
func NormalizeSnapshot(snapshot WorkbenchSnapshot) (json.RawMessage, int, error) {
return normalizeWorkbenchSnapshot(snapshot)
}
func normalizeWorkbenchSnapshot(snapshot WorkbenchSnapshot) (json.RawMessage, int, error) {
if snapshot.SchemaVersion != workbenchSnapshotContractSchemaVersion {
return nil, 0, fmt.Errorf("unsupported workbench snapshot schema version: %d", snapshot.SchemaVersion)
}
if snapshot.Nodes == nil {
return nil, 0, errors.New("workbench snapshot nodes are required")
}
if len(snapshot.Nodes) > workbenchSnapshotContractMaxNodes {
return nil, 0, fmt.Errorf("workbench snapshot nodes must contain at most %d items", workbenchSnapshotContractMaxNodes)
}
ids := make(map[string]struct{}, len(snapshot.Nodes))
for index, node := range snapshot.Nodes {
if strings.TrimSpace(node.ID) == "" {
return nil, 0, fmt.Errorf("workbench snapshot node %d id is required", index)
}
nodeID := strings.TrimSpace(node.ID)
if _, exists := ids[nodeID]; exists {
return nil, 0, fmt.Errorf("workbench snapshot node %d id is duplicated", index)
}
ids[nodeID] = struct{}{}
if len(node.ID) > workbenchSnapshotContractMaxNodeIDLength {
return nil, 0, fmt.Errorf("workbench snapshot node %d id is too long", index)
}
if strings.TrimSpace(node.Kind) == "" {
return nil, 0, fmt.Errorf("workbench snapshot node %d kind is required", index)
}
if len(node.Kind) > workbenchSnapshotContractMaxKindLength {
return nil, 0, fmt.Errorf("workbench snapshot node %d kind is too long", index)
}
if len(node.Title) > workbenchSnapshotContractMaxTitleLength {
return nil, 0, fmt.Errorf("workbench snapshot node %d title is too long", index)
}
if node.DisplayMode != nil {
if _, ok := workbenchSnapshotContractDisplayModes[*node.DisplayMode]; !ok {
return nil, 0, fmt.Errorf("workbench snapshot node %d display mode is invalid", index)
}
}
if err := validateWorkbenchFrame(node.Frame); err != nil {
return nil, 0, fmt.Errorf("workbench snapshot node %d frame: %w", index, err)
}
if node.RestoreFrame != nil {
if err := validateWorkbenchFrame(*node.RestoreFrame); err != nil {
return nil, 0, fmt.Errorf("workbench snapshot node %d restore frame: %w", index, err)
}
}
if node.MinimizedAtUnixMs != nil && *node.MinimizedAtUnixMs < 0 {
return nil, 0, fmt.Errorf("workbench snapshot node %d minimized timestamp is invalid", index)
}
}
if snapshot.NodeStack != nil {
for index, nodeID := range *snapshot.NodeStack {
if strings.TrimSpace(nodeID) == "" {
return nil, 0, fmt.Errorf("workbench snapshot node stack item %d is required", index)
}
}
}
if snapshot.Spaces != nil {
for index, space := range *snapshot.Spaces {
if strings.TrimSpace(space.ID) == "" {
return nil, 0, fmt.Errorf("workbench snapshot space %d id is required", index)
}
if space.NodeIDs == nil {
return nil, 0, fmt.Errorf("workbench snapshot space %d node ids are required", index)
}
for nodeIDIndex, nodeID := range space.NodeIDs {
if strings.TrimSpace(nodeID) == "" {
return nil, 0, fmt.Errorf("workbench snapshot space %d node id %d is required", index, nodeIDIndex)
}
}
if space.Frame != nil {
if err := validateWorkbenchFrame(*space.Frame); err != nil {
return nil, 0, fmt.Errorf("workbench snapshot space %d frame: %w", index, err)
}
}
}
}
if snapshot.LayoutBasis != nil {
if err := validateWorkbenchLayoutBasis(*snapshot.LayoutBasis); err != nil {
return nil, 0, fmt.Errorf("workbench snapshot layout basis: %w", err)
}
}
if snapshot.LockedLayout != nil {
if err := validateWorkbenchLockedLayout(*snapshot.LockedLayout, ids); err != nil {
return nil, 0, fmt.Errorf("workbench snapshot locked layout: %w", err)
}
}
canonicalSnapshot := canonicalizeWorkbenchSnapshot(snapshot)
normalizedJSON, err := json.Marshal(canonicalSnapshot)
if err != nil {
return nil, 0, fmt.Errorf("encode workbench snapshot: %w", err)
}
if len(normalizedJSON) > workbenchSnapshotContractMaxSerializedBytes {
return nil, 0, fmt.Errorf("workbench snapshot exceeds %d bytes", workbenchSnapshotContractMaxSerializedBytes)
}
return normalizedJSON, snapshot.SchemaVersion, nil
}
type canonicalWorkbenchSnapshot struct {
SchemaVersion int `json:"schemaVersion"`
Nodes []canonicalWorkbenchSnapshotNode `json:"nodes"`
NodeStack []string `json:"nodeStack"`
ActiveNodeID *string `json:"activeNodeId"`
Spaces *[]canonicalWorkbenchSnapshotSpace `json:"spaces,omitempty"`
ActiveSpaceID *string `json:"activeSpaceId"`
LayoutBasis *WorkbenchSnapshotLayoutBasis `json:"layoutBasis,omitempty"`
LockedLayout *canonicalWorkbenchSnapshotLockedLayout `json:"lockedLayout,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type canonicalWorkbenchSnapshotLockedLayout struct {
Preset WorkbenchSnapshotLayoutPreset `json:"preset"`
NodeIDs []string `json:"nodeIDs"`
NormalizedFrames map[string]WorkbenchSnapshotNormalizedFrame `json:"normalizedFrames,omitempty"`
}
type canonicalWorkbenchSnapshotNode struct {
ID string `json:"id"`
Kind string `json:"kind"`
Title string `json:"title"`
Frame WorkbenchSnapshotFrame `json:"frame"`
DisplayMode WorkbenchSnapshotDisplayMode `json:"displayMode"`
RestoreFrame *WorkbenchSnapshotFrame `json:"restoreFrame"`
IsMinimized bool `json:"isMinimized"`
MinimizedAtUnixMs *int64 `json:"minimizedAtUnixMs,omitempty"`
Data interface{} `json:"data,omitempty"`
AdapterState map[string]interface{} `json:"adapterState,omitempty"`
}
type canonicalWorkbenchSnapshotSpace struct {
ID string `json:"id"`
Name string `json:"name"`
NodeIDs []string `json:"nodeIds"`
Frame *WorkbenchSnapshotFrame `json:"frame"`
Data interface{} `json:"data,omitempty"`
}
func canonicalizeWorkbenchSnapshot(snapshot WorkbenchSnapshot) canonicalWorkbenchSnapshot {
nodes := make([]canonicalWorkbenchSnapshotNode, len(snapshot.Nodes))
for index, node := range snapshot.Nodes {
nodes[index] = canonicalizeWorkbenchSnapshotNode(node)
}
sort.Slice(nodes, func(left, right int) bool {
return nodes[left].ID < nodes[right].ID
})
nodeIDs := make(map[string]struct{}, len(nodes))
for _, node := range nodes {
nodeIDs[node.ID] = struct{}{}
}
nodeStack := uniqueTrimmedStrings(derefStringSlice(snapshot.NodeStack))
filteredNodeStack := make([]string, 0, len(nodeStack))
for _, nodeID := range nodeStack {
if _, ok := nodeIDs[nodeID]; ok {
filteredNodeStack = append(filteredNodeStack, nodeID)
}
}
for _, node := range nodes {
if !containsString(filteredNodeStack, node.ID) {
filteredNodeStack = append(filteredNodeStack, node.ID)
}
}
var spaces *[]canonicalWorkbenchSnapshotSpace
spaceIDs := map[string]struct{}{}
if snapshot.Spaces != nil {
normalizedSpaces := make([]canonicalWorkbenchSnapshotSpace, len(*snapshot.Spaces))
for index, space := range *snapshot.Spaces {
normalizedSpaces[index] = canonicalizeWorkbenchSnapshotSpace(space)
}
sort.Slice(normalizedSpaces, func(left, right int) bool {
return normalizedSpaces[left].ID < normalizedSpaces[right].ID
})
for _, space := range normalizedSpaces {
spaceIDs[space.ID] = struct{}{}
}
spaces = &normalizedSpaces
}
return canonicalWorkbenchSnapshot{
SchemaVersion: workbenchSnapshotContractSchemaVersion,
Nodes: nodes,
NodeStack: filteredNodeStack,
ActiveNodeID: canonicalActiveNodeID(snapshot.ActiveNodeID, filteredNodeStack, nodeIDs),
Spaces: spaces,
ActiveSpaceID: canonicalActiveSpaceID(snapshot.ActiveSpaceID, spaces, spaceIDs),
LayoutBasis: canonicalizeWorkbenchLayoutBasis(snapshot.LayoutBasis),
LockedLayout: canonicalizeWorkbenchLockedLayout(snapshot.LockedLayout, nodeIDs),
Metadata: snapshot.Metadata,
}
}
func canonicalizeWorkbenchSnapshotNode(node WorkbenchSnapshotNode) canonicalWorkbenchSnapshotNode {
return canonicalWorkbenchSnapshotNode{
ID: strings.TrimSpace(node.ID),
Kind: strings.TrimSpace(node.Kind),
Title: node.Title,
Frame: canonicalizeWorkbenchFrame(node.Frame),
DisplayMode: canonicalDisplayMode(node.DisplayMode),
RestoreFrame: canonicalRestoreFrame(node.RestoreFrame),
IsMinimized: canonicalIsMinimized(node.IsMinimized),
MinimizedAtUnixMs: canonicalMinimizedAtUnixMs(node.IsMinimized, node.MinimizedAtUnixMs),
Data: node.Data,
AdapterState: node.AdapterState,
}
}
func canonicalizeWorkbenchSnapshotSpace(space WorkbenchSnapshotSpace) canonicalWorkbenchSnapshotSpace {
return canonicalWorkbenchSnapshotSpace{
ID: strings.TrimSpace(space.ID),
Name: space.Name,
NodeIDs: uniqueTrimmedStrings(space.NodeIDs),
Frame: canonicalRestoreFrame(space.Frame),
Data: space.Data,
}
}
func canonicalizeWorkbenchFrame(frame WorkbenchSnapshotFrame) WorkbenchSnapshotFrame {
return WorkbenchSnapshotFrame{
X: canonicalizeWorkbenchNumber(frame.X),
Y: canonicalizeWorkbenchNumber(frame.Y),
Width: math.Max(workbenchSnapshotContractMinFrameWidth, canonicalizeWorkbenchNumber(frame.Width)),
Height: math.Max(workbenchSnapshotContractMinFrameHeight, canonicalizeWorkbenchNumber(frame.Height)),
}
}
func canonicalizeWorkbenchLayoutBasis(
layoutBasis *WorkbenchSnapshotLayoutBasis,
) *WorkbenchSnapshotLayoutBasis {
if layoutBasis == nil {
return nil
}
return &WorkbenchSnapshotLayoutBasis{
SurfaceSize: WorkbenchSnapshotSize{
Width: canonicalizeWorkbenchNumber(layoutBasis.SurfaceSize.Width),
Height: canonicalizeWorkbenchNumber(layoutBasis.SurfaceSize.Height),
},
LayoutConstraints: WorkbenchSnapshotLayoutConstraints{
MinWidth: canonicalizeWorkbenchNumber(layoutBasis.LayoutConstraints.MinWidth),
MinHeight: canonicalizeWorkbenchNumber(layoutBasis.LayoutConstraints.MinHeight),
SurfacePadding: canonicalizeWorkbenchNumber(layoutBasis.LayoutConstraints.SurfacePadding),
SafeArea: WorkbenchSnapshotSafeArea{
Top: canonicalizeWorkbenchNumber(layoutBasis.LayoutConstraints.SafeArea.Top),
Right: canonicalizeWorkbenchNumber(layoutBasis.LayoutConstraints.SafeArea.Right),
Bottom: canonicalizeWorkbenchNumber(layoutBasis.LayoutConstraints.SafeArea.Bottom),
Left: canonicalizeWorkbenchNumber(layoutBasis.LayoutConstraints.SafeArea.Left),
},
},
}
}
func canonicalizeWorkbenchLockedLayout(
lockedLayout *WorkbenchSnapshotLockedLayout,
knownNodeIDs map[string]struct{},
) *canonicalWorkbenchSnapshotLockedLayout {
if lockedLayout == nil {
return nil
}
nodeIDs := make([]string, 0, len(lockedLayout.NodeIDs))
for _, nodeID := range uniqueTrimmedStrings(lockedLayout.NodeIDs) {
if _, ok := knownNodeIDs[nodeID]; ok {
nodeIDs = append(nodeIDs, nodeID)
}
}
if len(nodeIDs) < 2 {
return nil
}
canonical := &canonicalWorkbenchSnapshotLockedLayout{
Preset: lockedLayout.Preset,
NodeIDs: nodeIDs,
}
if lockedLayout.NormalizedFrames == nil {
return canonical
}
canonical.NormalizedFrames = make(
map[string]WorkbenchSnapshotNormalizedFrame,
len(nodeIDs),
)
for _, nodeID := range nodeIDs {
frame := lockedLayout.NormalizedFrames[nodeID]
canonical.NormalizedFrames[nodeID] = canonicalizeWorkbenchNormalizedFrame(frame)
}
return canonical
}
func canonicalizeWorkbenchNormalizedFrame(
frame WorkbenchSnapshotNormalizedFrame,
) WorkbenchSnapshotNormalizedFrame {
x := canonicalizeWorkbenchNumber(frame.X)
y := canonicalizeWorkbenchNumber(frame.Y)
return WorkbenchSnapshotNormalizedFrame{
X: x,
Y: y,
Width: math.Min(canonicalizeWorkbenchNumber(frame.Width), canonicalizeWorkbenchNumber(1-x)),
Height: math.Min(canonicalizeWorkbenchNumber(frame.Height), canonicalizeWorkbenchNumber(1-y)),
}
}
func canonicalizeWorkbenchNumber(value float64) float64 {
normalized := math.Round(value*1000) / 1000
if normalized == 0 {
return 0
}
return normalized
}
func canonicalDisplayMode(displayMode *WorkbenchSnapshotDisplayMode) WorkbenchSnapshotDisplayMode {
if displayMode == nil {
return WorkbenchSnapshotDisplayModeFloating
}
return *displayMode
}
func canonicalRestoreFrame(frame *WorkbenchSnapshotFrame) *WorkbenchSnapshotFrame {
if frame == nil {
return nil
}
normalized := canonicalizeWorkbenchFrame(*frame)
return &normalized
}
func canonicalIsMinimized(value *bool) bool {
if value == nil {
return false
}
return *value
}
func canonicalMinimizedAtUnixMs(isMinimized *bool, value *int64) *int64 {
if !canonicalIsMinimized(isMinimized) || value == nil {
return nil
}
normalized := *value
return &normalized
}
func canonicalActiveNodeID(activeNodeID *string, nodeStack []string, nodeIDs map[string]struct{}) *string {
if activeNodeID != nil {
if _, ok := nodeIDs[*activeNodeID]; ok {
return cloneStringPointer(*activeNodeID)
}
}
if len(nodeStack) == 0 {
return nil
}
return cloneStringPointer(nodeStack[len(nodeStack)-1])
}
func canonicalActiveSpaceID(
activeSpaceID *string,
spaces *[]canonicalWorkbenchSnapshotSpace,
spaceIDs map[string]struct{},
) *string {
if activeSpaceID != nil {
if _, ok := spaceIDs[*activeSpaceID]; ok {
return cloneStringPointer(*activeSpaceID)
}
}
if spaces == nil || len(*spaces) == 0 {
return nil
}
return cloneStringPointer((*spaces)[0].ID)
}
func derefStringSlice(value *[]string) []string {
if value == nil {
return nil
}
return append([]string(nil), (*value)...)
}
func uniqueTrimmedStrings(values []string) []string {
if len(values) == 0 {
return []string{}
}
unique := make([]string, 0, len(values))
seen := make(map[string]struct{}, len(values))
for _, value := range values {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
continue
}
if _, ok := seen[trimmed]; ok {
continue
}
seen[trimmed] = struct{}{}
unique = append(unique, trimmed)
}
return unique
}
func containsString(values []string, target string) bool {
for _, value := range values {
if value == target {
return true
}
}
return false
}
func cloneStringPointer(value string) *string {
cloned := value
return &cloned
}
func validateWorkbenchFrame(frame WorkbenchSnapshotFrame) error {
if !isFinite(frame.X) || !isFinite(frame.Y) || !isFinite(frame.Width) || !isFinite(frame.Height) {
return errors.New("numbers must be finite")
}
if frame.Width < workbenchSnapshotContractMinFrameWidth {
return fmt.Errorf("width must be at least %d", workbenchSnapshotContractMinFrameWidth)
}
if frame.Height < workbenchSnapshotContractMinFrameHeight {
return fmt.Errorf("height must be at least %d", workbenchSnapshotContractMinFrameHeight)
}
return nil
}
func validateWorkbenchLayoutBasis(layoutBasis WorkbenchSnapshotLayoutBasis) error {
if !isPositiveFinite(layoutBasis.SurfaceSize.Width) || !isPositiveFinite(layoutBasis.SurfaceSize.Height) {
return errors.New("surface size must contain positive finite numbers")
}
constraints := layoutBasis.LayoutConstraints
if !isNonNegativeFinite(constraints.MinWidth) ||
!isNonNegativeFinite(constraints.MinHeight) ||
!isNonNegativeFinite(constraints.SurfacePadding) {
return errors.New("layout constraints must contain non-negative finite numbers")
}
safeArea := constraints.SafeArea
if !isNonNegativeFinite(safeArea.Top) ||
!isNonNegativeFinite(safeArea.Right) ||
!isNonNegativeFinite(safeArea.Bottom) ||
!isNonNegativeFinite(safeArea.Left) {
return errors.New("safe area must contain non-negative finite numbers")
}
return nil
}
func validateWorkbenchLockedLayout(
lockedLayout WorkbenchSnapshotLockedLayout,
knownNodeIDs map[string]struct{},
) error {
if _, ok := workbenchSnapshotContractLayoutPresetKinds[lockedLayout.Preset.Kind]; !ok {
return errors.New("preset kind is invalid")
}
if len(lockedLayout.NodeIDs) < 2 {
return errors.New("must contain at least two node IDs")
}
lockedNodeIDs := make(map[string]struct{}, len(lockedLayout.NodeIDs))
for index, nodeID := range lockedLayout.NodeIDs {
trimmedNodeID := strings.TrimSpace(nodeID)
if trimmedNodeID == "" {
return fmt.Errorf("node ID %d is required", index)
}
if len(nodeID) > workbenchSnapshotContractMaxNodeIDLength {
return fmt.Errorf("node ID %d is too long", index)
}
if _, duplicate := lockedNodeIDs[trimmedNodeID]; duplicate {
return fmt.Errorf("node ID %d is duplicated", index)
}
if _, exists := knownNodeIDs[trimmedNodeID]; !exists {
return fmt.Errorf("node ID %d does not exist", index)
}
lockedNodeIDs[trimmedNodeID] = struct{}{}
}
if lockedLayout.NormalizedFrames == nil {
return nil
}
for nodeID := range lockedNodeIDs {
frame, exists := lockedLayout.NormalizedFrames[nodeID]
if !exists {
return fmt.Errorf("normalized frame for node %q is required", nodeID)
}
if err := validateWorkbenchNormalizedFrame(frame); err != nil {
return fmt.Errorf("normalized frame for node %q: %w", nodeID, err)
}
}
for nodeID := range lockedLayout.NormalizedFrames {
if _, ok := lockedNodeIDs[nodeID]; !ok {
return fmt.Errorf("normalized frame node %q is not locked", nodeID)
}
}
return nil
}
func validateWorkbenchNormalizedFrame(frame WorkbenchSnapshotNormalizedFrame) error {
if !isUnitInterval(frame.X, false) || !isUnitInterval(frame.Y, false) ||
!isUnitInterval(frame.Width, true) || !isUnitInterval(frame.Height, true) {
return errors.New("values must stay within the unit layout rect")
}
if frame.X+frame.Width > 1+workbenchNormalizedFrameUnitRectTransportTolerance ||
frame.Y+frame.Height > 1+workbenchNormalizedFrameUnitRectTransportTolerance {
return errors.New("must stay within the unit layout rect")
}
return nil
}
const workbenchNormalizedFrameUnitRectTransportTolerance = 0.000001
func isUnitInterval(value float64, exclusiveMinimum bool) bool {
if !isFinite(value) || value > 1 {
return false
}
if exclusiveMinimum {
return value > 0
}
return value >= 0
}
func isPositiveFinite(value float64) bool {
return isFinite(value) && value > 0
}
func isNonNegativeFinite(value float64) bool {
return isFinite(value) && value >= 0
}
func isFinite(value float64) bool {
return !math.IsNaN(value) && !math.IsInf(value, 0)
}