fss-link 1.1.5 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bundle/fss-link.js +176 -43
  2. package/package.json +1 -1
@@ -22090,7 +22090,7 @@ async function createContentGeneratorConfig(config, authType) {
22090
22090
  async function createContentGenerator(config, gcConfig, sessionId2) {
22091
22091
  if (DEBUG_CONTENT)
22092
22092
  console.log(`\u{1F41B} DEBUG createContentGenerator: authType=${config.authType}, apiKey=${config.apiKey}, baseUrl=${config.baseUrl}`);
22093
- const version = "1.1.5";
22093
+ const version = "1.1.6";
22094
22094
  const userAgent = `FSS-Link/${version} (${process.platform}; ${process.arch})`;
22095
22095
  const baseHeaders = {
22096
22096
  "User-Agent": userAgent
@@ -81787,6 +81787,29 @@ var init_databaseMigrations = __esm({
81787
81787
  db.exec("DROP TABLE IF EXISTS model_configs");
81788
81788
  }
81789
81789
  });
81790
+ this.migrations.push({
81791
+ version: 2,
81792
+ name: "normalize_endpoint_urls",
81793
+ up: (db) => {
81794
+ db.exec(`
81795
+ UPDATE model_configs
81796
+ SET endpoint_url = NULL
81797
+ WHERE endpoint_url = '' OR TRIM(endpoint_url) = ''
81798
+ `);
81799
+ if (process.env["FSS_DEBUG"] === "true") {
81800
+ const result = db.exec(`
81801
+ SELECT COUNT(*) as count
81802
+ FROM model_configs
81803
+ WHERE endpoint_url IS NULL
81804
+ `);
81805
+ const count = result[0]?.values?.[0]?.[0] || 0;
81806
+ console.log(`Migration v2: Normalized endpoints (${count} records with NULL endpoint)`);
81807
+ }
81808
+ },
81809
+ down: (db) => {
81810
+ console.warn("Migration v2 rollback not implemented - endpoint normalization is one-way");
81811
+ }
81812
+ });
81790
81813
  }
81791
81814
  /**
81792
81815
  * Get current schema version from database
@@ -83117,57 +83140,167 @@ var init_model_database = __esm({
83117
83140
  [id]
83118
83141
  );
83119
83142
  }
83143
+ /**
83144
+ * Normalize endpoint URL to canonical form:
83145
+ * - undefined → NULL
83146
+ * - '' (empty string) → NULL
83147
+ * - whitespace-only → NULL
83148
+ * - Non-empty string → trimmed string
83149
+ *
83150
+ * This ensures UNIQUE(auth_type, model_name, endpoint_url) works correctly
83151
+ */
83152
+ normalizeEndpointUrl(endpointUrl) {
83153
+ if (!endpointUrl || endpointUrl.trim() === "") {
83154
+ return null;
83155
+ }
83156
+ return endpointUrl.trim();
83157
+ }
83158
+ /**
83159
+ * Find model by UNIQUE constraint (auth_type, model_name, endpoint_url)
83160
+ * With canonical NULL normalization, we use simple exact match
83161
+ */
83162
+ async findByUniqueKey(authType, modelName, endpointUrl) {
83163
+ if (endpointUrl === null) {
83164
+ return await safeQueryFirstWithLocking(
83165
+ this.db,
83166
+ `SELECT id FROM model_configs
83167
+ WHERE auth_type = ?
83168
+ AND model_name = ?
83169
+ AND endpoint_url IS NULL
83170
+ LIMIT 1`,
83171
+ [authType, modelName]
83172
+ );
83173
+ } else {
83174
+ return await safeQueryFirstWithLocking(
83175
+ this.db,
83176
+ `SELECT id FROM model_configs
83177
+ WHERE auth_type = ?
83178
+ AND model_name = ?
83179
+ AND endpoint_url = ?
83180
+ LIMIT 1`,
83181
+ [authType, modelName, endpointUrl]
83182
+ );
83183
+ }
83184
+ }
83185
+ /**
83186
+ * Update existing model record with canonical normalization
83187
+ */
83188
+ async updateModelRecord(recordId, config, normalizedModelName, normalizedEndpointUrl, encryptedApiKey) {
83189
+ const updateParams = [
83190
+ config.authType || "",
83191
+ normalizedModelName,
83192
+ normalizedEndpointUrl,
83193
+ // NULL or actual URL (never '')
83194
+ encryptedApiKey,
83195
+ config.displayName || null,
83196
+ config.isFavorite === true ? 1 : 0,
83197
+ config.isActive === true ? 1 : 0,
83198
+ recordId
83199
+ ];
83200
+ await safeExecWithLocking(this.db, `
83201
+ UPDATE model_configs SET
83202
+ auth_type = ?,
83203
+ model_name = ?,
83204
+ endpoint_url = ?, -- Can be NULL
83205
+ api_key = ?,
83206
+ display_name = ?,
83207
+ is_favorite = ?,
83208
+ is_active = ?,
83209
+ last_used = CURRENT_TIMESTAMP
83210
+ WHERE id = ?
83211
+ `, updateParams);
83212
+ return recordId;
83213
+ }
83214
+ /**
83215
+ * Insert new model record with canonical normalization
83216
+ */
83217
+ async insertModelRecord(config, normalizedModelName, normalizedEndpointUrl, encryptedApiKey) {
83218
+ const insertParams = [
83219
+ config.authType || "",
83220
+ normalizedModelName,
83221
+ normalizedEndpointUrl,
83222
+ // NULL or actual URL (never '')
83223
+ encryptedApiKey,
83224
+ config.displayName || null,
83225
+ config.isFavorite === true ? 1 : 0,
83226
+ config.isActive === true ? 1 : 0,
83227
+ config.source || "db"
83228
+ ];
83229
+ await safeExecWithLocking(this.db, `
83230
+ INSERT INTO model_configs
83231
+ (auth_type, model_name, endpoint_url, api_key, display_name, is_favorite, is_active, last_used, source)
83232
+ VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, ?)
83233
+ `, insertParams);
83234
+ return getLastInsertId(this.db);
83235
+ }
83120
83236
  /**
83121
83237
  * Upsert model configuration - UPDATE existing or INSERT new
83238
+ *
83239
+ * Uses cascading lookup strategy:
83240
+ * 1. Level 1: Lookup by ID (explicit update)
83241
+ * 2. Level 2: Lookup by UNIQUE key (auth_type, model_name, endpoint_url)
83242
+ * 3. Level 3: Fallback to auth_type only (backward compatibility)
83122
83243
  */
83123
83244
  async upsertModelConfig(config) {
83124
83245
  if (!this.db) {
83125
83246
  throw new Error("Database not initialized");
83126
83247
  }
83127
83248
  const encryptedApiKey = config.apiKey ? FSSLinkDatabase.encryptApiKey(config.apiKey) : null;
83128
- const normalizedEndpointUrl = config.endpointUrl || "";
83129
- const existingRecord = await safeQueryFirstWithLocking(
83130
- this.db,
83131
- "SELECT id FROM model_configs WHERE auth_type = ?",
83132
- [config.authType]
83249
+ const normalizedEndpointUrl = this.normalizeEndpointUrl(config.endpointUrl);
83250
+ const normalizedModelName = config.modelName?.trim() || "";
83251
+ let existingRecord = null;
83252
+ if (config.id) {
83253
+ existingRecord = await safeQueryFirstWithLocking(
83254
+ this.db,
83255
+ "SELECT id FROM model_configs WHERE id = ?",
83256
+ [config.id]
83257
+ );
83258
+ if (existingRecord) {
83259
+ return await this.updateModelRecord(
83260
+ existingRecord.id,
83261
+ config,
83262
+ normalizedModelName,
83263
+ normalizedEndpointUrl,
83264
+ encryptedApiKey
83265
+ );
83266
+ }
83267
+ }
83268
+ existingRecord = await this.findByUniqueKey(
83269
+ config.authType,
83270
+ normalizedModelName,
83271
+ normalizedEndpointUrl
83133
83272
  );
83134
- if (existingRecord || config.id) {
83135
- const recordId = existingRecord?.id || config.id;
83136
- const updateParams = [
83137
- config.authType || "",
83138
- config.modelName || "",
83139
- normalizedEndpointUrl,
83140
- encryptedApiKey || null,
83141
- config.displayName || null,
83142
- config.isFavorite === true ? 1 : 0,
83143
- config.isActive === true ? 1 : 0,
83144
- recordId
83145
- ];
83146
- await safeExecWithLocking(this.db, `
83147
- UPDATE model_configs SET
83148
- auth_type = ?, model_name = ?, endpoint_url = ?, api_key = ?,
83149
- display_name = ?, is_favorite = ?, is_active = ?, last_used = CURRENT_TIMESTAMP
83150
- WHERE id = ?
83151
- `, updateParams);
83152
- return recordId || 0;
83153
- } else {
83154
- const insertParams = [
83155
- config.authType || "",
83156
- config.modelName || "",
83273
+ if (existingRecord) {
83274
+ return await this.updateModelRecord(
83275
+ existingRecord.id,
83276
+ config,
83277
+ normalizedModelName,
83157
83278
  normalizedEndpointUrl,
83158
- encryptedApiKey || null,
83159
- config.displayName || null,
83160
- config.isFavorite === true ? 1 : 0,
83161
- config.isActive === true ? 1 : 0,
83162
- config.source || "db"
83163
- ];
83164
- await safeExecWithLocking(this.db, `
83165
- INSERT INTO model_configs
83166
- (auth_type, model_name, endpoint_url, api_key, display_name, is_favorite, is_active, last_used, source)
83167
- VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, ?)
83168
- `, insertParams);
83169
- return getLastInsertId(this.db);
83279
+ encryptedApiKey
83280
+ );
83170
83281
  }
83282
+ if (!normalizedModelName || normalizedModelName === "") {
83283
+ existingRecord = await safeQueryFirstWithLocking(
83284
+ this.db,
83285
+ "SELECT id FROM model_configs WHERE auth_type = ? LIMIT 1",
83286
+ [config.authType]
83287
+ );
83288
+ if (existingRecord) {
83289
+ return await this.updateModelRecord(
83290
+ existingRecord.id,
83291
+ config,
83292
+ normalizedModelName,
83293
+ normalizedEndpointUrl,
83294
+ encryptedApiKey
83295
+ );
83296
+ }
83297
+ }
83298
+ return await this.insertModelRecord(
83299
+ config,
83300
+ normalizedModelName,
83301
+ normalizedEndpointUrl,
83302
+ encryptedApiKey
83303
+ );
83171
83304
  }
83172
83305
  /**
83173
83306
  * Get all model configurations
@@ -94147,7 +94280,7 @@ async function getPackageJson() {
94147
94280
  // packages/cli/src/utils/version.ts
94148
94281
  async function getCliVersion() {
94149
94282
  const pkgJson = await getPackageJson();
94150
- return "1.1.5";
94283
+ return "1.1.6";
94151
94284
  }
94152
94285
 
94153
94286
  // packages/cli/src/ui/commands/aboutCommand.ts
@@ -94199,7 +94332,7 @@ import open4 from "open";
94199
94332
  import process11 from "node:process";
94200
94333
 
94201
94334
  // packages/cli/src/generated/git-commit.ts
94202
- var GIT_COMMIT_INFO = "425b354b";
94335
+ var GIT_COMMIT_INFO = "2fbcfa4c";
94203
94336
 
94204
94337
  // packages/cli/src/ui/commands/bugCommand.ts
94205
94338
  init_dist2();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fss-link",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "engines": {
5
5
  "node": ">=20.0.0"
6
6
  },