gitlab-ai-provider 5.2.0 → 5.2.1

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
4
4
 
5
+ ## <small>5.2.1 (2026-03-20)</small>
6
+
7
+ - fix: cache workflow model discovery to file and resolve model name on selection ([8ba7056](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/8ba7056))
8
+
5
9
  ## 5.2.0 (2026-03-18)
6
10
 
7
11
  - fix: update dists ([57692b5](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/57692b5))
package/dist/index.d.mts CHANGED
@@ -174,6 +174,67 @@ declare class GitLabModelDiscovery {
174
174
  invalidateCache(): void;
175
175
  }
176
176
 
177
+ /**
178
+ * Simple in-memory cache for GitLab project information
179
+ * Used to avoid repeated API calls when detecting projects from git remotes
180
+ */
181
+ interface GitLabProject {
182
+ id: number;
183
+ path: string;
184
+ pathWithNamespace: string;
185
+ name: string;
186
+ namespaceId?: number;
187
+ }
188
+ /**
189
+ * In-memory cache for GitLab project information with TTL support
190
+ */
191
+ declare class GitLabProjectCache {
192
+ private cache;
193
+ private defaultTTL;
194
+ /**
195
+ * Create a new project cache
196
+ * @param defaultTTL - Default time-to-live in milliseconds (default: 5 minutes)
197
+ */
198
+ constructor(defaultTTL?: number);
199
+ /**
200
+ * Get a cached project by key
201
+ * @param key - Cache key (typically the working directory path)
202
+ * @returns The cached project or null if not found or expired
203
+ */
204
+ get(key: string): GitLabProject | null;
205
+ /**
206
+ * Store a project in the cache
207
+ * @param key - Cache key (typically the working directory path)
208
+ * @param project - The project to cache
209
+ * @param ttl - Optional custom TTL in milliseconds
210
+ */
211
+ set(key: string, project: GitLabProject, ttl?: number): void;
212
+ /**
213
+ * Check if a key exists in the cache (and is not expired)
214
+ * @param key - Cache key to check
215
+ * @returns true if the key exists and is not expired
216
+ */
217
+ has(key: string): boolean;
218
+ /**
219
+ * Remove a specific entry from the cache
220
+ * @param key - Cache key to remove
221
+ */
222
+ delete(key: string): void;
223
+ /**
224
+ * Clear all entries from the cache
225
+ */
226
+ clear(): void;
227
+ /**
228
+ * Get the number of entries in the cache (including expired ones)
229
+ */
230
+ get size(): number;
231
+ /**
232
+ * Clean up expired entries from the cache
233
+ * This is useful for long-running processes to prevent memory leaks
234
+ */
235
+ cleanup(): void;
236
+ }
237
+
177
238
  /**
178
239
  * File-based cache for workflow model discovery results and user selection.
179
240
  *
@@ -186,6 +247,7 @@ declare class GitLabModelDiscovery {
186
247
  * that switching instances for the same workspace invalidates the cache.
187
248
  * Each value holds:
188
249
  * - `discovery`: The full DiscoveredModels JSON from the last successful discovery
250
+ * - `project`: The GitLab project detected for this workspace
189
251
  * - `selectedModelRef`: The model ref the user last selected
190
252
  * - `selectedModelName`: Human-readable name of the selected model
191
253
  * - `updatedAt`: ISO timestamp of the last write
@@ -193,6 +255,7 @@ declare class GitLabModelDiscovery {
193
255
 
194
256
  interface ModelCacheEntry {
195
257
  discovery: DiscoveredModels | null;
258
+ project: GitLabProject | null;
196
259
  selectedModelRef: string | null;
197
260
  selectedModelName: string | null;
198
261
  updatedAt: string;
@@ -212,12 +275,19 @@ declare class GitLabModelCache {
212
275
  * Persist the full cache entry to disk.
213
276
  */
214
277
  save(entry: ModelCacheEntry): void;
278
+ /**
279
+ * Returns true if the discovery data is missing or older than DISCOVERY_TTL_MS.
280
+ */
281
+ isDiscoveryExpired(): boolean;
215
282
  /**
216
283
  * Update only the discovery portion of the cache, preserving selection.
284
+ * Optionally persists the associated GitLab project.
217
285
  */
218
- saveDiscovery(discovery: DiscoveredModels): void;
286
+ saveDiscovery(discovery: DiscoveredModels, project?: GitLabProject | null): void;
219
287
  /**
220
288
  * Update only the selected model, preserving the discovery data.
289
+ * If name is null but ref is provided, attempts to resolve the name
290
+ * from the cached discovery data.
221
291
  */
222
292
  saveSelection(ref: string | null, name: string | null): void;
223
293
  /**
@@ -1170,67 +1240,6 @@ declare class GitLabOAuthManager {
1170
1240
  private createExpiresTimestamp;
1171
1241
  }
1172
1242
 
1173
- /**
1174
- * Simple in-memory cache for GitLab project information
1175
- * Used to avoid repeated API calls when detecting projects from git remotes
1176
- */
1177
- interface GitLabProject {
1178
- id: number;
1179
- path: string;
1180
- pathWithNamespace: string;
1181
- name: string;
1182
- namespaceId?: number;
1183
- }
1184
- /**
1185
- * In-memory cache for GitLab project information with TTL support
1186
- */
1187
- declare class GitLabProjectCache {
1188
- private cache;
1189
- private defaultTTL;
1190
- /**
1191
- * Create a new project cache
1192
- * @param defaultTTL - Default time-to-live in milliseconds (default: 5 minutes)
1193
- */
1194
- constructor(defaultTTL?: number);
1195
- /**
1196
- * Get a cached project by key
1197
- * @param key - Cache key (typically the working directory path)
1198
- * @returns The cached project or null if not found or expired
1199
- */
1200
- get(key: string): GitLabProject | null;
1201
- /**
1202
- * Store a project in the cache
1203
- * @param key - Cache key (typically the working directory path)
1204
- * @param project - The project to cache
1205
- * @param ttl - Optional custom TTL in milliseconds
1206
- */
1207
- set(key: string, project: GitLabProject, ttl?: number): void;
1208
- /**
1209
- * Check if a key exists in the cache (and is not expired)
1210
- * @param key - Cache key to check
1211
- * @returns true if the key exists and is not expired
1212
- */
1213
- has(key: string): boolean;
1214
- /**
1215
- * Remove a specific entry from the cache
1216
- * @param key - Cache key to remove
1217
- */
1218
- delete(key: string): void;
1219
- /**
1220
- * Clear all entries from the cache
1221
- */
1222
- clear(): void;
1223
- /**
1224
- * Get the number of entries in the cache (including expired ones)
1225
- */
1226
- get size(): number;
1227
- /**
1228
- * Clean up expired entries from the cache
1229
- * This is useful for long-running processes to prevent memory leaks
1230
- */
1231
- cleanup(): void;
1232
- }
1233
-
1234
1243
  interface GitLabProjectDetectorConfig {
1235
1244
  instanceUrl: string;
1236
1245
  getHeaders: () => Record<string, string>;
package/dist/index.d.ts CHANGED
@@ -174,6 +174,67 @@ declare class GitLabModelDiscovery {
174
174
  invalidateCache(): void;
175
175
  }
176
176
 
177
+ /**
178
+ * Simple in-memory cache for GitLab project information
179
+ * Used to avoid repeated API calls when detecting projects from git remotes
180
+ */
181
+ interface GitLabProject {
182
+ id: number;
183
+ path: string;
184
+ pathWithNamespace: string;
185
+ name: string;
186
+ namespaceId?: number;
187
+ }
188
+ /**
189
+ * In-memory cache for GitLab project information with TTL support
190
+ */
191
+ declare class GitLabProjectCache {
192
+ private cache;
193
+ private defaultTTL;
194
+ /**
195
+ * Create a new project cache
196
+ * @param defaultTTL - Default time-to-live in milliseconds (default: 5 minutes)
197
+ */
198
+ constructor(defaultTTL?: number);
199
+ /**
200
+ * Get a cached project by key
201
+ * @param key - Cache key (typically the working directory path)
202
+ * @returns The cached project or null if not found or expired
203
+ */
204
+ get(key: string): GitLabProject | null;
205
+ /**
206
+ * Store a project in the cache
207
+ * @param key - Cache key (typically the working directory path)
208
+ * @param project - The project to cache
209
+ * @param ttl - Optional custom TTL in milliseconds
210
+ */
211
+ set(key: string, project: GitLabProject, ttl?: number): void;
212
+ /**
213
+ * Check if a key exists in the cache (and is not expired)
214
+ * @param key - Cache key to check
215
+ * @returns true if the key exists and is not expired
216
+ */
217
+ has(key: string): boolean;
218
+ /**
219
+ * Remove a specific entry from the cache
220
+ * @param key - Cache key to remove
221
+ */
222
+ delete(key: string): void;
223
+ /**
224
+ * Clear all entries from the cache
225
+ */
226
+ clear(): void;
227
+ /**
228
+ * Get the number of entries in the cache (including expired ones)
229
+ */
230
+ get size(): number;
231
+ /**
232
+ * Clean up expired entries from the cache
233
+ * This is useful for long-running processes to prevent memory leaks
234
+ */
235
+ cleanup(): void;
236
+ }
237
+
177
238
  /**
178
239
  * File-based cache for workflow model discovery results and user selection.
179
240
  *
@@ -186,6 +247,7 @@ declare class GitLabModelDiscovery {
186
247
  * that switching instances for the same workspace invalidates the cache.
187
248
  * Each value holds:
188
249
  * - `discovery`: The full DiscoveredModels JSON from the last successful discovery
250
+ * - `project`: The GitLab project detected for this workspace
189
251
  * - `selectedModelRef`: The model ref the user last selected
190
252
  * - `selectedModelName`: Human-readable name of the selected model
191
253
  * - `updatedAt`: ISO timestamp of the last write
@@ -193,6 +255,7 @@ declare class GitLabModelDiscovery {
193
255
 
194
256
  interface ModelCacheEntry {
195
257
  discovery: DiscoveredModels | null;
258
+ project: GitLabProject | null;
196
259
  selectedModelRef: string | null;
197
260
  selectedModelName: string | null;
198
261
  updatedAt: string;
@@ -212,12 +275,19 @@ declare class GitLabModelCache {
212
275
  * Persist the full cache entry to disk.
213
276
  */
214
277
  save(entry: ModelCacheEntry): void;
278
+ /**
279
+ * Returns true if the discovery data is missing or older than DISCOVERY_TTL_MS.
280
+ */
281
+ isDiscoveryExpired(): boolean;
215
282
  /**
216
283
  * Update only the discovery portion of the cache, preserving selection.
284
+ * Optionally persists the associated GitLab project.
217
285
  */
218
- saveDiscovery(discovery: DiscoveredModels): void;
286
+ saveDiscovery(discovery: DiscoveredModels, project?: GitLabProject | null): void;
219
287
  /**
220
288
  * Update only the selected model, preserving the discovery data.
289
+ * If name is null but ref is provided, attempts to resolve the name
290
+ * from the cached discovery data.
221
291
  */
222
292
  saveSelection(ref: string | null, name: string | null): void;
223
293
  /**
@@ -1170,67 +1240,6 @@ declare class GitLabOAuthManager {
1170
1240
  private createExpiresTimestamp;
1171
1241
  }
1172
1242
 
1173
- /**
1174
- * Simple in-memory cache for GitLab project information
1175
- * Used to avoid repeated API calls when detecting projects from git remotes
1176
- */
1177
- interface GitLabProject {
1178
- id: number;
1179
- path: string;
1180
- pathWithNamespace: string;
1181
- name: string;
1182
- namespaceId?: number;
1183
- }
1184
- /**
1185
- * In-memory cache for GitLab project information with TTL support
1186
- */
1187
- declare class GitLabProjectCache {
1188
- private cache;
1189
- private defaultTTL;
1190
- /**
1191
- * Create a new project cache
1192
- * @param defaultTTL - Default time-to-live in milliseconds (default: 5 minutes)
1193
- */
1194
- constructor(defaultTTL?: number);
1195
- /**
1196
- * Get a cached project by key
1197
- * @param key - Cache key (typically the working directory path)
1198
- * @returns The cached project or null if not found or expired
1199
- */
1200
- get(key: string): GitLabProject | null;
1201
- /**
1202
- * Store a project in the cache
1203
- * @param key - Cache key (typically the working directory path)
1204
- * @param project - The project to cache
1205
- * @param ttl - Optional custom TTL in milliseconds
1206
- */
1207
- set(key: string, project: GitLabProject, ttl?: number): void;
1208
- /**
1209
- * Check if a key exists in the cache (and is not expired)
1210
- * @param key - Cache key to check
1211
- * @returns true if the key exists and is not expired
1212
- */
1213
- has(key: string): boolean;
1214
- /**
1215
- * Remove a specific entry from the cache
1216
- * @param key - Cache key to remove
1217
- */
1218
- delete(key: string): void;
1219
- /**
1220
- * Clear all entries from the cache
1221
- */
1222
- clear(): void;
1223
- /**
1224
- * Get the number of entries in the cache (including expired ones)
1225
- */
1226
- get size(): number;
1227
- /**
1228
- * Clean up expired entries from the cache
1229
- * This is useful for long-running processes to prevent memory leaks
1230
- */
1231
- cleanup(): void;
1232
- }
1233
-
1234
1243
  interface GitLabProjectDetectorConfig {
1235
1244
  instanceUrl: string;
1236
1245
  getHeaders: () => Record<string, string>;
package/dist/index.js CHANGED
@@ -2746,6 +2746,7 @@ var fs = __toESM(require("fs"));
2746
2746
  var path2 = __toESM(require("path"));
2747
2747
  var os = __toESM(require("os"));
2748
2748
  var crypto = __toESM(require("crypto"));
2749
+ var DISCOVERY_TTL_MS = 10 * 60 * 1e3;
2749
2750
  function getCacheFilePath() {
2750
2751
  const cacheHome = process.env.XDG_CACHE_HOME || path2.join(os.homedir(), ".cache");
2751
2752
  return path2.join(cacheHome, "opencode", "gitlab-workflow-model-cache.json");
@@ -2795,13 +2796,23 @@ var GitLabModelCache = class {
2795
2796
  data[this.key] = entry;
2796
2797
  this.writeAll(data);
2797
2798
  }
2799
+ /**
2800
+ * Returns true if the discovery data is missing or older than DISCOVERY_TTL_MS.
2801
+ */
2802
+ isDiscoveryExpired() {
2803
+ const entry = this.load();
2804
+ if (!entry?.discovery || !entry.updatedAt) return true;
2805
+ return Date.now() - new Date(entry.updatedAt).getTime() > DISCOVERY_TTL_MS;
2806
+ }
2798
2807
  /**
2799
2808
  * Update only the discovery portion of the cache, preserving selection.
2809
+ * Optionally persists the associated GitLab project.
2800
2810
  */
2801
- saveDiscovery(discovery) {
2811
+ saveDiscovery(discovery, project) {
2802
2812
  const existing = this.load();
2803
2813
  this.save({
2804
2814
  discovery,
2815
+ project: project !== void 0 ? project : existing?.project ?? null,
2805
2816
  selectedModelRef: existing?.selectedModelRef ?? null,
2806
2817
  selectedModelName: existing?.selectedModelName ?? null,
2807
2818
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -2809,13 +2820,22 @@ var GitLabModelCache = class {
2809
2820
  }
2810
2821
  /**
2811
2822
  * Update only the selected model, preserving the discovery data.
2823
+ * If name is null but ref is provided, attempts to resolve the name
2824
+ * from the cached discovery data.
2812
2825
  */
2813
2826
  saveSelection(ref, name) {
2814
2827
  const existing = this.load();
2828
+ let resolved = name;
2829
+ if (!resolved && ref && existing?.discovery) {
2830
+ const d = existing.discovery;
2831
+ const match = d.pinnedModel?.ref === ref ? d.pinnedModel : d.selectableModels.find((m) => m.ref === ref) ?? (d.defaultModel?.ref === ref ? d.defaultModel : null);
2832
+ resolved = match?.name ?? null;
2833
+ }
2815
2834
  this.save({
2816
2835
  discovery: existing?.discovery ?? null,
2836
+ project: existing?.project ?? null,
2817
2837
  selectedModelRef: ref,
2818
- selectedModelName: name,
2838
+ selectedModelName: resolved,
2819
2839
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2820
2840
  });
2821
2841
  }
@@ -2957,7 +2977,8 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
2957
2977
  */
2958
2978
  set selectedModelRef(ref) {
2959
2979
  this._selectedModelRef = ref ?? void 0;
2960
- this.modelCache.saveSelection(ref, this._selectedModelName ?? null);
2980
+ this._selectedModelName = void 0;
2981
+ this.modelCache.saveSelection(ref, null);
2961
2982
  }
2962
2983
  /**
2963
2984
  * Get the cached selected model display name.
@@ -4497,29 +4518,8 @@ function parseModelsYml(text) {
4497
4518
 
4498
4519
  // src/gitlab-workflow-discovery.ts
4499
4520
  var configRegistry = new GitLabModelConfigRegistry();
4500
- async function discoverWorkflowModels(config, options) {
4501
- const detector = new GitLabProjectDetector({
4502
- instanceUrl: config.instanceUrl,
4503
- getHeaders: config.getHeaders,
4504
- fetch: config.fetch
4505
- });
4506
- let project = null;
4507
- try {
4508
- project = await detector.detectProject(options.workingDirectory);
4509
- } catch {
4510
- return { models: [], project: null };
4511
- }
4512
- const namespaceId = project?.namespaceId;
4513
- if (!namespaceId) return { models: [], project };
4514
- const discovery = new GitLabModelDiscovery({
4515
- instanceUrl: config.instanceUrl,
4516
- getHeaders: config.getHeaders,
4517
- fetch: config.fetch
4518
- });
4519
- const [discovered, configs] = await Promise.all([
4520
- discovery.discover(`gid://gitlab/Group/${namespaceId}`),
4521
- configRegistry.getConfigs()
4522
- ]);
4521
+ async function buildModels(discovered, project) {
4522
+ const configs = await configRegistry.getConfigs();
4523
4523
  const refToID = /* @__PURE__ */ new Map();
4524
4524
  for (const [id, mapping] of Object.entries(MODEL_MAPPINGS)) {
4525
4525
  if (mapping.provider === "workflow" && id !== "duo-workflow" && id !== "duo-workflow-default") {
@@ -4548,6 +4548,34 @@ async function discoverWorkflowModels(config, options) {
4548
4548
  }
4549
4549
  return { models, project };
4550
4550
  }
4551
+ async function discoverWorkflowModels(config, options) {
4552
+ const cache = new GitLabModelCache(options.workingDirectory, config.instanceUrl);
4553
+ if (!cache.isDiscoveryExpired()) {
4554
+ const entry = cache.load();
4555
+ return buildModels(entry.discovery, entry.project);
4556
+ }
4557
+ const detector = new GitLabProjectDetector({
4558
+ instanceUrl: config.instanceUrl,
4559
+ getHeaders: config.getHeaders,
4560
+ fetch: config.fetch
4561
+ });
4562
+ let project = null;
4563
+ try {
4564
+ project = await detector.detectProject(options.workingDirectory);
4565
+ } catch {
4566
+ return { models: [], project: null };
4567
+ }
4568
+ const namespaceId = project?.namespaceId;
4569
+ if (!namespaceId) return { models: [], project };
4570
+ const discovery = new GitLabModelDiscovery({
4571
+ instanceUrl: config.instanceUrl,
4572
+ getHeaders: config.getHeaders,
4573
+ fetch: config.fetch
4574
+ });
4575
+ const discovered = await discovery.discover(`gid://gitlab/Group/${namespaceId}`);
4576
+ cache.saveDiscovery(discovered, project);
4577
+ return buildModels(discovered, project);
4578
+ }
4551
4579
  // Annotate the CommonJS export names for ESM import in node:
4552
4580
  0 && (module.exports = {
4553
4581
  AGENT_PRIVILEGES,