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

369 lines
11 KiB
Go

package agentcontext
import (
"fmt"
"net/url"
"path/filepath"
"strings"
agentactivitybiz "github.com/tutti-os/tutti/packages/agent/store-sqlite"
agentservice "github.com/tutti-os/tutti/services/tuttid/service/agent"
)
type imageLocalPathResolver func(agentSessionID string, attachmentID string, mimeType string) (string, bool)
func agentSessionMentionURI(workspaceID string, sessionID string) string {
workspaceID = strings.TrimSpace(workspaceID)
sessionID = strings.TrimSpace(sessionID)
if workspaceID == "" || sessionID == "" {
return ""
}
query := url.Values{"workspaceId": []string{workspaceID}}
return "mention://agent-session/" + url.PathEscape(sessionID) + "?" + query.Encode()
}
func addAgentSessionReference(value map[string]any, workspaceID string, sessionID string) {
workspaceID = strings.TrimSpace(workspaceID)
sessionID = strings.TrimSpace(sessionID)
mentionURI := agentSessionMentionURI(workspaceID, sessionID)
if mentionURI == "" {
return
}
value["agentSessionId"] = sessionID
value["workspaceId"] = workspaceID
value["mentionUri"] = mentionURI
}
func sessionSummaryValue(workspaceID string, session agentservice.Session) map[string]any {
value := map[string]any{
"agentSessionId": strings.TrimSpace(session.ID),
"agentTargetId": strings.TrimSpace(session.AgentTargetID),
"provider": strings.TrimSpace(session.Provider),
"cwd": strings.TrimSpace(session.Cwd),
"visible": session.Visible,
"resumable": session.Resumable,
"createdAtUnixMs": session.CreatedAt.UnixMilli(),
"activeTurnId": nil,
}
addAgentSessionReference(value, workspaceID, session.ID)
if session.UpdatedAt != nil {
value["updatedAtUnixMs"] = session.UpdatedAt.UnixMilli()
}
if session.EndedAt != nil {
value["endedAtUnixMs"] = session.EndedAt.UnixMilli()
}
if turnID := strings.TrimSpace(session.ActiveTurnID); turnID != "" {
value["activeTurnId"] = turnID
}
if session.ActiveTurn != nil {
value["activeTurn"] = turnCompactValue(*session.ActiveTurn)
}
if session.LatestTurn != nil {
value["latestTurn"] = turnCompactValue(*session.LatestTurn)
}
value["pendingInteractions"] = interactionCompactValues(session.PendingInteractions)
if session.Title != nil {
value["title"] = strings.TrimSpace(*session.Title)
}
if session.Isolation != nil {
value["isolation"] = isolationCompactValue(*session.Isolation)
}
return value
}
func sessionInspectValue(workspaceID string, session agentservice.Session) map[string]any {
value := sessionSummaryValue(workspaceID, session)
if session.Settings != nil {
value["settings"] = agentservice.ComposerSettingsToMap(*session.Settings)
}
return value
}
func sessionActionValue(workspaceID string, session agentservice.Session) map[string]any {
value := map[string]any{
"agentSessionId": strings.TrimSpace(session.ID),
"agentTargetId": strings.TrimSpace(session.AgentTargetID),
"provider": strings.TrimSpace(session.Provider),
"activeTurnId": strings.TrimSpace(session.ActiveTurnID),
}
addAgentSessionReference(value, workspaceID, session.ID)
if session.Title != nil {
title := strings.TrimSpace(*session.Title)
if title != "" {
value["title"] = title
}
}
if session.Isolation != nil {
value["isolation"] = isolationCompactValue(*session.Isolation)
}
return value
}
func isolationCompactValue(isolation agentservice.SessionIsolation) map[string]any {
value := map[string]any{
"mode": strings.TrimSpace(isolation.Mode), "worktreePath": strings.TrimSpace(isolation.WorktreePath),
"branch": strings.TrimSpace(isolation.Branch), "baseCommit": strings.TrimSpace(isolation.BaseCommit),
}
if worktreeID := strings.TrimSpace(isolation.WorktreeID); worktreeID != "" {
value["worktreeId"] = worktreeID
}
return value
}
func sessionSummaryValues(workspaceID string, sessions []agentservice.Session) []any {
values := make([]any, 0, len(sessions))
for _, session := range sessions {
values = append(values, sessionSummaryValue(workspaceID, session))
}
return values
}
func turnCompactValue(value agentactivitybiz.Turn) map[string]any {
result := map[string]any{
"turnId": strings.TrimSpace(value.TurnID),
"phase": strings.TrimSpace(value.Phase),
}
if outcome := strings.TrimSpace(value.Outcome); outcome != "" {
result["outcome"] = outcome
}
return result
}
func interactionCompactValues(values []agentactivitybiz.Interaction) []any {
result := make([]any, 0, len(values))
for _, value := range values {
result = append(result, map[string]any{
"requestId": strings.TrimSpace(value.RequestID), "turnId": strings.TrimSpace(value.TurnID),
"kind": strings.TrimSpace(value.Kind), "status": strings.TrimSpace(value.Status),
})
}
return result
}
func messageCompactValue(message agentservice.SessionMessage, imageLocalPath imageLocalPathResolver) map[string]any {
value := map[string]any{
"role": strings.TrimSpace(message.Role),
"kind": strings.TrimSpace(message.Kind),
"status": strings.TrimSpace(message.Status),
}
if messageID := strings.TrimSpace(message.MessageID); messageID != "" {
value["messageId"] = messageID
}
if turnID := strings.TrimSpace(message.TurnID); turnID != "" {
value["turnId"] = turnID
}
if message.Version > 0 {
value["version"] = message.Version
}
if message.OccurredAtUnixMS > 0 {
value["occurredAtUnixMs"] = message.OccurredAtUnixMS
}
if text := messageCompactText(message.Payload, message.Kind); text != "" {
value["text"] = text
}
if images := messageCompactImages(message, imageLocalPath); len(images) > 0 {
value["images"] = images
}
return value
}
func messageCompactValues(messages []agentservice.SessionMessage, imageLocalPath imageLocalPathResolver) []any {
values := make([]any, 0, len(messages))
for _, message := range messages {
values = append(values, messageCompactValue(message, imageLocalPath))
}
return values
}
func messageCompactText(payload map[string]any, kind string) string {
if len(payload) == 0 {
return ""
}
if text, ok := payload["text"].(string); ok {
if trimmed := strings.TrimSpace(text); trimmed != "" {
return trimmed
}
}
if strings.TrimSpace(kind) == "tool_call" {
for _, key := range []string{"output", "error"} {
if text := messageCompactToolBodyText(payload[key]); text != "" {
return text
}
}
if name := strings.TrimSpace(fmt.Sprint(payload["name"])); name != "" && name != "<nil>" {
return strings.TrimSpace(kind + ": " + name)
}
if status := strings.TrimSpace(fmt.Sprint(payload["status"])); status != "" && status != "<nil>" {
return strings.TrimSpace(kind + ": " + status)
}
return ""
}
if content, ok := payload["content"].(string); ok {
if trimmed := strings.TrimSpace(content); trimmed != "" {
return trimmed
}
}
if blocks, ok := payload["content"].([]any); ok {
if text := compactTextFromContentBlocks(blocks); text != "" {
return text
}
}
if name := strings.TrimSpace(fmt.Sprint(payload["name"])); name != "" && name != "<nil>" {
return strings.TrimSpace(kind + ": " + name)
}
if status := strings.TrimSpace(fmt.Sprint(payload["status"])); status != "" && status != "<nil>" {
return strings.TrimSpace(kind + ": " + status)
}
return ""
}
func messageCompactToolBodyText(value any) string {
body, ok := value.(map[string]any)
if !ok {
return ""
}
for _, key := range []string{"text", "message", "summary", "stdout", "stderr"} {
if text, ok := body[key].(string); ok {
if trimmed := strings.TrimSpace(text); trimmed != "" {
return trimmed
}
}
}
if matches, ok := body["matches"].([]any); ok {
values := make([]string, 0, len(matches))
for _, match := range matches {
if text, ok := match.(string); ok {
if trimmed := strings.TrimSpace(text); trimmed != "" {
values = append(values, trimmed)
}
}
}
return strings.Join(values, ", ")
}
return ""
}
func compactTextFromContentBlocks(blocks []any) string {
parts := make([]string, 0, len(blocks))
for _, block := range blocks {
item, ok := block.(map[string]any)
if !ok {
continue
}
if text, ok := item["text"].(string); ok {
if trimmed := strings.TrimSpace(text); trimmed != "" {
parts = append(parts, trimmed)
}
}
}
return strings.Join(parts, "\n")
}
func messageCompactImages(message agentservice.SessionMessage, imageLocalPath imageLocalPathResolver) []any {
images := messageCompactToolImages(message)
if strings.TrimSpace(message.Kind) == "tool_call" {
return images
}
blocks, ok := message.Payload["content"].([]any)
if !ok || len(blocks) == 0 {
return images
}
for _, block := range blocks {
item, ok := block.(map[string]any)
if !ok || strings.TrimSpace(fmt.Sprint(item["type"])) != "image" {
continue
}
attachmentID := strings.TrimSpace(fmt.Sprint(item["attachmentId"]))
mimeType := strings.TrimSpace(fmt.Sprint(item["mimeType"]))
image := map[string]any{}
if attachmentID != "" && attachmentID != "<nil>" {
image["attachmentId"] = attachmentID
}
if mimeType != "" && mimeType != "<nil>" {
image["mimeType"] = mimeType
}
if name := strings.TrimSpace(fmt.Sprint(item["name"])); name != "" && name != "<nil>" {
image["name"] = name
}
if localPath := compactImageLocalPath(message.AgentSessionID, attachmentID, mimeType, item, imageLocalPath); localPath != "" {
image["localPath"] = localPath
if _, ok := image["name"]; !ok {
image["name"] = filepath.Base(localPath)
}
}
if len(image) > 0 {
images = append(images, image)
}
}
return images
}
func messageCompactToolImages(message agentservice.SessionMessage) []any {
if strings.TrimSpace(message.Kind) != "tool_call" {
return nil
}
output, ok := message.Payload["output"].(map[string]any)
if !ok {
return nil
}
paths := make([]string, 0)
if values, ok := output["savedPaths"].([]any); ok {
for _, value := range values {
if path, ok := value.(string); ok {
if trimmed := strings.TrimSpace(path); trimmed != "" {
paths = appendUniqueCompactString(paths, trimmed)
}
}
}
}
if path, ok := output["savedPath"].(string); ok {
if trimmed := strings.TrimSpace(path); trimmed != "" {
paths = appendUniqueCompactString(paths, trimmed)
}
}
mimeType, _ := output["imageMimeType"].(string)
images := make([]any, 0, len(paths))
for _, path := range paths {
image := map[string]any{
"localPath": path,
"name": filepath.Base(path),
}
if trimmed := strings.TrimSpace(mimeType); trimmed != "" {
image["mimeType"] = trimmed
}
images = append(images, image)
}
return images
}
func appendUniqueCompactString(values []string, value string) []string {
for _, current := range values {
if current == value {
return values
}
}
return append(values, value)
}
func compactImageLocalPath(
agentSessionID string,
attachmentID string,
mimeType string,
block map[string]any,
imageLocalPath imageLocalPathResolver,
) string {
if imageLocalPath != nil && attachmentID != "" && attachmentID != "<nil>" {
if path, ok := imageLocalPath(agentSessionID, attachmentID, mimeType); ok {
if trimmed := strings.TrimSpace(path); trimmed != "" {
return trimmed
}
}
}
for _, key := range []string{"localPath", "path"} {
if path := strings.TrimSpace(fmt.Sprint(block[key])); path != "" && path != "<nil>" {
return path
}
}
return ""
}