mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
refactor(avatars): drop orphaned app-avatar: symbolic-ref machinery (#1235)
Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
co-authored by
npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w
parent
1d01d88383
commit
9a19c2d197
@@ -371,11 +371,11 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_persona_files_accepts_plain_md_with_avatar_ref() {
|
||||
fn parse_persona_files_carries_opaque_avatar_ref() {
|
||||
let md = br#"---
|
||||
name: fizz
|
||||
display_name: Fizz
|
||||
avatar: app-avatar:persona-12
|
||||
avatar: https://example.com/avatars/fizz.png
|
||||
runtime: goose
|
||||
---
|
||||
You are Fizz.
|
||||
@@ -388,7 +388,7 @@ You are Fizz.
|
||||
assert_eq!(result.personas[0].display_name, "Fizz");
|
||||
assert_eq!(
|
||||
result.personas[0].avatar_ref.as_deref(),
|
||||
Some("app-avatar:persona-12")
|
||||
Some("https://example.com/avatars/fizz.png")
|
||||
);
|
||||
assert_eq!(result.personas[0].source_file, "fizz.md");
|
||||
}
|
||||
|
||||
@@ -404,12 +404,12 @@ fn parse_json_malformed() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_md_persona_preserves_app_avatar_ref() {
|
||||
fn parse_md_persona_preserves_avatar_ref() {
|
||||
let md = br#"---
|
||||
name: goosey
|
||||
display_name: Goosey
|
||||
description: Goose internal agent.
|
||||
avatar: app-avatar:persona-19
|
||||
avatar: https://example.com/avatars/goosey.png
|
||||
model: anthropic:claude-sonnet-4
|
||||
runtime: goose
|
||||
---
|
||||
@@ -418,7 +418,10 @@ You are Goosey.
|
||||
let result = parse_md_persona(md).unwrap();
|
||||
assert_eq!(result.display_name, "Goosey");
|
||||
assert_eq!(result.avatar_data_url, None);
|
||||
assert_eq!(result.avatar_ref.as_deref(), Some("app-avatar:persona-19"));
|
||||
assert_eq!(
|
||||
result.avatar_ref.as_deref(),
|
||||
Some("https://example.com/avatars/goosey.png")
|
||||
);
|
||||
assert_eq!(result.model.as_deref(), Some("claude-sonnet-4"));
|
||||
assert_eq!(result.provider.as_deref(), Some("anthropic"));
|
||||
assert_eq!(result.runtime.as_deref(), Some("goose"));
|
||||
@@ -446,7 +449,7 @@ fn parse_md_persona_accepts_goose_internal_frontmatter() {
|
||||
let md = br#"---
|
||||
name: block.md
|
||||
description: Opinionated guide to Block's intelligence operating model.
|
||||
avatar: app-avatar:persona-19
|
||||
avatar: https://avatars.example.com/block-md.png
|
||||
metadata:
|
||||
gooseInternalBundled: true
|
||||
---
|
||||
@@ -454,7 +457,10 @@ You are block.md.
|
||||
"#;
|
||||
let result = parse_md_persona(md).unwrap();
|
||||
assert_eq!(result.display_name, "block.md");
|
||||
assert_eq!(result.avatar_ref.as_deref(), Some("app-avatar:persona-19"));
|
||||
assert_eq!(
|
||||
result.avatar_ref.as_deref(),
|
||||
Some("https://avatars.example.com/block-md.png")
|
||||
);
|
||||
assert_eq!(result.system_prompt, "You are block.md.\n");
|
||||
}
|
||||
|
||||
@@ -495,7 +501,7 @@ fn parse_zip_with_plain_md_persona_preserves_avatar_ref() {
|
||||
name: fizz
|
||||
display_name: Fizz
|
||||
description: Engineering agent.
|
||||
avatar: app-avatar:persona-12
|
||||
avatar: https://avatars.example.com/fizz.png
|
||||
runtime: goose
|
||||
model: anthropic:claude-sonnet-4
|
||||
---
|
||||
@@ -508,7 +514,7 @@ You are Fizz.
|
||||
assert_eq!(result.personas[0].display_name, "Fizz");
|
||||
assert_eq!(
|
||||
result.personas[0].avatar_ref.as_deref(),
|
||||
Some("app-avatar:persona-12")
|
||||
Some("https://avatars.example.com/fizz.png")
|
||||
);
|
||||
assert_eq!(result.personas[0].source_file, "fizz.md");
|
||||
}
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
import type { ParsePersonaFilesResult } from "@/shared/api/tauriPersonas";
|
||||
import { resolveImportedPersonaAvatarUrl } from "@/shared/avatars/gooseAppAvatarRefs";
|
||||
import type {
|
||||
AgentPersona,
|
||||
CreatePersonaInput,
|
||||
UpdatePersonaInput,
|
||||
} from "@/shared/api/types";
|
||||
|
||||
function isPersistableAvatarUrl(value: string): boolean {
|
||||
return /^(?:https?:|data:image\/|blob:)/i.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks an avatar URL to seed the import dialog with. Only persistable
|
||||
* http(s)/data/blob URLs survive; anything else (e.g. a bare filename or an
|
||||
* unrenderable symbolic ref) is dropped so the dialog starts blank rather than
|
||||
* carrying a value the app can't display.
|
||||
*/
|
||||
function resolveImportedPersonaAvatarUrl({
|
||||
avatarDataUrl,
|
||||
avatarRef,
|
||||
}: {
|
||||
avatarDataUrl?: string | null;
|
||||
avatarRef?: string | null;
|
||||
}): string | null {
|
||||
for (const candidate of [avatarRef, avatarDataUrl]) {
|
||||
const trimmed = candidate?.trim();
|
||||
if (trimmed && isPersistableAvatarUrl(trimmed)) {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export type PersonaDialogState = {
|
||||
description: string;
|
||||
initialValues: CreatePersonaInput | UpdatePersonaInput;
|
||||
|
||||
@@ -22,7 +22,6 @@ import type {
|
||||
UsersBatchResponse,
|
||||
} from "@/shared/api/types";
|
||||
import { useIdentityQuery } from "@/shared/api/hooks";
|
||||
import { isGooseAppAvatarRef } from "@/shared/avatars/gooseAppAvatarRefs";
|
||||
import { getAvatarSnapshotUrl } from "@/shared/lib/animatedAvatar";
|
||||
import { rewriteRelayUrl } from "@/shared/lib/mediaUrl";
|
||||
import {
|
||||
@@ -56,9 +55,7 @@ async function persistSelfProfile(
|
||||
profile: Profile,
|
||||
): Promise<void> {
|
||||
const existing = readSelfProfileCache(relayUrl, pubkey);
|
||||
const avatarSnapshotUrl = isGooseAppAvatarRef(profile.avatarUrl)
|
||||
? null
|
||||
: getAvatarSnapshotUrl(profile.avatarUrl);
|
||||
const avatarSnapshotUrl = getAvatarSnapshotUrl(profile.avatarUrl);
|
||||
const fetched =
|
||||
shouldFetchAvatar(profile.avatarUrl, existing) && avatarSnapshotUrl !== null
|
||||
? await fetchAvatarDataUrl(rewriteRelayUrl(avatarSnapshotUrl))
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
resolveImportedPersonaAvatarUrl,
|
||||
toGooseAppAvatarRef,
|
||||
} from "./gooseAppAvatarRefs.ts";
|
||||
|
||||
test("toGooseAppAvatarRef canonicalizes app-avatar refs", () => {
|
||||
assert.equal(
|
||||
toGooseAppAvatarRef("app-avatar:persona-19"),
|
||||
"app-avatar:persona-19",
|
||||
);
|
||||
});
|
||||
|
||||
test("toGooseAppAvatarRef ignores filesystem-looking paths", () => {
|
||||
assert.equal(toGooseAppAvatarRef("./avatars/persona_2.png"), null);
|
||||
});
|
||||
|
||||
test("resolveImportedPersonaAvatarUrl prefers app-avatar refs over data URLs", () => {
|
||||
assert.equal(
|
||||
resolveImportedPersonaAvatarUrl({
|
||||
avatarDataUrl: "https://example.com/avatar.png",
|
||||
avatarRef: "app-avatar:persona-1",
|
||||
}),
|
||||
"app-avatar:persona-1",
|
||||
);
|
||||
});
|
||||
|
||||
test("resolveImportedPersonaAvatarUrl preserves ordinary image URLs", () => {
|
||||
assert.equal(
|
||||
resolveImportedPersonaAvatarUrl({
|
||||
avatarDataUrl: "https://example.com/avatar.png",
|
||||
avatarRef: null,
|
||||
}),
|
||||
"https://example.com/avatar.png",
|
||||
);
|
||||
});
|
||||
|
||||
test("resolveImportedPersonaAvatarUrl does not rewrite remote image URLs", () => {
|
||||
assert.equal(
|
||||
resolveImportedPersonaAvatarUrl({
|
||||
avatarDataUrl: "https://cdn.example.com/avatars/persona_2.png",
|
||||
avatarRef: null,
|
||||
}),
|
||||
"https://cdn.example.com/avatars/persona_2.png",
|
||||
);
|
||||
});
|
||||
|
||||
test("resolveImportedPersonaAvatarUrl preserves URL avatar refs", () => {
|
||||
assert.equal(
|
||||
resolveImportedPersonaAvatarUrl({
|
||||
avatarDataUrl: null,
|
||||
avatarRef: " https://example.com/persona-avatar.png ",
|
||||
}),
|
||||
"https://example.com/persona-avatar.png",
|
||||
);
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
export const GOOSE_APP_AVATAR_REF_PREFIX = "app-avatar:" as const;
|
||||
|
||||
const APP_AVATAR_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
||||
|
||||
function cleanAvatarCandidate(value: string): string {
|
||||
const basename = value
|
||||
.trim()
|
||||
.split(/[?#]/)[0]
|
||||
?.split(/[\\/]/)
|
||||
.pop()
|
||||
?.replace(/\.(?:apng|gif|heic|heif|jpeg|jpg|mp4|png|webm)$/i, "");
|
||||
return (basename ?? "").toLowerCase().replace(/_/g, "-").trim();
|
||||
}
|
||||
|
||||
export function parseGooseAppAvatarId(
|
||||
value: string | null | undefined,
|
||||
): string | null {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const refIndex = trimmed.indexOf(GOOSE_APP_AVATAR_REF_PREFIX);
|
||||
if (refIndex >= 0) {
|
||||
const rawId = trimmed.slice(refIndex + GOOSE_APP_AVATAR_REF_PREFIX.length);
|
||||
const id = cleanAvatarCandidate(rawId);
|
||||
return APP_AVATAR_ID_PATTERN.test(id) ? id : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function toGooseAppAvatarRef(
|
||||
value: string | null | undefined,
|
||||
): string | null {
|
||||
const id = parseGooseAppAvatarId(value);
|
||||
return id ? `${GOOSE_APP_AVATAR_REF_PREFIX}${id}` : null;
|
||||
}
|
||||
|
||||
export function isGooseAppAvatarRef(value: string | null | undefined): boolean {
|
||||
return toGooseAppAvatarRef(value) !== null;
|
||||
}
|
||||
|
||||
function isPersistableAvatarUrl(value: string): boolean {
|
||||
return /^(?:https?:|data:image\/|blob:)/i.test(value);
|
||||
}
|
||||
|
||||
export function resolveImportedPersonaAvatarUrl({
|
||||
avatarDataUrl,
|
||||
avatarRef,
|
||||
}: {
|
||||
avatarDataUrl?: string | null;
|
||||
avatarRef?: string | null;
|
||||
}): string | null {
|
||||
const gooseRef =
|
||||
toGooseAppAvatarRef(avatarRef) ?? toGooseAppAvatarRef(avatarDataUrl);
|
||||
if (gooseRef) {
|
||||
return gooseRef;
|
||||
}
|
||||
|
||||
for (const candidate of [avatarRef, avatarDataUrl]) {
|
||||
const trimmedAvatarUrl = candidate?.trim();
|
||||
if (trimmedAvatarUrl && isPersistableAvatarUrl(trimmedAvatarUrl)) {
|
||||
return trimmedAvatarUrl;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user