Google GenAI JS SDK
The official @google/genai package, on plugboard's Gemini API.
Connect
The SDK adds /v1beta itself, so the base URL is the plugboard origin:
ts
import { GoogleGenAI } from "@google/genai"
const ai = new GoogleGenAI({ apiKey: process.env.PLUGBOARD_KEY, httpOptions: { baseUrl: "http://127.0.0.1:4010" } })Chat
ts
const response = await ai.models.generateContent({ model, contents: "Reply with exactly: pong", config: { maxOutputTokens: 1024 } })Tool calls
Gemini calls carry no id: the function name pairs a functionResponse with its call.
ts
const tools = [
{
functionDeclarations: [
{
name: "get_weather",
description: "Current weather for a city",
parametersJsonSchema: { type: "object", properties: { city: { type: "string" } }, required: ["city"] },
},
],
},
]
const contents: Content[] = [{ role: "user", parts: [{ text: "What is the weather in Paris? Use the tool." }] }]
const first = await ai.models.generateContent({ model, contents, config: { tools, maxOutputTokens: 1024 } })
const call = first.functionCalls?.[0]
// Send the model's turn back as it came, then the result as a functionResponse. Gemini calls carry no id: the name pairs them.
const turn: Content[] = [...contents, ...(first.candidates?.[0]?.content ? [first.candidates[0].content] : [])]
if (call) turn.push({ role: "user", parts: [{ functionResponse: { name: call.name, response: { temp_c: 18, sky: "cloudy" } } }] })
const second = await ai.models.generateContent({ model, contents: turn, config: { tools, maxOutputTokens: 1024 } })Streaming
ts
const stream = await ai.models.generateContentStream({ model, contents: "Reply with exactly: pong", config: { maxOutputTokens: 1024 } })
let text = ""
let finish: string | null = null
let usage: unknown = null
for await (const chunk of stream) {
text += chunk.text ?? "" // each chunk carries the next text delta
finish = chunk.candidates?.[0]?.finishReason?.toLowerCase() ?? finish
usage = chunk.usageMetadata ?? usage
}A streamed tool call arrives whole, in one chunk with its full args:
ts
const tools = [
{
functionDeclarations: [
{
name: "get_weather",
description: "Current weather for a city",
parametersJsonSchema: { type: "object", properties: { city: { type: "string" } }, required: ["city"] },
},
],
},
]
const stream = await ai.models.generateContentStream({
model,
contents: "What is the weather in Paris? Use the tool.",
config: { tools, maxOutputTokens: 1024 },
})
let call: { name?: string; args?: Record<string, unknown>; id?: string } | undefined
let finish: string | null = null
for await (const chunk of stream) {
call = chunk.functionCalls?.[0] ?? call
finish = chunk.candidates?.[0]?.finishReason?.toLowerCase() ?? finish
}Images
ts
// A 32px red PNG (examples/_shared/red32.dataurl.txt). Gemini takes the base64 bytes and the mime type, not a data URL.
const dataUrl = readFileSync(new URL("../_shared/red32.dataurl.txt", import.meta.url), "utf8").trim()
const base64 = dataUrl.slice(dataUrl.indexOf(",") + 1)
const response = await ai.models.generateContent({
model,
contents: [
{
role: "user",
parts: [
{ text: "What color is this image? One word." },
{ inlineData: { mimeType: "image/png", data: base64 } },
],
},
],
config: { maxOutputTokens: 1024 },
})Structured output
Pass a JSON schema as responseJsonSchema with responseMimeType: "application/json":
ts
// Gemini enforces responseJsonSchema on the model's output, unlike plugboard's forced-tool route for other APIs.
const schema = {
type: "object",
properties: { calories: { type: "number" }, protein_g: { type: "number" } },
required: ["calories", "protein_g"],
additionalProperties: false,
}
const response = await ai.models.generateContent({
model,
contents: "Estimate: 2 boiled eggs and a slice of toast.",
config: { responseMimeType: "application/json", responseJsonSchema: schema, maxOutputTokens: 1024 },
})Files
ai.files.upload({ file: "doc.pdf" }) uploads through plugboard; pass the returned file's uri in a fileData part. See Files.
Model names
A # in a model name must be sent as %23 (openai/gpt-5.6-luna%23low); a / goes as it is. Aliases avoid both. See Gemini model names.
Limits
- A wrong key or an unknown model throws
ApiErrorwith the HTTP status; the body names Google's status (UNAUTHENTICATED,NOT_FOUND). - Gemini reports
STOPon a turn with a function call too; check the parts for afunctionCall. - The schema in structured output is not enforced by the provider. Validate the answer.