Merge pull request #3605 from Jay1/jay/gpt-5-5-native-sisyphus

feat(agents): add GPT-5.5 native Sisyphus support
This commit is contained in:
acamq
2026-04-23 20:52:14 -06:00
committed by GitHub
7 changed files with 101 additions and 28 deletions
+30
View File
@@ -23,6 +23,23 @@ describe("getHephaestusPromptSource", () => {
expect(source3).toBe("gpt-5-4"); expect(source3).toBe("gpt-5-4");
}); });
test("returns 'gpt-5-4' for gpt-5.5 models", () => {
// given
const model1 = "openai/gpt-5.5";
const model2 = "openai/gpt-5-5";
const model3 = "github-copilot/gpt-5.5";
// when
const source1 = getHephaestusPromptSource(model1);
const source2 = getHephaestusPromptSource(model2);
const source3 = getHephaestusPromptSource(model3);
// then
expect(source1).toBe("gpt-5-4");
expect(source2).toBe("gpt-5-4");
expect(source3).toBe("gpt-5-4");
});
test("returns 'gpt-5-3-codex' for GPT 5.3 Codex models", () => { test("returns 'gpt-5-3-codex' for GPT 5.3 Codex models", () => {
// given // given
const model1 = "openai/gpt-5.3-codex"; const model1 = "openai/gpt-5.3-codex";
@@ -96,6 +113,19 @@ describe("getHephaestusPrompt", () => {
expect(prompt).toContain("<tool_usage_rules>"); expect(prompt).toContain("<tool_usage_rules>");
}); });
test("GPT 5.5 model returns GPT-5.4 optimized prompt", () => {
// given
const model = "openai/gpt-5.5";
// when
const prompt = getHephaestusPrompt(model);
// then
expect(prompt).toContain("You build context by examining");
expect(prompt).toContain("Never chain together bash commands");
expect(prompt).toContain("<tool_usage_rules>");
});
test("GPT 5.3-codex model returns GPT-5.3 prompt", () => { test("GPT 5.3-codex model returns GPT-5.3 prompt", () => {
// given // given
const model = "openai/gpt-5.3-codex"; const model = "openai/gpt-5.3-codex";
+2 -2
View File
@@ -1,6 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk"; import type { AgentConfig } from "@opencode-ai/sdk";
import type { AgentMode, AgentPromptMetadata } from "../types"; import type { AgentMode, AgentPromptMetadata } from "../types";
import { isGpt5_4Model, isGpt5_3CodexModel } from "../types"; import { isGpt5_3CodexModel, isGptNativeSisyphusModel } from "../types";
import type { import type {
AvailableAgent, AvailableAgent,
AvailableTool, AvailableTool,
@@ -21,7 +21,7 @@ export type HephaestusPromptSource = "gpt-5-4" | "gpt-5-3-codex" | "gpt";
export function getHephaestusPromptSource( export function getHephaestusPromptSource(
model?: string, model?: string,
): HephaestusPromptSource { ): HephaestusPromptSource {
if (model && isGpt5_4Model(model)) { if (model && isGptNativeSisyphusModel(model)) {
return "gpt-5-4"; return "gpt-5-4";
} }
if (model && isGpt5_3CodexModel(model)) { if (model && isGpt5_3CodexModel(model)) {
+2 -2
View File
@@ -1,6 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk"; import type { AgentConfig } from "@opencode-ai/sdk";
import type { AgentMode, AgentPromptMetadata } from "./types"; import type { AgentMode, AgentPromptMetadata } from "./types";
import { isGptModel, isGeminiModel, isGpt5_4Model } from "./types"; import { isGptModel, isGeminiModel, isGptNativeSisyphusModel } from "./types";
import { import {
buildGeminiToolMandate, buildGeminiToolMandate,
buildGeminiDelegationOverride, buildGeminiDelegationOverride,
@@ -480,7 +480,7 @@ export function createSisyphusAgent(
const categories = availableCategories ?? []; const categories = availableCategories ?? [];
const agents = availableAgents ?? []; const agents = availableAgents ?? [];
if (isGpt5_4Model(model)) { if (isGptNativeSisyphusModel(model)) {
const prompt = buildGpt54SisyphusPrompt( const prompt = buildGpt54SisyphusPrompt(
model, model,
agents, agents,
+37 -17
View File
@@ -1,26 +1,46 @@
import { describe, test, expect } from "bun:test"; import { describe, test, expect } from "bun:test";
import { isGptModel, isGeminiModel, isGlmModel, isGpt5_4Model, isMiniMaxModel } from "./types"; import {
isGptModel,
isGeminiModel,
isGlmModel,
isGptNativeSisyphusModel,
isMiniMaxModel,
} from "./types";
describe("isGpt5_4Model", () => { describe("isGptNativeSisyphusModel", () => {
test("detects gpt-5.4 models", () => { test("allows GPT-5.x where x >= 4", () => {
expect(isGpt5_4Model("openai/gpt-5.4")).toBe(true); expect(isGptNativeSisyphusModel("openai/gpt-5.4")).toBe(true);
expect(isGpt5_4Model("openai/gpt-5-4")).toBe(true); expect(isGptNativeSisyphusModel("openai/gpt-5-4")).toBe(true);
expect(isGpt5_4Model("openai/gpt-5.4-codex")).toBe(true); expect(isGptNativeSisyphusModel("openai/gpt-5.5")).toBe(true);
expect(isGpt5_4Model("github-copilot/gpt-5.4")).toBe(true); expect(isGptNativeSisyphusModel("openai/gpt-5-5")).toBe(true);
expect(isGpt5_4Model("venice/gpt-5-4")).toBe(true); expect(isGptNativeSisyphusModel("openai/gpt-5.9")).toBe(true);
expect(isGptNativeSisyphusModel("openai/gpt-5-9")).toBe(true);
expect(isGptNativeSisyphusModel("openai/gpt-5.10")).toBe(true);
expect(isGptNativeSisyphusModel("openai/gpt-5-10")).toBe(true);
}); });
test("does not match other GPT models", () => { test("allows with various providers and suffixes", () => {
expect(isGpt5_4Model("openai/gpt-5.3-codex")).toBe(false); expect(isGptNativeSisyphusModel("github-copilot/gpt-5.4")).toBe(true);
expect(isGpt5_4Model("openai/gpt-5.1")).toBe(false); expect(isGptNativeSisyphusModel("venice/gpt-5-4")).toBe(true);
expect(isGpt5_4Model("openai/gpt-4o")).toBe(false); expect(isGptNativeSisyphusModel("openai/gpt-5.4-codex")).toBe(true);
expect(isGpt5_4Model("github-copilot/gpt-4o")).toBe(false); expect(isGptNativeSisyphusModel("openai/gpt-5.5-mini")).toBe(true);
}); });
test("does not match non-GPT models", () => { test("rejects GPT-5.x where x < 4", () => {
expect(isGpt5_4Model("anthropic/claude-opus-4-7")).toBe(false); expect(isGptNativeSisyphusModel("openai/gpt-5.3-codex")).toBe(false);
expect(isGpt5_4Model("google/gemini-3.1-pro")).toBe(false); expect(isGptNativeSisyphusModel("openai/gpt-5.1")).toBe(false);
expect(isGpt5_4Model("openai/o1")).toBe(false); expect(isGptNativeSisyphusModel("openai/gpt-5-0")).toBe(false);
});
test("rejects other GPT models", () => {
expect(isGptNativeSisyphusModel("openai/gpt-4o")).toBe(false);
expect(isGptNativeSisyphusModel("github-copilot/gpt-4o")).toBe(false);
});
test("rejects non-GPT models", () => {
expect(isGptNativeSisyphusModel("anthropic/claude-opus-4-7")).toBe(false);
expect(isGptNativeSisyphusModel("google/gemini-3.1-pro")).toBe(false);
expect(isGptNativeSisyphusModel("openai/o1")).toBe(false);
}); });
}); });
+4 -2
View File
@@ -79,9 +79,11 @@ export function isGptModel(model: string): boolean {
return modelName.includes("gpt"); return modelName.includes("gpt");
} }
export function isGpt5_4Model(model: string): boolean { const GPT_NATIVE_SISYPHUS_RE = /gpt-5[.-](?:[4-9]|\d{2,})/i;
export function isGptNativeSisyphusModel(model: string): boolean {
const modelName = extractModelName(model).toLowerCase(); const modelName = extractModelName(model).toLowerCase();
return modelName.includes("gpt-5.4") || modelName.includes("gpt-5-4"); return GPT_NATIVE_SISYPHUS_RE.test(modelName);
} }
export function isGpt5_3CodexModel(model: string): boolean { export function isGpt5_3CodexModel(model: string): boolean {
+4 -4
View File
@@ -1,5 +1,5 @@
import type { PluginInput } from "@opencode-ai/plugin" import type { PluginInput } from "@opencode-ai/plugin"
import { isGptModel, isGpt5_4Model } from "../../agents/types" import { isGptModel, isGptNativeSisyphusModel } from "../../agents/types"
import { import {
getSessionAgent, getSessionAgent,
resolveRegisteredAgentName, resolveRegisteredAgentName,
@@ -11,8 +11,8 @@ import { getAgentConfigKey } from "../../shared/agent-display-names"
const TOAST_TITLE = "NEVER Use Sisyphus with GPT" const TOAST_TITLE = "NEVER Use Sisyphus with GPT"
const TOAST_MESSAGE = [ const TOAST_MESSAGE = [
"Sisyphus works best with Claude Opus, and works fine with Kimi/GLM models.", "Sisyphus works best with Claude Opus, and works fine with Kimi/GLM models.",
"Do NOT use Sisyphus with GPT (except GPT-5.4 which has specialized support).", "Do NOT use Sisyphus with GPT (except GPT-5.4 and GPT-5.5 which have specialized support).",
"For GPT models (other than 5.4), always use Hephaestus.", "For other GPT models, always use Hephaestus.",
].join("\n") ].join("\n")
function showToast(ctx: PluginInput, sessionID: string): void { function showToast(ctx: PluginInput, sessionID: string): void {
ctx.client.tui.showToast({ ctx.client.tui.showToast({
@@ -43,7 +43,7 @@ export function createNoSisyphusGptHook(ctx: PluginInput) {
const agentKey = getAgentConfigKey(rawAgent) const agentKey = getAgentConfigKey(rawAgent)
const modelID = input.model?.modelID const modelID = input.model?.modelID
if (agentKey === "sisyphus" && modelID && isGptModel(modelID) && !isGpt5_4Model(modelID)) { if (agentKey === "sisyphus" && modelID && isGptModel(modelID) && !isGptNativeSisyphusModel(modelID)) {
showToast(ctx, input.sessionID) showToast(ctx, input.sessionID)
input.agent = resolveRegisteredAgentName("hephaestus") ?? "hephaestus" input.agent = resolveRegisteredAgentName("hephaestus") ?? "hephaestus"
if (output?.message) { if (output?.message) {
+22 -1
View File
@@ -43,7 +43,7 @@ describe("no-sisyphus-gpt hook", () => {
expect(showToast.mock.calls[0]?.[0]).toMatchObject({ expect(showToast.mock.calls[0]?.[0]).toMatchObject({
body: { body: {
title: "NEVER Use Sisyphus with GPT", title: "NEVER Use Sisyphus with GPT",
message: expect.stringContaining("For GPT models (other than 5.4), always use Hephaestus."), message: expect.stringContaining("For other GPT models, always use Hephaestus."),
variant: "error", variant: "error",
}, },
}) })
@@ -70,6 +70,27 @@ describe("no-sisyphus-gpt hook", () => {
expect(output.message.agent).toBeUndefined() expect(output.message.agent).toBeUndefined()
}) })
test("does not show toast for gpt-5.5 model (native Sisyphus support)", async () => {
// given - sisyphus with gpt-5.5 model (should be allowed)
const showToast = spyOn({ fn: async () => ({}) }, "fn")
const hook = createNoSisyphusGptHook({
client: { tui: { showToast } },
} as any)
const output = createOutput()
// when - chat.message runs with gpt-5.5
await hook["chat.message"]?.({
sessionID: "ses_gpt55",
agent: SISYPHUS_DISPLAY,
model: { providerID: "openai", modelID: "gpt-5.5" },
}, output)
// then - no toast, agent NOT switched to Hephaestus
expect(showToast).toHaveBeenCalledTimes(0)
expect(output.message.agent).toBeUndefined()
})
test("does not show toast for non-gpt model", async () => { test("does not show toast for non-gpt model", async () => {
// given - sisyphus with claude model // given - sisyphus with claude model
const showToast = spyOn({ fn: async () => ({}) }, "fn") const showToast = spyOn({ fn: async () => ({}) }, "fn")