gitlab-ai-provider 6.15.0 → 6.16.0

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