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");
});
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", () => {
// given
const model1 = "openai/gpt-5.3-codex";
@@ -96,6 +113,19 @@ describe("getHephaestusPrompt", () => {
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", () => {
// given
const model = "openai/gpt-5.3-codex";
+2 -2
View File
@@ -1,6 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk";
import type { AgentMode, AgentPromptMetadata } from "../types";
import { isGpt5_4Model, isGpt5_3CodexModel } from "../types";
import { isGpt5_3CodexModel, isGptNativeSisyphusModel } from "../types";
import type {
AvailableAgent,
AvailableTool,
@@ -21,7 +21,7 @@ export type HephaestusPromptSource = "gpt-5-4" | "gpt-5-3-codex" | "gpt";
export function getHephaestusPromptSource(
model?: string,
): HephaestusPromptSource {
if (model && isGpt5_4Model(model)) {
if (model && isGptNativeSisyphusModel(model)) {
return "gpt-5-4";
}
if (model && isGpt5_3CodexModel(model)) {
+2 -2
View File
@@ -1,6 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk";
import type { AgentMode, AgentPromptMetadata } from "./types";
import { isGptModel, isGeminiModel, isGpt5_4Model } from "./types";
import { isGptModel, isGeminiModel, isGptNativeSisyphusModel } from "./types";
import {
buildGeminiToolMandate,
buildGeminiDelegationOverride,
@@ -480,7 +480,7 @@ export function createSisyphusAgent(
const categories = availableCategories ?? [];
const agents = availableAgents ?? [];
if (isGpt5_4Model(model)) {
if (isGptNativeSisyphusModel(model)) {
const prompt = buildGpt54SisyphusPrompt(
model,
agents,
+37 -17
View File
@@ -1,26 +1,46 @@
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", () => {
test("detects gpt-5.4 models", () => {
expect(isGpt5_4Model("openai/gpt-5.4")).toBe(true);
expect(isGpt5_4Model("openai/gpt-5-4")).toBe(true);
expect(isGpt5_4Model("openai/gpt-5.4-codex")).toBe(true);
expect(isGpt5_4Model("github-copilot/gpt-5.4")).toBe(true);
expect(isGpt5_4Model("venice/gpt-5-4")).toBe(true);
describe("isGptNativeSisyphusModel", () => {
test("allows GPT-5.x where x >= 4", () => {
expect(isGptNativeSisyphusModel("openai/gpt-5.4")).toBe(true);
expect(isGptNativeSisyphusModel("openai/gpt-5-4")).toBe(true);
expect(isGptNativeSisyphusModel("openai/gpt-5.5")).toBe(true);
expect(isGptNativeSisyphusModel("openai/gpt-5-5")).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", () => {
expect(isGpt5_4Model("openai/gpt-5.3-codex")).toBe(false);
expect(isGpt5_4Model("openai/gpt-5.1")).toBe(false);
expect(isGpt5_4Model("openai/gpt-4o")).toBe(false);
expect(isGpt5_4Model("github-copilot/gpt-4o")).toBe(false);
test("allows with various providers and suffixes", () => {
expect(isGptNativeSisyphusModel("github-copilot/gpt-5.4")).toBe(true);
expect(isGptNativeSisyphusModel("venice/gpt-5-4")).toBe(true);
expect(isGptNativeSisyphusModel("openai/gpt-5.4-codex")).toBe(true);
expect(isGptNativeSisyphusModel("openai/gpt-5.5-mini")).toBe(true);
});
test("does not match non-GPT models", () => {
expect(isGpt5_4Model("anthropic/claude-opus-4-7")).toBe(false);
expect(isGpt5_4Model("google/gemini-3.1-pro")).toBe(false);
expect(isGpt5_4Model("openai/o1")).toBe(false);
test("rejects GPT-5.x where x < 4", () => {
expect(isGptNativeSisyphusModel("openai/gpt-5.3-codex")).toBe(false);
expect(isGptNativeSisyphusModel("openai/gpt-5.1")).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");
}
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();
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 {
+4 -4
View File
@@ -1,5 +1,5 @@
import type { PluginInput } from "@opencode-ai/plugin"
import { isGptModel, isGpt5_4Model } from "../../agents/types"
import { isGptModel, isGptNativeSisyphusModel } from "../../agents/types"
import {
getSessionAgent,
resolveRegisteredAgentName,
@@ -11,8 +11,8 @@ import { getAgentConfigKey } from "../../shared/agent-display-names"
const TOAST_TITLE = "NEVER Use Sisyphus with GPT"
const TOAST_MESSAGE = [
"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).",
"For GPT models (other than 5.4), always use Hephaestus.",
"Do NOT use Sisyphus with GPT (except GPT-5.4 and GPT-5.5 which have specialized support).",
"For other GPT models, always use Hephaestus.",
].join("\n")
function showToast(ctx: PluginInput, sessionID: string): void {
ctx.client.tui.showToast({
@@ -43,7 +43,7 @@ export function createNoSisyphusGptHook(ctx: PluginInput) {
const agentKey = getAgentConfigKey(rawAgent)
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)
input.agent = resolveRegisteredAgentName("hephaestus") ?? "hephaestus"
if (output?.message) {
+22 -1
View File
@@ -43,7 +43,7 @@ describe("no-sisyphus-gpt hook", () => {
expect(showToast.mock.calls[0]?.[0]).toMatchObject({
body: {
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",
},
})
@@ -70,6 +70,27 @@ describe("no-sisyphus-gpt hook", () => {
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 () => {
// given - sisyphus with claude model
const showToast = spyOn({ fn: async () => ({}) }, "fn")