gitlab-ai-provider 6.14.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
@@ -1532,6 +1532,10 @@ var MODEL_MAPPINGS = {
1532
1532
  "duo-chat-opus-4-5": { provider: "anthropic", model: "claude-opus-4-5-20251101" },
1533
1533
  "duo-chat-sonnet-4-5": { provider: "anthropic", model: "claude-sonnet-4-5-20250929" },
1534
1534
  "duo-chat-haiku-4-5": { provider: "anthropic", model: "claude-haiku-4-5-20251001" },
1535
+ // OpenAI models - Responses API
1536
+ // GPT-6 Astra rejects `reasoning_effort` with function tools on Chat Completions;
1537
+ // it must use the Responses API (matches OpenAI's own routing for GPT-5.x/GPT-6).
1538
+ "duo-chat-gpt-6-astra": { provider: "openai", model: "gpt-6-astra", openaiApiType: "responses" },
1535
1539
  // OpenAI models - Chat Completions API
1536
1540
  "duo-chat-gpt-5-1": { provider: "openai", model: "gpt-5.1-2025-11-13", openaiApiType: "chat" },
1537
1541
  "duo-chat-gpt-5-2": { provider: "openai", model: "gpt-5.2-2025-12-11", openaiApiType: "chat" },
@@ -1579,14 +1583,14 @@ var MODEL_MAPPINGS = {
1579
1583
  "duo-workflow-default": { provider: "workflow", model: "default" },
1580
1584
  "duo-workflow-sonnet-4-5": {
1581
1585
  provider: "workflow",
1582
- model: "anthropic/claude-sonnet-4-5-20250929"
1586
+ model: "claude_sonnet_4_5_20250929"
1583
1587
  },
1584
1588
  "duo-workflow-sonnet-5": { provider: "workflow", model: "claude_sonnet_5" },
1585
1589
  "duo-workflow-opus-5": { provider: "workflow", model: "claude_opus_5" },
1586
1590
  "duo-workflow-sonnet-4-6": { provider: "workflow", model: "claude_sonnet_4_6" },
1587
1591
  "duo-workflow-opus-4-5": {
1588
1592
  provider: "workflow",
1589
- model: "anthropic/claude-opus-4-5-20251101"
1593
+ model: "claude_opus_4_5_20251101"
1590
1594
  },
1591
1595
  "duo-workflow-haiku-4-5": { provider: "workflow", model: "claude_haiku_4_5_20251001" },
1592
1596
  "duo-workflow-opus-4-6": { provider: "workflow", model: "claude_opus_4_6_20260205" }
@@ -2688,7 +2692,7 @@ import { AsyncResource } from "async_hooks";
2688
2692
  import WebSocket from "isomorphic-ws";
2689
2693
 
2690
2694
  // src/version.ts
2691
- var VERSION = true ? "6.13.0" : "0.0.0-dev";
2695
+ var VERSION = true ? "6.15.0" : "0.0.0-dev";
2692
2696
 
2693
2697
  // src/gitlab-workflow-client.ts
2694
2698
  var WS_CONNECT_TIMEOUT_MS = 3e4;
@@ -3408,8 +3412,14 @@ var GitLabProjectDetector = class {
3408
3412
  };
3409
3413
 
3410
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
+ }
3411
3421
  var AI_CHAT_AVAILABLE_MODELS_QUERY = `
3412
- query aiChatAvailableModels($rootNamespaceId: GroupID!) {
3422
+ query aiChatAvailableModels($projectId: ProjectID, $rootNamespaceId: GroupID) {
3413
3423
  metadata {
3414
3424
  featureFlags(names: ["ai_user_model_switching"]) {
3415
3425
  enabled
@@ -3418,7 +3428,7 @@ var AI_CHAT_AVAILABLE_MODELS_QUERY = `
3418
3428
  version
3419
3429
  }
3420
3430
 
3421
- aiChatAvailableModels(rootNamespaceId: $rootNamespaceId) {
3431
+ aiChatAvailableModels(projectId: $projectId, rootNamespaceId: $rootNamespaceId) {
3422
3432
  defaultModel {
3423
3433
  name
3424
3434
  ref
@@ -3444,18 +3454,42 @@ var GitLabModelDiscovery = class {
3444
3454
  this.fetchFn = config.fetch ?? fetch;
3445
3455
  }
3446
3456
  /**
3447
- * Discover available models for a given root namespace.
3457
+ * Discover available models for a given project or root namespace.
3448
3458
  *
3449
- * Results are cached per `rootNamespaceId` with a 10-minute TTL.
3459
+ * Results are cached per scope with a 10-minute TTL.
3450
3460
  * Use `invalidateCache()` to force an immediate refresh.
3451
3461
  *
3452
- * @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)
3453
3464
  */
3454
- async discover(rootNamespaceId) {
3455
- 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);
3456
3469
  if (cached && cached.expiresAt > Date.now()) {
3457
3470
  return cached.data;
3458
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) {
3459
3493
  const url = `${this.config.instanceUrl}/api/graphql`;
3460
3494
  try {
3461
3495
  const response = await this.fetchFn(url, {
@@ -3466,7 +3500,7 @@ var GitLabModelDiscovery = class {
3466
3500
  },
3467
3501
  body: JSON.stringify({
3468
3502
  query: AI_CHAT_AVAILABLE_MODELS_QUERY,
3469
- variables: { rootNamespaceId }
3503
+ variables: "projectId" in scope ? { projectId: scope.projectId, rootNamespaceId: void 0 } : { projectId: void 0, rootNamespaceId: scope.rootNamespaceId }
3470
3504
  })
3471
3505
  });
3472
3506
  if (!response.ok) {
@@ -3485,19 +3519,15 @@ var GitLabModelDiscovery = class {
3485
3519
  }
3486
3520
  const models = json.data?.aiChatAvailableModels;
3487
3521
  const metadata = json.data?.metadata;
3488
- const modelSwitchingEnabled = metadata?.featureFlags?.find((f) => f.name === "ai_user_model_switching")?.enabled ?? false;
3489
- const result = {
3522
+ const flag = metadata?.featureFlags?.find((f) => f.name === "ai_user_model_switching");
3523
+ const modelSwitchingEnabled = flag ? flag.enabled : true;
3524
+ return {
3490
3525
  defaultModel: models?.defaultModel ?? null,
3491
3526
  selectableModels: models?.selectableModels ?? [],
3492
3527
  pinnedModel: models?.pinnedModel ?? null,
3493
3528
  modelSwitchingEnabled,
3494
3529
  instanceVersion: metadata?.version ?? null
3495
3530
  };
3496
- this.cache.set(rootNamespaceId, {
3497
- data: result,
3498
- expiresAt: Date.now() + DISCOVERY_CACHE_TTL_MS
3499
- });
3500
- return result;
3501
3531
  } catch (error) {
3502
3532
  if (error instanceof GitLabError) throw error;
3503
3533
  throw new GitLabError({
@@ -3511,11 +3541,11 @@ var GitLabModelDiscovery = class {
3511
3541
  *
3512
3542
  * Priority: pinned > user-selected > default.
3513
3543
  *
3514
- * @param rootNamespaceId - GitLab group ID
3544
+ * @param scope - Project or root namespace to discover models for
3515
3545
  * @param userSelectedRef - Optional user preference
3516
3546
  */
3517
- async getEffectiveModelRef(rootNamespaceId, userSelectedRef) {
3518
- const discovered = await this.discover(rootNamespaceId);
3547
+ async getEffectiveModelRef(scope, userSelectedRef) {
3548
+ const discovered = await this.discover(scope);
3519
3549
  if (discovered.pinnedModel) {
3520
3550
  return discovered.pinnedModel.ref;
3521
3551
  }
@@ -3962,6 +3992,25 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3962
3992
  this._rootNamespaceId = null;
3963
3993
  return null;
3964
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
+ }
3965
4014
  /**
3966
4015
  * Resolve the effective DWS model ref to use for this stream.
3967
4016
  * Deduplicates concurrent calls via a shared promise.
@@ -3992,13 +4041,13 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3992
4041
  return this._discoveryPromise;
3993
4042
  }
3994
4043
  async doResolveModelRef() {
3995
- const rootNamespaceId = await this.resolveRootNamespaceId();
3996
- if (!rootNamespaceId) {
4044
+ const scope = await this.resolveDiscoveryScope();
4045
+ if (!scope) {
3997
4046
  this._selectedModelRef = "default";
3998
4047
  return "default";
3999
4048
  }
4000
4049
  try {
4001
- const discovered = await this.modelDiscovery.discover(rootNamespaceId);
4050
+ const discovered = await this.modelDiscovery.discover(scope);
4002
4051
  this.modelCache.saveDiscovery(discovered);
4003
4052
  if (discovered.pinnedModel) {
4004
4053
  this._selectedModelRef = discovered.pinnedModel.ref;
@@ -4041,11 +4090,13 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
4041
4090
  * Call this early (e.g., on IDE startup) to avoid blocking the first stream.
4042
4091
  * Results are persisted to the workspace model cache.
4043
4092
  *
4044
- * @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.
4045
4096
  * @returns Discovered models with default, selectable, and pinned models
4046
4097
  */
4047
- async discoverModels(rootNamespaceId) {
4048
- const result = await this.modelDiscovery.discover(rootNamespaceId);
4098
+ async discoverModels(scope) {
4099
+ const result = await this.modelDiscovery.discover(scope);
4049
4100
  this.modelCache.saveDiscovery(result);
4050
4101
  return result;
4051
4102
  }
@@ -5787,7 +5838,7 @@ async function buildModels(discovered, project) {
5787
5838
  for (const model of all) {
5788
5839
  if (!model.ref || seen.has(model.ref)) continue;
5789
5840
  seen.add(model.ref);
5790
- 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, "-")}`);
5791
5842
  const limits = configs.get(model.ref);
5792
5843
  models.push({
5793
5844
  id,
@@ -5819,14 +5870,22 @@ async function discoverWorkflowModels(config, options) {
5819
5870
  } catch {
5820
5871
  return { models: [], project: null };
5821
5872
  }
5822
- const namespaceId = project?.namespaceId;
5823
- 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
+ };
5824
5878
  const discovery = new GitLabModelDiscovery({
5825
5879
  instanceUrl: config.instanceUrl,
5826
5880
  getHeaders: config.getHeaders,
5827
5881
  fetch: config.fetch
5828
5882
  });
5829
- 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
+ }
5830
5889
  cache.saveDiscovery(discovered, project);
5831
5890
  if (!cache.getSelectedModelRef()) {
5832
5891
  const pick = discovered.pinnedModel ?? discovered.defaultModel;