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