dotfiles/pi/extensions/openai-server-compaction/openai.ts

117 lines
4.1 KiB
TypeScript

/**
* OpenAI API and OpenAI Codex model/payload helpers.
*
* Keeps provider-specific detection, request patching, endpoint classification,
* and model-key logic out of the higher-level extension wiring.
*/
import type { JsonRecord } from "./config.ts";
import type { ResponsesReasoningConfig, ResponsesTextConfig } from "./remote-compaction.ts";
import { isRecord } from "./config.ts";
export type ModelLike = {
api?: unknown;
provider?: unknown;
id?: unknown;
baseUrl?: unknown;
reasoning?: unknown;
input?: readonly unknown[];
};
export function hostnameFromBaseUrl(baseUrl: unknown): string | undefined {
if (typeof baseUrl !== "string" || !baseUrl.trim()) return undefined;
try {
return new URL(baseUrl).hostname.toLowerCase();
} catch {
return undefined;
}
}
export function isOpenAIResponsesModel(model: unknown): model is ModelLike {
return (
isRecord(model) &&
(
model.api === "openai-responses" ||
model.api === "openai-codex-responses"
)
);
}
export function isDirectOpenAIResponsesModel(model: ModelLike): boolean {
if (model.api !== "openai-responses") return false;
if (model.provider !== "openai") return false;
const host = hostnameFromBaseUrl(model.baseUrl);
return host === undefined || host === "api.openai.com";
}
export function isOpenAICodexResponsesModel(model: ModelLike): boolean {
if (model.api !== "openai-codex-responses") return false;
const provider = typeof model.provider === "string" ? model.provider : "";
if (provider === "openai-codex") return true;
const host = hostnameFromBaseUrl(model.baseUrl);
return host === "chatgpt.com";
}
function isGptModel(model: ModelLike): boolean {
return typeof model.id === "string" && model.id.startsWith("gpt-");
}
export function supportsRemoteCompactionModel(model: unknown): model is ModelLike {
if (!isOpenAIResponsesModel(model) || !isGptModel(model)) return false;
return isDirectOpenAIResponsesModel(model) || isOpenAICodexResponsesModel(model);
}
export function looksLikeResponsesPayload(payload: JsonRecord): boolean {
return "input" in payload || "model" in payload || "messages" in payload;
}
export function modelKey(model: ModelLike): string {
return `${String(model.provider)}:${String(model.api)}:${String(model.id)}`;
}
export function thinkingLevelToResponsesReasoning(
thinkingLevel: unknown,
): ResponsesReasoningConfig | undefined {
if (thinkingLevel === "minimal") return { effort: "minimal", summary: "auto" };
if (thinkingLevel === "low") return { effort: "low", summary: "auto" };
if (thinkingLevel === "medium") return { effort: "medium", summary: "auto" };
if (thinkingLevel === "high") return { effort: "high", summary: "auto" };
if (thinkingLevel === "xhigh") return { effort: "xhigh", summary: "auto" };
return undefined;
}
export function applyRemoteHistoryPayloadPatch(params: {
payload: JsonRecord;
explicitHistory: unknown[];
}): JsonRecord {
const nextPayload: JsonRecord = {
...params.payload,
input: params.explicitHistory,
};
delete nextPayload.messages;
delete nextPayload.previous_response_id;
return nextPayload;
}
export function extractResponsesReasoningConfig(payload: unknown): ResponsesReasoningConfig | undefined {
if (!isRecord(payload) || !isRecord(payload.reasoning)) return undefined;
const effort = payload.reasoning.effort;
const summary = payload.reasoning.summary;
const normalized: ResponsesReasoningConfig = {
...(typeof effort === "string" ? { effort: effort as ResponsesReasoningConfig["effort"] } : {}),
...(
summary === null || typeof summary === "string"
? { summary: summary as ResponsesReasoningConfig["summary"] }
: {}
),
};
return Object.keys(normalized).length > 0 ? normalized : undefined;
}
export function extractResponsesTextConfig(payload: unknown): ResponsesTextConfig | undefined {
return isRecord(payload) && isRecord(payload.text) ? payload.text : undefined;
}
export function messageMatchesModel(message: unknown, model: ModelLike): boolean {
if (!isRecord(message)) return false;
return message.provider === model.provider && message.model === model.id;
}