Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/opencode/src/plugin/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ export async function CodexAuthPlugin(input: PluginInput): Promise<Hooks> {
"gpt-5.1-codex-mini",
"gpt-5.2",
"gpt-5.2-codex",
"gpt-5.4",
"gpt-5.3-codex",
"gpt-5.1-codex",
])
Expand Down
58 changes: 54 additions & 4 deletions packages/opencode/src/provider/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,71 @@ export namespace ModelsDev {

export type Provider = z.infer<typeof Provider>

const gpt54: Model = {
id: "gpt-5.4",
name: "GPT-5.4",
family: "gpt",
release_date: "2026-03-05",
attachment: true,
reasoning: true,
temperature: false,
tool_call: true,
cost: {
input: 2.5,
output: 15,
cache_read: 0.25,
},
limit: {
context: 1_050_000,
input: 922_000,
output: 128_000,
},
modalities: {
input: ["text", "image"],
output: ["text"],
},
options: {},
}

const gpt54op: Model = {
...gpt54,
modalities: {
input: ["text", "image", "pdf"],
output: ["text"],
},
provider: {
npm: "@ai-sdk/openai",
},
}

function add(data: Record<string, Provider>, id: string, model: Model) {
const provider = data[id]
if (!provider) return
if (provider.models[model.id]) return
provider.models[model.id] = model
}

function patch(data: Record<string, Provider>) {
add(data, "openai", gpt54)
add(data, "opencode", gpt54op)
return data
}

function url() {
return Flag.OPENCODE_MODELS_URL || "https://models.dev"
}

export const Data = lazy(async () => {
const result = await Filesystem.readJson(Flag.OPENCODE_MODELS_PATH ?? filepath).catch(() => {})
if (result) return result
if (result) return patch(result as Record<string, Provider>)
// @ts-ignore
const snapshot = await import("./models-snapshot")
.then((m) => m.snapshot as Record<string, unknown>)
.catch(() => undefined)
if (snapshot) return snapshot
if (Flag.OPENCODE_DISABLE_MODELS_FETCH) return {}
if (snapshot) return patch(snapshot as Record<string, Provider>)
if (Flag.OPENCODE_DISABLE_MODELS_FETCH) return patch({})
const json = await fetch(`${url()}/api.json`).then((x) => x.text())
return JSON.parse(json)
return patch(JSON.parse(json) as Record<string, Provider>)
})

export async function get() {
Expand Down
5 changes: 3 additions & 2 deletions packages/opencode/src/provider/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ export namespace ProviderTransform {
}
}
const copilotEfforts = iife(() => {
if (id.includes("5.1-codex-max") || id.includes("5.2") || id.includes("5.3"))
if (id.includes("5.1-codex-max") || id.includes("5.2") || id.includes("5.3") || id.includes("5.4"))
return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
return WIDELY_SUPPORTED_EFFORTS
})
Expand Down Expand Up @@ -488,7 +488,8 @@ export namespace ProviderTransform {
if (id === "gpt-5-pro") return {}
const openaiEfforts = iife(() => {
if (id.includes("codex")) {
if (id.includes("5.2") || id.includes("5.3")) return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
if (id.includes("5.2") || id.includes("5.3") || id.includes("5.4"))
return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
return WIDELY_SUPPORTED_EFFORTS
}
const arr = [...WIDELY_SUPPORTED_EFFORTS]
Expand Down
68 changes: 68 additions & 0 deletions packages/opencode/test/plugin/codex.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,49 @@
import { describe, expect, test } from "bun:test"
import type { PluginInput } from "@opencode-ai/plugin"
import {
CodexAuthPlugin,
parseJwtClaims,
extractAccountIdFromClaims,
extractAccountId,
type IdTokenClaims,
} from "../../src/plugin/codex"
import type { Provider as ProviderNS } from "../../src/provider/provider"

function createTestJwt(payload: object): string {
const header = Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url")
const body = Buffer.from(JSON.stringify(payload)).toString("base64url")
return `${header}.${body}.sig`
}

function createModel(id: string) {
return {
id,
providerID: "openai",
api: {
id,
url: "https://api.openai.com",
npm: "@ai-sdk/openai",
},
name: id,
capabilities: {
temperature: false,
reasoning: true,
attachment: true,
toolcall: true,
input: { text: true, audio: false, image: true, video: false, pdf: false },
output: { text: true, audio: false, image: false, video: false, pdf: false },
interleaved: false,
},
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
limit: { context: 400_000, input: 272_000, output: 128_000 },
status: "active",
options: {},
headers: {},
release_date: "2026-03-05",
variants: {},
}
}

describe("plugin.codex", () => {
describe("parseJwtClaims", () => {
test("parses valid JWT with claims", () => {
Expand Down Expand Up @@ -120,4 +152,40 @@ describe("plugin.codex", () => {
).toBe("acc-123")
})
})

describe("auth loader", () => {
test("keeps gpt-5.4 in oauth model allowlist", async () => {
const hooks = await CodexAuthPlugin({
client: {
auth: {
set: async () => {},
},
},
} as unknown as PluginInput)

if (!hooks.auth?.loader) {
throw new Error("Missing auth loader")
}

const provider = {
models: {
"gpt-5.4": createModel("gpt-5.4"),
"gpt-4.1": createModel("gpt-4.1"),
},
} as unknown as ProviderNS.Info

await hooks.auth.loader(
async () => ({
type: "oauth",
access: "token",
refresh: "refresh",
expires: Date.now() + 60_000,
}),
provider,
)

expect(provider.models["gpt-5.4"]).toBeDefined()
expect(provider.models["gpt-4.1"]).toBeUndefined()
})
})
})
30 changes: 30 additions & 0 deletions packages/opencode/test/provider/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,36 @@ test("enabled_providers restricts to only listed providers", async () => {
})
})

test("gpt-5.4 is backfilled for openai and opencode", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
}),
)
},
})
await Instance.provide({
directory: tmp.path,
init: async () => {
Env.set("OPENAI_API_KEY", "test-openai-key")
Env.set("OPENCODE_API_KEY", "test-opencode-key")
},
fn: async () => {
const providers = await Provider.list()
const openai = providers["openai"]?.models["gpt-5.4"]
const opencode = providers["opencode"]?.models["gpt-5.4"]
expect(openai).toBeDefined()
expect(opencode).toBeDefined()
expect(openai?.limit.context).toBe(1_050_000)
expect(openai?.limit.input).toBe(922_000)
expect(opencode?.api.npm).toBe("@ai-sdk/openai")
},
})
})

test("model whitelist filters models for provider", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
Expand Down
25 changes: 25 additions & 0 deletions packages/opencode/test/provider/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => {
expect(result.textVerbosity).toBe("low")
})

test("gpt-5.4 should have textVerbosity set to low", () => {
const model = createGpt5Model("gpt-5.4")
const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
expect(result.textVerbosity).toBe("low")
})

test("gpt-5.1 should have textVerbosity set to low", () => {
const model = createGpt5Model("gpt-5.1")
const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
Expand Down Expand Up @@ -1989,6 +1995,25 @@ describe("ProviderTransform.variants", () => {
})
})

test("gpt-5.4 includes xhigh", () => {
const model = createMockModel({
id: "gpt-5.4",
providerID: "github-copilot",
api: {
id: "gpt-5.4",
url: "https://api.githubcopilot.com",
npm: "@ai-sdk/github-copilot",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"])
expect(result.xhigh).toEqual({
reasoningEffort: "xhigh",
reasoningSummary: "auto",
include: ["reasoning.encrypted_content"],
})
})

test("gpt-5.2-codex includes xhigh", () => {
const model = createMockModel({
id: "gpt-5.2-codex",
Expand Down
Loading