fix route grouping and codex websocket regressions
This commit is contained in:
@@ -8,8 +8,9 @@ CREATE TABLE IF NOT EXISTS `model_availability` (`id` INT AUTO_INCREMENT NOT NUL
|
||||
CREATE TABLE IF NOT EXISTS `proxy_files` (`id` INT AUTO_INCREMENT NOT NULL PRIMARY KEY, `public_id` TEXT NOT NULL, `owner_type` TEXT NOT NULL, `owner_id` TEXT NOT NULL, `filename` TEXT NOT NULL, `mime_type` TEXT NOT NULL, `purpose` TEXT, `byte_size` INT NOT NULL, `sha256` TEXT NOT NULL, `content_base64` TEXT NOT NULL, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `deleted_at` DATETIME);
|
||||
CREATE TABLE IF NOT EXISTS `proxy_logs` (`id` INT AUTO_INCREMENT NOT NULL PRIMARY KEY, `route_id` INT, `channel_id` INT, `account_id` INT, `model_requested` TEXT, `model_actual` TEXT, `status` TEXT, `http_status` INT, `latency_ms` INT, `prompt_tokens` INT, `completion_tokens` INT, `total_tokens` INT, `estimated_cost` DOUBLE, `error_message` TEXT, `retry_count` INT DEFAULT 0, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `billing_details` JSON, `downstream_api_key_id` INT);
|
||||
CREATE TABLE IF NOT EXISTS `proxy_video_tasks` (`id` INT AUTO_INCREMENT NOT NULL PRIMARY KEY, `public_id` TEXT NOT NULL, `upstream_video_id` TEXT NOT NULL, `site_url` TEXT NOT NULL, `token_value` TEXT NOT NULL, `requested_model` TEXT, `actual_model` TEXT, `channel_id` INT, `account_id` INT, `status_snapshot` JSON, `upstream_response_meta` JSON, `last_upstream_status` INT, `last_polled_at` DATETIME, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP);
|
||||
CREATE TABLE IF NOT EXISTS `token_routes` (`id` INT AUTO_INCREMENT NOT NULL PRIMARY KEY, `model_pattern` TEXT NOT NULL, `model_mapping` JSON, `enabled` BOOLEAN DEFAULT true, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `display_name` TEXT, `display_icon` TEXT, `decision_snapshot` JSON, `decision_refreshed_at` DATETIME, `routing_strategy` VARCHAR(191) DEFAULT 'weighted');
|
||||
CREATE TABLE IF NOT EXISTS `token_routes` (`id` INT AUTO_INCREMENT NOT NULL PRIMARY KEY, `model_pattern` TEXT NOT NULL, `model_mapping` JSON, `enabled` BOOLEAN DEFAULT true, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `display_name` TEXT, `display_icon` TEXT, `decision_snapshot` JSON, `decision_refreshed_at` DATETIME, `routing_strategy` VARCHAR(191) DEFAULT 'weighted', `route_mode` VARCHAR(191) DEFAULT 'pattern');
|
||||
CREATE TABLE IF NOT EXISTS `route_channels` (`id` INT AUTO_INCREMENT NOT NULL PRIMARY KEY, `route_id` INT NOT NULL, `account_id` INT NOT NULL, `token_id` INT, `source_model` TEXT, `priority` INT DEFAULT 0, `weight` INT DEFAULT 10, `enabled` BOOLEAN DEFAULT true, `manual_override` BOOLEAN DEFAULT false, `success_count` INT DEFAULT 0, `fail_count` INT DEFAULT 0, `total_latency_ms` INT DEFAULT 0, `total_cost` DOUBLE DEFAULT 0, `last_used_at` DATETIME, `last_selected_at` DATETIME, `last_fail_at` DATETIME, `consecutive_fail_count` INT NOT NULL DEFAULT 0, `cooldown_level` INT NOT NULL DEFAULT 0, `cooldown_until` DATETIME, FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE CASCADE, FOREIGN KEY (`route_id`) REFERENCES `token_routes`(`id`) ON DELETE CASCADE, FOREIGN KEY (`token_id`) REFERENCES `account_tokens`(`id`) ON DELETE SET NULL);
|
||||
CREATE TABLE IF NOT EXISTS `route_group_sources` (`id` INT AUTO_INCREMENT NOT NULL PRIMARY KEY, `group_route_id` INT NOT NULL, `source_route_id` INT NOT NULL, FOREIGN KEY (`group_route_id`) REFERENCES `token_routes`(`id`) ON DELETE CASCADE, FOREIGN KEY (`source_route_id`) REFERENCES `token_routes`(`id`) ON DELETE CASCADE);
|
||||
CREATE TABLE IF NOT EXISTS `settings` (`key` VARCHAR(191) NOT NULL PRIMARY KEY, `value` TEXT);
|
||||
CREATE TABLE IF NOT EXISTS `site_disabled_models` (`id` INT AUTO_INCREMENT NOT NULL PRIMARY KEY, `site_id` INT NOT NULL, `model_name` TEXT NOT NULL, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`site_id`) REFERENCES `sites`(`id`) ON DELETE CASCADE);
|
||||
CREATE TABLE IF NOT EXISTS `token_model_availability` (`id` INT AUTO_INCREMENT NOT NULL PRIMARY KEY, `token_id` INT NOT NULL, `model_name` TEXT NOT NULL, `available` BOOLEAN, `latency_ms` INT, `checked_at` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`token_id`) REFERENCES `account_tokens`(`id`) ON DELETE CASCADE);
|
||||
@@ -17,6 +18,7 @@ CREATE UNIQUE INDEX `downstream_api_keys_key_unique` ON `downstream_api_keys` (`
|
||||
CREATE UNIQUE INDEX `model_availability_account_model_unique` ON `model_availability` (`account_id`, `model_name`(191));
|
||||
CREATE UNIQUE INDEX `proxy_files_public_id_unique` ON `proxy_files` (`public_id`(191));
|
||||
CREATE UNIQUE INDEX `proxy_video_tasks_public_id_unique` ON `proxy_video_tasks` (`public_id`(191));
|
||||
CREATE UNIQUE INDEX `route_group_sources_group_source_unique` ON `route_group_sources` (`group_route_id`, `source_route_id`);
|
||||
CREATE UNIQUE INDEX `site_disabled_models_site_model_unique` ON `site_disabled_models` (`site_id`, `model_name`(191));
|
||||
CREATE UNIQUE INDEX `sites_platform_url_unique` ON `sites` (`platform`(191), `url`(191));
|
||||
CREATE UNIQUE INDEX `token_model_availability_token_model_unique` ON `token_model_availability` (`token_id`, `model_name`(191));
|
||||
@@ -52,6 +54,7 @@ CREATE INDEX `route_channels_route_enabled_idx` ON `route_channels` (`route_id`,
|
||||
CREATE INDEX `route_channels_route_id_idx` ON `route_channels` (`route_id`);
|
||||
CREATE INDEX `route_channels_route_token_idx` ON `route_channels` (`route_id`, `token_id`);
|
||||
CREATE INDEX `route_channels_token_id_idx` ON `route_channels` (`token_id`);
|
||||
CREATE INDEX `route_group_sources_source_route_id_idx` ON `route_group_sources` (`source_route_id`);
|
||||
CREATE INDEX `site_disabled_models_site_id_idx` ON `site_disabled_models` (`site_id`);
|
||||
CREATE INDEX `sites_status_idx` ON `sites` (`status`(191));
|
||||
CREATE INDEX `token_model_availability_available_idx` ON `token_model_availability` (`available`);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
ALTER TABLE `accounts` ADD COLUMN `oauth_provider` TEXT;
|
||||
ALTER TABLE `accounts` ADD COLUMN `oauth_account_key` TEXT;
|
||||
ALTER TABLE `accounts` ADD COLUMN `oauth_project_id` TEXT;
|
||||
CREATE UNIQUE INDEX `sites_platform_url_unique` ON `sites` (`platform`(191), `url`(191));
|
||||
CREATE INDEX `accounts_oauth_identity_idx` ON `accounts` (`oauth_provider`(191), `oauth_account_key`(191), `oauth_project_id`(191));
|
||||
CREATE INDEX `accounts_oauth_provider_idx` ON `accounts` (`oauth_provider`(191));
|
||||
CREATE TABLE IF NOT EXISTS `route_group_sources` (`id` INT AUTO_INCREMENT NOT NULL PRIMARY KEY, `group_route_id` INT NOT NULL, `source_route_id` INT NOT NULL, FOREIGN KEY (`group_route_id`) REFERENCES `token_routes`(`id`) ON DELETE CASCADE, FOREIGN KEY (`source_route_id`) REFERENCES `token_routes`(`id`) ON DELETE CASCADE);
|
||||
ALTER TABLE `token_routes` ADD COLUMN `route_mode` VARCHAR(191) DEFAULT 'pattern';
|
||||
CREATE UNIQUE INDEX `route_group_sources_group_source_unique` ON `route_group_sources` (`group_route_id`, `source_route_id`);
|
||||
CREATE INDEX `route_group_sources_source_route_id_idx` ON `route_group_sources` (`source_route_id`);
|
||||
|
||||
@@ -8,8 +8,9 @@ CREATE TABLE IF NOT EXISTS "model_availability" ("id" INTEGER GENERATED BY DEFAU
|
||||
CREATE TABLE IF NOT EXISTS "proxy_files" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "public_id" TEXT NOT NULL, "owner_type" TEXT NOT NULL, "owner_id" TEXT NOT NULL, "filename" TEXT NOT NULL, "mime_type" TEXT NOT NULL, "purpose" TEXT, "byte_size" INTEGER NOT NULL, "sha256" TEXT NOT NULL, "content_base64" TEXT NOT NULL, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "deleted_at" TIMESTAMP);
|
||||
CREATE TABLE IF NOT EXISTS "proxy_logs" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "route_id" INTEGER, "channel_id" INTEGER, "account_id" INTEGER, "model_requested" TEXT, "model_actual" TEXT, "status" TEXT, "http_status" INTEGER, "latency_ms" INTEGER, "prompt_tokens" INTEGER, "completion_tokens" INTEGER, "total_tokens" INTEGER, "estimated_cost" DOUBLE PRECISION, "error_message" TEXT, "retry_count" INTEGER DEFAULT 0, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "billing_details" JSONB, "downstream_api_key_id" INTEGER);
|
||||
CREATE TABLE IF NOT EXISTS "proxy_video_tasks" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "public_id" TEXT NOT NULL, "upstream_video_id" TEXT NOT NULL, "site_url" TEXT NOT NULL, "token_value" TEXT NOT NULL, "requested_model" TEXT, "actual_model" TEXT, "channel_id" INTEGER, "account_id" INTEGER, "status_snapshot" JSONB, "upstream_response_meta" JSONB, "last_upstream_status" INTEGER, "last_polled_at" TIMESTAMP, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
|
||||
CREATE TABLE IF NOT EXISTS "token_routes" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "model_pattern" TEXT NOT NULL, "model_mapping" JSONB, "enabled" BOOLEAN DEFAULT true, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "display_name" TEXT, "display_icon" TEXT, "decision_snapshot" JSONB, "decision_refreshed_at" TIMESTAMP, "routing_strategy" TEXT DEFAULT 'weighted');
|
||||
CREATE TABLE IF NOT EXISTS "token_routes" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "model_pattern" TEXT NOT NULL, "model_mapping" JSONB, "enabled" BOOLEAN DEFAULT true, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "display_name" TEXT, "display_icon" TEXT, "decision_snapshot" JSONB, "decision_refreshed_at" TIMESTAMP, "routing_strategy" TEXT DEFAULT 'weighted', "route_mode" TEXT DEFAULT 'pattern');
|
||||
CREATE TABLE IF NOT EXISTS "route_channels" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "route_id" INTEGER NOT NULL, "account_id" INTEGER NOT NULL, "token_id" INTEGER, "source_model" TEXT, "priority" INTEGER DEFAULT 0, "weight" INTEGER DEFAULT 10, "enabled" BOOLEAN DEFAULT true, "manual_override" BOOLEAN DEFAULT false, "success_count" INTEGER DEFAULT 0, "fail_count" INTEGER DEFAULT 0, "total_latency_ms" INTEGER DEFAULT 0, "total_cost" DOUBLE PRECISION DEFAULT 0, "last_used_at" TIMESTAMP, "last_selected_at" TIMESTAMP, "last_fail_at" TIMESTAMP, "consecutive_fail_count" INTEGER NOT NULL DEFAULT 0, "cooldown_level" INTEGER NOT NULL DEFAULT 0, "cooldown_until" TIMESTAMP, FOREIGN KEY ("account_id") REFERENCES "accounts"("id") ON DELETE CASCADE, FOREIGN KEY ("route_id") REFERENCES "token_routes"("id") ON DELETE CASCADE, FOREIGN KEY ("token_id") REFERENCES "account_tokens"("id") ON DELETE SET NULL);
|
||||
CREATE TABLE IF NOT EXISTS "route_group_sources" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "group_route_id" INTEGER NOT NULL, "source_route_id" INTEGER NOT NULL, FOREIGN KEY ("group_route_id") REFERENCES "token_routes"("id") ON DELETE CASCADE, FOREIGN KEY ("source_route_id") REFERENCES "token_routes"("id") ON DELETE CASCADE);
|
||||
CREATE TABLE IF NOT EXISTS "settings" ("key" TEXT NOT NULL PRIMARY KEY, "value" TEXT);
|
||||
CREATE TABLE IF NOT EXISTS "site_disabled_models" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "site_id" INTEGER NOT NULL, "model_name" TEXT NOT NULL, "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY ("site_id") REFERENCES "sites"("id") ON DELETE CASCADE);
|
||||
CREATE TABLE IF NOT EXISTS "token_model_availability" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "token_id" INTEGER NOT NULL, "model_name" TEXT NOT NULL, "available" BOOLEAN, "latency_ms" INTEGER, "checked_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY ("token_id") REFERENCES "account_tokens"("id") ON DELETE CASCADE);
|
||||
@@ -17,6 +18,7 @@ CREATE UNIQUE INDEX "downstream_api_keys_key_unique" ON "downstream_api_keys" ("
|
||||
CREATE UNIQUE INDEX "model_availability_account_model_unique" ON "model_availability" ("account_id", "model_name");
|
||||
CREATE UNIQUE INDEX "proxy_files_public_id_unique" ON "proxy_files" ("public_id");
|
||||
CREATE UNIQUE INDEX "proxy_video_tasks_public_id_unique" ON "proxy_video_tasks" ("public_id");
|
||||
CREATE UNIQUE INDEX "route_group_sources_group_source_unique" ON "route_group_sources" ("group_route_id", "source_route_id");
|
||||
CREATE UNIQUE INDEX "site_disabled_models_site_model_unique" ON "site_disabled_models" ("site_id", "model_name");
|
||||
CREATE UNIQUE INDEX "sites_platform_url_unique" ON "sites" ("platform", "url");
|
||||
CREATE UNIQUE INDEX "token_model_availability_token_model_unique" ON "token_model_availability" ("token_id", "model_name");
|
||||
@@ -52,6 +54,7 @@ CREATE INDEX "route_channels_route_enabled_idx" ON "route_channels" ("route_id",
|
||||
CREATE INDEX "route_channels_route_id_idx" ON "route_channels" ("route_id");
|
||||
CREATE INDEX "route_channels_route_token_idx" ON "route_channels" ("route_id", "token_id");
|
||||
CREATE INDEX "route_channels_token_id_idx" ON "route_channels" ("token_id");
|
||||
CREATE INDEX "route_group_sources_source_route_id_idx" ON "route_group_sources" ("source_route_id");
|
||||
CREATE INDEX "site_disabled_models_site_id_idx" ON "site_disabled_models" ("site_id");
|
||||
CREATE INDEX "sites_status_idx" ON "sites" ("status");
|
||||
CREATE INDEX "token_model_availability_available_idx" ON "token_model_availability" ("available");
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
ALTER TABLE "accounts" ADD COLUMN "oauth_provider" TEXT;
|
||||
ALTER TABLE "accounts" ADD COLUMN "oauth_account_key" TEXT;
|
||||
ALTER TABLE "accounts" ADD COLUMN "oauth_project_id" TEXT;
|
||||
CREATE UNIQUE INDEX "sites_platform_url_unique" ON "sites" ("platform", "url");
|
||||
CREATE INDEX "accounts_oauth_identity_idx" ON "accounts" ("oauth_provider", "oauth_account_key", "oauth_project_id");
|
||||
CREATE INDEX "accounts_oauth_provider_idx" ON "accounts" ("oauth_provider");
|
||||
CREATE TABLE IF NOT EXISTS "route_group_sources" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "group_route_id" INTEGER NOT NULL, "source_route_id" INTEGER NOT NULL, FOREIGN KEY ("group_route_id") REFERENCES "token_routes"("id") ON DELETE CASCADE, FOREIGN KEY ("source_route_id") REFERENCES "token_routes"("id") ON DELETE CASCADE);
|
||||
ALTER TABLE "token_routes" ADD COLUMN "route_mode" TEXT DEFAULT 'pattern';
|
||||
CREATE UNIQUE INDEX "route_group_sources_group_source_unique" ON "route_group_sources" ("group_route_id", "source_route_id");
|
||||
CREATE INDEX "route_group_sources_source_route_id_idx" ON "route_group_sources" ("source_route_id");
|
||||
|
||||
@@ -868,6 +868,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"route_group_sources": {
|
||||
"columns": {
|
||||
"id": {
|
||||
"logicalType": "integer",
|
||||
"notNull": true,
|
||||
"defaultValue": null,
|
||||
"primaryKey": true
|
||||
},
|
||||
"group_route_id": {
|
||||
"logicalType": "integer",
|
||||
"notNull": true,
|
||||
"defaultValue": null,
|
||||
"primaryKey": false
|
||||
},
|
||||
"source_route_id": {
|
||||
"logicalType": "integer",
|
||||
"notNull": true,
|
||||
"defaultValue": null,
|
||||
"primaryKey": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"columns": {
|
||||
"key": {
|
||||
@@ -1113,6 +1135,12 @@
|
||||
"notNull": false,
|
||||
"defaultValue": "'weighted'",
|
||||
"primaryKey": false
|
||||
},
|
||||
"route_mode": {
|
||||
"logicalType": "text",
|
||||
"notNull": false,
|
||||
"defaultValue": "'pattern'",
|
||||
"primaryKey": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1423,6 +1451,23 @@
|
||||
],
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"name": "route_group_sources_group_source_unique",
|
||||
"table": "route_group_sources",
|
||||
"columns": [
|
||||
"group_route_id",
|
||||
"source_route_id"
|
||||
],
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"name": "route_group_sources_source_route_id_idx",
|
||||
"table": "route_group_sources",
|
||||
"columns": [
|
||||
"source_route_id"
|
||||
],
|
||||
"unique": false
|
||||
},
|
||||
{
|
||||
"name": "site_disabled_models_site_id_idx",
|
||||
"table": "site_disabled_models",
|
||||
@@ -1538,6 +1583,14 @@
|
||||
"public_id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "route_group_sources_group_source_unique",
|
||||
"table": "route_group_sources",
|
||||
"columns": [
|
||||
"group_route_id",
|
||||
"source_route_id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "site_disabled_models_site_model_unique",
|
||||
"table": "site_disabled_models",
|
||||
@@ -1641,6 +1694,28 @@
|
||||
],
|
||||
"onDelete": "SET NULL"
|
||||
},
|
||||
{
|
||||
"table": "route_group_sources",
|
||||
"columns": [
|
||||
"group_route_id"
|
||||
],
|
||||
"referencedTable": "token_routes",
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "CASCADE"
|
||||
},
|
||||
{
|
||||
"table": "route_group_sources",
|
||||
"columns": [
|
||||
"source_route_id"
|
||||
],
|
||||
"referencedTable": "token_routes",
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "CASCADE"
|
||||
},
|
||||
{
|
||||
"table": "site_disabled_models",
|
||||
"columns": [
|
||||
|
||||
@@ -27,6 +27,7 @@ const TABLES_WITH_NUMERIC_ID = new Set([
|
||||
'model_availability',
|
||||
'token_model_availability',
|
||||
'token_routes',
|
||||
'route_group_sources',
|
||||
'route_channels',
|
||||
'proxy_logs',
|
||||
'proxy_video_tasks',
|
||||
@@ -463,6 +464,10 @@ function ensureRouteGroupingSchema() {
|
||||
execSqliteLegacyCompat(`ALTER TABLE token_routes ADD COLUMN display_icon text;`);
|
||||
}
|
||||
|
||||
if (!tableColumnExists('token_routes', 'route_mode')) {
|
||||
execSqliteLegacyCompat(`ALTER TABLE token_routes ADD COLUMN route_mode text DEFAULT 'pattern';`);
|
||||
}
|
||||
|
||||
if (!tableColumnExists('token_routes', 'decision_snapshot')) {
|
||||
execSqliteLegacyCompat(`ALTER TABLE token_routes ADD COLUMN decision_snapshot text;`);
|
||||
}
|
||||
@@ -490,6 +495,22 @@ function ensureRouteGroupingSchema() {
|
||||
if (!tableColumnExists('route_channels', 'cooldown_level')) {
|
||||
execSqliteLegacyCompat(`ALTER TABLE route_channels ADD COLUMN cooldown_level integer NOT NULL DEFAULT 0;`);
|
||||
}
|
||||
|
||||
execSqliteLegacyCompat(`
|
||||
CREATE TABLE IF NOT EXISTS route_group_sources (
|
||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
group_route_id integer NOT NULL REFERENCES token_routes(id) ON DELETE cascade,
|
||||
source_route_id integer NOT NULL REFERENCES token_routes(id) ON DELETE cascade
|
||||
);
|
||||
`);
|
||||
execSqliteLegacyCompat(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS route_group_sources_group_source_unique
|
||||
ON route_group_sources(group_route_id, source_route_id);
|
||||
`);
|
||||
execSqliteLegacyCompat(`
|
||||
CREATE INDEX IF NOT EXISTS route_group_sources_source_route_id_idx
|
||||
ON route_group_sources(source_route_id);
|
||||
`);
|
||||
}
|
||||
|
||||
function ensureDownstreamApiKeySchema() {
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface LegacySchemaCompatInspector extends
|
||||
const LEGACY_COMPAT_TABLES = new Set([
|
||||
'account_tokens',
|
||||
'token_model_availability',
|
||||
'route_group_sources',
|
||||
'proxy_video_tasks',
|
||||
'proxy_files',
|
||||
'downstream_api_keys',
|
||||
@@ -49,6 +50,7 @@ const LEGACY_COMPAT_COLUMNS = new Set([
|
||||
'account_tokens.value_status',
|
||||
'token_routes.display_name',
|
||||
'token_routes.display_icon',
|
||||
'token_routes.route_mode',
|
||||
'token_routes.decision_snapshot',
|
||||
'token_routes.decision_refreshed_at',
|
||||
'token_routes.routing_strategy',
|
||||
@@ -80,6 +82,8 @@ const LEGACY_COMPAT_INDEXES = new Set([
|
||||
'downstream_api_keys_enabled_idx',
|
||||
'downstream_api_keys_expires_at_idx',
|
||||
'proxy_logs_downstream_api_key_created_at_idx',
|
||||
'route_group_sources_group_source_unique',
|
||||
'route_group_sources_source_route_id_idx',
|
||||
...SHARED_INDEX_COMPATIBILITY_SPECS.map((spec) => spec.indexName),
|
||||
]);
|
||||
|
||||
|
||||
@@ -8,9 +8,11 @@ function createInspector(
|
||||
dialect: RouteGroupingSchemaInspector['dialect'],
|
||||
options?: {
|
||||
existingColumnsByTable?: Partial<Record<'token_routes' | 'route_channels', string[]>>;
|
||||
existingTables?: Array<'token_routes' | 'route_channels' | 'route_group_sources'>;
|
||||
},
|
||||
) {
|
||||
const executedSql: string[] = [];
|
||||
const existingTables = new Set(options?.existingTables ?? ['token_routes', 'route_channels']);
|
||||
const existingColumnsByTable = {
|
||||
token_routes: new Set(options?.existingColumnsByTable?.token_routes ?? []),
|
||||
route_channels: new Set(options?.existingColumnsByTable?.route_channels ?? []),
|
||||
@@ -19,7 +21,7 @@ function createInspector(
|
||||
const inspector: RouteGroupingSchemaInspector = {
|
||||
dialect,
|
||||
async tableExists(table) {
|
||||
return table === 'token_routes' || table === 'route_channels';
|
||||
return existingTables.has(table as 'token_routes' | 'route_channels' | 'route_group_sources');
|
||||
},
|
||||
async columnExists(table, column) {
|
||||
if (table === 'token_routes' || table === 'route_channels') {
|
||||
@@ -40,8 +42,12 @@ describe('ensureRouteGroupingSchemaCompatibility', () => {
|
||||
{
|
||||
dialect: 'sqlite' as const,
|
||||
expectedSql: [
|
||||
'CREATE TABLE IF NOT EXISTS route_group_sources (id integer PRIMARY KEY AUTOINCREMENT NOT NULL, group_route_id integer NOT NULL REFERENCES token_routes(id) ON DELETE cascade, source_route_id integer NOT NULL REFERENCES token_routes(id) ON DELETE cascade);',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS route_group_sources_group_source_unique ON route_group_sources(group_route_id, source_route_id);',
|
||||
'CREATE INDEX IF NOT EXISTS route_group_sources_source_route_id_idx ON route_group_sources(source_route_id);',
|
||||
'ALTER TABLE token_routes ADD COLUMN display_name text;',
|
||||
'ALTER TABLE token_routes ADD COLUMN display_icon text;',
|
||||
'ALTER TABLE token_routes ADD COLUMN route_mode text DEFAULT \'pattern\';',
|
||||
'ALTER TABLE token_routes ADD COLUMN decision_snapshot text;',
|
||||
'ALTER TABLE token_routes ADD COLUMN decision_refreshed_at text;',
|
||||
'ALTER TABLE token_routes ADD COLUMN routing_strategy text DEFAULT \'weighted\';',
|
||||
@@ -54,8 +60,12 @@ describe('ensureRouteGroupingSchemaCompatibility', () => {
|
||||
{
|
||||
dialect: 'postgres' as const,
|
||||
expectedSql: [
|
||||
'CREATE TABLE IF NOT EXISTS "route_group_sources" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "group_route_id" INTEGER NOT NULL, "source_route_id" INTEGER NOT NULL, CONSTRAINT "route_group_sources_group_route_id_token_routes_id_fk" FOREIGN KEY ("group_route_id") REFERENCES "token_routes"("id") ON DELETE CASCADE, CONSTRAINT "route_group_sources_source_route_id_token_routes_id_fk" FOREIGN KEY ("source_route_id") REFERENCES "token_routes"("id") ON DELETE CASCADE)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "route_group_sources_group_source_unique" ON "route_group_sources" ("group_route_id", "source_route_id")',
|
||||
'CREATE INDEX IF NOT EXISTS "route_group_sources_source_route_id_idx" ON "route_group_sources" ("source_route_id")',
|
||||
'ALTER TABLE "token_routes" ADD COLUMN "display_name" TEXT',
|
||||
'ALTER TABLE "token_routes" ADD COLUMN "display_icon" TEXT',
|
||||
'ALTER TABLE "token_routes" ADD COLUMN "route_mode" TEXT DEFAULT \'pattern\'',
|
||||
'ALTER TABLE "token_routes" ADD COLUMN "decision_snapshot" TEXT',
|
||||
'ALTER TABLE "token_routes" ADD COLUMN "decision_refreshed_at" TEXT',
|
||||
'ALTER TABLE "token_routes" ADD COLUMN "routing_strategy" TEXT DEFAULT \'weighted\'',
|
||||
@@ -68,8 +78,12 @@ describe('ensureRouteGroupingSchemaCompatibility', () => {
|
||||
{
|
||||
dialect: 'mysql' as const,
|
||||
expectedSql: [
|
||||
'CREATE TABLE IF NOT EXISTS `route_group_sources` (`id` int AUTO_INCREMENT NOT NULL PRIMARY KEY, `group_route_id` int NOT NULL, `source_route_id` int NOT NULL, CONSTRAINT `route_group_sources_group_route_id_token_routes_id_fk` FOREIGN KEY (`group_route_id`) REFERENCES `token_routes`(`id`) ON DELETE cascade, CONSTRAINT `route_group_sources_source_route_id_token_routes_id_fk` FOREIGN KEY (`source_route_id`) REFERENCES `token_routes`(`id`) ON DELETE cascade)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS `route_group_sources_group_source_unique` ON `route_group_sources` (`group_route_id`,`source_route_id`)',
|
||||
'CREATE INDEX IF NOT EXISTS `route_group_sources_source_route_id_idx` ON `route_group_sources` (`source_route_id`)',
|
||||
'ALTER TABLE `token_routes` ADD COLUMN `display_name` TEXT NULL',
|
||||
'ALTER TABLE `token_routes` ADD COLUMN `display_icon` TEXT NULL',
|
||||
'ALTER TABLE `token_routes` ADD COLUMN `route_mode` VARCHAR(32) NULL DEFAULT \'pattern\'',
|
||||
'ALTER TABLE `token_routes` ADD COLUMN `decision_snapshot` TEXT NULL',
|
||||
'ALTER TABLE `token_routes` ADD COLUMN `decision_refreshed_at` TEXT NULL',
|
||||
'ALTER TABLE `token_routes` ADD COLUMN `routing_strategy` VARCHAR(32) NULL DEFAULT \'weighted\'',
|
||||
@@ -90,9 +104,10 @@ describe('ensureRouteGroupingSchemaCompatibility', () => {
|
||||
it('skips existing columns', async () => {
|
||||
const { inspector, executedSql } = createInspector('postgres', {
|
||||
existingColumnsByTable: {
|
||||
token_routes: ['display_name', 'display_icon', 'decision_snapshot', 'decision_refreshed_at', 'routing_strategy'],
|
||||
token_routes: ['display_name', 'display_icon', 'route_mode', 'decision_snapshot', 'decision_refreshed_at', 'routing_strategy'],
|
||||
route_channels: ['source_model', 'last_selected_at', 'consecutive_fail_count', 'cooldown_level'],
|
||||
},
|
||||
existingTables: ['token_routes', 'route_channels', 'route_group_sources'],
|
||||
});
|
||||
|
||||
await ensureRouteGroupingSchemaCompatibility(inspector);
|
||||
|
||||
@@ -13,6 +13,11 @@ type RouteGroupingColumnCompatibilitySpec = {
|
||||
addSql: Record<RouteGroupingSchemaDialect, string>;
|
||||
};
|
||||
|
||||
type RouteGroupingTableCompatibilitySpec = {
|
||||
table: 'route_group_sources';
|
||||
createSql: Record<RouteGroupingSchemaDialect, string[]>;
|
||||
};
|
||||
|
||||
const ROUTE_GROUPING_COLUMN_COMPATIBILITY_SPECS: RouteGroupingColumnCompatibilitySpec[] = [
|
||||
{
|
||||
table: 'token_routes',
|
||||
@@ -32,6 +37,15 @@ const ROUTE_GROUPING_COLUMN_COMPATIBILITY_SPECS: RouteGroupingColumnCompatibilit
|
||||
postgres: 'ALTER TABLE "token_routes" ADD COLUMN "display_icon" TEXT',
|
||||
},
|
||||
},
|
||||
{
|
||||
table: 'token_routes',
|
||||
column: 'route_mode',
|
||||
addSql: {
|
||||
sqlite: 'ALTER TABLE token_routes ADD COLUMN route_mode text DEFAULT \'pattern\';',
|
||||
mysql: 'ALTER TABLE `token_routes` ADD COLUMN `route_mode` VARCHAR(32) NULL DEFAULT \'pattern\'',
|
||||
postgres: 'ALTER TABLE "token_routes" ADD COLUMN "route_mode" TEXT DEFAULT \'pattern\'',
|
||||
},
|
||||
},
|
||||
{
|
||||
table: 'token_routes',
|
||||
column: 'decision_snapshot',
|
||||
@@ -97,6 +111,29 @@ const ROUTE_GROUPING_COLUMN_COMPATIBILITY_SPECS: RouteGroupingColumnCompatibilit
|
||||
},
|
||||
];
|
||||
|
||||
const ROUTE_GROUPING_TABLE_COMPATIBILITY_SPECS: RouteGroupingTableCompatibilitySpec[] = [
|
||||
{
|
||||
table: 'route_group_sources',
|
||||
createSql: {
|
||||
sqlite: [
|
||||
'CREATE TABLE IF NOT EXISTS route_group_sources (id integer PRIMARY KEY AUTOINCREMENT NOT NULL, group_route_id integer NOT NULL REFERENCES token_routes(id) ON DELETE cascade, source_route_id integer NOT NULL REFERENCES token_routes(id) ON DELETE cascade);',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS route_group_sources_group_source_unique ON route_group_sources(group_route_id, source_route_id);',
|
||||
'CREATE INDEX IF NOT EXISTS route_group_sources_source_route_id_idx ON route_group_sources(source_route_id);',
|
||||
],
|
||||
mysql: [
|
||||
'CREATE TABLE IF NOT EXISTS `route_group_sources` (`id` int AUTO_INCREMENT NOT NULL PRIMARY KEY, `group_route_id` int NOT NULL, `source_route_id` int NOT NULL, CONSTRAINT `route_group_sources_group_route_id_token_routes_id_fk` FOREIGN KEY (`group_route_id`) REFERENCES `token_routes`(`id`) ON DELETE cascade, CONSTRAINT `route_group_sources_source_route_id_token_routes_id_fk` FOREIGN KEY (`source_route_id`) REFERENCES `token_routes`(`id`) ON DELETE cascade)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS `route_group_sources_group_source_unique` ON `route_group_sources` (`group_route_id`,`source_route_id`)',
|
||||
'CREATE INDEX IF NOT EXISTS `route_group_sources_source_route_id_idx` ON `route_group_sources` (`source_route_id`)',
|
||||
],
|
||||
postgres: [
|
||||
'CREATE TABLE IF NOT EXISTS "route_group_sources" ("id" INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY, "group_route_id" INTEGER NOT NULL, "source_route_id" INTEGER NOT NULL, CONSTRAINT "route_group_sources_group_route_id_token_routes_id_fk" FOREIGN KEY ("group_route_id") REFERENCES "token_routes"("id") ON DELETE CASCADE, CONSTRAINT "route_group_sources_source_route_id_token_routes_id_fk" FOREIGN KEY ("source_route_id") REFERENCES "token_routes"("id") ON DELETE CASCADE)',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS "route_group_sources_group_source_unique" ON "route_group_sources" ("group_route_id", "source_route_id")',
|
||||
'CREATE INDEX IF NOT EXISTS "route_group_sources_source_route_id_idx" ON "route_group_sources" ("source_route_id")',
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function normalizeSchemaErrorMessage(error: unknown): string {
|
||||
if (typeof error === 'object' && error && 'message' in error) {
|
||||
return String((error as { message?: unknown }).message || '');
|
||||
@@ -124,6 +161,18 @@ async function executeAddColumn(inspector: RouteGroupingSchemaInspector, sqlText
|
||||
export async function ensureRouteGroupingSchemaCompatibility(inspector: RouteGroupingSchemaInspector): Promise<void> {
|
||||
const tableExistsCache = new Map<string, boolean>();
|
||||
|
||||
for (const spec of ROUTE_GROUPING_TABLE_COMPATIBILITY_SPECS) {
|
||||
const hasTable = await inspector.tableExists(spec.table);
|
||||
tableExistsCache.set(spec.table, hasTable);
|
||||
if (hasTable) {
|
||||
continue;
|
||||
}
|
||||
for (const sqlText of spec.createSql[inspector.dialect]) {
|
||||
await executeAddColumn(inspector, sqlText);
|
||||
}
|
||||
tableExistsCache.set(spec.table, true);
|
||||
}
|
||||
|
||||
for (const spec of ROUTE_GROUPING_COLUMN_COMPATIBILITY_SPECS) {
|
||||
let hasTable = tableExistsCache.get(spec.table);
|
||||
if (hasTable === undefined) {
|
||||
|
||||
@@ -127,6 +127,7 @@ export const tokenRoutes = sqliteTable('token_routes', {
|
||||
modelPattern: text('model_pattern').notNull(),
|
||||
displayName: text('display_name'),
|
||||
displayIcon: text('display_icon'),
|
||||
routeMode: text('route_mode').default('pattern'),
|
||||
modelMapping: text('model_mapping'), // JSON
|
||||
decisionSnapshot: text('decision_snapshot'), // JSON
|
||||
decisionRefreshedAt: text('decision_refreshed_at'),
|
||||
@@ -139,6 +140,15 @@ export const tokenRoutes = sqliteTable('token_routes', {
|
||||
enabledIdx: index('token_routes_enabled_idx').on(table.enabled),
|
||||
}));
|
||||
|
||||
export const routeGroupSources = sqliteTable('route_group_sources', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
groupRouteId: integer('group_route_id').notNull().references(() => tokenRoutes.id, { onDelete: 'cascade' }),
|
||||
sourceRouteId: integer('source_route_id').notNull().references(() => tokenRoutes.id, { onDelete: 'cascade' }),
|
||||
}, (table) => ({
|
||||
groupSourceUnique: uniqueIndex('route_group_sources_group_source_unique').on(table.groupRouteId, table.sourceRouteId),
|
||||
sourceRouteIdx: index('route_group_sources_source_route_id_idx').on(table.sourceRouteId),
|
||||
}));
|
||||
|
||||
export const routeChannels = sqliteTable('route_channels', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
routeId: integer('route_id').notNull().references(() => tokenRoutes.id, { onDelete: 'cascade' }),
|
||||
|
||||
Reference in New Issue
Block a user