feat: 实现ChatGPT Codex路由器的核心功能
- 添加完整的项目基础结构,包括配置、类型定义和常量 - 实现OAuth认证流程和令牌管理 - 开发请求转换和响应处理逻辑 - 添加SSE流处理和ChatCompletions API转换 - 实现模型映射和提示指令系统 - 包含Docker部署配置和快速启动文档 - 添加自动登录功能和测试脚本
This commit is contained in:
182
src/prompts/index.ts
Normal file
182
src/prompts/index.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { logError, logWarn } from "../logger.js";
|
||||
import { getModelFamily } from "../request/model-map.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const CACHE_DIR = join(homedir(), ".chatgpt-codex-router", "cache");
|
||||
|
||||
const GITHUB_API_RELEASES =
|
||||
"https://api.github.com/repos/openai/codex/releases/latest";
|
||||
const GITHUB_HTML_RELEASES =
|
||||
"https://github.com/openai/codex/releases/latest";
|
||||
|
||||
type ModelFamily = "gpt-5.2-codex" | "codex-max" | "codex" | "gpt-5.2" | "gpt-5.1";
|
||||
|
||||
const PROMPT_FILES: Record<ModelFamily, string> = {
|
||||
"gpt-5.2-codex": "gpt-5.2-codex_prompt.md",
|
||||
"codex-max": "gpt-5.1-codex-max_prompt.md",
|
||||
codex: "gpt_5_codex_prompt.md",
|
||||
"gpt-5.2": "gpt_5_2_prompt.md",
|
||||
"gpt-5.1": "gpt_5_1_prompt.md",
|
||||
};
|
||||
|
||||
const CACHE_FILES: Record<ModelFamily, string> = {
|
||||
"gpt-5.2-codex": "gpt-5.2-codex-instructions.md",
|
||||
"codex-max": "codex-max-instructions.md",
|
||||
codex: "codex-instructions.md",
|
||||
"gpt-5.2": "gpt-5.2-instructions.md",
|
||||
"gpt-5.1": "gpt-5.1-instructions.md",
|
||||
};
|
||||
|
||||
interface CacheMetadata {
|
||||
etag: string | null;
|
||||
tag: string | null;
|
||||
lastChecked: number | null;
|
||||
url: string;
|
||||
}
|
||||
|
||||
async function getLatestReleaseTag(): Promise<string> {
|
||||
try {
|
||||
const response = await fetch(GITHUB_API_RELEASES);
|
||||
if (response.ok) {
|
||||
const data = (await response.json()) as { tag_name: string };
|
||||
if (data.tag_name) {
|
||||
return data.tag_name;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
|
||||
const htmlResponse = await fetch(GITHUB_HTML_RELEASES);
|
||||
if (!htmlResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch latest release: ${htmlResponse.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
const finalUrl = htmlResponse.url;
|
||||
if (finalUrl) {
|
||||
const parts = finalUrl.split("/tag/");
|
||||
const last = parts[parts.length - 1];
|
||||
if (last && !last.includes("/")) {
|
||||
return last;
|
||||
}
|
||||
}
|
||||
|
||||
const html = await htmlResponse.text();
|
||||
const match = html.match(/\/openai\/codex\/releases\/tag\/([^"]+)/);
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
}
|
||||
|
||||
throw new Error("Failed to determine latest release tag from GitHub");
|
||||
}
|
||||
|
||||
async function getCodexInstructions(
|
||||
normalizedModel = "gpt-5.1-codex",
|
||||
): Promise<string> {
|
||||
const modelFamily = getModelFamily(normalizedModel) as ModelFamily;
|
||||
const promptFile = PROMPT_FILES[modelFamily];
|
||||
const cacheFile = join(CACHE_DIR, CACHE_FILES[modelFamily]);
|
||||
const cacheMetaFile = join(
|
||||
CACHE_DIR,
|
||||
`${CACHE_FILES[modelFamily].replace(".md", "-meta.json")}`,
|
||||
);
|
||||
|
||||
try {
|
||||
let cachedETag: string | null = null;
|
||||
let cachedTag: string | null = null;
|
||||
let cachedTimestamp: number | null = null;
|
||||
|
||||
if (existsSync(cacheMetaFile)) {
|
||||
const metadata = JSON.parse(
|
||||
readFileSync(cacheMetaFile, "utf8"),
|
||||
) as CacheMetadata;
|
||||
cachedETag = metadata.etag;
|
||||
cachedTag = metadata.tag;
|
||||
cachedTimestamp = metadata.lastChecked;
|
||||
}
|
||||
|
||||
const CACHE_TTL_MS = 15 * 60 * 1000;
|
||||
if (
|
||||
cachedTimestamp &&
|
||||
Date.now() - cachedTimestamp < CACHE_TTL_MS &&
|
||||
existsSync(cacheFile)
|
||||
) {
|
||||
return readFileSync(cacheFile, "utf8");
|
||||
}
|
||||
|
||||
const latestTag = await getLatestReleaseTag();
|
||||
const CODEX_INSTRUCTIONS_URL = `https://raw.githubusercontent.com/openai/codex/${latestTag}/codex-rs/core/${promptFile}`;
|
||||
|
||||
if (cachedTag !== latestTag) {
|
||||
cachedETag = null;
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {};
|
||||
if (cachedETag) {
|
||||
headers["If-None-Match"] = cachedETag;
|
||||
}
|
||||
|
||||
const response = await fetch(CODEX_INSTRUCTIONS_URL, { headers });
|
||||
|
||||
if (response.status === 304) {
|
||||
if (existsSync(cacheFile)) {
|
||||
return readFileSync(cacheFile, "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
const instructions = await response.text();
|
||||
const newETag = response.headers.get("etag");
|
||||
|
||||
if (!existsSync(CACHE_DIR)) {
|
||||
mkdirSync(CACHE_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
writeFileSync(cacheFile, instructions, "utf8");
|
||||
writeFileSync(
|
||||
cacheMetaFile,
|
||||
JSON.stringify({
|
||||
etag: newETag,
|
||||
tag: latestTag,
|
||||
lastChecked: Date.now(),
|
||||
url: CODEX_INSTRUCTIONS_URL,
|
||||
} satisfies CacheMetadata),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
logError(null, `Failed to fetch ${modelFamily} instructions from GitHub: ${err.message}`);
|
||||
|
||||
if (existsSync(cacheFile)) {
|
||||
logWarn(null, `Using cached ${modelFamily} instructions`);
|
||||
return readFileSync(cacheFile, "utf8");
|
||||
}
|
||||
|
||||
logWarn(null, `Using fallback instructions for ${modelFamily}`);
|
||||
return getFallbackPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
function getFallbackPrompt(): string {
|
||||
return `You are a helpful AI assistant with strong coding capabilities.
|
||||
You can help users with a wide range of programming tasks, including:
|
||||
- Writing and debugging code
|
||||
- Explaining programming concepts
|
||||
- Refactoring and optimizing code
|
||||
- Generating code examples
|
||||
- Answering technical questions
|
||||
|
||||
Be concise, accurate, and provide practical solutions.`;
|
||||
}
|
||||
|
||||
export { getCodexInstructions };
|
||||
Reference in New Issue
Block a user