GlassKit
Add-ons

AI Showcase

Enable the AI Showcase add-on to wire Anthropic + OpenAI through the Vercel AI SDK, the useAi seam in the glasses app, and six sensor/AI demo apps that ship as references.

The AI Showcase add-on wires a single Convex action: api.ai.complete. It wraps the Vercel AI SDK with Anthropic and OpenAI providers, runs on the Convex side so your provider key never ships to the device, and is rate-limited per user.

Six example demo apps in app/src/apps/ all use the same seam through a useAi() hook. They demonstrate the platform's full capability surface (GPS / orientation / motion / Neural Band) paired with AI prompts. Fork the closest one to your product.

Off by default

AI Showcase is one of three opt-in add-ons (see Add-ons). The base boilerplate ships with no AI keys required and the glasses launcher empty, until you enable AI Showcase or add your own apps to the APPS registry in app/src/App.tsx.

Enable

pnpm run addon enable ai

This single command:

  • renames .disabled files for convex/ai.ts, app/src/lib/useAi.ts, app/src/lib/AiResponse.tsx, plus the six demo apps in app/src/apps/
  • inserts the AI env schema (AI_PROVIDER, ANTHROPIC_API_KEY or OPENAI_API_KEY, optional AI_MODEL) in env.ts
  • inserts the demo-app imports + APPS registry entries in app/src/App.tsx so all six show up in the launcher

Then pick a provider and push its key to Convex:

cd packages/backend
pnpm exec convex env set AI_PROVIDER anthropic    # or `openai`
pnpm exec convex env set ANTHROPIC_API_KEY sk-ant-...    # or OPENAI_API_KEY=sk-...
pnpm exec convex dev --once

pnpm run addon disable ai reverses everything (renames files back to .disabled, removes the demo apps from the launcher). Drop the env keys with pnpm exec convex env remove ….

What ships in the box

FileWhat it does
packages/backend/convex/ai.tsThe complete action: picks provider, calls generateText, returns text
packages/backend/convex/rateLimiter.tsaiPrompt token bucket + consumeAiPrompt mutation
app/src/lib/useAi.tsReact hook: ask(prompt, opts) returns text + loading + error
app/src/lib/AiResponse.tsxRenders the hook's state (placeholder → loading → text or error)

Why a server-side action

Provider keys must not ship to the device

The action keeps keys on the Convex side; the glasses app calls useAction(api.ai.complete) and gets text back. The same seam works from the companion site; both apps share one Convex deployment.

This also lets Convex enforce rate limits per user (via the @convex-dev/rate-limiter component) before paying a provider for the call. See rateLimiter.ts's aiPrompt token bucket.

Configuring the provider

Set in the Convex dashboard (Settings → Environment Variables). Convex functions run in Convex's cloud, not from a local .env:

Convex environment variables
AI_PROVIDER=anthropic        # or "openai"

# Pick one — the action chooses based on AI_PROVIDER above:
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...

# Optional — override the specific model. Defaults to a cheap fast one:
#   anthropic → claude-3-5-haiku-latest
#   openai    → gpt-4o-mini
AI_MODEL=

Swap providers by changing AI_PROVIDER. No code changes needed.

Calling the seam

app/src/apps/MyApp.tsx
import { useAi } from "../lib/useAi";
import { AiResponse } from "../lib/AiResponse";

function MyApp() {
  const ai = useAi();
  return (
    <>
      <button
        className="focusable"
        onClick={() => ai.ask("Write a haiku about glasses.")}
        disabled={ai.loading}
      >
        {ai.loading ? "Thinking…" : "Generate"}
      </button>
      <AiResponse ai={ai} placeholder="Press Generate." />
    </>
  );
}

ai.ask takes an optional second argument for system and maxTokens:

ai.ask(prompt, {
  system: "You are a concise navigation assistant.",
  maxTokens: 80,
});

The same action: just useAction it from a client component:

companion/app/something/page.tsx
"use client";
import { useAction } from "convex/react";
import { api } from "@glasskit/backend/convex/_generated/api";

export function Generate() {
  const complete = useAction(api.ai.complete);
  // …
}

Examples in the box

The six example apps each pair a sensor mix with one AI prompt. Copy the pattern:

AppSensorsPrompt shape
AI WayfinderGPS + orientationOne-line walking direction to nearest landmark
Tour GuideGPS + orientationOne fascinating fact about your location
Running CoachMotion + timerLive pacing / form cue
Sous-ChefNeural Band pinchElaborate on the current recipe step
Presentation CoachNeural Band pinchScore pace given words / elapsed time
Trail CompanionGPS + motionSafety + pacing note for the terrain

Open any of them in app/src/apps/ to see the full call.

Extending: streaming

The shipped action uses generateText (one-shot). For long responses you'll want streaming. Replace generateText with streamText and return a stream from an HTTP route on the Convex side, then consume it on the client with the AI SDK's useChat hook.

A streaming complete action in convex/ai.ts:

packages/backend/convex/ai.ts
import { streamText } from "ai";
// in an httpAction:
const result = streamText({ model: model(), prompt, system });
return result.toDataStreamResponse();

The companion side then uses useChat({ api: "/your-route" }) to consume the stream. See the Vercel AI SDK docs.

Extending: structured output

import { generateObject } from "ai";
import { z } from "zod";

const { object } = await generateObject({
  model: model(),
  schema: z.object({
    title: z.string(),
    tags: z.array(z.string()).max(5),
  }),
  prompt,
});

Significantly more reliable than asking for JSON in the prompt and parsing it manually: the SDK does retries on schema failures.

Extending: tool use

import { generateText, tool } from "ai";
import { z } from "zod";

const { text } = await generateText({
  model: model(),
  prompt,
  tools: {
    getWeather: tool({
      description: "Get the current weather for a city",
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => ({ city, tempC: 22 }),
    }),
  },
  // Allow the model to call tools then synthesize:
  maxSteps: 5,
});

If you need to call a Convex query/mutation from a tool, do it via ctx.runQuery / ctx.runMutation inside the tool's execute.

Cost discipline

Defaults are tuned for cheap

  • Use the smallest model that works. The defaults (claude-3-5-haiku-latest, gpt-4o-mini) are 30–60× cheaper than the flagship models on most tasks.
  • Cap maxOutputTokens. The shipped action defaults to 220, so a runaway generation can't bankrupt you.
  • Rate-limit per user. Already done via aiPrompt in rateLimiter.ts. Adjust the bucket if your app fires more.
  • Cache prompts. Anthropic supports prompt caching; OpenAI has prompt caching on certain endpoints. Useful when system prompts are stable and long.

Provider-specific features

If you need a provider-specific feature the AI SDK hasn't surfaced yet (Anthropic extended thinking, OpenAI o1 reasoning budgets, etc.), drop down to the native SDK. Both @ai-sdk/anthropic and @ai-sdk/openai are already installed; you can also pnpm add @anthropic-ai/sdk (or openai) directly and call them from a Convex action exactly the same way.

On this page