import OpenAI from "openai"; import { writeFileSync, mkdirSync } from "node:fs"; import { deflateSync } from "node:zlib"; export const BASE = "https://api.moonshot.ai/v1"; export const KEY = process.env.KIMI_API_KEY ?? ""; if (!KEY) throw new Error("KIMI_API_KEY missing (set -a; . ./.env; set +a)"); export const MODELS = ["kimi-k2.6", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k3"] as const; export type ModelId = (typeof MODELS)[number]; export const client = new OpenAI({ apiKey: KEY, baseURL: BASE, timeout: 600_000, maxRetries: 0 }); mkdirSync(new URL("./out/", import.meta.url), { recursive: true }); export function save(name: string, data: unknown) { const p = new URL(`./out/${name}.json`, import.meta.url); writeFileSync(p, JSON.stringify(data, null, 2)); } /** Raw POST returning status, headers (redacted) and parsed body (or text). */ export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); export let last429: { status: number; headers: Record; body: unknown } | null = null; /** Retries 429 rate_limit/engine_overloaded up to 8× (3 s, 6 s, …) unless RETRY429=0. */ export async function rawPost(path: string, body: unknown, extraHeaders: Record = {}, key = KEY) { const maxTries = process.env.RETRY429 === "0" ? 1 : 8; let attempt = 0; for (;;) { attempt++; const res = await fetch(`${BASE}${path}`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${key}`, ...extraHeaders }, body: JSON.stringify(body), }); const text = await res.text(); let json: unknown = null; try { json = JSON.parse(text); } catch { /* text */ } const out = { status: res.status, headers: pickHeaders(res.headers), body: json ?? text, attempts: attempt }; if (res.status === 429 && attempt < maxTries) { last429 = out; const t = (json as any)?.error?.type; if (t === "rate_limit_reached_error" || t === "engine_overloaded_error") { await sleep(3000 * attempt); continue; } } return out; } } export async function rawGet(path: string, key = KEY) { const res = await fetch(`${BASE}${path}`, { headers: { authorization: `Bearer ${key}` } }); const text = await res.text(); let json: unknown = null; try { json = JSON.parse(text); } catch { /* text */ } return { status: res.status, headers: pickHeaders(res.headers), body: json ?? text }; } export function pickHeaders(h: Headers) { const out: Record = {}; h.forEach((v, k) => { if (/^(set-cookie|authorization)$/i.test(k)) return; if (/^(msh-|x-msh-|x-ratelimit|ratelimit|retry-after|content-type|cf-ray|server|date)/i.test(k)) out[k] = v; }); return out; } /** Raw SSE POST: returns every parsed `data:` JSON chunk plus status/headers. */ export async function rawSSE(path: string, body: unknown, extraHeaders: Record = {}) { const res = await fetch(`${BASE}${path}`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${KEY}`, ...extraHeaders }, body: JSON.stringify({ ...(body as object), stream: true }), }); const headers = pickHeaders(res.headers); if (!res.ok || !res.body) { const text = await res.text(); let json: unknown = null; try { json = JSON.parse(text); } catch {} return { status: res.status, headers, chunks: [] as any[], error: json ?? text, rawLines: [] as string[] }; } const reader = res.body.getReader(); const dec = new TextDecoder(); let buf = ""; const chunks: any[] = []; const rawLines: string[] = []; let done = false; while (!done) { const r = await reader.read(); if (r.done) break; buf += dec.decode(r.value, { stream: true }); let idx; while ((idx = buf.indexOf("\n\n")) >= 0) { const evt = buf.slice(0, idx); buf = buf.slice(idx + 2); for (const line of evt.split("\n")) { rawLines.push(line); if (!line.startsWith("data:")) continue; const payload = line.slice(5).trim(); if (payload === "[DONE]") { done = true; chunks.push("[DONE]"); continue; } try { chunks.push(JSON.parse(payload)); } catch { chunks.push({ unparsed: payload }); } } } } return { status: res.status, headers, chunks, rawLines, error: null }; } /** Minimal valid PNG (RGBA), size x size, solid colour. */ export function pngDataUrl(size = 32, rgba: [number, number, number, number] = [200, 30, 30, 255]) { const crcTable = new Int32Array(256); for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; crcTable[n] = c; } const crc32 = (buf: Buffer) => { let c = -1; for (const b of buf) c = crcTable[(c ^ b) & 0xff] ^ (c >>> 8); return (c ^ -1) >>> 0; }; const chunk = (type: string, data: Buffer) => { const len = Buffer.alloc(4); len.writeUInt32BE(data.length); const td = Buffer.concat([Buffer.from(type, "ascii"), data]); const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(td)); return Buffer.concat([len, td, crc]); }; const ihdr = Buffer.alloc(13); ihdr.writeUInt32BE(size, 0); ihdr.writeUInt32BE(size, 4); ihdr[8] = 8; ihdr[9] = 6; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0; const row = Buffer.alloc(1 + size * 4); for (let x = 0; x < size; x++) row.set(rgba, 1 + x * 4); const raw = Buffer.concat(Array.from({ length: size }, () => row)); const png = Buffer.concat([ Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), chunk("IHDR", ihdr), chunk("IDAT", deflateSync(raw)), chunk("IEND", Buffer.alloc(0)), ]); return `data:image/png;base64,${png.toString("base64")}`; } export function short(s: unknown, n = 300) { const t = typeof s === "string" ? s : (JSON.stringify(s) ?? String(s)); return t.length > n ? t.slice(0, n) + `…(${t.length})` : t; } export const WEATHER_TOOL = { type: "function" as const, function: { name: "get_weather", description: "Get the current weather for a city", parameters: { type: "object", properties: { city: { type: "string", description: "City name" } }, required: ["city"] }, }, };