Files
ttstd-infinite-canvas/backend/internal/app/creation_agent_references.go

171 lines
4.7 KiB
Go

package app
import (
"encoding/json"
"strings"
)
func validateAgentResourcePlaceholders(input canvasGenerationInput) error {
_, err := resolveAgentResourcePlaceholders(input, false)
return err
}
// Only hydrated references can replace protocol placeholders. The returned request
// is an in-memory copy and must never be written back to Task.InputJSON.
func resolveAgentResourcePlaceholders(input canvasGenerationInput, hydrate bool) (canvasGenerationInput, error) {
if input.AgentRequests == nil {
return input, nil
}
if hydrate {
encoded, err := json.Marshal(input.AgentRequests)
if err != nil {
return input, err
}
if !strings.Contains(string(encoded), `"resource:`) {
return input, nil
}
}
references := map[string]string{}
for _, media := range input.ReferenceImages {
if !strings.HasPrefix(media.StorageKey, "resource:") {
return input, BadAuthRequest("规划图片必须引用当前账号资源")
}
value := media.StorageKey
if hydrate {
value = firstNonEmpty(media.DataURL, media.URL)
if value == "" {
return input, BadAuthRequest("规划图片尚未读取成功")
}
}
references[media.StorageKey] = value
}
b, err := json.Marshal(input.AgentRequests)
if err != nil {
return input, err
}
var root any
if err = json.Unmarshal(b, &root); err != nil {
return input, err
}
var visit func(any) (any, error)
visit = func(value any) (any, error) {
switch typed := value.(type) {
case string:
if strings.HasPrefix(typed, "resource:") {
resolved, ok := references[typed]
if !ok {
return nil, BadAuthRequest("模型协议引用了未获准的图片")
}
return resolved, nil
}
return typed, nil
case []any:
for i, child := range typed {
next, e := visit(child)
if e != nil {
return nil, e
}
typed[i] = next
}
return typed, nil
case map[string]any:
if !hydrate {
var imageRef any
switch typed["type"] {
case "image_url":
imageRef = typed["image_url"]
case "input_image":
imageRef = typed["image_url"]
case "image":
source, _ := typed["source"].(map[string]any)
if source["type"] != "url" {
return nil, BadAuthRequest("规划图片必须使用资源占位")
}
imageRef = source["url"]
}
if object, ok := imageRef.(map[string]any); ok {
imageRef = object["url"]
}
if imageRef != nil {
ref, ok := imageRef.(string)
if !ok || references[ref] == "" {
return nil, BadAuthRequest("规划图片不在获准资源清单")
}
}
if raw, ok := typed["fileData"].(map[string]any); ok {
ref, _ := raw["fileUri"].(string)
if references[ref] == "" {
return nil, BadAuthRequest("规划图片不在获准资源清单")
}
}
if typed["inlineData"] != nil {
return nil, BadAuthRequest("规划图片不能持久化内嵌数据")
}
}
// Claude URL source and Gemini fileData need protocol-specific base64 envelopes.
if hydrate && typed["type"] == "url" {
if ref, ok := typed["url"].(string); ok && strings.HasPrefix(ref, "resource:") {
data, ok := references[ref]
if !ok {
return nil, BadAuthRequest("模型图片未获准")
}
mime, payload, ok := splitCreationDataURL(data)
if !ok {
return nil, BadAuthRequest("模型图片需要可读取的图片数据")
}
return map[string]any{"type": "base64", "media_type": mime, "data": payload}, nil
}
}
if hydrate {
if raw, ok := typed["fileData"].(map[string]any); ok {
if ref, ok := raw["fileUri"].(string); ok && strings.HasPrefix(ref, "resource:") {
data, ok := references[ref]
if !ok {
return nil, BadAuthRequest("模型图片未获准")
}
mime, payload, ok := splitCreationDataURL(data)
if !ok {
return nil, BadAuthRequest("模型图片需要可读取的图片数据")
}
delete(typed, "fileData")
typed["inlineData"] = map[string]any{"mimeType": mime, "data": payload}
}
}
}
for key, child := range typed {
next, e := visit(child)
if e != nil {
return nil, e
}
typed[key] = next
}
return typed, nil
}
return value, nil
}
root, err = visit(root)
if err != nil {
return input, err
}
b, err = json.Marshal(root)
if err != nil {
return input, err
}
var requests agentToolRequests
if err = json.Unmarshal(b, &requests); err != nil {
return input, err
}
input.AgentRequests = &requests
return input, nil
}
func splitCreationDataURL(value string) (string, string, bool) {
if !strings.HasPrefix(value, "data:") {
return "", "", false
}
parts := strings.SplitN(strings.TrimPrefix(value, "data:"), ";base64,", 2)
if len(parts) != 2 || !strings.HasPrefix(parts[0], "image/") {
return "", "", false
}
return parts[0], parts[1], true
}