ai-chatbot/lib/ai/detail-agent-kimi-profile.ts

160 lines
4.7 KiB
TypeScript

export const DETAIL_KIMI_THINKING_HEADER = "x-detail-agent-kimi-thinking";
export const DETAIL_KIMI_PROFILE_HEADER = "x-detail-agent-kimi-profile";
export type DetailKimiThinkingMode = "enabled" | "disabled";
export type DetailKimiPurpose = "plan" | "preflight" | "review" | "repair";
export type DetailKimiGenerateOptions = {
temperature?: number;
topP?: number;
maxTokens?: number;
headers?: Record<string, string>;
};
const KIMI_K26_PATTERN = /^kimi[-_.]?k2\.6(?:\b|[-_.])/i;
const KIMI_DETAIL_MODEL_PATTERN =
/^(?:kimi[-_.]?k2\.(?:6|7)(?:\b|[-_.])|kimi-for-coding$)/i;
const MOONSHOT_BASE_URL_PATTERN =
/moonshot\.(?:cn|ai)|api\.kimi\.com\/coding|kimi\.com/i;
export function isKimiK26ModelId(value: unknown) {
return KIMI_K26_PATTERN.test(String(value || "").trim());
}
export function isKimiDetailModelId(value: unknown) {
return KIMI_DETAIL_MODEL_PATTERN.test(String(value || "").trim());
}
export function isMoonshotOfficialBaseUrl(value: unknown) {
return MOONSHOT_BASE_URL_PATTERN.test(String(value || "").trim());
}
function normalizeThinkingMode(value: unknown): DetailKimiThinkingMode | null {
const normalized = String(value || "").trim().toLowerCase();
if (normalized === "enabled" || normalized === "on" || normalized === "true") {
return "enabled";
}
if (normalized === "disabled" || normalized === "off" || normalized === "false") {
return "disabled";
}
return null;
}
function getPurposeThinkingMode(purpose: DetailKimiPurpose): DetailKimiThinkingMode {
if (purpose === "plan" || purpose === "repair") return "enabled";
return "disabled";
}
function getPurposeMaxTokens(params: {
purpose: DetailKimiPurpose;
generationMode?: string | null;
}) {
if (params.purpose === "plan") {
return params.generationMode === "agent_layout" ? 16_000 : 10_000;
}
if (params.purpose === "repair") return 8_000;
if (params.purpose === "preflight") return 4_096;
return 3_000;
}
export function shouldUseKimiDetailProfile(params: {
modelId?: string | null;
providerBaseUrl?: string | null;
isLocalMoonshot?: boolean;
}) {
if (!isKimiDetailModelId(params.modelId)) return false;
return Boolean(params.isLocalMoonshot) || isMoonshotOfficialBaseUrl(params.providerBaseUrl);
}
export function buildDetailKimiGenerateOptions(params: {
purpose: DetailKimiPurpose;
generationMode?: string | null;
modelId?: string | null;
providerBaseUrl?: string | null;
isLocalMoonshot?: boolean;
}): DetailKimiGenerateOptions {
if (!shouldUseKimiDetailProfile(params)) return {};
const thinking = getPurposeThinkingMode(params.purpose);
return {
temperature: thinking === "enabled" ? 1 : 0.6,
topP: 0.95,
maxTokens: getPurposeMaxTokens(params),
headers: {
[DETAIL_KIMI_THINKING_HEADER]: thinking,
[DETAIL_KIMI_PROFILE_HEADER]: `detail-ui-${params.purpose}`,
},
};
}
function toHeaders(headers: HeadersInit | undefined) {
const next = new Headers();
if (!headers) return next;
if (headers instanceof Headers) {
headers.forEach((value, key) => next.set(key, value));
return next;
}
if (Array.isArray(headers)) {
headers.forEach(([key, value]) => {
if (value !== undefined && value !== null) next.set(key, String(value));
});
return next;
}
Object.entries(headers).forEach(([key, value]) => {
if (value !== undefined && value !== null) next.set(key, String(value));
});
return next;
}
function bodyToString(body: BodyInit | null | undefined) {
if (!body) return null;
if (typeof body === "string") return body;
if (body instanceof Uint8Array) {
return new TextDecoder().decode(body);
}
return null;
}
function applyKimiThinkingToBody(params: {
rawBody: string;
thinking: DetailKimiThinkingMode | null;
}) {
if (!params.thinking) return null;
try {
const body = JSON.parse(params.rawBody) as Record<string, unknown>;
if (!isKimiK26ModelId(body.model)) return null;
return JSON.stringify({
...body,
thinking: { type: params.thinking },
});
} catch {
return null;
}
}
export function createMoonshotKimiFetch(baseFetch: typeof fetch = fetch): typeof fetch {
return async (input, init) => {
const headers = toHeaders(
init?.headers || (input instanceof Request ? input.headers : undefined)
);
const thinking = normalizeThinkingMode(headers.get(DETAIL_KIMI_THINKING_HEADER));
headers.delete(DETAIL_KIMI_THINKING_HEADER);
headers.delete(DETAIL_KIMI_PROFILE_HEADER);
const rawBody = bodyToString(init?.body);
const patchedBody = rawBody ? applyKimiThinkingToBody({ rawBody, thinking }) : null;
return baseFetch(input, {
...init,
headers,
body: patchedBody || init?.body,
});
};
}