Skip to content

OpenAI

Use OpenAI’s chat completion API with std/openai. This integration enables access to OpenAI’s language models without needing to acquire API keys.

Val Town free users can use any affordable model – such as gpt-5.4-nano and gpt-5-nano. Val Town Pro users can make 10 expensive requests per rolling 24-hour window, and are then bumped down to gpt-5.4-nano.

This SDK is powered by our openapiproxy.

ExampleRun in Val Town ↗
import { OpenAI } from "https://esm.town/v/std/openai/main.ts";
const openai = new OpenAI();
const completion = await openai.chat.completions.create({
messages: [
{ role: "user", content: "Say hello in a creative way" },
],
model: "gpt-5.4-nano",
max_completion_tokens: 100,
});
console.log(completion.choices[0].message.content);

The free proxy is great for getting started, but you may outgrow the rate limits. The easiest upgrade path is to bring your own OpenAI API key — just set OPENAI_API_KEY and use the npm:openai client directly when it’s available, falling back to ValTownOpenAI otherwise. This way your val works with or without a key.

Bring Your Own KeyRun in Val Town ↗
import { ValTownOpenAI } from "https://esm.town/v/std/openai/main.ts";
import OpenAI from "npm:openai";
// Use your own key if available, otherwise the Val Town proxy.
const apiKey = Deno.env.get("OPENAI_API_KEY");
const openai = apiKey ? new OpenAI({ apiKey }) : new ValTownOpenAI({});
const completion = await openai.chat.completions.create({
model: "gpt-5.4-nano",
messages: [{ role: "user", content: "Say hello in one word." }],
});
console.log(completion.choices[0].message.content);

See the runnable example: examples/bring-your-own-key.ts

The Responses API (responses.create) is fully supported through the Val Town proxy. It enables the web_search tool, which lets the model browse the web in real time — incredibly effective for enrichment, research, and anything where fresh data beats stale training knowledge.

Web SearchRun in Val Town ↗
import { ValTownOpenAI } from "https://esm.town/v/std/openai/main.ts";
const openai = new ValTownOpenAI({});
const response = await openai.responses.create({
model: "gpt-5.4-nano",
tools: [{ type: "web_search" }],
instructions:
"You research companies. Given a company name, reply in 2-3 short plain-text lines: what they do, their stage, and why a developer might care. No markdown.",
input: "Val Town",
});
console.log(response.output_text);

See the runnable example: examples/web-search.ts

Instead of asking the model to “return JSON” and manually parsing the response, pass a JSON schema in text.format. The model is constrained to output JSON that matches your schema exactly — no fence stripping, no regex, no try/catch on malformed JSON. This is the idiomatic approach for classification, scoring, extraction, routing, or any task where you need typed data back.

Structured OutputsRun in Val Town ↗
import { ValTownOpenAI } from "https://esm.town/v/std/openai/main.ts";
const openai = new ValTownOpenAI({});
const response = await openai.responses.create({
model: "gpt-5.4-nano",
instructions:
"You classify user feedback. Given a piece of feedback, classify it and write a one-sentence summary.",
input: "The app keeps logging me out every 5 minutes, it's really frustrating.",
text: {
format: {
type: "json_schema",
name: "classification",
schema: {
type: "object",
properties: {
category: {
type: "string",
enum: ["bug", "feature_request", "praise", "question"],
},
priority: {
type: "string",
enum: ["low", "medium", "high"],
},
summary: { type: "string" },
},
required: ["category", "priority", "summary"],
additionalProperties: false,
},
},
},
});
// output_text is guaranteed valid JSON matching the schema.
const result = JSON.parse(response.output_text);
console.log(result);

See the runnable example: examples/structured-outputs.ts

To send an image to ChatGPT, the easiest way is by converting it to a data URL, which is easiest to do with @stevekrouse/fileToDataURL.

Image ExampleRun in Val Town ↗
import { fileToDataURL } from "https://esm.town/v/stevekrouse/fileToDataURL";
const dataURL = await fileToDataURL(file);
const response = await chat([
{
role: "system",
content: `You are an nutritionist.
Estimate the calories.
We only need a VERY ROUGH estimate.
Respond ONLY in a JSON array with values conforming to: {ingredient: string, calories: number}
`,
},
{
role: "user",
content: [{
type: "image_url",
image_url: {
url: dataURL,
},
}],
},
], {
model: "gpt-5.4-nano",
max_completion_tokens: 200,
});

The OpenAI Agents SDK works with std/openai too, including hosted tools like web search:

Agents ExampleRun in Val Town ↗
import { ValTownOpenAI } from "https://esm.town/v/std/openai/main.ts";
import {
Agent,
run,
setDefaultOpenAIClient,
setTracingDisabled,
webSearchTool,
} from "npm:@openai/agents";
setTracingDisabled(true); // tracing requires your own OpenAI key
setDefaultOpenAIClient(new ValTownOpenAI({}));
const agent = new Agent({
name: "Researcher",
tools: [webSearchTool()],
});
const result = await run(agent, "What's new in Val Town?");
console.log(result.finalOutput);

While our wrapper simplifies the integration of OpenAI, there are a few limitations to keep in mind:

  • Usage Quota: We limit each user to 10 requests per minute.
  • Features: Chat completions and the Responses API are the only endpoints available. The Responses API supports the web_search tool and structured outputs (text.format with json_schema).

If these limits are too low, let us know! You can also get around the limitation by bringing your own API key.