Quickstart

Try It Now

Run a complete call chain with the default database, Admin City, and User City.

1. Start City

import {
  Federation,
  AIService,
  Provider,
  type Context,
  type AIProviderChargedOutput,
  type AIProviderChargedResponse,
} from "@downcity/city";
import type { UIMessage } from "ai";
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";

const sqlite = new Database("./data.sqlite");
sqlite.pragma("journal_mode = WAL");

const db = Object.assign(drizzle(sqlite), {
  $client: { exec: (sql: string) => sqlite.exec(sql) },
});

const base = new Federation({ db });

class EchoProvider extends Provider {
  constructor() {
    super({ id: "echo" });
  }

  async text(ctx: Context): Promise<AIProviderChargedOutput<UIMessage>> {
    return {
      output: {
        id: crypto.randomUUID(),
        role: "assistant",
        parts: [{ type: "text", text: `Echo: ${ctx.input.prompt}` }],
      },
    };
  }

  async stream(ctx: Context): Promise<AIProviderChargedResponse> {
    const { createUIMessageStreamResponse } = await import("ai");
    return {
      response: createUIMessageStreamResponse({
        execute: async ({ writer }) => {
          writer.write({ type: "text-delta", textDelta: `Echo: ${ctx.input.prompt}` });
        },
      }),
    };
  }
}

const echo = new EchoProvider();

const ai = new AIService();
ai.use(echo.model({ id: "local-echo", name: "Local Echo", default: true }));
base.use(ai);

await base.health();
serve({ fetch: base.router().fetch, port: 43127, hostname: "127.0.0.1" });

2. Create City & Token

const admin = new City({
  role: "admin",
  federation_url: "http://127.0.0.1:43127",
  city_id: "city_demo",
  admin_secret_key: process.env.DOWNCITY_FEDERATION_ADMIN_SECRET_KEY,
});
const token = await admin.tokens.apply({
  user_id: "user_123",
  ttl: "7d",
});

3. Client Call

const client = new City({
  role: "user",
  federation_url: "http://127.0.0.1:43127",
  city_id: token.city_id,
  user_token: token.user_token,
});

// SDK pathway
const result = await client.ai.text({ prompt: "Hello" });

// OpenAI-compatible pathway
// POST /v1/ai/chat/completions

Next Steps