gitlab-ai-provider 6.15.0 → 6.15.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/dist/index.mjs CHANGED
@@ -1583,14 +1583,14 @@ var MODEL_MAPPINGS = {
1583
1583
  "duo-workflow-default": { provider: "workflow", model: "default" },
1584
1584
  "duo-workflow-sonnet-4-5": {
1585
1585
  provider: "workflow",
1586
- model: "anthropic/claude-sonnet-4-5-20250929"
1586
+ model: "claude_sonnet_4_5_20250929"
1587
1587
  },
1588
1588
  "duo-workflow-sonnet-5": { provider: "workflow", model: "claude_sonnet_5" },
1589
1589
  "duo-workflow-opus-5": { provider: "workflow", model: "claude_opus_5" },
1590
1590
  "duo-workflow-sonnet-4-6": { provider: "workflow", model: "claude_sonnet_4_6" },
1591
1591
  "duo-workflow-opus-4-5": {
1592
1592
  provider: "workflow",
1593
- model: "anthropic/claude-opus-4-5-20251101"
1593
+ model: "claude_opus_4_5_20251101"
1594
1594
  },
1595
1595
  "duo-workflow-haiku-4-5": { provider: "workflow", model: "claude_haiku_4_5_20251001" },
1596
1596
  "duo-workflow-opus-4-6": { provider: "workflow", model: "claude_opus_4_6_20260205" }
@@ -2692,7 +2692,7 @@ import { AsyncResource } from "async_hooks";
2692
2692
  import WebSocket from "isomorphic-ws";
2693
2693
 
2694
2694
  // src/version.ts
2695
- var VERSION = true ? "6.14.0" : "0.0.0-dev";
2695
+ var VERSION = true ? "6.15.0" : "0.0.0-dev";
2696
2696
 
2697
2697
  // src/gitlab-workflow-client.ts
2698
2698
  var WS_CONNECT_TIMEOUT_MS = 3e4;
@@ -3412,8 +3412,14 @@ var GitLabProjectDetector = class {
3412
3412
  };
3413
3413
 
3414
3414
  // src/gitlab-model-discovery.ts
3415
+ function scopeCacheKey(scope) {
3416
+ return "projectId" in scope ? `p:${scope.projectId}` : `n:${scope.rootNamespaceId}`;
3417
+ }
3418
+ function normalizeScope(scope) {
3419
+ return typeof scope === "string" ? { rootNamespaceId: scope } : scope;
3420
+ }
3415
3421
  var AI_CHAT_AVAILABLE_MODELS_QUERY = `
3416
- query aiChatAvailableModels($rootNamespaceId: GroupID!) {
3422
+ query aiChatAvailableModels($projectId: ProjectID, $rootNamespaceId: GroupID) {
3417
3423
  metadata {
3418
3424
  featureFlags(names: ["ai_user_model_switching"]) {
3419
3425
  enabled
@@ -3422,7 +3428,7 @@ var AI_CHAT_AVAILABLE_MODELS_QUERY = `
3422
3428
  version
3423
3429
  }
3424
3430
 
3425
- aiChatAvailableModels(rootNamespaceId: $rootNamespaceId) {
3431
+ aiChatAvailableModels(projectId: $projectId, rootNamespaceId: $rootNamespaceId) {
3426
3432
  defaultModel {
3427
3433
  name
3428
3434
  ref
@@ -3448,18 +3454,42 @@ var GitLabModelDiscovery = class {
3448
3454
  this.fetchFn = config.fetch ?? fetch;
3449
3455
  }
3450
3456
  /**
3451
- * Discover available models for a given root namespace.
3457
+ * Discover available models for a given project or root namespace.
3452
3458
  *
3453
- * Results are cached per `rootNamespaceId` with a 10-minute TTL.
3459
+ * Results are cached per scope with a 10-minute TTL.
3454
3460
  * Use `invalidateCache()` to force an immediate refresh.
3455
3461
  *
3456
- * @param rootNamespaceId - GitLab group ID (e.g., 'gid://gitlab/Group/12345')
3462
+ * @param scope - `{ projectId }` (preferred), `{ rootNamespaceId }`, or a
3463
+ * bare string (treated as `rootNamespaceId` for backwards compatibility)
3457
3464
  */
3458
- async discover(rootNamespaceId) {
3459
- const cached = this.cache.get(rootNamespaceId);
3465
+ async discover(scope) {
3466
+ const normalized = normalizeScope(scope);
3467
+ const cacheKey = scopeCacheKey(normalized);
3468
+ const cached = this.cache.get(cacheKey);
3460
3469
  if (cached && cached.expiresAt > Date.now()) {
3461
3470
  return cached.data;
3462
3471
  }
3472
+ try {
3473
+ const result = await this.query(normalized);
3474
+ this.cache.set(cacheKey, {
3475
+ data: result,
3476
+ expiresAt: Date.now() + DISCOVERY_CACHE_TTL_MS
3477
+ });
3478
+ return result;
3479
+ } catch (error) {
3480
+ if ("projectId" in normalized && normalized.rootNamespaceId) {
3481
+ const fallbackScope = { rootNamespaceId: normalized.rootNamespaceId };
3482
+ const result = await this.query(fallbackScope);
3483
+ this.cache.set(scopeCacheKey(fallbackScope), {
3484
+ data: result,
3485
+ expiresAt: Date.now() + DISCOVERY_CACHE_TTL_MS
3486
+ });
3487
+ return result;
3488
+ }
3489
+ throw error;
3490
+ }
3491
+ }
3492
+ async query(scope) {
3463
3493
  const url = `${this.config.instanceUrl}/api/graphql`;
3464
3494
  try {
3465
3495
  const response = await this.fetchFn(url, {
@@ -3470,7 +3500,7 @@ var GitLabModelDiscovery = class {
3470
3500
  },
3471
3501
  body: JSON.stringify({
3472
3502
  query: AI_CHAT_AVAILABLE_MODELS_QUERY,
3473
- variables: { rootNamespaceId }
3503
+ variables: "projectId" in scope ? { projectId: scope.projectId, rootNamespaceId: void 0 } : { projectId: void 0, rootNamespaceId: scope.rootNamespaceId }
3474
3504
  })
3475
3505
  });
3476
3506
  if (!response.ok) {
@@ -3489,19 +3519,15 @@ var GitLabModelDiscovery = class {
3489
3519
  }
3490
3520
  const models = json.data?.aiChatAvailableModels;
3491
3521
  const metadata = json.data?.metadata;
3492
- const modelSwitchingEnabled = metadata?.featureFlags?.find((f) => f.name === "ai_user_model_switching")?.enabled ?? false;
3493
- const result = {
3522
+ const flag = metadata?.featureFlags?.find((f) => f.name === "ai_user_model_switching");
3523
+ const modelSwitchingEnabled = flag ? flag.enabled : true;
3524
+ return {
3494
3525
  defaultModel: models?.defaultModel ?? null,
3495
3526
  selectableModels: models?.selectableModels ?? [],
3496
3527
  pinnedModel: models?.pinnedModel ?? null,
3497
3528
  modelSwitchingEnabled,
3498
3529
  instanceVersion: metadata?.version ?? null
3499
3530
  };
3500
- this.cache.set(rootNamespaceId, {
3501
- data: result,
3502
- expiresAt: Date.now() + DISCOVERY_CACHE_TTL_MS
3503
- });
3504
- return result;
3505
3531
  } catch (error) {
3506
3532
  if (error instanceof GitLabError) throw error;
3507
3533
  throw new GitLabError({
@@ -3515,11 +3541,11 @@ var GitLabModelDiscovery = class {
3515
3541
  *
3516
3542
  * Priority: pinned > user-selected > default.
3517
3543
  *
3518
- * @param rootNamespaceId - GitLab group ID
3544
+ * @param scope - Project or root namespace to discover models for
3519
3545
  * @param userSelectedRef - Optional user preference
3520
3546
  */
3521
- async getEffectiveModelRef(rootNamespaceId, userSelectedRef) {
3522
- const discovered = await this.discover(rootNamespaceId);
3547
+ async getEffectiveModelRef(scope, userSelectedRef) {
3548
+ const discovered = await this.discover(scope);
3523
3549
  if (discovered.pinnedModel) {
3524
3550
  return discovered.pinnedModel.ref;
3525
3551
  }
@@ -3966,6 +3992,25 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3966
3992
  this._rootNamespaceId = null;
3967
3993
  return null;
3968
3994
  }
3995
+ /**
3996
+ * Resolve the model discovery scope to use for `aiChatAvailableModels`.
3997
+ *
3998
+ * `projectId` is preferred — it works for both group- and user-namespace
3999
+ * projects — with `rootNamespaceId` attached as a fallback for GitLab
4000
+ * instances too old to support `projectId`-scoped discovery.
4001
+ */
4002
+ async resolveDiscoveryScope() {
4003
+ const rootNamespaceId = await this.resolveRootNamespaceId();
4004
+ const workDir = this.workflowOptions.workingDirectory ?? process.cwd();
4005
+ const project = await this.projectDetector.detectProject(workDir);
4006
+ if (project?.id) {
4007
+ return {
4008
+ projectId: `gid://gitlab/Project/${project.id}`,
4009
+ ...rootNamespaceId ? { rootNamespaceId } : {}
4010
+ };
4011
+ }
4012
+ return rootNamespaceId ? { rootNamespaceId } : null;
4013
+ }
3969
4014
  /**
3970
4015
  * Resolve the effective DWS model ref to use for this stream.
3971
4016
  * Deduplicates concurrent calls via a shared promise.
@@ -3996,13 +4041,13 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3996
4041
  return this._discoveryPromise;
3997
4042
  }
3998
4043
  async doResolveModelRef() {
3999
- const rootNamespaceId = await this.resolveRootNamespaceId();
4000
- if (!rootNamespaceId) {
4044
+ const scope = await this.resolveDiscoveryScope();
4045
+ if (!scope) {
4001
4046
  this._selectedModelRef = "default";
4002
4047
  return "default";
4003
4048
  }
4004
4049
  try {
4005
- const discovered = await this.modelDiscovery.discover(rootNamespaceId);
4050
+ const discovered = await this.modelDiscovery.discover(scope);
4006
4051
  this.modelCache.saveDiscovery(discovered);
4007
4052
  if (discovered.pinnedModel) {
4008
4053
  this._selectedModelRef = discovered.pinnedModel.ref;
@@ -4045,11 +4090,13 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
4045
4090
  * Call this early (e.g., on IDE startup) to avoid blocking the first stream.
4046
4091
  * Results are persisted to the workspace model cache.
4047
4092
  *
4048
- * @param rootNamespaceId - GitLab group ID (e.g., 'gid://gitlab/Group/12345')
4093
+ * @param scope - Project or root namespace GID to discover models for.
4094
+ * A bare string is treated as a `rootNamespaceId` (e.g.
4095
+ * 'gid://gitlab/Group/12345') for backwards compatibility.
4049
4096
  * @returns Discovered models with default, selectable, and pinned models
4050
4097
  */
4051
- async discoverModels(rootNamespaceId) {
4052
- const result = await this.modelDiscovery.discover(rootNamespaceId);
4098
+ async discoverModels(scope) {
4099
+ const result = await this.modelDiscovery.discover(scope);
4053
4100
  this.modelCache.saveDiscovery(result);
4054
4101
  return result;
4055
4102
  }
@@ -5791,7 +5838,7 @@ async function buildModels(discovered, project) {
5791
5838
  for (const model of all) {
5792
5839
  if (!model.ref || seen.has(model.ref)) continue;
5793
5840
  seen.add(model.ref);
5794
- const id = refToID.get(model.ref) ?? `duo-workflow-${model.ref.replace(/[/_]/g, "-")}`;
5841
+ const id = refToID.get(model.ref) ?? (model.ref.startsWith("__default__") ? "duo-workflow-default" : `duo-workflow-${model.ref.replace(/[/_]/g, "-")}`);
5795
5842
  const limits = configs.get(model.ref);
5796
5843
  models.push({
5797
5844
  id,
@@ -5823,14 +5870,22 @@ async function discoverWorkflowModels(config, options) {
5823
5870
  } catch {
5824
5871
  return { models: [], project: null };
5825
5872
  }
5826
- const namespaceId = project?.namespaceId;
5827
- if (!namespaceId) return { models: [], project };
5873
+ if (!project?.id) return { models: [], project };
5874
+ const scope = {
5875
+ projectId: `gid://gitlab/Project/${project.id}`,
5876
+ ...project.namespaceId ? { rootNamespaceId: `gid://gitlab/Group/${project.namespaceId}` } : {}
5877
+ };
5828
5878
  const discovery = new GitLabModelDiscovery({
5829
5879
  instanceUrl: config.instanceUrl,
5830
5880
  getHeaders: config.getHeaders,
5831
5881
  fetch: config.fetch
5832
5882
  });
5833
- const discovered = await discovery.discover(`gid://gitlab/Group/${namespaceId}`);
5883
+ let discovered;
5884
+ try {
5885
+ discovered = await discovery.discover(scope);
5886
+ } catch {
5887
+ return { models: [], project };
5888
+ }
5834
5889
  cache.saveDiscovery(discovered, project);
5835
5890
  if (!cache.getSelectedModelRef()) {
5836
5891
  const pick = discovered.pinnedModel ?? discovered.defaultModel;