How to use the Apple Foundation Model from Node.js
July 2, 2026 ยท View on GitHub
Call Apple's on-device Foundation Model from Node.js using the official openai npm package, pointed at a local apfel --serve. 100% on-device, zero API cost.
Runnable scripts + tests: Arthur-Ficial/apfel-guides-lab/scripts/nodejs.
Prerequisites
- macOS 26+ Tahoe, Apple Silicon, Apple Intelligence enabled
brew install apfelapfel --serverunning (port11434)- Node.js 20+
npm install openai"type": "module"inpackage.json(or use.mjsfiles)
1. One-shot chat completion
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "http://localhost:11434/v1", apiKey: "not-needed" });
const response = await client.chat.completions.create({
model: "apple-foundationmodel",
messages: [{ role: "user", content: "In one sentence, what is the Swift programming language?" }],
max_tokens: 80,
});
console.log((response.choices[0].message.content || "").trim());
Real output:
Swift is a modern, high-performance, and easy-to-learn programming language developed by Apple for building applications on iOS, macOS, watchOS, and tvOS.
Lab script: 01_oneshot.mjs.
2. Streaming
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "http://localhost:11434/v1", apiKey: "not-needed" });
const stream = await client.chat.completions.create({
model: "apple-foundationmodel",
messages: [{ role: "user", content: "List three Apple silicon chips, one per line." }],
max_tokens: 80,
stream: true,
});
for await (const chunk of stream) {
if (!chunk.choices || chunk.choices.length === 0) continue;
const delta = chunk.choices[0].delta?.content ?? "";
process.stdout.write(delta);
}
process.stdout.write("\n");
Real output:
Apple M1
Apple M2
Apple M2 Pro
Lab script: 02_stream.mjs.
3. JSON mode
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "http://localhost:11434/v1", apiKey: "not-needed" });
const response = await client.chat.completions.create({
model: "apple-foundationmodel",
messages: [{
role: "user",
content: "Return JSON with fields 'chip', 'year', 'cores'. Describe the Apple M1 chip. Return ONLY JSON.",
}],
response_format: { type: "json_object" },
max_tokens: 120,
});
let raw = (response.choices[0].message.content || "").trim();
raw = raw.replace(/^```(?:json)?\s*|\s*```$/gm, "").trim();
console.log(JSON.stringify(JSON.parse(raw), null, 2));
Real output:
{
"chip": "Apple M1",
"year": 2020,
"cores": {
"CPU": 8,
"GPU": 8
}
}
Lab script: 03_json.mjs.
4. Error handling
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "http://localhost:11434/v1", apiKey: "not-needed" });
try {
await client.embeddings.create({
model: "apple-foundationmodel",
input: "apfel runs 100% on-device.",
});
} catch (err) {
if (err instanceof OpenAI.APIError) {
console.log(`Got expected error: HTTP ${err.status} - ${err.message}`);
} else {
throw err;
}
}
Real output:
Got expected error: HTTP 501 - 501 Embeddings not supported by Apple's on-device model.
Lab script: 04_errors.mjs.
5. Tool calling
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "http://localhost:11434/v1", apiKey: "not-needed" });
const tools = [{
type: "function",
function: {
name: "get_weather",
description: "Get the current temperature in Celsius for a city.",
parameters: {
type: "object",
properties: { city: { type: "string", description: "City name" } },
required: ["city"],
},
},
}];
function getWeather({ city }) {
const fake = { Vienna: 14, Cupertino: 19, Tokyo: 11 };
return JSON.stringify({ city, temp_c: fake[city] ?? 15 });
}
const messages = [{ role: "user", content: "What is the temperature in Vienna right now?" }];
const first = await client.chat.completions.create({
model: "apple-foundationmodel", messages, tools, max_tokens: 256,
});
const msg = first.choices[0].message;
messages.push(msg);
if (msg.tool_calls?.length) {
for (const call of msg.tool_calls) {
const args = JSON.parse(call.function.arguments);
messages.push({ role: "tool", tool_call_id: call.id, content: getWeather(args) });
}
const final = await client.chat.completions.create({
model: "apple-foundationmodel", messages, max_tokens: 120,
});
console.log((final.choices[0].message.content || "").trim());
}
Real output:
The current temperature in Vienna is 14 degrees Celsius.
Lab script: 05_tools.mjs.
6. Real example - summarize stdin
import OpenAI from "openai";
const text = await new Promise((resolve) => {
let buf = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (c) => (buf += c));
process.stdin.on("end", () => resolve(buf.trim()));
});
if (!text) {
console.error("usage: cat file.txt | node 06_example.mjs");
process.exit(1);
}
const client = new OpenAI({ baseURL: "http://localhost:11434/v1", apiKey: "not-needed" });
const response = await client.chat.completions.create({
model: "apple-foundationmodel",
messages: [
{ role: "system", content: "You are a concise summarizer. Reply with one short paragraph." },
{ role: "user", content: `Summarize:\n\n${text}` },
],
max_tokens: 150,
});
console.log((response.choices[0].message.content || "").trim());
Real output (M1 paragraph):
The Apple M1 chip, released in November 2020, was Apple's first ARM-based system-on-a-chip for Mac computers. It uses an 8-core CPU with four performance and four efficiency cores, plus an integrated GPU with up to 8 cores. The chip unified CPU, GPU, memory, and neural engine on a single die, delivering significant performance-per-watt improvements over the Intel chips it replaced.
Lab script: 06_example.mjs.
Troubleshooting
ECONNREFUSED- startapfel --servebefore running your Node script.- Missing
choices[0]during streaming - handle the final usage chunk with theif (!chunk.choices || chunk.choices.length === 0) continue;guard above. - TypeScript - same code works;
npm install -D @types/nodefor Node types.
Tested with
- apfel v1.0.3 / macOS 26.3.1 Apple Silicon (original capture; the CLI and HTTP surfaces used here are release-gated by apfel's test suite on every version)
- Node.js v25.8.1 / openai 4.x
- Date: 2026-04-16
Runnable tests: tests/test_nodejs.py.