package acp import ( "encoding/json" "errors" "net/url" "path/filepath" "strings" ) // translatePrompt validates the complete ACP prompt and returns daemon content blocks. func translatePrompt(raw []json.RawMessage) ([]json.RawMessage, error) { if raw == nil { return nil, errors.New("prompt must be an array") } blocks := make([]json.RawMessage, 0, len(raw)) for _, block := range raw { var fields map[string]json.RawMessage if json.Unmarshal(block, &fields) != nil || fields == nil { return nil, errors.New("invalid content block") } var kind string _ = json.Unmarshal(fields["type"], &kind) required := []string{"text"} switch kind { case "text": case "resource_link": required = []string{"uri", "name"} default: return nil, errors.New("unsupported prompt block") } for _, key := range required { var value string if json.Unmarshal(fields[key], &value) != nil || string(fields[key]) == "null" { return nil, errors.New("missing block " + key) } } for _, key := range []string{"_meta", "annotations"} { if value, exists := fields[key]; exists && string(value) != "null" { var object map[string]json.RawMessage if json.Unmarshal(value, &object) != nil || object == nil { return nil, errors.New("invalid block " + key) } } } if kind == "resource_link" { for _, key := range []string{"mimeType", "description", "title"} { if value, exists := fields[key]; exists && string(value) != "null" { var text string if json.Unmarshal(value, &text) != nil { return nil, errors.New("invalid block " + key) } } } if value, exists := fields["size"]; exists && string(value) != "null" { var size int64 if json.Unmarshal(value, &size) != nil || size < 0 { return nil, errors.New("invalid resource size") } } } if kind == "resource_link" { var uri string _ = json.Unmarshal(fields["uri"], &uri) _, err := resourcePath(uri) if err != nil { return nil, err } } for old, newName := range map[string]string{"_meta": "meta", "mimeType": "mime_type"} { if value, ok := fields[old]; ok { fields[newName] = value delete(fields, old) } } encoded, err := json.Marshal(fields) if err != nil { return nil, err } blocks = append(blocks, encoded) } return blocks, nil } func resourcePath(uri string) (*string, error) { parsed, err := url.Parse(uri) if err != nil || parsed.Scheme == "" || strings.ContainsRune(uri, 0) { return nil, errors.New("invalid resource URI") } if !strings.EqualFold(parsed.Scheme, "file") { return nil, nil } if parsed.Opaque != "" || parsed.User != nil || (parsed.Host != "" && parsed.Host != "localhost") || parsed.RawQuery != "" || parsed.ForceQuery || strings.Contains(uri, "#") || strings.Contains(uri, "?") || !filepath.IsAbs(parsed.Path) || strings.ContainsRune(parsed.Path, 0) { return nil, errors.New("file URI must identify an absolute local path") } return &parsed.Path, nil }