Cloud fallback
One call site — the local model in Napcar, your chosen cloud provider (OpenAI by default, or any OpenAI-compatible API) everywhere else.
The @napcar/sdk keeps one call site for AI in your app. Inside Napcar it
runs the local model through the trusted navigator.napcar.ai binding —
nothing leaves the machine. In every other browser it falls back to a cloud
provider you choose. You write the prompt once; the SDK picks the backend.
import { prompt } from "@napcar/sdk";
// In Napcar -> local model. Anywhere else -> OpenAI with your key.
const answer = await prompt("In one sentence, what is a napcar?", {
cloud: { provider: "openai", apiKey: OPENAI_API_KEY },
});How routing works
createClient({ cloud }) and the one-shot helpers all route the same way:
- Inside Napcar (
mode: "auto"and the native binding is present): generation runs locally. Thecloudconfig is ignored — cloud is a fallback only, never used when the local model is available. - Everywhere else: requests go to the configured
cloudprovider. With nocloudset, they go to the virtual endpointhttps://local.napcar.ai/v1.
An explicit endpoint or model always overrides the provider preset, so you
can pin either independently.
The one-shot helpers
The call_llm("…") shape: each helper opens a session, runs, and disposes it.
All take (input, opts?: PromptOptions).
import { prompt, generate, promptStreaming } from "@napcar/sdk";
const cloud = { provider: "openai", apiKey: OPENAI_API_KEY } as const;
// Just the text.
const text = await prompt("Summarize this page", { cloud });
// The full result: { text, model?, finishReason, usage? }.
const result = await generate("Summarize this page", { cloud });
// Streaming text deltas.
for await (const delta of promptStreaming("Write a haiku", { cloud })) {
appendToUI(delta);
}PromptOptions extends ClientOptions (mode, endpoint, model, handoff,
fetch, cloud) and adds per-call generation knobs: task (defaults to
"chat"), systemPrompt, temperature, maxOutputTokens, responseFormat,
and signal.
Keep an explicit client
For reused sessions, configure cloud on createClient — routing is identical:
import { createClient } from "@napcar/sdk";
const client = createClient({
cloud: { provider: "groq", apiKey: GROQ_API_KEY },
});
const session = await client.requestSession({ task: "chat" });
const { text, model } = await session.generate({ input: "Hello" });
console.log("local:", client.isNative, "model:", model);Provider presets
cloud.provider selects a built-in preset. Each is an OpenAI wire-compatible
endpoint (/chat/completions, /embeddings, SSE streaming), so streaming,
structured output, and embeddings all work unchanged. Override cloud.model
to pick a different model on the same provider.
| Provider | Base URL | Default model | API key |
|---|---|---|---|
openai | https://api.openai.com/v1 | gpt-4o-mini | Required |
openrouter | https://openrouter.ai/api/v1 | openai/gpt-4o-mini | Required |
groq | https://api.groq.com/openai/v1 | llama-3.3-70b-versatile | Required |
together | https://api.together.xyz/v1 | meta-llama/Llama-3.3-70B-Instruct-Turbo | Required |
mistral | https://api.mistral.ai/v1 | mistral-small-latest | Required |
deepseek | https://api.deepseek.com/v1 | deepseek-chat | Required |
ollama | http://localhost:11434/v1 | llama3.2 | None |
provider defaults to "openai", or to "custom" when you set baseUrl and
omit provider. The hosted providers require apiKey — resolveCloudConfig
throws a NapcarAIError with code permission_denied if it is missing.
ollama is a local OpenAI-compatible server, so no key is needed.
The exported CLOUD_PROVIDERS array lists these preset names (it excludes
"custom").
The custom provider
Point "custom" at any other OpenAI-compatible /v1 endpoint. Both baseUrl
and model are required — resolveCloudConfig throws a NapcarAIError
with code invalid_request otherwise.
createClient({
cloud: {
provider: "custom",
baseUrl: "https://api.example.com/v1",
model: "my-model",
apiKey,
},
});Extra headers
cloud.headers are merged into every request — useful for provider-specific
ranking headers such as OpenRouter's HTTP-Referer and X-Title:
createClient({
cloud: {
provider: "openrouter",
apiKey: OPENROUTER_API_KEY,
headers: {
"HTTP-Referer": "https://your-site.example",
"X-Title": "Your App",
},
},
});Under the hood the HTTP transport sends Authorization: Bearer <apiKey> plus
these headers, speaking the OpenAI wire format.
API keys and privacy
A cloud.apiKey placed in page JavaScript is visible to the user and to
anyone who can read the page. Only use a key in client code when it is either:
- a user-supplied bring-your-own-key (BYOK) value the user pastes in, or
- a same-origin backend proxy reached via
provider: "custom"+baseUrl.
For a server-held provider key, never ship the secret in client code. Point
baseUrl at your own backend proxy that injects the key:
createClient({
cloud: {
provider: "custom",
baseUrl: "https://your-site.example/api/llm", // your proxy adds the real key
model: "gpt-4o-mini",
},
});Local-in-Napcar generation never leaves the machine. The cloud fallback sends the prompt and content to a third-party provider — a real data-egress difference. Tell your users when generation happens in the cloud.
Related
- OpenAI-compatible endpoint — the local
virtual endpoint (
https://local.napcar.ai/v1) synthesized inside Napcar. - JavaScript API — the native
navigator.napcar.aisurface used in Napcar.