chore: drop external project name references from code comments

This commit is contained in:
matevip 2026-05-20 08:09:43 +08:00
parent a828de1306
commit db16ff02a5
22 changed files with 42 additions and 67 deletions

View File

@ -26,7 +26,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
* 会话历史上下文窗口管理器Hermes 风格升级版 * 会话历史上下文窗口管理器四阶段压缩升级版
* <p> * <p>
* 四阶段压缩策略 * 四阶段压缩策略
* <ol> * <ol>
@ -274,7 +274,7 @@ public class ConversationWindowManager {
} }
int historyBudget = effectiveMax - reservedTokens; int historyBudget = effectiveMax - reservedTokens;
// 尾部保护 token 预算阈值的 20% Hermes 一致 // 尾部保护 token 预算阈值的 20%
int tailTokenBudget = (int) (triggerThreshold * 0.20); int tailTokenBudget = (int) (triggerThreshold * 0.20);
return compactMessages(messages, historyBudget, tailTokenBudget, chatModel, return compactMessages(messages, historyBudget, tailTokenBudget, chatModel,

View File

@ -772,7 +772,7 @@ public class NodeStreamingChatHelper {
boolean broadcast, int attempt) { boolean broadcast, int attempt) {
if (attempt > 0) { if (attempt > 0) {
long delay = Math.min(BACKOFF_BASE_MS * (1L << (attempt - 1)), BACKOFF_CAP_MS); long delay = Math.min(BACKOFF_BASE_MS * (1L << (attempt - 1)), BACKOFF_CAP_MS);
// 加入 jitter 防止雷群效应Hermes 风格 // 加入 jitter 防止雷群效应
delay += ThreadLocalRandom.current().nextLong(0, Math.max(1, delay / 2)); delay += ThreadLocalRandom.current().nextLong(0, Math.max(1, delay / 2));
delay = Math.min(delay, BACKOFF_CAP_MS); delay = Math.min(delay, BACKOFF_CAP_MS);
log.warn("[{}] Retry attempt {}/{} after {}ms for conversation {}", log.warn("[{}] Retry attempt {}/{} after {}ms for conversation {}",
@ -813,7 +813,7 @@ public class NodeStreamingChatHelper {
AtomicInteger cacheWriteTokens = new AtomicInteger(0); AtomicInteger cacheWriteTokens = new AtomicInteger(0);
// thinking-only soft cap 触发后设为 true外层轮询线程据此 dispose 订阅 // thinking-only soft cap 触发后设为 true外层轮询线程据此 dispose 订阅
// 注意内容流的字符级 / 句子级重复检测已整体移除参考 Hermes 思路 // 注意内容流的字符级 / 句子级重复检测已整体移除设计取舍
// agent 不替模型审核输出退化 max_tokens + max_iterations 兜底 // agent 不替模型审核输出退化 max_tokens + max_iterations 兜底
// 仅保留 thinking-only 这条体积兜底处理 volcengine-plan provider // 仅保留 thinking-only 这条体积兜底处理 volcengine-plan provider
// thinking 通道堆字符不出 content 的死循环生产 trace c1eefa45 // thinking 通道堆字符不出 content 的死循环生产 trace c1eefa45

View File

@ -72,7 +72,7 @@ public class ObservationNode implements NodeAction {
// 合并为单条观察记录 // 合并为单条观察记录
String combinedObservation = String.join("\n---\n", processedObservations); String combinedObservation = String.join("\n---\n", processedObservations);
// Budget Pressure WarningHermes 风格接近上限时注入警告到工具结果中 // Budget Pressure Warning接近上限时注入警告到工具结果中
// LLM 下一轮 reasoning 时能看到从而主动收束而非被硬性截断 // LLM 下一轮 reasoning 时能看到从而主动收束而非被硬性截断
if (maxIterations > 0) { if (maxIterations > 0) {
int progress = (int) ((double) nextIteration / maxIterations * 100); int progress = (int) ((double) nextIteration / maxIterations * 100);

View File

@ -83,7 +83,7 @@ public class ReasoningNode implements NodeAction {
private static final int DASHSCOPE_MAX_OUTPUT_TOKENS = 8192; private static final int DASHSCOPE_MAX_OUTPUT_TOKENS = 8192;
/** /**
* Hermes-agent style enforcement clause appended to every ReasoningNode * Tool-use enforcement clause appended to every ReasoningNode
* system prompt. Treats narration ("I will now …") as a protocol violation * system prompt. Treats narration ("I will now …") as a protocol violation
* to prevent the recurring failure mode where a model says it will call a * to prevent the recurring failure mode where a model says it will call a
* tool but emits the description as final_answer text instead. * tool but emits the description as final_answer text instead.

View File

@ -24,7 +24,7 @@ public class ConversationWindowProperties {
/** 摘要自身最大 token 数(仅作 LLM maxToken 参数上限,实际预算由动态计算) */ /** 摘要自身最大 token 数(仅作 LLM maxToken 参数上限,实际预算由动态计算) */
private int summaryMaxTokens = 800; private int summaryMaxTokens = 800;
// ==================== 动态压缩配置Hermes 风格 ==================== // ==================== 动态压缩配置 ====================
/** 尾部保护的最小消息数(即使 token 预算用完也至少保留这么多) */ /** 尾部保护的最小消息数(即使 token 预算用完也至少保留这么多) */
private int protectLastMinMessages = 10; private int protectLastMinMessages = 10;

View File

@ -11,8 +11,6 @@ import java.util.List;
* *
* <p>OAuth requests get extra beta headers + a User-Agent that masquerades as * <p>OAuth requests get extra beta headers + a User-Agent that masquerades as
* Claude Code. Without these, Anthropic's infrastructure intermittently 500s. * Claude Code. Without these, Anthropic's infrastructure intermittently 500s.
* Reference: hermes-agent {@code anthropic_adapter} lines 226-238 + the
* dispatch in {@code build_anthropic_client} (line 423-433).
* *
* <h2>Header reference</h2> * <h2>Header reference</h2>
* *
@ -48,8 +46,7 @@ public class ClaudeCodeApiHeaders {
/** /**
* Comma-joined beta header list to send in {@code anthropic-beta}. * Comma-joined beta header list to send in {@code anthropic-beta}.
* <p>Order matches hermes-agent {@code anthropic_adapter} line 427: * <p>Order is common betas first, OAuth-only betas appended.
* {@code common_betas + _OAUTH_ONLY_BETAS} common betas first, OAuth betas appended.
*/ */
public String allBetas() { public String allBetas() {
return String.join(",", return String.join(",",
@ -61,14 +58,12 @@ public class ClaudeCodeApiHeaders {
* Format: {@code claude-cli/<version>} bare, no suffix. * Format: {@code claude-cli/<version>} bare, no suffix.
* *
* <p><b>History note:</b> we previously appended {@code (external, cli)} * <p><b>History note:</b> we previously appended {@code (external, cli)}
* after hermes-agent's pattern. That turned out to be wrong: Anthropic's * to the User-Agent. That turned out to be wrong: Anthropic's anti-abuse
* anti-abuse gate uses the suffix to fingerprint third-party clients * gate uses the suffix to fingerprint third-party clients and rate-limits
* (hermes / OpenCode / Cline) and rate-limits them harder. Real Claude * them harder. Real Claude Code (Electron + Node + official Anthropic JS
* Code (Electron + Node + official Anthropic JS SDK) emits the bare * SDK) emits the bare {@code claude-cli/<v>} form. Verified by reproducing
* {@code claude-cli/<v>} form, which is what openclaw * 429 with the suffix and {@code anthropic-ratelimit-*} headers absent
* ({@code anthropic-transport-stream.ts:30,572}) also uses. Verified by * the diagnostic signature of the anti-abuse path.
* reproducing 429 with the suffix and {@code anthropic-ratelimit-*}
* headers absent the diagnostic signature of the anti-abuse path.
*/ */
public String userAgent() { public String userAgent() {
return "claude-cli/" + versionDetector.get(); return "claude-cli/" + versionDetector.get();

View File

@ -36,9 +36,6 @@ import java.util.concurrent.TimeUnit;
* } * }
* } * }
* </pre> * </pre>
*
* <p>Reference: hermes-agent {@code anthropic_adapter._read_claude_code_credentials_from_keychain}
* (line 470) and {@code read_claude_code_credentials} (line 530).
*/ */
@Slf4j @Slf4j
@Component @Component
@ -48,8 +45,8 @@ public class ClaudeCodeCredentialsReader {
/** macOS Keychain service name written by Claude Code. */ /** macOS Keychain service name written by Claude Code. */
static final String KEYCHAIN_SERVICE_NAME = "Claude Code-credentials"; static final String KEYCHAIN_SERVICE_NAME = "Claude Code-credentials";
/** Hermes also queries {@code ~/.claude.json primaryApiKey} but that's a /** {@code ~/.claude.json primaryApiKey} is a managed key, not OAuth
* managed key, not OAuth intentionally not read here. */ * intentionally not read here. */
static final Path JSON_CREDENTIALS_PATH = static final Path JSON_CREDENTIALS_PATH =
Paths.get(System.getProperty("user.home"), ".claude", ".credentials.json"); Paths.get(System.getProperty("user.home"), ".claude", ".credentials.json");

View File

@ -45,10 +45,6 @@ import java.util.concurrent.TimeUnit;
* preserve {@code scopes} (Claude Code &gt;= 2.1.81 requires * preserve {@code scopes} (Claude Code &gt;= 2.1.81 requires
* {@code user:inference}) and any future fields we don't know about.</li> * {@code user:inference}) and any future fields we don't know about.</li>
* </ol> * </ol>
*
* <p>Reference: hermes-agent
* {@code anthropic_adapter._write_claude_code_credentials} (line 684-727)
* and {@code _write_claude_code_credentials_to_keychain} (line 730+).
*/ */
@Slf4j @Slf4j
@Component @Component

View File

@ -31,9 +31,6 @@ import java.util.Optional;
* <p>This service does NOT handle the OAuth login flow itself that is * <p>This service does NOT handle the OAuth login flow itself that is
* RFC-062 PR-4. Until then, MateClaw piggybacks on whatever credentials the * RFC-062 PR-4. Until then, MateClaw piggybacks on whatever credentials the
* user already has on disk from their installed Claude Code client. * user already has on disk from their installed Claude Code client.
*
* <p>Reference: hermes-agent {@code anthropic_adapter._get_claude_code_token}
* + {@code _ensure_claude_code_token_fresh} (lines 540-605).
*/ */
@Slf4j @Slf4j
@Service @Service

View File

@ -32,16 +32,13 @@ import java.util.Map;
* <p>The refresh request is a vanilla OAuth 2.0 refresh-token grant with the * <p>The refresh request is a vanilla OAuth 2.0 refresh-token grant with the
* public Claude Code {@code client_id}. We must spoof the Claude Code * public Claude Code {@code client_id}. We must spoof the Claude Code
* {@code User-Agent} Anthropic's edge filters drop unrecognised UAs. * {@code User-Agent} Anthropic's edge filters drop unrecognised UAs.
*
* <p>Reference: hermes-agent {@code anthropic_adapter._refresh_claude_code_token}
* (line 605+).
*/ */
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class ClaudeCodeTokenRefresher { public class ClaudeCodeTokenRefresher {
/** Public Claude Code OAuth client_id. Same value hermes-agent and OpenCode use. */ /** Public Claude Code OAuth client_id. */
static final String CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"; static final String CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
/** Endpoints tried in order until one succeeds. */ /** Endpoints tried in order until one succeeds. */

View File

@ -22,9 +22,6 @@ import java.util.regex.Pattern;
* where Claude Code isn't installed locally (the OAuth flow may still work via * where Claude Code isn't installed locally (the OAuth flow may still work via
* a manually-imported credentials file). * a manually-imported credentials file).
* *
* <p>Reference: hermes-agent {@code anthropic_adapter._detect_claude_code_version}
* (line 239) + {@code _CLAUDE_CODE_VERSION_FALLBACK} (line 235).
*
* <p>Result is cached for the JVM lifetime (Anthropic's UA validation tolerates * <p>Result is cached for the JVM lifetime (Anthropic's UA validation tolerates
* a stable version per process). Restart MateClaw to pick up a Claude Code * a stable version per process). Restart MateClaw to pick up a Claude Code
* upgrade. * upgrade.
@ -36,7 +33,7 @@ public class ClaudeCodeVersionDetector {
/** /**
* Static fallback version. Update this when bumping the floor at which * Static fallback version. Update this when bumping the floor at which
* Anthropic accepts spoofed Claude Code traffic (track Anthropic's * Anthropic accepts spoofed Claude Code traffic (track Anthropic's
* announcements + hermes-agent's same constant for cadence). * announcements for cadence).
*/ */
static final String FALLBACK_VERSION = "2.1.74"; static final String FALLBACK_VERSION = "2.1.74";

View File

@ -19,7 +19,7 @@ import java.util.Map;
* *
* <p>映射规则对应 RFC-014 Change 1 SystemAndTailCacheStrategy 默认行为 * <p>映射规则对应 RFC-014 Change 1 SystemAndTailCacheStrategy 默认行为
* <ul> * <ul>
* <li>{@code includeToolsBlock=true} {@link AnthropicCacheStrategy#CONVERSATION_HISTORY}system + tools + 最新一段对话最完整也最贴近 hermes-agent system_and_3</li> * <li>{@code includeToolsBlock=true} {@link AnthropicCacheStrategy#CONVERSATION_HISTORY}system + tools + 最新一段对话覆盖最完整</li>
* <li>{@code includeToolsBlock=false} {@link AnthropicCacheStrategy#SYSTEM_ONLY}</li> * <li>{@code includeToolsBlock=false} {@link AnthropicCacheStrategy#SYSTEM_ONLY}</li>
* <li>{@code ttl=extended-1h} SYSTEM/USER 两个消息类型映射为 {@link AnthropicCacheTtl#ONE_HOUR}</li> * <li>{@code ttl=extended-1h} SYSTEM/USER 两个消息类型映射为 {@link AnthropicCacheTtl#ONE_HOUR}</li>
* <li>{@code minPromptTokens} 转字符长度× 4粗粒度估算作为 SYSTEM 段的 min content length</li> * <li>{@code minPromptTokens} 转字符长度× 4粗粒度估算作为 SYSTEM 段的 min content length</li>

View File

@ -7,7 +7,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* 默认策略 system / tools / messages 尾部最多打 4 个缓存断点仿 hermes-agent {@code system_and_3} * 默认策略 system / tools / messages 尾部最多打 4 个缓存断点
* *
* <p>断点选择规则按优先级递减 * <p>断点选择规则按优先级递减
* <ol> * <ol>

View File

@ -103,7 +103,7 @@ public class BuiltinSkillSeedService implements ApplicationRunner {
// from the previous successful run AND the DB still holds the same // from the previous successful run AND the DB still holds the same
// number of builtin rows, nothing on disk changed since last seed // number of builtin rows, nothing on disk changed since last seed
// and we can skip the parse / select / update loop entirely. // and we can skip the parse / select / update loop entirely.
// Hermes-style trick: stat-only check, no content read. // The check is stat-only no content read.
Map<String, long[]> currentManifest = buildResourceManifest(resources); Map<String, long[]> currentManifest = buildResourceManifest(resources);
SeedSnapshot snapshot = loadSnapshot(); SeedSnapshot snapshot = loadSnapshot();
if (snapshot != null if (snapshot != null

View File

@ -643,7 +643,7 @@ public class SkillService {
return ""; return "";
} }
// --- 第零层Skill 自治引导RFC-023对标 hermes-agent prompt_builder.py:164-171 --- // --- 第零层Skill 自治引导 ---
StringBuilder catalog = new StringBuilder(); StringBuilder catalog = new StringBuilder();
catalog.append("\n\n## Skill Management\n\n"); catalog.append("\n\n## Skill Management\n\n");
catalog.append("After completing a complex task (5+ tool calls), fixing a tricky error, "); catalog.append("After completing a complex task (5+ tool calls), fixing a tricky error, ");

View File

@ -21,8 +21,8 @@ import java.util.regex.Pattern;
/** /**
* RFC-023: Agent 自治 Skill 管理工具 * RFC-023: Agent 自治 Skill 管理工具
* <p> * <p>
* 对标 hermes-agent skill_manager_tool.py Agent 在对话中自主创建编辑 * Agent 在对话中自主创建编辑修补和删除 Skill
* 修补和删除 Skill每次写入前强制安全扫描失败则拒绝并返回原因 * 每次写入前强制安全扫描失败则拒绝并返回原因
* <p> * <p>
* 系统 prompt 引导 Agent 使用此工具 * 系统 prompt 引导 Agent 使用此工具
* <blockquote> * <blockquote>

View File

@ -23,8 +23,7 @@ import java.util.Set;
* 同步模式返回图片 URLDALL-E 系列 base64 data URLgpt-image-2 系列 * 同步模式返回图片 URLDALL-E 系列 base64 data URLgpt-image-2 系列
* 复用已有的 OpenAI LLM provider API Key * 复用已有的 OpenAI LLM provider API Key
* *
* <p>gpt-image-2 三档质量做成 3 个虚拟 model ID参考 hermes-agent * <p>gpt-image-2 三档质量做成 3 个虚拟 model ID picker
* plugins/image_gen/openai/__init__.py model catalog 设计 picker
* 能直接选 fast/balanced/high三档底层都打到 API model {@code "gpt-image-2"} * 能直接选 fast/balanced/high三档底层都打到 API model {@code "gpt-image-2"}
* 区别仅在 {@code quality} 参数 * 区别仅在 {@code quality} 参数
* *

View File

@ -2,9 +2,9 @@
-- is no claude-sonnet-4-7 model. Calls return HTTP 404 with body -- is no claude-sonnet-4-7 model. Calls return HTTP 404 with body
-- {"type":"not_found_error","message":"model: claude-sonnet-4-7"}. -- {"type":"not_found_error","message":"model: claude-sonnet-4-7"}.
-- --
-- Reference: hermes-agent anthropic_adapter.py _ANTHROPIC_OUTPUT_LIMITS -- Anthropic's current model line includes claude-opus-4-7 but no
-- (lines 65-93) lists claude-opus-4-7 but no claude-sonnet-4-7. The latest -- claude-sonnet-4-7. The latest released Sonnet remains claude-sonnet-4-6
-- released Sonnet remains claude-sonnet-4-6 (released alongside Opus 4.6). -- (released alongside Opus 4.6).
-- --
-- Strategy: rename in place — preserve ids 1000000271, 1000000273, 1000000281 -- Strategy: rename in place — preserve ids 1000000271, 1000000273, 1000000281
-- so user-customised settings (default flag, enabled flag) survive. -- so user-customised settings (default flag, enabled flag) survive.

View File

@ -2,9 +2,9 @@
-- is no claude-sonnet-4-7 model. Calls return HTTP 404 with body -- is no claude-sonnet-4-7 model. Calls return HTTP 404 with body
-- {"type":"not_found_error","message":"model: claude-sonnet-4-7"}. -- {"type":"not_found_error","message":"model: claude-sonnet-4-7"}.
-- --
-- Reference: hermes-agent anthropic_adapter.py _ANTHROPIC_OUTPUT_LIMITS -- Anthropic's current model line includes claude-opus-4-7 but no
-- (lines 65-93) lists claude-opus-4-7 but no claude-sonnet-4-7. The latest -- claude-sonnet-4-7. The latest released Sonnet remains claude-sonnet-4-6
-- released Sonnet remains claude-sonnet-4-6 (released alongside Opus 4.6). -- (released alongside Opus 4.6).
-- --
-- Strategy: rename in place — preserve ids 1000000271, 1000000273, 1000000281 -- Strategy: rename in place — preserve ids 1000000271, 1000000273, 1000000281
-- so user-customised settings (default flag, enabled flag) survive. -- so user-customised settings (default flag, enabled flag) survive.

View File

@ -16,8 +16,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
* AND {@code oauth-2025-04-20}, comma-joined (no spaces).</li> * AND {@code oauth-2025-04-20}, comma-joined (no spaces).</li>
* <li>{@code User-Agent} must be the bare {@code claude-cli/<ver>} * <li>{@code User-Agent} must be the bare {@code claude-cli/<ver>}
* NOT {@code claude-cli/<ver> (external, cli)}. The {@code (external, cli)} * NOT {@code claude-cli/<ver> (external, cli)}. The {@code (external, cli)}
* suffix is what hermes-agent and other third-party clients append, and * suffix is what third-party clients append, and Anthropic uses it as a
* Anthropic uses it as a fingerprint to rate-limit the anti-abuse path. * fingerprint to rate-limit the anti-abuse path.
* Real Claude Code emits the bare form via the official JS SDK.</li> * Real Claude Code emits the bare form via the official JS SDK.</li>
* </ol> * </ol>
*/ */
@ -36,7 +36,7 @@ class ClaudeCodeApiHeadersTest {
} }
@Test @Test
@DisplayName("allBetas: common betas appear before OAuth-only betas (matches hermes-agent ordering)") @DisplayName("allBetas: common betas appear before OAuth-only betas")
void allBetas_orderedCommonFirst() { void allBetas_orderedCommonFirst() {
String result = headers.allBetas(); String result = headers.allBetas();
int oauthIdx = result.indexOf("oauth-2025-04-20"); int oauthIdx = result.indexOf("oauth-2025-04-20");

View File

@ -28,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/** /**
* Verifies the OAuth-mode prompt rewriting that prevents Anthropic's edge * Verifies the OAuth-mode prompt rewriting that prevents Anthropic's edge
* from rate-limiting MateClaw traffic. Each test corresponds to one of the * from rate-limiting MateClaw traffic. Each test corresponds to one of the
* transforms hermes-agent applies on {@code is_oauth=True} requests. * transforms applied on OAuth-authenticated requests.
*/ */
class ClaudeCodeIdentityChatModelDecoratorTest { class ClaudeCodeIdentityChatModelDecoratorTest {
@ -56,8 +56,8 @@ class ClaudeCodeIdentityChatModelDecoratorTest {
Prompt input = new Prompt(List.of(new UserMessage("hello"))); Prompt input = new Prompt(List.of(new UserMessage("hello")));
Prompt result = d.transform(input); Prompt result = d.transform(input);
// First message must be a system message with just the identity prefix // First message must be a system message with just the identity prefix:
// hermes-agent does the same: system = [cc_block] when none was supplied. // when none was supplied, system = [identity block].
Message first = result.getInstructions().get(0); Message first = result.getInstructions().get(0);
assertTrue(first instanceof SystemMessage); assertTrue(first instanceof SystemMessage);
assertEquals(ClaudeCodeIdentityChatModelDecorator.CLAUDE_CODE_SYSTEM_PREFIX, assertEquals(ClaudeCodeIdentityChatModelDecorator.CLAUDE_CODE_SYSTEM_PREFIX,
@ -70,9 +70,8 @@ class ClaudeCodeIdentityChatModelDecoratorTest {
@DisplayName("transform is idempotent — second pass doesn't double-prefix") @DisplayName("transform is idempotent — second pass doesn't double-prefix")
void transform_idempotent() { void transform_idempotent() {
// Defends against accidental double-wrapping (e.g. nested decorators or // Defends against accidental double-wrapping (e.g. nested decorators or
// a re-issue of the same Prompt). hermes-agent doesn't have this concern // a re-issue of the same Prompt). The guard keeps the identity prefix
// because its rewrite happens in one place; we keep this guard so the // from compounding to "You are Claude Code...You are Claude Code...".
// identity prefix doesn't compound to "You are Claude Code...You are Claude Code...".
ClaudeCodeIdentityChatModelDecorator d = new ClaudeCodeIdentityChatModelDecorator(noopDelegate()); ClaudeCodeIdentityChatModelDecorator d = new ClaudeCodeIdentityChatModelDecorator(noopDelegate());
Prompt original = new Prompt(List.of(new SystemMessage("Body"), new UserMessage("hi"))); Prompt original = new Prompt(List.of(new SystemMessage("Body"), new UserMessage("hi")));
Prompt once = d.transform(original); Prompt once = d.transform(original);

View File

@ -11,8 +11,7 @@ import static org.junit.jupiter.api.Assertions.*;
/** /**
* Unit tests for {@link OpenAiImageProvider} GPT-Image-2 wiring. * Unit tests for {@link OpenAiImageProvider} GPT-Image-2 wiring.
* *
* <p>Inspired by hermes-agent's plugins/image_gen/openai/__init__.py three * <p>Three virtual model IDs (gpt-image-2-low/medium/high) all map to API model
* virtual model IDs (gpt-image-2-low/medium/high) all map to API model
* {@code gpt-image-2} with a different {@code quality} parameter. The new * {@code gpt-image-2} with a different {@code quality} parameter. The new
* size set is 1024x1024 / 1024x1536 / 1536x1024, distinct from DALL-E's * size set is 1024x1024 / 1024x1536 / 1536x1024, distinct from DALL-E's
* 1024x1024 / 1024x1792 / 1792x1024. * 1024x1024 / 1024x1792 / 1792x1024.
@ -86,8 +85,7 @@ class OpenAiImageProviderGptImage2Test {
assertEquals("medium", OpenAiImageProvider.qualityForTier("gpt-image-2-medium")); assertEquals("medium", OpenAiImageProvider.qualityForTier("gpt-image-2-medium"));
assertEquals("high", OpenAiImageProvider.qualityForTier("gpt-image-2-high")); assertEquals("high", OpenAiImageProvider.qualityForTier("gpt-image-2-high"));
// Defensive: any unrecognised id falls back to medium (sane default; // Defensive: any unrecognised id falls back to medium (sane default).
// matches hermes-agent DEFAULT_MODEL = gpt-image-2-medium).
assertEquals("medium", OpenAiImageProvider.qualityForTier("anything-else")); assertEquals("medium", OpenAiImageProvider.qualityForTier("anything-else"));
assertEquals("medium", OpenAiImageProvider.qualityForTier("")); assertEquals("medium", OpenAiImageProvider.qualityForTier(""));
} }
@ -134,7 +132,7 @@ class OpenAiImageProviderGptImage2Test {
@DisplayName("normalizeSize: extra gpt-image-2 aspect-ratio aliases (3:4, 2:3, 4:3, 3:2) work") @DisplayName("normalizeSize: extra gpt-image-2 aspect-ratio aliases (3:4, 2:3, 4:3, 3:2) work")
void normalizeSize_gptImage2_extraAspectAliases() { void normalizeSize_gptImage2_extraAspectAliases() {
OpenAiImageProvider p = newProvider(); OpenAiImageProvider p = newProvider();
// Per hermes-agent's spec: portrait aliases 1024x1536, landscape 1536x1024 // Portrait aliases 1024x1536, landscape 1536x1024
assertEquals("1024x1536", p.normalizeSize(null, "3:4", true)); assertEquals("1024x1536", p.normalizeSize(null, "3:4", true));
assertEquals("1024x1536", p.normalizeSize(null, "2:3", true)); assertEquals("1024x1536", p.normalizeSize(null, "2:3", true));
assertEquals("1536x1024", p.normalizeSize(null, "4:3", true)); assertEquals("1536x1024", p.normalizeSize(null, "4:3", true));