152 lines
5.2 KiB
TypeScript
152 lines
5.2 KiB
TypeScript
import { FORMATS } from "../../translator/formats.ts";
|
|
import { buildAccountSemaphoreKey } from "../../services/accountSemaphore.ts";
|
|
import { getHeaderValueCaseInsensitive } from "./headers.ts";
|
|
|
|
function toFiniteNumberOrNull(value: unknown): number | null {
|
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
return value;
|
|
}
|
|
if (typeof value === "string" && value.trim().length > 0) {
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) ? parsed : null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function resolveAccountSemaphoreAccountKey(
|
|
connectionId: string | null | undefined,
|
|
credentials: Record<string, unknown> | null | undefined
|
|
): string | null {
|
|
if (typeof connectionId === "string" && connectionId.trim().length > 0) {
|
|
return connectionId;
|
|
}
|
|
|
|
const candidateKeys = [
|
|
credentials?.connectionId,
|
|
credentials?.id,
|
|
credentials?.email,
|
|
credentials?.name,
|
|
credentials?.displayName,
|
|
];
|
|
|
|
for (const candidate of candidateKeys) {
|
|
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
return candidate.trim();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export function resolveAccountSemaphoreMaxConcurrency(
|
|
credentials: Record<string, unknown> | null | undefined
|
|
): number | null {
|
|
return toFiniteNumberOrNull(credentials?.maxConcurrent);
|
|
}
|
|
|
|
export function resolveAccountSemaphoreKey({
|
|
provider,
|
|
model,
|
|
connectionId,
|
|
credentials,
|
|
}: {
|
|
provider: string | null | undefined;
|
|
model: string;
|
|
connectionId: string | null | undefined;
|
|
credentials: Record<string, unknown> | null | undefined;
|
|
}): string | null {
|
|
const accountKey = resolveAccountSemaphoreAccountKey(connectionId, credentials);
|
|
if (!accountKey || !provider) return null;
|
|
return buildAccountSemaphoreKey({ provider, accountKey });
|
|
}
|
|
|
|
export function buildClaudePromptCacheLogMeta(
|
|
targetFormat: string,
|
|
finalBody: Record<string, unknown> | null | undefined,
|
|
providerHeaders: Record<string, unknown> | Headers | null | undefined,
|
|
clientHeaders?: Headers | Record<string, unknown> | null | undefined
|
|
) {
|
|
if (targetFormat !== FORMATS.CLAUDE || !finalBody || typeof finalBody !== "object") return null;
|
|
|
|
const describeCacheControl = (cacheControl: Record<string, unknown> | undefined, extra = {}) => ({
|
|
type:
|
|
cacheControl && typeof cacheControl.type === "string" && cacheControl.type.trim()
|
|
? cacheControl.type.trim()
|
|
: "ephemeral",
|
|
ttl:
|
|
cacheControl && typeof cacheControl.ttl === "string" && cacheControl.ttl.trim()
|
|
? cacheControl.ttl.trim()
|
|
: null,
|
|
...extra,
|
|
});
|
|
|
|
const systemBreakpoints = Array.isArray(finalBody.system)
|
|
? finalBody.system.flatMap((block, index) => {
|
|
if (!block || typeof block !== "object") return [];
|
|
const text =
|
|
typeof block.text === "string" && block.text.trim().length > 0 ? block.text.trim() : "";
|
|
if (text.startsWith("x-anthropic-billing-header:")) {
|
|
return [];
|
|
}
|
|
const cacheControl =
|
|
block.cache_control && typeof block.cache_control === "object"
|
|
? block.cache_control
|
|
: null;
|
|
return cacheControl ? [describeCacheControl(cacheControl, { index })] : [];
|
|
})
|
|
: [];
|
|
|
|
const toolBreakpoints = Array.isArray(finalBody.tools)
|
|
? finalBody.tools.flatMap((tool, index) => {
|
|
if (!tool || typeof tool !== "object") return [];
|
|
const cacheControl =
|
|
tool.cache_control && typeof tool.cache_control === "object" ? tool.cache_control : null;
|
|
const name = typeof tool.name === "string" && tool.name.trim() ? tool.name.trim() : null;
|
|
return cacheControl ? [describeCacheControl(cacheControl, { index, name })] : [];
|
|
})
|
|
: [];
|
|
|
|
const messageBreakpoints = Array.isArray(finalBody.messages)
|
|
? finalBody.messages.flatMap((message, messageIndex) => {
|
|
if (!message || typeof message !== "object" || !Array.isArray(message.content)) return [];
|
|
const role =
|
|
typeof message.role === "string" && message.role.trim() ? message.role.trim() : "unknown";
|
|
return message.content.flatMap((block, contentIndex) => {
|
|
if (!block || typeof block !== "object") return [];
|
|
const cacheControl =
|
|
block.cache_control && typeof block.cache_control === "object"
|
|
? block.cache_control
|
|
: null;
|
|
if (!cacheControl) return [];
|
|
return [
|
|
describeCacheControl(cacheControl, {
|
|
messageIndex,
|
|
contentIndex,
|
|
role,
|
|
blockType:
|
|
typeof block.type === "string" && block.type.trim() ? block.type.trim() : "unknown",
|
|
}),
|
|
];
|
|
});
|
|
})
|
|
: [];
|
|
|
|
const totalBreakpoints =
|
|
systemBreakpoints.length + toolBreakpoints.length + messageBreakpoints.length;
|
|
let anthropicBeta = getHeaderValueCaseInsensitive(providerHeaders, "Anthropic-Beta");
|
|
if (!anthropicBeta) {
|
|
anthropicBeta = getHeaderValueCaseInsensitive(clientHeaders, "Anthropic-Beta");
|
|
}
|
|
|
|
if (totalBreakpoints === 0 && !anthropicBeta) return null;
|
|
|
|
return {
|
|
applied: totalBreakpoints > 0,
|
|
totalBreakpoints,
|
|
anthropicBeta,
|
|
systemBreakpoints,
|
|
toolBreakpoints,
|
|
messageBreakpoints,
|
|
};
|
|
}
|