fix(api): propagate note to Gemini virtual auths and align priority parsing

- Read note from Attributes (consistent with priority) in buildAuthFileEntry,
  fixing missing note on Gemini multi-project virtual auth cards.
- Propagate note from primary to virtual auths in SynthesizeGeminiVirtualAuths,
  mirroring existing priority propagation.
- Sync note/priority writes to both Metadata and Attributes in PatchAuthFileFields,
  with refactored nil-check to reduce duplication (review feedback).
- Validate priority type in fallback disk-read path instead of coercing all values
  to 0 via gjson.Int(), aligning with the auth-manager code path.
- Add regression tests for note synthesis, virtual-auth note propagation, and
  end-to-end multi-project Gemini note inheritance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
RGBadmin
2026-03-15 16:47:01 +08:00
parent 5b6342e6ac
commit f90120f846
3 changed files with 237 additions and 23 deletions

View File

@@ -333,10 +333,19 @@ func (h *Handler) listAuthFilesFromDisk(c *gin.Context) {
fileData["type"] = typeValue
fileData["email"] = emailValue
if pv := gjson.GetBytes(data, "priority"); pv.Exists() {
fileData["priority"] = int(pv.Int())
switch pv.Type {
case gjson.Number:
fileData["priority"] = int(pv.Int())
case gjson.String:
if parsed, errAtoi := strconv.Atoi(strings.TrimSpace(pv.String())); errAtoi == nil {
fileData["priority"] = parsed
}
}
}
if nv := gjson.GetBytes(data, "note"); nv.Exists() && strings.TrimSpace(nv.String()) != "" {
fileData["note"] = strings.TrimSpace(nv.String())
if nv := gjson.GetBytes(data, "note"); nv.Exists() {
if trimmed := strings.TrimSpace(nv.String()); trimmed != "" {
fileData["note"] = trimmed
}
}
}
@@ -427,11 +436,9 @@ func (h *Handler) buildAuthFileEntry(auth *coreauth.Auth) gin.H {
entry["priority"] = parsed
}
}
// Expose note from Metadata.
if note, ok := auth.Metadata["note"].(string); ok {
if trimmed := strings.TrimSpace(note); trimmed != "" {
entry["note"] = trimmed
}
// Expose note from Attributes (set by synthesizer from JSON "note" field).
if note := strings.TrimSpace(authAttribute(auth, "note")); note != "" {
entry["note"] = note
}
return entry
}
@@ -912,26 +919,32 @@ func (h *Handler) PatchAuthFileFields(c *gin.Context) {
targetAuth.ProxyURL = *req.ProxyURL
changed = true
}
if req.Priority != nil {
if req.Priority != nil || req.Note != nil {
if targetAuth.Metadata == nil {
targetAuth.Metadata = make(map[string]any)
}
if *req.Priority == 0 {
delete(targetAuth.Metadata, "priority")
} else {
targetAuth.Metadata["priority"] = *req.Priority
if targetAuth.Attributes == nil {
targetAuth.Attributes = make(map[string]string)
}
changed = true
}
if req.Note != nil {
if targetAuth.Metadata == nil {
targetAuth.Metadata = make(map[string]any)
if req.Priority != nil {
if *req.Priority == 0 {
delete(targetAuth.Metadata, "priority")
delete(targetAuth.Attributes, "priority")
} else {
targetAuth.Metadata["priority"] = *req.Priority
targetAuth.Attributes["priority"] = strconv.Itoa(*req.Priority)
}
}
trimmedNote := strings.TrimSpace(*req.Note)
if trimmedNote == "" {
delete(targetAuth.Metadata, "note")
} else {
targetAuth.Metadata["note"] = trimmedNote
if req.Note != nil {
trimmedNote := strings.TrimSpace(*req.Note)
if trimmedNote == "" {
delete(targetAuth.Metadata, "note")
delete(targetAuth.Attributes, "note")
} else {
targetAuth.Metadata["note"] = trimmedNote
targetAuth.Attributes["note"] = trimmedNote
}
}
changed = true
}