mirror of
https://github.com/hanzoai/opencode-openai-codex-auth.git
synced 2026-09-24 17:01:02 +00:00
## 🎯 Major Improvements ### Smart ETag-Based Caching - Replaced 24-hour TTL cache with HTTP ETag-based conditional requests - Only downloads instructions when content actually changes (304 Not Modified responses) - Significantly reduces GitHub API calls while staying up-to-date ### Release Tag Tracking for Stability - Now fetches Codex instructions from latest GitHub release tag instead of main branch - Ensures compatibility with ChatGPT Codex API (main branch may have unreleased features) - Prevents "Instructions are not valid" errors from bleeding-edge changes ## 🐛 Bug Fixes ### Model Normalization - Fixed default model fallback: unsupported models now default to `gpt-5` (not `gpt-5-codex`) - Preserves user's choice between `gpt-5` and `gpt-5-codex` when explicitly specified - Only codex model variants normalize to `gpt-5-codex` ### Error Prevention - Added `body.text` initialization check to prevent TypeError on `body.text.verbosity` - Improved error handling in request transformation ### Code Quality - Standardized all console.error prefixes to `[openai-codex-plugin]` - Updated documentation to reflect ETag caching implementation - Added npm version and downloads badges to README ## 📚 Documentation - Updated README with accurate caching behavior description - Added npm package badges for version tracking - Clarified release-based fetching strategy Co-authored-by: Hanzo Dev <[email protected]>
179 lines
5.5 KiB
JavaScript
179 lines
5.5 KiB
JavaScript
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
import { join, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { homedir } from "node:os";
|
|
|
|
// Codex instructions constants
|
|
const GITHUB_API_RELEASES = "https://api.github.com/repos/openai/codex/releases/latest";
|
|
const CACHE_DIR = join(homedir(), ".opencode", "cache");
|
|
const CACHE_FILE = join(CACHE_DIR, "codex-instructions.md");
|
|
const CACHE_METADATA_FILE = join(CACHE_DIR, "codex-instructions-meta.json");
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
/**
|
|
* Get the latest release tag from GitHub
|
|
* @returns {Promise<string>} Release tag name (e.g., "rust-v0.43.0")
|
|
*/
|
|
async function getLatestReleaseTag() {
|
|
const response = await fetch(GITHUB_API_RELEASES);
|
|
if (!response.ok) throw new Error(`Failed to fetch latest release: ${response.status}`);
|
|
const data = await response.json();
|
|
return data.tag_name;
|
|
}
|
|
|
|
/**
|
|
* Fetch Codex instructions from GitHub with ETag-based caching
|
|
* Uses HTTP conditional requests to efficiently check for updates
|
|
* Always fetches from the latest release tag, not main branch
|
|
* @returns {Promise<string>} Codex instructions
|
|
*/
|
|
export async function getCodexInstructions() {
|
|
try {
|
|
// Get the latest release tag
|
|
const latestTag = await getLatestReleaseTag();
|
|
const CODEX_INSTRUCTIONS_URL = `https://raw.githubusercontent.com/openai/codex/${latestTag}/codex-rs/core/gpt_5_codex_prompt.md`;
|
|
|
|
// Load cached metadata (includes ETag and tag)
|
|
let cachedETag = null;
|
|
let cachedTag = null;
|
|
if (existsSync(CACHE_METADATA_FILE)) {
|
|
const metadata = JSON.parse(readFileSync(CACHE_METADATA_FILE, "utf8"));
|
|
cachedETag = metadata.etag;
|
|
cachedTag = metadata.tag;
|
|
}
|
|
|
|
// If tag changed, we need to fetch new instructions
|
|
if (cachedTag !== latestTag) {
|
|
cachedETag = null; // Force re-fetch
|
|
}
|
|
|
|
// Make conditional request with If-None-Match header
|
|
const headers = {};
|
|
if (cachedETag) {
|
|
headers["If-None-Match"] = cachedETag;
|
|
}
|
|
|
|
const response = await fetch(CODEX_INSTRUCTIONS_URL, { headers });
|
|
|
|
// 304 Not Modified - our cached version is still current
|
|
if (response.status === 304) {
|
|
if (existsSync(CACHE_FILE)) {
|
|
return readFileSync(CACHE_FILE, "utf8");
|
|
}
|
|
// Cache file missing but GitHub says not modified - fall through to re-fetch
|
|
}
|
|
|
|
// 200 OK - new content or first fetch
|
|
if (response.ok) {
|
|
const instructions = await response.text();
|
|
const newETag = response.headers.get("etag");
|
|
|
|
// Create cache directory if it doesn't exist
|
|
if (!existsSync(CACHE_DIR)) {
|
|
mkdirSync(CACHE_DIR, { recursive: true });
|
|
}
|
|
|
|
// Cache the instructions with ETag and tag (verbatim from GitHub)
|
|
writeFileSync(CACHE_FILE, instructions, "utf8");
|
|
writeFileSync(
|
|
CACHE_METADATA_FILE,
|
|
JSON.stringify({
|
|
etag: newETag,
|
|
tag: latestTag,
|
|
lastChecked: Date.now(),
|
|
url: CODEX_INSTRUCTIONS_URL,
|
|
}),
|
|
"utf8",
|
|
);
|
|
|
|
return instructions;
|
|
}
|
|
|
|
throw new Error(`HTTP ${response.status}`);
|
|
} catch (error) {
|
|
console.error(
|
|
"[openai-codex-plugin] Failed to fetch instructions from GitHub:",
|
|
error.message,
|
|
);
|
|
|
|
// Try to use cached version even if stale
|
|
if (existsSync(CACHE_FILE)) {
|
|
console.error("[openai-codex-plugin] Using cached instructions");
|
|
return readFileSync(CACHE_FILE, "utf8");
|
|
}
|
|
|
|
// Fall back to bundled version
|
|
console.error("[openai-codex-plugin] Falling back to bundled instructions");
|
|
return readFileSync(join(__dirname, "codex-instructions.md"), "utf8");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tool remapping instructions for opencode tools
|
|
*/
|
|
export const TOOL_REMAP_MESSAGE = `<user_instructions priority="0">
|
|
<environment_override priority="0">
|
|
YOU ARE IN A DIFFERENT ENVIRONMENT. These instructions override ALL previous tool references.
|
|
</environment_override>
|
|
|
|
<tool_replacements priority="0">
|
|
<critical_rule priority="0">
|
|
❌ APPLY_PATCH DOES NOT EXIST → ✅ USE "edit" INSTEAD
|
|
- NEVER use: apply_patch, applyPatch
|
|
- ALWAYS use: edit tool for ALL file modifications
|
|
- Before modifying files: Verify you're using "edit", NOT "apply_patch"
|
|
</critical_rule>
|
|
|
|
<critical_rule priority="0">
|
|
❌ UPDATE_PLAN DOES NOT EXIST → ✅ USE "todowrite" INSTEAD
|
|
- NEVER use: update_plan, updatePlan
|
|
- ALWAYS use: todowrite for ALL task/plan operations
|
|
- Use todoread to read current plan
|
|
- Before plan operations: Verify you're using "todowrite", NOT "update_plan"
|
|
</critical_rule>
|
|
</tool_replacements>
|
|
|
|
<available_tools priority="0">
|
|
File Operations:
|
|
• write - Create new files
|
|
• edit - Modify existing files (REPLACES apply_patch)
|
|
• patch - Apply diff patches
|
|
• read - Read file contents
|
|
|
|
Search/Discovery:
|
|
• grep - Search file contents
|
|
• glob - Find files by pattern
|
|
• list - List directories (use relative paths)
|
|
|
|
Execution:
|
|
• bash - Run shell commands
|
|
|
|
Network:
|
|
• webfetch - Fetch web content
|
|
|
|
Task Management:
|
|
• todowrite - Manage tasks/plans (REPLACES update_plan)
|
|
• todoread - Read current plan
|
|
</available_tools>
|
|
|
|
<substitution_rules priority="0">
|
|
Base instruction says: You MUST use instead:
|
|
apply_patch → edit
|
|
update_plan → todowrite
|
|
read_plan → todoread
|
|
absolute paths → relative paths
|
|
</substitution_rules>
|
|
|
|
<verification_checklist priority="0">
|
|
Before file/plan modifications:
|
|
1. Am I using "edit" NOT "apply_patch"?
|
|
2. Am I using "todowrite" NOT "update_plan"?
|
|
3. Is this tool in the approved list above?
|
|
4. Am I using relative paths?
|
|
|
|
If ANY answer is NO → STOP and correct before proceeding.
|
|
</verification_checklist>
|
|
</user_instructions>`;
|