Files
oh-my-opencode/src/shared/agent-display-names.ts
T
YeonGyu-Kim f8c626086e fix(agent-names): use HTTP-header-safe display names and config keys for API calls (#3138)
Display names with parentheses like 'Atlas (Plan Executor)' cause HTTP
header validation errors in x-opencode-agent-name. This was blocking
Atlas/Prometheus from working via /start-work and auto-retry.

Changes:
- Display names: parens -> dashes ('Atlas - Plan Executor')
- Hooks (start-work, no-hephaestus-non-gpt, no-sisyphus-gpt): use
  config keys ('atlas', 'sisyphus', 'hephaestus') for agent API fields
- auto-retry: use config key instead of display name for promptAsync
- agent-override-protection: handle dash-suffix normalization
- Updated all test expectations to match new format

Closes #3138
2026-04-07 10:08:04 +09:00

126 lines
3.7 KiB
TypeScript

/**
* Agent config keys to display names mapping.
* Config keys are lowercase (e.g., "sisyphus", "atlas").
* Display names include suffixes for UI/logs (e.g., "Sisyphus (Ultraworker)").
*/
export const AGENT_DISPLAY_NAMES: Record<string, string> = {
sisyphus: "Sisyphus - Ultraworker",
hephaestus: "Hephaestus - Deep Agent",
prometheus: "Prometheus - Plan Builder",
atlas: "Atlas - Plan Executor",
"sisyphus-junior": "Sisyphus-Junior",
metis: "Metis - Plan Consultant",
momus: "Momus - Plan Critic",
athena: "Athena - Council",
"athena-junior": "Athena-Junior - Council",
oracle: "oracle",
librarian: "librarian",
explore: "explore",
"multimodal-looker": "multimodal-looker",
"council-member": "council-member",
}
const AGENT_LIST_SORT_PREFIXES: Record<string, string> = {
sisyphus: "\u200B",
hephaestus: "\u200B\u200B",
prometheus: "\u200B\u200B\u200B",
atlas: "\u200B\u200B\u200B\u200B",
}
function stripAgentListSortPrefix(agentName: string): string {
return agentName.replace(/^\u200B+/, "")
}
/**
* Get display name for an agent config key.
* Uses case-insensitive lookup for backward compatibility.
* Returns original key if not found.
*/
export function getAgentDisplayName(configKey: string): string {
// Try exact match first
const exactMatch = AGENT_DISPLAY_NAMES[configKey]
if (exactMatch !== undefined) return exactMatch
// Fall back to case-insensitive search
const lowerKey = configKey.toLowerCase()
for (const [k, v] of Object.entries(AGENT_DISPLAY_NAMES)) {
if (k.toLowerCase() === lowerKey) return v
}
// Unknown agent: return original key
return configKey
}
export function getAgentListDisplayName(configKey: string): string {
const displayName = getAgentDisplayName(configKey)
const prefix = AGENT_LIST_SORT_PREFIXES[configKey.toLowerCase()]
return prefix ? `${prefix}${displayName}` : displayName
}
const REVERSE_DISPLAY_NAMES: Record<string, string> = Object.fromEntries(
Object.entries(AGENT_DISPLAY_NAMES).map(([key, displayName]) => [displayName.toLowerCase(), key]),
)
/**
* Resolve an agent name (display name or config key) to its lowercase config key.
* "Atlas (Plan Executor)" → "atlas", "atlas" → "atlas", "unknown" → "unknown"
*/
export function getAgentConfigKey(agentName: string): string {
const lower = stripAgentListSortPrefix(agentName).toLowerCase()
const reversed = REVERSE_DISPLAY_NAMES[lower]
if (reversed !== undefined) return reversed
if (AGENT_DISPLAY_NAMES[lower] !== undefined) return lower
return lower
}
/**
* Normalize an agent name for prompt APIs.
* - Known display names -> canonical display names
* - Known config keys (any case) -> canonical display names
* - Unknown/custom names -> preserved as-is (trimmed)
*/
export function normalizeAgentForPrompt(agentName: string | undefined): string | undefined {
if (typeof agentName !== "string") {
return undefined
}
const trimmed = stripAgentListSortPrefix(agentName.trim())
if (!trimmed) {
return undefined
}
const lower = trimmed.toLowerCase()
const reversed = REVERSE_DISPLAY_NAMES[lower]
if (reversed !== undefined) {
return AGENT_DISPLAY_NAMES[reversed] ?? trimmed
}
if (AGENT_DISPLAY_NAMES[lower] !== undefined) {
return AGENT_DISPLAY_NAMES[lower]
}
return trimmed
}
export function normalizeAgentForPromptKey(agentName: string | undefined): string | undefined {
if (typeof agentName !== "string") {
return undefined
}
const trimmed = stripAgentListSortPrefix(agentName.trim())
if (!trimmed) {
return undefined
}
const lower = trimmed.toLowerCase()
const reversed = REVERSE_DISPLAY_NAMES[lower]
if (reversed !== undefined) {
return reversed
}
if (AGENT_DISPLAY_NAMES[lower] !== undefined) {
return lower
}
return trimmed
}