How Bot Configurations Are Stored and Retrieved in y-gui: A Complete Technical Guide

y-gui stores bot configurations as JSON documents in a Cloudflare D1 SQLite database, using the BotD1Repository class to handle CRUD operations while isolating data per user through prefixed keys.

The y-gui system manages AI bot settings through a robust persistence layer that ensures each user maintains independent control over their model configurations. Understanding how bot configurations are stored and retrieved is essential for developers extending the platform or integrating custom models. This guide examines the complete data flow from type definitions to API endpoints based on the actual implementation in the luohy15/y-gui repository.

Bot Configuration Data Structure

The BotConfig Interface

All bot configurations in y-gui conform to the BotConfig interface defined in shared/types/index.ts. This TypeScript interface establishes the contract for bot settings across the entire application stack.

// shared/types/index.ts
export interface BotConfig {
  name: string;
  model: string;
  base_url?: string;
  api_key?: string;
  openrouter_config?: Record<string, any>;
  api_type?: string;
  custom_api_path?: string;
  max_tokens?: number;
}

The interface supports various provider configurations through optional fields like openrouter_config and custom_api_path, allowing the system to accommodate both standard OpenAI-compatible endpoints and specialized routing requirements.

Database Storage Architecture

Cloudflare D1 SQLite Table Structure

y-gui persists bot configurations using Cloudflare's D1 database service, which provides SQLite-compatible storage at the edge. The BotD1Repository class in backend/src/repository/d1/bot-d1-repository.ts manages all database interactions.

The system stores each bot as a row in the bot table with three columns:

  • user_prefix: A string identifying the user namespace
  • name: The bot identifier within that namespace
  • json_content: The stringified JSON representation of the BotConfig object

User Isolation Through Prefix Keys

To ensure multi-tenant isolation, y-gui prepends a user-specific prefix to all database operations. When instantiating BotD1Repository, the constructor receives a userPrefix parameter that scopes all subsequent queries:

// Conceptual usage within the repository
await this.db.prepare(
  "INSERT INTO bot (user_prefix, name, json_content) VALUES (?, ?, ?)"
).bind(this.userPrefix, bot.name, JSON.stringify(bot)).run();

This design prevents users from accessing or modifying other users' bot configurations while allowing the same underlying table structure to serve all tenants.

CRUD Operations with BotD1Repository

Adding and Updating Bot Configurations

The BotD1Repository class provides addBot() for creating new configurations and implicit update functionality through SQL UPDATE statements. When adding a bot, the method serializes the BotConfig object to JSON and inserts it into the D1 table:

// backend/src/repository/d1/bot-d1-repository.ts
await this.db.prepare(
  "INSERT INTO bot (user_prefix, name, json_content) VALUES (?, ?, ?)"
).bind(this.userPrefix, bot.name, JSON.stringify(bot)).run();

For updates, the repository uses json_extract to locate the specific bot by name within the JSON content:

await this.db.prepare(
  "UPDATE bot SET json_content = ? WHERE user_prefix = ? AND json_extract(json_content, '$.name') = ?"
).bind(JSON.stringify(bot), this.userPrefix, bot.name).run();

Retrieving Bot Configurations

The getBots() method queries all configurations for the current user prefix and deserializes the JSON content back into BotConfig objects:

const result = await this.db.prepare(
  "SELECT json_content FROM bot WHERE user_prefix = ?"
).bind(this.userPrefix).all();

return result.results.map(row => JSON.parse(row.json_content));

Default Bot Fallback Mechanism

If the database query returns no rows or encounters an error, BotD1Repository ensures the UI remains functional by injecting a default bot configuration. The getDefaultBots() function returns a hardcoded fallback:

// backend/src/repository/d1/bot-d1-repository.ts
function getDefaultBots(): BotConfig[] {
  return [{ name: "default", model: "google/gemini-3-flash-preview" }];
}

This guarantees that users always have at least one usable bot even before creating custom configurations.

API Endpoints for Bot Configuration Access

GET /api/bots Endpoint

The HTTP layer exposes bot retrieval through the /api/bots route defined in backend/src/api/bot.ts. This endpoint instantiates BotD1Repository with the current user's prefix and returns the serialized bot list:

// backend/src/api/bot.ts
router.get('/api/bots', async (request, env) => {
  const botRepo = new BotD1Repository(env.CHAT_DB, env, userPrefix);
  const bots = await botRepo.getBots();
  return new Response(JSON.stringify(bots), { headers: { 'Content-Type': 'application/json' } });
});

POST /api/bot Endpoint

Creating or updating bots occurs through the /api/bot endpoint. The handler parses the incoming JSON body into a BotConfig object and delegates persistence to the repository:

// backend/src/api/bot.ts
router.post('/api/bot', async (request, env) => {
  const botConfig = await request.json() as BotConfig;
  const botRepo = new BotD1Repository(env.CHAT_DB, env, userPrefix);
  await botRepo.addBot(botConfig);
  return new Response(JSON.stringify({ success: true }), { headers: { 'Content-Type': 'application/json' } });
});

Both routes rely on the userPrefix extracted from the request context to ensure proper data isolation.

Runtime Provider Instantiation

ProviderFactory Integration

When a chat request requires model interaction, y-gui converts the stored BotConfig into an active provider through the ProviderFactory class located in backend/src/providers/provider-factory.ts. This factory receives the configuration object and returns an appropriate provider instance:

// backend/src/providers/provider-factory.ts
const provider = ProviderFactory.createProvider(botConfig);

OpenAIFormatProvider Usage

Currently, the factory instantiates OpenAIFormatProvider for all bot configurations. This provider, defined in backend/src/providers/openai-format-provider.ts, translates the generic BotConfig fields into OpenAI-compatible API requests:

// Conceptual flow
const provider = new OpenAIFormatProvider(botConfig);
const response = await provider.chat(messages);

The provider uses the base_url, api_key, and model fields from the BotConfig to construct the HTTP request to the external model endpoint.

Summary

  • JSON Document Storage: y-gui stores bot configurations as serialized JSON in a Cloudflare D1 SQLite database, with each row containing a user prefix, bot name, and JSON content.

  • User Isolation: The BotD1Repository class enforces multi-tenancy by binding all queries to a specific userPrefix, ensuring users cannot access other users' bot configurations.

  • Default Fallback: When no bots exist in the database, the system automatically provides a default Gemini 3 configuration to ensure the UI remains functional.

  • Repository Pattern: All CRUD operations flow through BotD1Repository in backend/src/repository/d1/bot-d1-repository.ts, which handles SQL generation and JSON serialization.

  • API Exposure: HTTP endpoints in backend/src/api/bot.ts expose bot management through /api/bots (retrieval) and /api/bot (creation/updates).

  • Runtime Instantiation: The ProviderFactory converts stored BotConfig objects into active OpenAIFormatProvider instances capable of executing chat requests.

Frequently Asked Questions

How does y-gui ensure that users cannot see each other's bot configurations?

y-gui implements user isolation through the userPrefix parameter passed to BotD1Repository. Every database query includes a WHERE user_prefix = ? clause that binds to the specific user's namespace. This design ensures that SQL operations in backend/src/repository/d1/bot-d1-repository.ts only return rows matching the requesting user's prefix, effectively creating a multi-tenant environment within a single D1 table.

What happens if a user has not created any bot configurations yet?

If the database query in BotD1Repository.getBots() returns no rows or encounters an error, the system invokes getDefaultBots() to provide a fallback configuration. This function returns a hardcoded array containing a default bot named "default" using the google/gemini-3-flash-preview model. This ensures that the frontend UI always has at least one usable bot available, preventing empty-state errors in the chat interface.

Can y-gui support bot configurations for non-OpenAI API formats?

While the current implementation in backend/src/providers/provider-factory.ts exclusively returns OpenAIFormatProvider instances, the architecture supports future expansion. The BotConfig interface in shared/types/index.ts includes optional fields like api_type and custom_api_path that could enable factory methods to instantiate different provider classes. Developers could extend ProviderFactory.createProvider() to check these fields and return specialized providers for Anthropic, Google, or other API formats while using the same BotConfig storage mechanism.

How are bot configurations validated before being stored in the database?

The current implementation relies on TypeScript interfaces rather than runtime validation schemas. When a bot configuration arrives via the POST /api/bot endpoint in backend/src/api/bot.ts, the handler casts the JSON body directly to BotConfig using await request.json() as BotConfig. The database layer then serializes this object to JSON without additional validation. This approach assumes the frontend or client provides well-formed data matching the interface defined in shared/types/index.ts. For production hardening, developers could add JSON Schema validation or Zod parsing at the API boundary before the repository layer persists the configuration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →