This commit is contained in:
decolua
2026-04-11 11:36:33 +07:00
parent ed17a8ffac
commit 875a1282ea
5 changed files with 102 additions and 34 deletions
+60 -2
View File
@@ -1,8 +1,62 @@
import { createHash } from "crypto";
import { BaseExecutor } from "./base.js";
import { CODEX_DEFAULT_INSTRUCTIONS } from "../config/codexInstructions.js";
import { PROVIDERS } from "../config/providers.js";
import { normalizeResponsesInput } from "../translator/helpers/responsesApiHelper.js";
// In-memory map: hash(first assistant content) → { sessionId, lastUsed }
const SESSION_TTL_MS = 60 * 60 * 1000; // 1 hour
const assistantSessionMap = new Map();
function hashContent(text) {
return createHash("sha256").update(text).digest("hex").slice(0, 16);
}
function generateSessionId() {
return `sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
}
// Extract text content from an input item
function extractItemText(item) {
if (!item) return "";
if (typeof item.content === "string") return item.content;
if (Array.isArray(item.content)) {
return item.content.map(c => c.text || c.output || "").filter(Boolean).join("");
}
return "";
}
// Resolve session_id from first assistant message in conversation history
function resolveConversationSessionId(input) {
if (!Array.isArray(input) || input.length === 0) return generateSessionId();
const firstAssistant = input.find(item => item.role === "assistant");
if (!firstAssistant) return generateSessionId(); // Turn 1: no assistant yet
const text = extractItemText(firstAssistant);
if (!text) return generateSessionId();
const hash = hashContent(text);
const entry = assistantSessionMap.get(hash);
if (entry) {
entry.lastUsed = Date.now();
return entry.sessionId;
}
const sessionId = generateSessionId();
assistantSessionMap.set(hash, { sessionId, lastUsed: Date.now() });
return sessionId;
}
// Cleanup expired entries periodically
setInterval(() => {
const now = Date.now();
for (const [key, entry] of assistantSessionMap) {
if (now - entry.lastUsed > SESSION_TTL_MS) assistantSessionMap.delete(key);
}
}, 10 * 60 * 1000);
/**
* Codex Executor - handles OpenAI Codex API (Responses API format)
* Automatically injects default instructions if missing
@@ -10,14 +64,16 @@ import { normalizeResponsesInput } from "../translator/helpers/responsesApiHelpe
export class CodexExecutor extends BaseExecutor {
constructor() {
super("codex", PROVIDERS.codex);
this._currentSessionId = null;
}
/**
* Override headers to add session_id per request
* Override headers to add session_id per conversation
* transformRequest runs BEFORE buildHeaders, sets this._currentSessionId
*/
buildHeaders(credentials, stream = true) {
const headers = super.buildHeaders(credentials, stream);
headers["session_id"] = `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
headers["session_id"] = this._currentSessionId || credentials?.connectionId || "default";
return headers;
}
@@ -25,6 +81,8 @@ export class CodexExecutor extends BaseExecutor {
* Transform request before sending - inject default instructions if missing
*/
transformRequest(model, body, stream, credentials) {
// Resolve conversation-stable session_id from input history
this._currentSessionId = resolveConversationSessionId(body.input);
// Convert string input to array format (Codex API requires input as array)
const normalized = normalizeResponsesInput(body.input);
if (normalized) body.input = normalized;