Skip to content

OpenAI JS SDK ​

The official openai package for Node, Bun and Deno. It works with plugboard on both Chat Completions and the Responses API.

Connect ​

ts
import OpenAI from "openai"

const client = new OpenAI({ baseURL: "http://127.0.0.1:4010/v1", apiKey: process.env.PLUGBOARD_KEY })

model is an alias from your aliases option, such as "fast", or any allowed provider/model[#variant].

Chat ​

ts
const response = await client.chat.completions.create({ model, messages: [{ role: "user", content: "Reply with exactly: pong" }], max_tokens: 1024 })

Tool calls ​

Plugboard returns the call. Your code runs the tool and sends the result back. client.chat.completions.runTools also works.

ts
const tool: OpenAI.ChatCompletionTool = {
  type: "function",
  function: {
    name: "get_weather",
    description: "Current weather for a city",
    parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] },
  },
}
const messages: OpenAI.ChatCompletionMessageParam[] = [{ role: "user", content: "What is the weather in Paris? Use the tool." }]
const first = await client.chat.completions.create({ model, messages, tools: [tool], max_tokens: 1024 })
const message = first.choices[0]!.message
const call = message.tool_calls![0]!
// Send the assistant turn back as the SDK typed it, then the result under the same call id.
messages.push(message, { role: "tool", tool_call_id: call.id, content: JSON.stringify({ temp_c: 18, sky: "cloudy" }) })
const second = await client.chat.completions.create({ model, messages, tools: [tool], max_tokens: 1024 })

Structured output ​

zodResponseFormat(Meal, "meal") with .parse() or .stream() fills message.parsed:

ts
const Meal = z.object({ calories: z.number(), protein_g: z.number() })
const messages: OpenAI.ChatCompletionMessageParam[] = [{ role: "user", content: "Estimate: 2 boiled eggs and a slice of toast." }]
const parsed = await client.chat.completions.parse({ model, messages, response_format: zodResponseFormat(Meal, "meal_estimate"), max_tokens: 1024 })
const meal = parsed.choices[0]?.message.parsed // a typed Meal, or undefined when the content did not parse

Responses API ​

Text:

ts
const response = await client.responses.create({ model, input: "Reply with exactly: pong", max_output_tokens: 1024 })

Tool calls. responsesTool is the tool in the Responses shape (name at the top level). With store: false you send the whole history in input; with the default you can pass previous_response_id instead.

ts
const first = await client.responses.create({ model, input: PROMPT, tools: [responsesTool], store: false, max_output_tokens: 1024 })
const call = first.output.find((item): item is OpenAI.Responses.ResponseFunctionToolCall => item.type === "function_call")
if (!call) throw new Error("no function_call in the answer")
// Store is off, so the whole conversation goes back in input: the answer's items as the SDK typed them, then the result.
// The SDK types output items apart from input items; the wire takes the same objects, so they are cast here.
const input = [
  { role: "user", content: PROMPT },
  ...(first.output as OpenAI.Responses.ResponseInputItem[]),
  { type: "function_call_output", call_id: call.call_id, output: TOOL_RESULT },
] satisfies OpenAI.Responses.ResponseInputItem[]
const second = await client.responses.create({ model, input, tools: [responsesTool], store: false, max_output_tokens: 1024 })

Streaming, from response.output_text.delta events:

ts
const stream = client.responses.stream({ model, input: "Reply with exactly: pong", max_output_tokens: 1024 })
let text = ""
for await (const event of stream) {
  if (event.type === "response.output_text.delta") text += event.delta
}
const final = await stream.finalResponse()

Images, as a data URL:

ts
const red = readFileSync(new URL("../_shared/red32.dataurl.txt", import.meta.url), "utf8").trim()
const response = await client.responses.create({
  model,
  input: [
    {
      role: "user",
      content: [
        { type: "input_text", text: "What color is this image? One word." },
        { type: "input_image", image_url: red, detail: "auto" },
      ],
    },
  ],
  max_output_tokens: 1024,
})

Structured output with responses.parse and zodTextFormat:

ts
const Meal = z.object({ calories: z.number(), protein_g: z.number() })
const parsed = await client.responses.parse({
  model,
  input: "Estimate: 2 boiled eggs and a slice of toast.",
  text: { format: zodTextFormat(Meal, "meal_estimate") },
  max_output_tokens: 1024,
})
const meal = parsed.output_parsed // a typed Meal, or null when the text did not parse

client.responses.retrieve, inputItems.list and delete work on stored responses. See Responses API.

Files ​

client.files.create({ file: fs.createReadStream("doc.pdf"), purpose: "user_data" }) returns an id you can use in a file part (Chat) or an input_file part (Responses). See Files.

Limits ​

  • Streaming usage needs stream_options: { include_usage: true }.
  • The schema in structured output is not enforced by the provider. Validate the answer.
  • A wrong key throws AuthenticationError (401). An unknown model throws NotFoundError (404).