// Probe 01: per model — (a) tiny generateContent with systemInstruction, (b) generateContentStream chunk shape, // usage arrival pattern, part.thought flag, finishReason, modelVersion. Also raw REST SSE for one model. import { ai, MODELS, KEY, SUFFIX, save, errInfo, shortText, withRetry, sleep, PACE_MS } from "./lib.js"; const results: any = {}; for (const model of MODELS) { const r: any = (results[model] = {}); // (a) + (i) non-streaming with systemInstruction try { const res = await withRetry(() => ai.models.generateContent({ model, contents: "Reply with exactly one word: what colour is the sky on a clear day?", config: { systemInstruction: "You are terse. Always answer in UPPERCASE.", maxOutputTokens: 400 }, })); r.generate = { ok: true, text: shortText(res), modelVersion: res.modelVersion, responseId: res.responseId, finishReason: res.candidates?.[0]?.finishReason, partKeys: res.candidates?.[0]?.content?.parts?.map((p: any) => Object.keys(p)), role: res.candidates?.[0]?.content?.role, usage: res.usageMetadata, topLevelKeys: Object.keys(res).filter((k) => !k.startsWith("_")), }; } catch (e) { r.generate = { ok: false, error: errInfo(e) }; } // (b) streaming try { await sleep(PACE_MS); const stream = await withRetry(() => ai.models.generateContentStream({ model, contents: "Count from 1 to 12 separated by spaces, then say DONE.", config: { maxOutputTokens: 2000, thinkingConfig: { includeThoughts: true } }, })); const chunks: any[] = []; for await (const c of stream) { chunks.push({ parts: c.candidates?.[0]?.content?.parts?.map((p: any) => ({ keys: Object.keys(p), thought: p.thought ?? undefined, textLen: p.text?.length, hasSig: !!p.thoughtSignature, })), role: c.candidates?.[0]?.content?.role, finishReason: c.candidates?.[0]?.finishReason, usage: c.usageMetadata, modelVersion: c.modelVersion, responseId: c.responseId, keys: Object.keys(c).filter((k) => !k.startsWith("_") && (c as any)[k] !== undefined), }); } r.stream = { ok: true, chunkCount: chunks.length, usageOnChunks: chunks.map((c) => (c.usage ? (c.usage.candidatesTokenCount != null ? "full" : "partial") : "none")), thoughtChunks: chunks.filter((c) => c.parts?.some((p: any) => p.thought)).length, lastUsage: chunks.at(-1)?.usage, finishReasons: chunks.map((c) => c.finishReason ?? null), sampleFirst: chunks[0], sampleLast: chunks.at(-1), }; } catch (e) { r.stream = { ok: false, error: errInfo(e) }; } await sleep(PACE_MS); console.log(model, JSON.stringify({ gen: r.generate.ok ? r.generate.text : r.generate.error, stream: r.stream.ok ? { n: r.stream.chunkCount, usage: r.stream.usageOnChunks, thoughts: r.stream.thoughtChunks } : r.stream.error })); } // Raw REST SSE protocol sample (one model) if (!process.argv[2]) { const model = "gemini-3.5-flash-lite"; const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse`, { method: "POST", headers: { "x-goog-api-key": KEY, "content-type": "application/json" }, body: JSON.stringify({ contents: [{ role: "user", parts: [{ text: "Say hello in three words." }] }], generationConfig: { maxOutputTokens: 50, thinkingConfig: { thinkingBudget: 0 } } }), }); const raw = await res.text(); results.restSse = { status: res.status, contentType: res.headers.get("content-type"), rawFirst1200: raw.slice(0, 1200), eventCount: raw.split("\n\n").filter((s) => s.trim()).length }; console.log("REST SSE", res.status, res.headers.get("content-type"), "events:", results.restSse.eventCount); } save(`01-basic-stream${SUFFIX}.json`, results);