wip: codex

This commit is contained in:
Aiden Cline 2026-01-09 16:27:44 -06:00
parent 75df5040ea
commit 2844086752
9 changed files with 767 additions and 8 deletions

View file

@ -3,6 +3,8 @@ import { Global } from "../global"
import fs from "fs/promises"
import z from "zod"
export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key"
export namespace Auth {
export const Oauth = z
.object({

View file

@ -0,0 +1,109 @@
import crypto from "crypto"
export namespace CodexAuth {
const ISSUER = "https://auth.openai.com"
const CLIENT_ID = "openai-codex-cli"
// Pending OAuth sessions: state -> { verifier, redirectUri }
const pending = new Map<string, { verifier: string; redirectUri: string }>()
function generatePkce() {
const verifier = crypto.randomBytes(64).toString("base64url")
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url")
return { verifier, challenge }
}
function generateState() {
return crypto.randomBytes(32).toString("base64url")
}
export function authorize(redirectUri: string) {
const pkce = generatePkce()
const state = generateState()
pending.set(state, { verifier: pkce.verifier, redirectUri })
// Clean up after 15 minutes
setTimeout(() => pending.delete(state), 15 * 60 * 1000)
const params = new URLSearchParams({
response_type: "code",
client_id: CLIENT_ID,
redirect_uri: redirectUri,
scope: "openid profile email offline_access",
code_challenge: pkce.challenge,
code_challenge_method: "S256",
id_token_add_organizations: "true",
codex_cli_simplified_flow: "true",
state,
originator: "opencode",
})
return { url: `${ISSUER}/oauth/authorize?${params}`, state }
}
export async function callback(code: string, state: string) {
const session = pending.get(state)
if (!session) throw new Error("Invalid or expired OAuth state")
pending.delete(state)
const resp = await fetch(`${ISSUER}/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: session.redirectUri,
client_id: CLIENT_ID,
code_verifier: session.verifier,
}),
})
if (!resp.ok) {
const text = await resp.text()
throw new Error(`Token exchange failed: ${resp.status} ${text}`)
}
const tokens = (await resp.json()) as {
id_token: string
access_token: string
refresh_token: string
expires_in?: number
}
return {
access: tokens.access_token,
refresh: tokens.refresh_token,
expires: Date.now() + (tokens.expires_in ?? 3600) * 1000,
}
}
export async function refresh(refreshToken: string) {
const resp = await fetch(`${ISSUER}/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "refresh_token",
client_id: CLIENT_ID,
refresh_token: refreshToken,
}),
})
if (!resp.ok) {
const text = await resp.text()
throw new Error(`Token refresh failed: ${resp.status} ${text}`)
}
const tokens = (await resp.json()) as {
access_token: string
refresh_token?: string
expires_in?: number
}
return {
access: tokens.access_token,
refresh: tokens.refresh_token ?? refreshToken,
expires: Date.now() + (tokens.expires_in ?? 3600) * 1000,
}
}
}

View file

@ -0,0 +1,416 @@
import type { Hooks, PluginInput } from "@opencode-ai/plugin"
import { Log } from "../util/log"
import { Installation } from "../installation"
import { OAUTH_DUMMY_KEY } from "../auth"
const log = Log.create({ service: "plugin.codex" })
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"
const ISSUER = "https://auth.openai.com"
const CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses"
const OAUTH_PORT = 1455
interface PkceCodes {
verifier: string
challenge: string
}
async function generatePKCE(): Promise<PkceCodes> {
const verifier = generateRandomString(43)
const encoder = new TextEncoder()
const data = encoder.encode(verifier)
const hash = await crypto.subtle.digest("SHA-256", data)
const challenge = base64UrlEncode(hash)
return { verifier, challenge }
}
function generateRandomString(length: number): string {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
const bytes = crypto.getRandomValues(new Uint8Array(length))
return Array.from(bytes)
.map((b) => chars[b % chars.length])
.join("")
}
function base64UrlEncode(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer)
const binary = String.fromCharCode(...bytes)
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")
}
function generateState(): string {
return base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)).buffer)
}
function buildAuthorizeUrl(redirectUri: string, pkce: PkceCodes, state: string): string {
const params = new URLSearchParams({
response_type: "code",
client_id: CLIENT_ID,
redirect_uri: redirectUri,
scope: "openid profile email offline_access",
code_challenge: pkce.challenge,
code_challenge_method: "S256",
id_token_add_organizations: "true",
codex_cli_simplified_flow: "true",
state,
originator: "opencode",
})
return `${ISSUER}/oauth/authorize?${params.toString()}`
}
interface TokenResponse {
id_token: string
access_token: string
refresh_token: string
expires_in?: number
}
async function exchangeCodeForTokens(code: string, redirectUri: string, pkce: PkceCodes): Promise<TokenResponse> {
const response = await fetch(`${ISSUER}/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: redirectUri,
client_id: CLIENT_ID,
code_verifier: pkce.verifier,
}).toString(),
})
if (!response.ok) {
throw new Error(`Token exchange failed: ${response.status}`)
}
return response.json()
}
async function refreshAccessToken(refreshToken: string): Promise<TokenResponse> {
const response = await fetch(`${ISSUER}/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: CLIENT_ID,
}).toString(),
})
if (!response.ok) {
throw new Error(`Token refresh failed: ${response.status}`)
}
return response.json()
}
const HTML_SUCCESS = `<!DOCTYPE html>
<html>
<head>
<title>OpenCode - Codex Authorization Successful</title>
<style>
body { font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e; color: #eee; }
.container { text-align: center; padding: 2rem; }
h1 { color: #4ade80; margin-bottom: 1rem; }
p { color: #aaa; }
</style>
</head>
<body>
<div class="container">
<h1>Authorization Successful</h1>
<p>You can close this window and return to OpenCode.</p>
</div>
<script>setTimeout(() => window.close(), 2000);</script>
</body>
</html>`
const HTML_ERROR = (error: string) => `<!DOCTYPE html>
<html>
<head>
<title>OpenCode - Codex Authorization Failed</title>
<style>
body { font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a2e; color: #eee; }
.container { text-align: center; padding: 2rem; }
h1 { color: #f87171; margin-bottom: 1rem; }
p { color: #aaa; }
.error { color: #fca5a5; font-family: monospace; margin-top: 1rem; padding: 1rem; background: rgba(248,113,113,0.1); border-radius: 0.5rem; }
</style>
</head>
<body>
<div class="container">
<h1>Authorization Failed</h1>
<p>An error occurred during authorization.</p>
<div class="error">${error}</div>
</div>
</body>
</html>`
interface PendingOAuth {
pkce: PkceCodes
state: string
resolve: (tokens: TokenResponse) => void
reject: (error: Error) => void
}
let oauthServer: ReturnType<typeof Bun.serve> | undefined
let pendingOAuth: PendingOAuth | undefined
async function startOAuthServer(): Promise<{ port: number; redirectUri: string }> {
if (oauthServer) {
return { port: OAUTH_PORT, redirectUri: `http://localhost:${OAUTH_PORT}/auth/callback` }
}
oauthServer = Bun.serve({
port: OAUTH_PORT,
fetch(req) {
const url = new URL(req.url)
if (url.pathname === "/auth/callback") {
const code = url.searchParams.get("code")
const state = url.searchParams.get("state")
const error = url.searchParams.get("error")
const errorDescription = url.searchParams.get("error_description")
if (error) {
const errorMsg = errorDescription || error
pendingOAuth?.reject(new Error(errorMsg))
pendingOAuth = undefined
return new Response(HTML_ERROR(errorMsg), {
headers: { "Content-Type": "text/html" },
})
}
if (!code) {
const errorMsg = "Missing authorization code"
pendingOAuth?.reject(new Error(errorMsg))
pendingOAuth = undefined
return new Response(HTML_ERROR(errorMsg), {
status: 400,
headers: { "Content-Type": "text/html" },
})
}
if (!pendingOAuth || state !== pendingOAuth.state) {
const errorMsg = "Invalid state - potential CSRF attack"
pendingOAuth?.reject(new Error(errorMsg))
pendingOAuth = undefined
return new Response(HTML_ERROR(errorMsg), {
status: 400,
headers: { "Content-Type": "text/html" },
})
}
const current = pendingOAuth
pendingOAuth = undefined
exchangeCodeForTokens(code, `http://localhost:${OAUTH_PORT}/auth/callback`, current.pkce)
.then((tokens) => current.resolve(tokens))
.catch((err) => current.reject(err))
return new Response(HTML_SUCCESS, {
headers: { "Content-Type": "text/html" },
})
}
if (url.pathname === "/cancel") {
pendingOAuth?.reject(new Error("Login cancelled"))
pendingOAuth = undefined
return new Response("Login cancelled", { status: 200 })
}
return new Response("Not found", { status: 404 })
},
})
log.info("codex oauth server started", { port: OAUTH_PORT })
return { port: OAUTH_PORT, redirectUri: `http://localhost:${OAUTH_PORT}/auth/callback` }
}
function stopOAuthServer() {
if (oauthServer) {
oauthServer.stop()
oauthServer = undefined
log.info("codex oauth server stopped")
}
}
function waitForOAuthCallback(pkce: PkceCodes, state: string): Promise<TokenResponse> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(
() => {
if (pendingOAuth) {
pendingOAuth = undefined
reject(new Error("OAuth callback timeout - authorization took too long"))
}
},
5 * 60 * 1000,
) // 5 minute timeout
pendingOAuth = {
pkce,
state,
resolve: (tokens) => {
clearTimeout(timeout)
resolve(tokens)
},
reject: (error) => {
clearTimeout(timeout)
reject(error)
},
}
})
}
function getUserAgent(): string {
const version = Installation.VERSION
const platform = process.platform
const arch = process.arch
return `opencode/${version} (${platform}; ${arch})`
}
export async function CodexAuthPlugin(input: PluginInput): Promise<Hooks> {
return {
auth: {
provider: "openai",
async loader(getAuth, provider) {
const auth = await getAuth()
if (auth.type !== "oauth") return {}
// Zero out costs for Codex (included with ChatGPT subscription)
for (const model of Object.values(provider.models)) {
model.cost = {
input: 0,
output: 0,
cache: { read: 0, write: 0 },
}
}
return {
apiKey: OAUTH_DUMMY_KEY,
async fetch(requestInput: RequestInfo | URL, init?: RequestInit) {
// Remove dummy API key authorization header
if (init?.headers) {
if (init.headers instanceof Headers) {
init.headers.delete("authorization")
init.headers.delete("Authorization")
} else if (Array.isArray(init.headers)) {
init.headers = init.headers.filter(([key]) => key.toLowerCase() !== "authorization")
} else {
delete init.headers["authorization"]
delete init.headers["Authorization"]
}
}
const currentAuth = await getAuth()
if (currentAuth.type !== "oauth") return fetch(requestInput, init)
// Check if token needs refresh
if (!currentAuth.access || currentAuth.expires < Date.now()) {
log.info("refreshing codex access token")
const tokens = await refreshAccessToken(currentAuth.refresh)
await input.client.auth.set({
path: { id: "codex" },
body: {
type: "oauth",
refresh: tokens.refresh_token,
access: tokens.access_token,
expires: Date.now() + (tokens.expires_in ?? 3600) * 1000,
},
})
currentAuth.access = tokens.access_token
}
// Build headers
const headers = new Headers()
if (init?.headers) {
if (init.headers instanceof Headers) {
init.headers.forEach((value, key) => headers.set(key, value))
} else if (Array.isArray(init.headers)) {
for (const [key, value] of init.headers) {
if (value !== undefined) headers.set(key, String(value))
}
} else {
for (const [key, value] of Object.entries(init.headers)) {
if (value !== undefined) headers.set(key, String(value))
}
}
}
// Set required Codex headers
headers.set("authorization", `Bearer ${currentAuth.access}`)
headers.set("originator", "opencode")
headers.set("user-agent", getUserAgent())
// Extract session_id from request body if present
let body = init?.body
if (body && typeof body === "string") {
const parsed = JSON.parse(body)
// The session ID should be passed in the request - we'll extract it from context
// For now, generate a UUIDv7-like ID based on timestamp
const sessionId = parsed.metadata?.sessionID || generateSessionId()
headers.set("session_id", sessionId)
}
// Rewrite URL to Codex endpoint
let url: URL
if (typeof requestInput === "string") {
url = new URL(requestInput)
} else if (requestInput instanceof URL) {
url = requestInput
} else {
url = new URL(requestInput.url)
}
// If this is a messages/responses request, redirect to Codex endpoint
if (url.pathname.includes("/v1/responses") || url.pathname.includes("/chat/completions")) {
url = new URL(CODEX_API_ENDPOINT)
}
return fetch(url, {
...init,
body,
headers,
})
},
}
},
methods: [
{
label: "ChatGPT Pro/Plus",
type: "oauth",
authorize: async () => {
const { redirectUri } = await startOAuthServer()
const pkce = await generatePKCE()
const state = generateState()
const authUrl = buildAuthorizeUrl(redirectUri, pkce, state)
const callbackPromise = waitForOAuthCallback(pkce, state)
return {
url: authUrl,
instructions: "Complete authorization in your browser. This window will close automatically.",
method: "auto" as const,
callback: async () => {
const tokens = await callbackPromise
stopOAuthServer()
return {
type: "success" as const,
refresh: tokens.refresh_token,
access: tokens.access_token,
expires: Date.now() + (tokens.expires_in ?? 3600) * 1000,
}
},
}
},
},
],
},
}
}
// Generate a UUIDv7-like session ID (timestamp-prefixed)
function generateSessionId(): string {
const timestamp = Date.now()
const timestampHex = timestamp.toString(16).padStart(12, "0")
const randomHex = Array.from(crypto.getRandomValues(new Uint8Array(10)))
.map((b) => b.toString(16).padStart(2, "0"))
.join("")
// UUIDv7 format: xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx
// First 48 bits are timestamp, version is 7
return `${timestampHex.slice(0, 8)}-${timestampHex.slice(8, 12)}-7${randomHex.slice(0, 3)}-${(0x80 | (parseInt(randomHex.slice(3, 4), 16) & 0x3f)).toString(16)}${randomHex.slice(4, 7)}-${randomHex.slice(7, 19)}`
}

View file

@ -7,12 +7,16 @@ import { Server } from "../server/server"
import { BunProc } from "../bun"
import { Instance } from "../project/instance"
import { Flag } from "../flag/flag"
import { CodexAuthPlugin } from "./codex"
export namespace Plugin {
const log = Log.create({ service: "plugin" })
const BUILTIN = ["opencode-copilot-auth@0.0.11", "opencode-anthropic-auth@0.0.8"]
// Built-in plugins that are directly imported (not installed from npm)
const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin]
const state = Instance.state(async () => {
const client = createOpencodeClient({
baseUrl: "http://localhost:4096",
@ -20,7 +24,7 @@ export namespace Plugin {
fetch: async (...args) => Server.App().fetch(...args),
})
const config = await Config.get()
const hooks = []
const hooks: Hooks[] = []
const input: PluginInput = {
client,
project: Instance.project,
@ -29,6 +33,16 @@ export namespace Plugin {
serverUrl: Server.url(),
$: Bun.$,
}
// Load internal plugins first
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
for (const plugin of INTERNAL_PLUGINS) {
log.info("loading internal plugin", { name: plugin.name })
const init = await plugin(input)
hooks.push(init)
}
}
const plugins = [...(config.plugin ?? [])]
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
plugins.push(...BUILTIN)

View file

@ -0,0 +1,180 @@
import { Hono } from "hono"
import { describeRoute, validator, resolver } from "hono-openapi"
import z from "zod"
import { errors } from "./error"
import { Auth } from "@/auth"
import { CodexAuth } from "@/codex/auth"
export const CodexRoute = new Hono()
.post(
"/auth/authorize",
describeRoute({
summary: "Start Codex OAuth",
description: "Initiate OAuth flow for Codex/ChatGPT authentication. Returns URL to open in browser.",
operationId: "codex.auth.authorize",
responses: {
200: {
description: "Authorization URL and state",
content: {
"application/json": {
schema: resolver(
z.object({
url: z.string(),
state: z.string(),
}),
),
},
},
},
},
}),
validator(
"query",
z.object({
port: z.coerce.number().optional(),
}),
),
async (c) => {
// Get port from request URL since we can't import Server (circular dep)
const url = new URL(c.req.url)
const port = c.req.valid("query").port ?? url.port ?? 4096
const redirectUri = `http://localhost:${port}/codex/auth/callback`
const result = CodexAuth.authorize(redirectUri)
return c.json(result)
},
)
.get(
"/auth/callback",
describeRoute({
summary: "Codex OAuth callback",
description: "Handle OAuth callback from ChatGPT auth. Called by browser after user authenticates.",
operationId: "codex.auth.callback",
responses: {
200: {
description: "Success page",
content: {
"text/html": {},
},
},
...errors(400),
},
}),
validator(
"query",
z.object({
code: z.string(),
state: z.string(),
}),
),
async (c) => {
const query = c.req.valid("query")
const tokens = await CodexAuth.callback(query.code, query.state)
await Auth.set("codex", {
type: "oauth",
access: tokens.access,
refresh: tokens.refresh,
expires: tokens.expires,
})
return c.html(`<!DOCTYPE html>
<html>
<head><title>Login Successful</title></head>
<body style="font-family: system-ui; text-align: center; padding: 50px; background: #1a1a1a; color: #fff;">
<h1>Login Successful</h1>
<p>You can close this window and return to OpenCode.</p>
</body>
</html>`)
},
)
.post(
"/auth/refresh",
describeRoute({
summary: "Refresh Codex tokens",
description: "Refresh the Codex access token using the stored refresh token.",
operationId: "codex.auth.refresh",
responses: {
200: {
description: "Tokens refreshed",
content: {
"application/json": {
schema: resolver(z.boolean()),
},
},
},
...errors(400),
},
}),
async (c) => {
const existing = await Auth.get("codex")
if (!existing || existing.type !== "oauth") {
throw new Error("No Codex OAuth credentials found")
}
const tokens = await CodexAuth.refresh(existing.refresh)
await Auth.set("codex", {
type: "oauth",
access: tokens.access,
refresh: tokens.refresh,
expires: tokens.expires,
})
return c.json(true)
},
)
.get(
"/auth/status",
describeRoute({
summary: "Get Codex auth status",
description: "Check if Codex OAuth credentials exist and whether they're expired.",
operationId: "codex.auth.status",
responses: {
200: {
description: "Auth status",
content: {
"application/json": {
schema: resolver(
z.object({
authenticated: z.boolean(),
expired: z.boolean().optional(),
}),
),
},
},
},
},
}),
async (c) => {
const existing = await Auth.get("codex")
if (!existing || existing.type !== "oauth") {
return c.json({ authenticated: false })
}
return c.json({
authenticated: true,
expired: existing.expires < Date.now(),
})
},
)
.delete(
"/auth",
describeRoute({
summary: "Remove Codex auth",
description: "Remove stored Codex OAuth credentials.",
operationId: "codex.auth.remove",
responses: {
200: {
description: "Auth removed",
content: {
"application/json": {
schema: resolver(z.boolean()),
},
},
},
},
}),
async (c) => {
await Auth.remove("codex")
return c.json(true)
},
)

View file

@ -52,6 +52,7 @@ import { QuestionRoute } from "./question"
import { Installation } from "@/installation"
import { MDNS } from "./mdns"
import { Worktree } from "../worktree"
import { CodexRoute } from "./codex"
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
globalThis.AI_SDK_LOG_WARNINGS = false
@ -74,6 +75,8 @@ export namespace Server {
const app = new Hono()
export const App: () => Hono = lazy(
() =>
// TODO: Break server.ts into smaller route files to fix type inference
// @ts-expect-error - Hono route chain is too deep for TypeScript's type inference
app
.onError((err, c) => {
log.error("failed", {
@ -1898,6 +1901,7 @@ export namespace Server {
return c.json(true)
},
)
.route("/codex", CodexRoute)
.get(
"/find",
describeRoute({

View file

@ -1,3 +1,5 @@
import os from "os"
import { Installation } from "@/installation"
import { Provider } from "@/provider/provider"
import { Log } from "@/util/log"
import {
@ -19,6 +21,7 @@ import { Plugin } from "@/plugin"
import { SystemPrompt } from "./system"
import { Flag } from "@/flag/flag"
import { PermissionNext } from "@/permission/next"
import { Auth } from "@/auth"
export namespace LLM {
const log = Log.create({ service: "llm" })
@ -82,12 +85,23 @@ export namespace LLM {
}
const provider = await Provider.getProvider(input.model.providerID)
const auth = await Auth.get(input.model.providerID)
const isCodex = provider.id === "openai" && auth?.type === "oauth"
const variant =
!input.small && input.model.variants && input.user.variant ? input.model.variants[input.user.variant] : {}
const base = input.small
? ProviderTransform.smallOptions(input.model)
: ProviderTransform.options(input.model, input.sessionID, provider.options)
const options = pipe(base, mergeDeep(input.model.options), mergeDeep(input.agent.options), mergeDeep(variant))
const options: Record<string, any> = pipe(
base,
mergeDeep(input.model.options),
mergeDeep(input.agent.options),
mergeDeep(variant),
)
if (isCodex) {
options.instructions = SystemPrompt.instructions()
}
const params = await Plugin.trigger(
"chat.params",
@ -157,6 +171,13 @@ export namespace LLM {
maxOutputTokens,
abortSignal: input.abort,
headers: {
...(isCodex
? {
originator: "opencode",
"User-Agent": `opencode/${Installation.VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`,
session_id: input.sessionID,
}
: undefined),
...(input.model.providerID.startsWith("opencode")
? {
"x-opencode-project": Instance.project.id,
@ -169,12 +190,19 @@ export namespace LLM {
},
maxRetries: input.retries ?? 0,
messages: [
...system.map(
(x): ModelMessage => ({
role: "system",
content: x,
}),
),
...(isCodex
? [
{
role: "user",
content: system.join("\n\n"),
} as ModelMessage,
]
: system.map(
(x): ModelMessage => ({
role: "system",
content: x,
}),
)),
...input.messages,
],
model: wrapLanguageModel({

View file

@ -0,0 +1 @@
You are a coding agent running in the opencode, a terminal-based coding assistant. opencode is an open source project. You are expected to be precise, safe, and helpful.

View file

@ -14,6 +14,7 @@ import PROMPT_GEMINI from "./prompt/gemini.txt"
import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt"
import PROMPT_CODEX from "./prompt/codex.txt"
import PROMPT_CODEX_INSTRUCTIONS from "./prompt/codex_header.txt"
import type { Provider } from "@/provider/provider"
import { Flag } from "@/flag/flag"
@ -23,6 +24,10 @@ export namespace SystemPrompt {
return []
}
export function instructions() {
return PROMPT_CODEX_INSTRUCTIONS.trim()
}
export function provider(model: Provider.Model) {
if (model.api.id.includes("gpt-5")) return [PROMPT_CODEX]
if (model.api.id.includes("gpt-") || model.api.id.includes("o1") || model.api.id.includes("o3"))