Napcar

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. The cloud config is ignored — cloud is a fallback only, never used when the local model is available.
  • Everywhere else: requests go to the configured cloud provider. With no cloud set, they go to the virtual endpoint https://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.

ProviderBase URLDefault modelAPI key
openaihttps://api.openai.com/v1gpt-4o-miniRequired
openrouterhttps://openrouter.ai/api/v1openai/gpt-4o-miniRequired
groqhttps://api.groq.com/openai/v1llama-3.3-70b-versatileRequired
togetherhttps://api.together.xyz/v1meta-llama/Llama-3.3-70B-Instruct-TurboRequired
mistralhttps://api.mistral.ai/v1mistral-small-latestRequired
deepseekhttps://api.deepseek.com/v1deepseek-chatRequired
ollamahttp://localhost:11434/v1llama3.2None

provider defaults to "openai", or to "custom" when you set baseUrl and omit provider. The hosted providers require apiKeyresolveCloudConfig 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 requiredresolveCloudConfig 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.