/**
* Puter.js GLM client — FREE GLM 5.2 in the browser, zero setup.
*
* This is the "exactly like this chat" experience: the user visits the site,
* Puter.js loads from CDN, they sign into their free Puter account (popup),
* and get unlimited GLM 5.2. No API keys, no backend, no payment.
*
* Puter's "User-Pays" model: the developer pays $0. Each user authenticates
* with their free Puter account and gets AI credits. If they exceed the free
* quota, they pay Puter directly (not the developer).
*
* Usage:
* 1. Add to index.html
* 2. import { puterChat } from "./api/puter-glm"
* 3. const response = await puterChat("Hello", [{role:"user",content:"Hi"}])
*
* The first call triggers a Puter sign-in popup if the user isn't authenticated.
* After that, calls are seamless.
*/
// Puter.js is loaded from CDN — declare the global type
declare global {
interface Window {
puter?: {
ai: {
chat: (prompt: string | Array<{role: string; content: string}>, options?: {
model?: string;
stream?: boolean;
}) => Promise;
};
auth?: {
signIn: () => Promise;
signOut: () => void;
isSignedIn: () => boolean;
getUser: () => Promise<{ username: string }>;
};
};
}
}
export interface PuterChatOptions {
model?: string; // default "z-ai/glm-5.2"
messages?: Array<{ role: string; content: string }>;
onAuthRequired?: () => void; // called when user needs to sign in
}
/**
* Check if Puter.js is loaded and available.
*/
export function isPuterAvailable(): boolean {
return typeof window !== "undefined" && !!window.puter?.ai?.chat;
}
/**
* Check if the user is signed into Puter.
*/
export function isPuterSignedIn(): boolean {
return isPuterAvailable() && (window.puter!.auth?.isSignedIn?.() ?? false);
}
/**
* Sign in to Puter (triggers popup).
*/
export async function puterSignIn(): Promise {
if (!isPuterAvailable()) {
throw new Error("Puter.js not loaded — check that https://js.puter.com/v2/ is in index.html");
}
await window.puter!.auth!.signIn();
}
/**
* Get the Puter username (if signed in).
*/
export async function puterGetUser(): Promise {
if (!isPuterSignedIn()) return null;
try {
const user = await window.puter!.auth!.getUser();
return user?.username || null;
} catch {
return null;
}
}
/**
* Call GLM 5.2 via Puter.js — FREE, no API key, no backend.
*
* The first call may trigger a sign-in popup. After that, calls are seamless.
* Returns the response text.
*/
export async function puterChat(
prompt: string,
options?: PuterChatOptions,
): Promise {
if (!isPuterAvailable()) {
throw new Error(
"Puter.js not loaded. Add to index.html.",
);
}
const model = options?.model || "z-ai/glm-5.2";
// Build the messages array. Puter.ai.chat accepts either a string or
// a messages array. We pass the full conversation for multi-turn context.
const messages = options?.messages || [{ role: "user", content: prompt }];
try {
const resp = await window.puter!.ai.chat(messages, { model });
// Puter returns various shapes depending on the model — normalize
if (typeof resp === "string") return resp;
const r = resp as Record;
const content =
(r.message as { content?: string })?.content ||
(r as { content?: string }).content ||
(r as { text?: string }).text ||
String(resp);
return content;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (msg.toLowerCase().includes("auth") || msg.toLowerCase().includes("unauthorized")) {
options?.onAuthRequired?.();
throw new Error("Puter sign-in required. Click the sign-in button to continue.");
}
throw new Error(`Puter GLM error: ${msg}`);
}
}
/**
* Multi-turn chat with GLM 5.2 via Puter.js.
* Maintains conversation history in memory (like the ZaiAdapter does).
*/
export class PuterGLMSession {
private messages: Array<{ role: string; content: string }> = [];
private model: string;
constructor(model = "z-ai/glm-5.2", systemPrompt?: string) {
this.model = model;
if (systemPrompt) {
this.messages.push({ role: "system", content: systemPrompt });
}
}
async send(userMessage: string): Promise {
this.messages.push({ role: "user", content: userMessage });
const response = await puterChat(userMessage, {
model: this.model,
messages: this.messages,
});
this.messages.push({ role: "assistant", content: response });
return response;
}
clear() {
this.messages = [];
}
get history() {
return [...this.messages];
}
}