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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
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>6.15.1 (2026-09-09)</small>
6
+
7
+ - Merge branch 'fix/agent-platform-model-discovery' into 'main' ([98c1fa7](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/98c1fa7))
8
+ - fix(discovery): scope Agent Platform model discovery by project and correct stale workflow refs ([9dd9681](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/9dd9681))
9
+
5
10
  ## 6.15.0 (2026-09-07)
6
11
 
7
12
  - Merge branch 'feature-add-gpt-6-astra' into 'main' ([19d1e4f](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/19d1e4f))
package/README.md CHANGED
@@ -629,7 +629,14 @@ const directToken = await client.getDirectAccessToken();
629
629
 
630
630
  #### GitLabModelDiscovery
631
631
 
632
- Discovers available workflow models via GraphQL.
632
+ Discovers available workflow models via GraphQL. Scope by `projectId`
633
+ (preferred — works for both group- and user-namespace projects) or
634
+ `rootNamespaceId` (group namespaces only, supported on older GitLab
635
+ instances). A bare string is treated as `rootNamespaceId` for backwards
636
+ compatibility. When scoped by `projectId` with a `rootNamespaceId` also
637
+ supplied, discovery falls back to the namespace-scoped query if the
638
+ `projectId` query fails (e.g. on GitLab versions predating `projectId`
639
+ support).
633
640
 
634
641
  ```typescript
635
642
  const discovery = new GitLabModelDiscovery({
@@ -637,11 +644,14 @@ const discovery = new GitLabModelDiscovery({
637
644
  getHeaders: () => ({ Authorization: `Bearer ${token}` }),
638
645
  });
639
646
 
640
- const models = await discovery.discover('gid://gitlab/Group/12345');
647
+ const models = await discovery.discover({
648
+ projectId: 'gid://gitlab/Project/12345',
649
+ rootNamespaceId: 'gid://gitlab/Group/6789', // optional fallback
650
+ });
641
651
  // Returns: { defaultModel, selectableModels, pinnedModel, modelSwitchingEnabled }
642
652
 
643
653
  const effectiveRef = await discovery.getEffectiveModelRef(
644
- 'gid://gitlab/Group/12345',
654
+ { projectId: 'gid://gitlab/Project/12345' },
645
655
  'claude_sonnet_4_6'
646
656
  );
647
657
  ```
package/dist/index.d.mts CHANGED
@@ -161,15 +161,18 @@ declare class GitLabAnthropicLanguageModel implements LanguageModelV3 {
161
161
  * Dynamic model discovery via GitLab GraphQL API.
162
162
  *
163
163
  * Queries `aiChatAvailableModels` to discover which models are available
164
- * for a given GitLab namespace. Used to dynamically populate `duo-workflow-*`
165
- * model IDs at runtime.
164
+ * for a given GitLab project or namespace. Used to dynamically populate
165
+ * `duo-workflow-*` model IDs at runtime.
166
166
  *
167
- * Requires GitLab 18.4+ (18.5+ for pinned model support).
167
+ * Requires GitLab 18.4+ (18.5+ for pinned model support). `projectId` scoping
168
+ * requires a newer GitLab release than `rootNamespaceId` scoping; when a
169
+ * `projectId` query fails and a `rootNamespaceId` is also known, discovery
170
+ * falls back to the namespace-scoped query.
168
171
  */
169
172
  interface AiModel {
170
173
  /** Display name (e.g., 'Claude Sonnet 4.6') */
171
174
  name: string;
172
- /** Model reference for API use (e.g., 'claude_sonnet_4_6' or 'anthropic/claude-sonnet-4-5-20250929') */
175
+ /** Model reference for API use (e.g., 'claude_sonnet_4_6') */
173
176
  ref: string;
174
177
  }
175
178
  interface AiChatAvailableModels {
@@ -197,29 +200,43 @@ interface ModelDiscoveryConfig {
197
200
  /** Custom fetch */
198
201
  fetch?: typeof fetch;
199
202
  }
203
+ /**
204
+ * Scope to discover models for. `projectId` is preferred (works for both
205
+ * group- and user-namespace projects). An optional `rootNamespaceId` may be
206
+ * supplied alongside it as a fallback for GitLab instances too old to
207
+ * support `projectId`-scoped discovery.
208
+ */
209
+ type DiscoveryScope = {
210
+ projectId: string;
211
+ rootNamespaceId?: string;
212
+ } | {
213
+ rootNamespaceId: string;
214
+ };
200
215
  declare class GitLabModelDiscovery {
201
216
  private readonly config;
202
217
  private readonly fetchFn;
203
218
  private cache;
204
219
  constructor(config: ModelDiscoveryConfig);
205
220
  /**
206
- * Discover available models for a given root namespace.
221
+ * Discover available models for a given project or root namespace.
207
222
  *
208
- * Results are cached per `rootNamespaceId` with a 10-minute TTL.
223
+ * Results are cached per scope with a 10-minute TTL.
209
224
  * Use `invalidateCache()` to force an immediate refresh.
210
225
  *
211
- * @param rootNamespaceId - GitLab group ID (e.g., 'gid://gitlab/Group/12345')
226
+ * @param scope - `{ projectId }` (preferred), `{ rootNamespaceId }`, or a
227
+ * bare string (treated as `rootNamespaceId` for backwards compatibility)
212
228
  */
213
- discover(rootNamespaceId: string): Promise<DiscoveredModels>;
229
+ discover(scope: DiscoveryScope | string): Promise<DiscoveredModels>;
230
+ private query;
214
231
  /**
215
232
  * Get the effective model ref to use for a workflow.
216
233
  *
217
234
  * Priority: pinned > user-selected > default.
218
235
  *
219
- * @param rootNamespaceId - GitLab group ID
236
+ * @param scope - Project or root namespace to discover models for
220
237
  * @param userSelectedRef - Optional user preference
221
238
  */
222
- getEffectiveModelRef(rootNamespaceId: string, userSelectedRef?: string): Promise<string | null>;
239
+ getEffectiveModelRef(scope: DiscoveryScope | string, userSelectedRef?: string): Promise<string | null>;
223
240
  /**
224
241
  * Invalidate the cached discovery results.
225
242
  */
@@ -885,6 +902,14 @@ declare class GitLabWorkflowLanguageModel implements LanguageModelV3 {
885
902
  * 3. Cached from previous call
886
903
  */
887
904
  private resolveRootNamespaceId;
905
+ /**
906
+ * Resolve the model discovery scope to use for `aiChatAvailableModels`.
907
+ *
908
+ * `projectId` is preferred — it works for both group- and user-namespace
909
+ * projects — with `rootNamespaceId` attached as a fallback for GitLab
910
+ * instances too old to support `projectId`-scoped discovery.
911
+ */
912
+ private resolveDiscoveryScope;
888
913
  /**
889
914
  * Resolve the effective DWS model ref to use for this stream.
890
915
  * Deduplicates concurrent calls via a shared promise.
@@ -905,10 +930,12 @@ declare class GitLabWorkflowLanguageModel implements LanguageModelV3 {
905
930
  * Call this early (e.g., on IDE startup) to avoid blocking the first stream.
906
931
  * Results are persisted to the workspace model cache.
907
932
  *
908
- * @param rootNamespaceId - GitLab group ID (e.g., 'gid://gitlab/Group/12345')
933
+ * @param scope - Project or root namespace GID to discover models for.
934
+ * A bare string is treated as a `rootNamespaceId` (e.g.
935
+ * 'gid://gitlab/Group/12345') for backwards compatibility.
909
936
  * @returns Discovered models with default, selectable, and pinned models
910
937
  */
911
- discoverModels(rootNamespaceId: string): Promise<DiscoveredModels>;
938
+ discoverModels(scope: DiscoveryScope | string): Promise<DiscoveredModels>;
912
939
  /**
913
940
  * Get the file-based model cache instance for this workspace.
914
941
  * Useful for consumers that need direct cache access (e.g., the discover route).
@@ -1851,4 +1878,4 @@ interface WorkflowDiscoveryResult {
1851
1878
  }
1852
1879
  declare function discoverWorkflowModels(config: WorkflowDiscoveryConfig, options: WorkflowDiscoveryOptions): Promise<WorkflowDiscoveryResult>;
1853
1880
 
1854
- export { AGENT_PRIVILEGES, type ActionResponsePayload, type AdditionalContext, type AiChatAvailableModels, type AiModel, type ApprovalDecision, BUNDLED_CLIENT_ID, CLIENT_VERSION, type ClientEvent, DEFAULT_AGENT_PRIVILEGES, DEFAULT_AI_GATEWAY_URL, DEFAULT_CLIENT_CAPABILITIES, DEFAULT_WORKFLOW_DEFINITION, type DirectAccessToken, type DiscoveredModels, type DiscoveredWorkflowModel, GITLAB_COM_URL, type GenerateTokenResponse, type GitLabAgenticOptions, type GitLabAnthropicConfig, GitLabAnthropicLanguageModel, GitLabDirectAccessClient, type GitLabDirectAccessConfig, GitLabError, type GitLabErrorOptions, GitLabModelCache, GitLabModelConfigRegistry, GitLabModelDiscovery, GitLabOAuthManager, type GitLabOAuthTokenResponse, type GitLabOAuthTokens, type GitLabOpenAIConfig, GitLabOpenAILanguageModel, type GitLabProject, GitLabProjectCache, GitLabProjectDetector, type GitLabProjectDetectorConfig, type GitLabProvider, type GitLabProviderSettings, type GitLabThinkingConfig, GitLabWorkflowClient, type GitLabWorkflowClientConfig, GitLabWorkflowLanguageModel, type GitLabWorkflowLanguageModelConfig, type GitLabWorkflowOptions, GitLabWorkflowTokenClient, MODEL_ID_TO_ANTHROPIC_MODEL, MODEL_MAPPINGS, type McpToolDefinition, type ModelCacheEntry, type ModelConfig, type ModelConfigRegistryOptions, type ModelDiscoveryConfig, type ModelMapping, type ModelProvider, type NewCheckpoint, OAUTH_SCOPES, OPENCODE_GITLAB_AUTH_CLIENT_ID, type OpenAIApiType, type OpenCodeAuth, type OpenCodeAuthApi, type OpenCodeAuthOAuth, type RunMcpTool, type StartRequest, TOKEN_EXPIRY_SKEW_MS, VERSION, WORKFLOW_ENVIRONMENT, WS_HEARTBEAT_INTERVAL_MS, WS_KEEPALIVE_PING_INTERVAL_MS, type WorkflowAction, type WorkflowClientEvent, type WorkflowDiscoveryConfig, type WorkflowDiscoveryOptions, type WorkflowDiscoveryResult, type WorkflowStatus, type WorkflowToolExecutor, WorkflowType, type WorkflowWebSocketOptions, createGitLab, discoverWorkflowModels, getAnthropicModelForModelId, getModelMapping, getOpenAIApiType, getOpenAIModelForModelId, getProviderForModelId, getValidModelsForProvider, getWorkflowModelRef, gitlab, gitlabOAuthTokenResponseSchema, isResponsesApiModel, isWorkflowModel, parseModelsYml };
1881
+ export { AGENT_PRIVILEGES, type ActionResponsePayload, type AdditionalContext, type AiChatAvailableModels, type AiModel, type ApprovalDecision, BUNDLED_CLIENT_ID, CLIENT_VERSION, type ClientEvent, DEFAULT_AGENT_PRIVILEGES, DEFAULT_AI_GATEWAY_URL, DEFAULT_CLIENT_CAPABILITIES, DEFAULT_WORKFLOW_DEFINITION, type DirectAccessToken, type DiscoveredModels, type DiscoveredWorkflowModel, type DiscoveryScope, GITLAB_COM_URL, type GenerateTokenResponse, type GitLabAgenticOptions, type GitLabAnthropicConfig, GitLabAnthropicLanguageModel, GitLabDirectAccessClient, type GitLabDirectAccessConfig, GitLabError, type GitLabErrorOptions, GitLabModelCache, GitLabModelConfigRegistry, GitLabModelDiscovery, GitLabOAuthManager, type GitLabOAuthTokenResponse, type GitLabOAuthTokens, type GitLabOpenAIConfig, GitLabOpenAILanguageModel, type GitLabProject, GitLabProjectCache, GitLabProjectDetector, type GitLabProjectDetectorConfig, type GitLabProvider, type GitLabProviderSettings, type GitLabThinkingConfig, GitLabWorkflowClient, type GitLabWorkflowClientConfig, GitLabWorkflowLanguageModel, type GitLabWorkflowLanguageModelConfig, type GitLabWorkflowOptions, GitLabWorkflowTokenClient, MODEL_ID_TO_ANTHROPIC_MODEL, MODEL_MAPPINGS, type McpToolDefinition, type ModelCacheEntry, type ModelConfig, type ModelConfigRegistryOptions, type ModelDiscoveryConfig, type ModelMapping, type ModelProvider, type NewCheckpoint, OAUTH_SCOPES, OPENCODE_GITLAB_AUTH_CLIENT_ID, type OpenAIApiType, type OpenCodeAuth, type OpenCodeAuthApi, type OpenCodeAuthOAuth, type RunMcpTool, type StartRequest, TOKEN_EXPIRY_SKEW_MS, VERSION, WORKFLOW_ENVIRONMENT, WS_HEARTBEAT_INTERVAL_MS, WS_KEEPALIVE_PING_INTERVAL_MS, type WorkflowAction, type WorkflowClientEvent, type WorkflowDiscoveryConfig, type WorkflowDiscoveryOptions, type WorkflowDiscoveryResult, type WorkflowStatus, type WorkflowToolExecutor, WorkflowType, type WorkflowWebSocketOptions, createGitLab, discoverWorkflowModels, getAnthropicModelForModelId, getModelMapping, getOpenAIApiType, getOpenAIModelForModelId, getProviderForModelId, getValidModelsForProvider, getWorkflowModelRef, gitlab, gitlabOAuthTokenResponseSchema, isResponsesApiModel, isWorkflowModel, parseModelsYml };
package/dist/index.d.ts CHANGED
@@ -161,15 +161,18 @@ declare class GitLabAnthropicLanguageModel implements LanguageModelV3 {
161
161
  * Dynamic model discovery via GitLab GraphQL API.
162
162
  *
163
163
  * Queries `aiChatAvailableModels` to discover which models are available
164
- * for a given GitLab namespace. Used to dynamically populate `duo-workflow-*`
165
- * model IDs at runtime.
164
+ * for a given GitLab project or namespace. Used to dynamically populate
165
+ * `duo-workflow-*` model IDs at runtime.
166
166
  *
167
- * Requires GitLab 18.4+ (18.5+ for pinned model support).
167
+ * Requires GitLab 18.4+ (18.5+ for pinned model support). `projectId` scoping
168
+ * requires a newer GitLab release than `rootNamespaceId` scoping; when a
169
+ * `projectId` query fails and a `rootNamespaceId` is also known, discovery
170
+ * falls back to the namespace-scoped query.
168
171
  */
169
172
  interface AiModel {
170
173
  /** Display name (e.g., 'Claude Sonnet 4.6') */
171
174
  name: string;
172
- /** Model reference for API use (e.g., 'claude_sonnet_4_6' or 'anthropic/claude-sonnet-4-5-20250929') */
175
+ /** Model reference for API use (e.g., 'claude_sonnet_4_6') */
173
176
  ref: string;
174
177
  }
175
178
  interface AiChatAvailableModels {
@@ -197,29 +200,43 @@ interface ModelDiscoveryConfig {
197
200
  /** Custom fetch */
198
201
  fetch?: typeof fetch;
199
202
  }
203
+ /**
204
+ * Scope to discover models for. `projectId` is preferred (works for both
205
+ * group- and user-namespace projects). An optional `rootNamespaceId` may be
206
+ * supplied alongside it as a fallback for GitLab instances too old to
207
+ * support `projectId`-scoped discovery.
208
+ */
209
+ type DiscoveryScope = {
210
+ projectId: string;
211
+ rootNamespaceId?: string;
212
+ } | {
213
+ rootNamespaceId: string;
214
+ };
200
215
  declare class GitLabModelDiscovery {
201
216
  private readonly config;
202
217
  private readonly fetchFn;
203
218
  private cache;
204
219
  constructor(config: ModelDiscoveryConfig);
205
220
  /**
206
- * Discover available models for a given root namespace.
221
+ * Discover available models for a given project or root namespace.
207
222
  *
208
- * Results are cached per `rootNamespaceId` with a 10-minute TTL.
223
+ * Results are cached per scope with a 10-minute TTL.
209
224
  * Use `invalidateCache()` to force an immediate refresh.
210
225
  *
211
- * @param rootNamespaceId - GitLab group ID (e.g., 'gid://gitlab/Group/12345')
226
+ * @param scope - `{ projectId }` (preferred), `{ rootNamespaceId }`, or a
227
+ * bare string (treated as `rootNamespaceId` for backwards compatibility)
212
228
  */
213
- discover(rootNamespaceId: string): Promise<DiscoveredModels>;
229
+ discover(scope: DiscoveryScope | string): Promise<DiscoveredModels>;
230
+ private query;
214
231
  /**
215
232
  * Get the effective model ref to use for a workflow.
216
233
  *
217
234
  * Priority: pinned > user-selected > default.
218
235
  *
219
- * @param rootNamespaceId - GitLab group ID
236
+ * @param scope - Project or root namespace to discover models for
220
237
  * @param userSelectedRef - Optional user preference
221
238
  */
222
- getEffectiveModelRef(rootNamespaceId: string, userSelectedRef?: string): Promise<string | null>;
239
+ getEffectiveModelRef(scope: DiscoveryScope | string, userSelectedRef?: string): Promise<string | null>;
223
240
  /**
224
241
  * Invalidate the cached discovery results.
225
242
  */
@@ -885,6 +902,14 @@ declare class GitLabWorkflowLanguageModel implements LanguageModelV3 {
885
902
  * 3. Cached from previous call
886
903
  */
887
904
  private resolveRootNamespaceId;
905
+ /**
906
+ * Resolve the model discovery scope to use for `aiChatAvailableModels`.
907
+ *
908
+ * `projectId` is preferred — it works for both group- and user-namespace
909
+ * projects — with `rootNamespaceId` attached as a fallback for GitLab
910
+ * instances too old to support `projectId`-scoped discovery.
911
+ */
912
+ private resolveDiscoveryScope;
888
913
  /**
889
914
  * Resolve the effective DWS model ref to use for this stream.
890
915
  * Deduplicates concurrent calls via a shared promise.
@@ -905,10 +930,12 @@ declare class GitLabWorkflowLanguageModel implements LanguageModelV3 {
905
930
  * Call this early (e.g., on IDE startup) to avoid blocking the first stream.
906
931
  * Results are persisted to the workspace model cache.
907
932
  *
908
- * @param rootNamespaceId - GitLab group ID (e.g., 'gid://gitlab/Group/12345')
933
+ * @param scope - Project or root namespace GID to discover models for.
934
+ * A bare string is treated as a `rootNamespaceId` (e.g.
935
+ * 'gid://gitlab/Group/12345') for backwards compatibility.
909
936
  * @returns Discovered models with default, selectable, and pinned models
910
937
  */
911
- discoverModels(rootNamespaceId: string): Promise<DiscoveredModels>;
938
+ discoverModels(scope: DiscoveryScope | string): Promise<DiscoveredModels>;
912
939
  /**
913
940
  * Get the file-based model cache instance for this workspace.
914
941
  * Useful for consumers that need direct cache access (e.g., the discover route).
@@ -1851,4 +1878,4 @@ interface WorkflowDiscoveryResult {
1851
1878
  }
1852
1879
  declare function discoverWorkflowModels(config: WorkflowDiscoveryConfig, options: WorkflowDiscoveryOptions): Promise<WorkflowDiscoveryResult>;
1853
1880
 
1854
- export { AGENT_PRIVILEGES, type ActionResponsePayload, type AdditionalContext, type AiChatAvailableModels, type AiModel, type ApprovalDecision, BUNDLED_CLIENT_ID, CLIENT_VERSION, type ClientEvent, DEFAULT_AGENT_PRIVILEGES, DEFAULT_AI_GATEWAY_URL, DEFAULT_CLIENT_CAPABILITIES, DEFAULT_WORKFLOW_DEFINITION, type DirectAccessToken, type DiscoveredModels, type DiscoveredWorkflowModel, GITLAB_COM_URL, type GenerateTokenResponse, type GitLabAgenticOptions, type GitLabAnthropicConfig, GitLabAnthropicLanguageModel, GitLabDirectAccessClient, type GitLabDirectAccessConfig, GitLabError, type GitLabErrorOptions, GitLabModelCache, GitLabModelConfigRegistry, GitLabModelDiscovery, GitLabOAuthManager, type GitLabOAuthTokenResponse, type GitLabOAuthTokens, type GitLabOpenAIConfig, GitLabOpenAILanguageModel, type GitLabProject, GitLabProjectCache, GitLabProjectDetector, type GitLabProjectDetectorConfig, type GitLabProvider, type GitLabProviderSettings, type GitLabThinkingConfig, GitLabWorkflowClient, type GitLabWorkflowClientConfig, GitLabWorkflowLanguageModel, type GitLabWorkflowLanguageModelConfig, type GitLabWorkflowOptions, GitLabWorkflowTokenClient, MODEL_ID_TO_ANTHROPIC_MODEL, MODEL_MAPPINGS, type McpToolDefinition, type ModelCacheEntry, type ModelConfig, type ModelConfigRegistryOptions, type ModelDiscoveryConfig, type ModelMapping, type ModelProvider, type NewCheckpoint, OAUTH_SCOPES, OPENCODE_GITLAB_AUTH_CLIENT_ID, type OpenAIApiType, type OpenCodeAuth, type OpenCodeAuthApi, type OpenCodeAuthOAuth, type RunMcpTool, type StartRequest, TOKEN_EXPIRY_SKEW_MS, VERSION, WORKFLOW_ENVIRONMENT, WS_HEARTBEAT_INTERVAL_MS, WS_KEEPALIVE_PING_INTERVAL_MS, type WorkflowAction, type WorkflowClientEvent, type WorkflowDiscoveryConfig, type WorkflowDiscoveryOptions, type WorkflowDiscoveryResult, type WorkflowStatus, type WorkflowToolExecutor, WorkflowType, type WorkflowWebSocketOptions, createGitLab, discoverWorkflowModels, getAnthropicModelForModelId, getModelMapping, getOpenAIApiType, getOpenAIModelForModelId, getProviderForModelId, getValidModelsForProvider, getWorkflowModelRef, gitlab, gitlabOAuthTokenResponseSchema, isResponsesApiModel, isWorkflowModel, parseModelsYml };
1881
+ export { AGENT_PRIVILEGES, type ActionResponsePayload, type AdditionalContext, type AiChatAvailableModels, type AiModel, type ApprovalDecision, BUNDLED_CLIENT_ID, CLIENT_VERSION, type ClientEvent, DEFAULT_AGENT_PRIVILEGES, DEFAULT_AI_GATEWAY_URL, DEFAULT_CLIENT_CAPABILITIES, DEFAULT_WORKFLOW_DEFINITION, type DirectAccessToken, type DiscoveredModels, type DiscoveredWorkflowModel, type DiscoveryScope, GITLAB_COM_URL, type GenerateTokenResponse, type GitLabAgenticOptions, type GitLabAnthropicConfig, GitLabAnthropicLanguageModel, GitLabDirectAccessClient, type GitLabDirectAccessConfig, GitLabError, type GitLabErrorOptions, GitLabModelCache, GitLabModelConfigRegistry, GitLabModelDiscovery, GitLabOAuthManager, type GitLabOAuthTokenResponse, type GitLabOAuthTokens, type GitLabOpenAIConfig, GitLabOpenAILanguageModel, type GitLabProject, GitLabProjectCache, GitLabProjectDetector, type GitLabProjectDetectorConfig, type GitLabProvider, type GitLabProviderSettings, type GitLabThinkingConfig, GitLabWorkflowClient, type GitLabWorkflowClientConfig, GitLabWorkflowLanguageModel, type GitLabWorkflowLanguageModelConfig, type GitLabWorkflowOptions, GitLabWorkflowTokenClient, MODEL_ID_TO_ANTHROPIC_MODEL, MODEL_MAPPINGS, type McpToolDefinition, type ModelCacheEntry, type ModelConfig, type ModelConfigRegistryOptions, type ModelDiscoveryConfig, type ModelMapping, type ModelProvider, type NewCheckpoint, OAUTH_SCOPES, OPENCODE_GITLAB_AUTH_CLIENT_ID, type OpenAIApiType, type OpenCodeAuth, type OpenCodeAuthApi, type OpenCodeAuthOAuth, type RunMcpTool, type StartRequest, TOKEN_EXPIRY_SKEW_MS, VERSION, WORKFLOW_ENVIRONMENT, WS_HEARTBEAT_INTERVAL_MS, WS_KEEPALIVE_PING_INTERVAL_MS, type WorkflowAction, type WorkflowClientEvent, type WorkflowDiscoveryConfig, type WorkflowDiscoveryOptions, type WorkflowDiscoveryResult, type WorkflowStatus, type WorkflowToolExecutor, WorkflowType, type WorkflowWebSocketOptions, createGitLab, discoverWorkflowModels, getAnthropicModelForModelId, getModelMapping, getOpenAIApiType, getOpenAIModelForModelId, getProviderForModelId, getValidModelsForProvider, getWorkflowModelRef, gitlab, gitlabOAuthTokenResponseSchema, isResponsesApiModel, isWorkflowModel, parseModelsYml };
package/dist/index.js CHANGED
@@ -1662,14 +1662,14 @@ var MODEL_MAPPINGS = {
1662
1662
  "duo-workflow-default": { provider: "workflow", model: "default" },
1663
1663
  "duo-workflow-sonnet-4-5": {
1664
1664
  provider: "workflow",
1665
- model: "anthropic/claude-sonnet-4-5-20250929"
1665
+ model: "claude_sonnet_4_5_20250929"
1666
1666
  },
1667
1667
  "duo-workflow-sonnet-5": { provider: "workflow", model: "claude_sonnet_5" },
1668
1668
  "duo-workflow-opus-5": { provider: "workflow", model: "claude_opus_5" },
1669
1669
  "duo-workflow-sonnet-4-6": { provider: "workflow", model: "claude_sonnet_4_6" },
1670
1670
  "duo-workflow-opus-4-5": {
1671
1671
  provider: "workflow",
1672
- model: "anthropic/claude-opus-4-5-20251101"
1672
+ model: "claude_opus_4_5_20251101"
1673
1673
  },
1674
1674
  "duo-workflow-haiku-4-5": { provider: "workflow", model: "claude_haiku_4_5_20251001" },
1675
1675
  "duo-workflow-opus-4-6": { provider: "workflow", model: "claude_opus_4_6_20260205" }
@@ -2771,7 +2771,7 @@ var import_node_async_hooks = require("async_hooks");
2771
2771
  var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
2772
2772
 
2773
2773
  // src/version.ts
2774
- var VERSION = true ? "6.14.0" : "0.0.0-dev";
2774
+ var VERSION = true ? "6.15.0" : "0.0.0-dev";
2775
2775
 
2776
2776
  // src/gitlab-workflow-client.ts
2777
2777
  var WS_CONNECT_TIMEOUT_MS = 3e4;
@@ -3491,8 +3491,14 @@ var GitLabProjectDetector = class {
3491
3491
  };
3492
3492
 
3493
3493
  // src/gitlab-model-discovery.ts
3494
+ function scopeCacheKey(scope) {
3495
+ return "projectId" in scope ? `p:${scope.projectId}` : `n:${scope.rootNamespaceId}`;
3496
+ }
3497
+ function normalizeScope(scope) {
3498
+ return typeof scope === "string" ? { rootNamespaceId: scope } : scope;
3499
+ }
3494
3500
  var AI_CHAT_AVAILABLE_MODELS_QUERY = `
3495
- query aiChatAvailableModels($rootNamespaceId: GroupID!) {
3501
+ query aiChatAvailableModels($projectId: ProjectID, $rootNamespaceId: GroupID) {
3496
3502
  metadata {
3497
3503
  featureFlags(names: ["ai_user_model_switching"]) {
3498
3504
  enabled
@@ -3501,7 +3507,7 @@ var AI_CHAT_AVAILABLE_MODELS_QUERY = `
3501
3507
  version
3502
3508
  }
3503
3509
 
3504
- aiChatAvailableModels(rootNamespaceId: $rootNamespaceId) {
3510
+ aiChatAvailableModels(projectId: $projectId, rootNamespaceId: $rootNamespaceId) {
3505
3511
  defaultModel {
3506
3512
  name
3507
3513
  ref
@@ -3527,18 +3533,42 @@ var GitLabModelDiscovery = class {
3527
3533
  this.fetchFn = config.fetch ?? fetch;
3528
3534
  }
3529
3535
  /**
3530
- * Discover available models for a given root namespace.
3536
+ * Discover available models for a given project or root namespace.
3531
3537
  *
3532
- * Results are cached per `rootNamespaceId` with a 10-minute TTL.
3538
+ * Results are cached per scope with a 10-minute TTL.
3533
3539
  * Use `invalidateCache()` to force an immediate refresh.
3534
3540
  *
3535
- * @param rootNamespaceId - GitLab group ID (e.g., 'gid://gitlab/Group/12345')
3541
+ * @param scope - `{ projectId }` (preferred), `{ rootNamespaceId }`, or a
3542
+ * bare string (treated as `rootNamespaceId` for backwards compatibility)
3536
3543
  */
3537
- async discover(rootNamespaceId) {
3538
- const cached = this.cache.get(rootNamespaceId);
3544
+ async discover(scope) {
3545
+ const normalized = normalizeScope(scope);
3546
+ const cacheKey = scopeCacheKey(normalized);
3547
+ const cached = this.cache.get(cacheKey);
3539
3548
  if (cached && cached.expiresAt > Date.now()) {
3540
3549
  return cached.data;
3541
3550
  }
3551
+ try {
3552
+ const result = await this.query(normalized);
3553
+ this.cache.set(cacheKey, {
3554
+ data: result,
3555
+ expiresAt: Date.now() + DISCOVERY_CACHE_TTL_MS
3556
+ });
3557
+ return result;
3558
+ } catch (error) {
3559
+ if ("projectId" in normalized && normalized.rootNamespaceId) {
3560
+ const fallbackScope = { rootNamespaceId: normalized.rootNamespaceId };
3561
+ const result = await this.query(fallbackScope);
3562
+ this.cache.set(scopeCacheKey(fallbackScope), {
3563
+ data: result,
3564
+ expiresAt: Date.now() + DISCOVERY_CACHE_TTL_MS
3565
+ });
3566
+ return result;
3567
+ }
3568
+ throw error;
3569
+ }
3570
+ }
3571
+ async query(scope) {
3542
3572
  const url = `${this.config.instanceUrl}/api/graphql`;
3543
3573
  try {
3544
3574
  const response = await this.fetchFn(url, {
@@ -3549,7 +3579,7 @@ var GitLabModelDiscovery = class {
3549
3579
  },
3550
3580
  body: JSON.stringify({
3551
3581
  query: AI_CHAT_AVAILABLE_MODELS_QUERY,
3552
- variables: { rootNamespaceId }
3582
+ variables: "projectId" in scope ? { projectId: scope.projectId, rootNamespaceId: void 0 } : { projectId: void 0, rootNamespaceId: scope.rootNamespaceId }
3553
3583
  })
3554
3584
  });
3555
3585
  if (!response.ok) {
@@ -3568,19 +3598,15 @@ var GitLabModelDiscovery = class {
3568
3598
  }
3569
3599
  const models = json.data?.aiChatAvailableModels;
3570
3600
  const metadata = json.data?.metadata;
3571
- const modelSwitchingEnabled = metadata?.featureFlags?.find((f) => f.name === "ai_user_model_switching")?.enabled ?? false;
3572
- const result = {
3601
+ const flag = metadata?.featureFlags?.find((f) => f.name === "ai_user_model_switching");
3602
+ const modelSwitchingEnabled = flag ? flag.enabled : true;
3603
+ return {
3573
3604
  defaultModel: models?.defaultModel ?? null,
3574
3605
  selectableModels: models?.selectableModels ?? [],
3575
3606
  pinnedModel: models?.pinnedModel ?? null,
3576
3607
  modelSwitchingEnabled,
3577
3608
  instanceVersion: metadata?.version ?? null
3578
3609
  };
3579
- this.cache.set(rootNamespaceId, {
3580
- data: result,
3581
- expiresAt: Date.now() + DISCOVERY_CACHE_TTL_MS
3582
- });
3583
- return result;
3584
3610
  } catch (error) {
3585
3611
  if (error instanceof GitLabError) throw error;
3586
3612
  throw new GitLabError({
@@ -3594,11 +3620,11 @@ var GitLabModelDiscovery = class {
3594
3620
  *
3595
3621
  * Priority: pinned > user-selected > default.
3596
3622
  *
3597
- * @param rootNamespaceId - GitLab group ID
3623
+ * @param scope - Project or root namespace to discover models for
3598
3624
  * @param userSelectedRef - Optional user preference
3599
3625
  */
3600
- async getEffectiveModelRef(rootNamespaceId, userSelectedRef) {
3601
- const discovered = await this.discover(rootNamespaceId);
3626
+ async getEffectiveModelRef(scope, userSelectedRef) {
3627
+ const discovered = await this.discover(scope);
3602
3628
  if (discovered.pinnedModel) {
3603
3629
  return discovered.pinnedModel.ref;
3604
3630
  }
@@ -4045,6 +4071,25 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
4045
4071
  this._rootNamespaceId = null;
4046
4072
  return null;
4047
4073
  }
4074
+ /**
4075
+ * Resolve the model discovery scope to use for `aiChatAvailableModels`.
4076
+ *
4077
+ * `projectId` is preferred — it works for both group- and user-namespace
4078
+ * projects — with `rootNamespaceId` attached as a fallback for GitLab
4079
+ * instances too old to support `projectId`-scoped discovery.
4080
+ */
4081
+ async resolveDiscoveryScope() {
4082
+ const rootNamespaceId = await this.resolveRootNamespaceId();
4083
+ const workDir = this.workflowOptions.workingDirectory ?? process.cwd();
4084
+ const project = await this.projectDetector.detectProject(workDir);
4085
+ if (project?.id) {
4086
+ return {
4087
+ projectId: `gid://gitlab/Project/${project.id}`,
4088
+ ...rootNamespaceId ? { rootNamespaceId } : {}
4089
+ };
4090
+ }
4091
+ return rootNamespaceId ? { rootNamespaceId } : null;
4092
+ }
4048
4093
  /**
4049
4094
  * Resolve the effective DWS model ref to use for this stream.
4050
4095
  * Deduplicates concurrent calls via a shared promise.
@@ -4075,13 +4120,13 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
4075
4120
  return this._discoveryPromise;
4076
4121
  }
4077
4122
  async doResolveModelRef() {
4078
- const rootNamespaceId = await this.resolveRootNamespaceId();
4079
- if (!rootNamespaceId) {
4123
+ const scope = await this.resolveDiscoveryScope();
4124
+ if (!scope) {
4080
4125
  this._selectedModelRef = "default";
4081
4126
  return "default";
4082
4127
  }
4083
4128
  try {
4084
- const discovered = await this.modelDiscovery.discover(rootNamespaceId);
4129
+ const discovered = await this.modelDiscovery.discover(scope);
4085
4130
  this.modelCache.saveDiscovery(discovered);
4086
4131
  if (discovered.pinnedModel) {
4087
4132
  this._selectedModelRef = discovered.pinnedModel.ref;
@@ -4124,11 +4169,13 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
4124
4169
  * Call this early (e.g., on IDE startup) to avoid blocking the first stream.
4125
4170
  * Results are persisted to the workspace model cache.
4126
4171
  *
4127
- * @param rootNamespaceId - GitLab group ID (e.g., 'gid://gitlab/Group/12345')
4172
+ * @param scope - Project or root namespace GID to discover models for.
4173
+ * A bare string is treated as a `rootNamespaceId` (e.g.
4174
+ * 'gid://gitlab/Group/12345') for backwards compatibility.
4128
4175
  * @returns Discovered models with default, selectable, and pinned models
4129
4176
  */
4130
- async discoverModels(rootNamespaceId) {
4131
- const result = await this.modelDiscovery.discover(rootNamespaceId);
4177
+ async discoverModels(scope) {
4178
+ const result = await this.modelDiscovery.discover(scope);
4132
4179
  this.modelCache.saveDiscovery(result);
4133
4180
  return result;
4134
4181
  }
@@ -5870,7 +5917,7 @@ async function buildModels(discovered, project) {
5870
5917
  for (const model of all) {
5871
5918
  if (!model.ref || seen.has(model.ref)) continue;
5872
5919
  seen.add(model.ref);
5873
- const id = refToID.get(model.ref) ?? `duo-workflow-${model.ref.replace(/[/_]/g, "-")}`;
5920
+ const id = refToID.get(model.ref) ?? (model.ref.startsWith("__default__") ? "duo-workflow-default" : `duo-workflow-${model.ref.replace(/[/_]/g, "-")}`);
5874
5921
  const limits = configs.get(model.ref);
5875
5922
  models.push({
5876
5923
  id,
@@ -5902,14 +5949,22 @@ async function discoverWorkflowModels(config, options) {
5902
5949
  } catch {
5903
5950
  return { models: [], project: null };
5904
5951
  }
5905
- const namespaceId = project?.namespaceId;
5906
- if (!namespaceId) return { models: [], project };
5952
+ if (!project?.id) return { models: [], project };
5953
+ const scope = {
5954
+ projectId: `gid://gitlab/Project/${project.id}`,
5955
+ ...project.namespaceId ? { rootNamespaceId: `gid://gitlab/Group/${project.namespaceId}` } : {}
5956
+ };
5907
5957
  const discovery = new GitLabModelDiscovery({
5908
5958
  instanceUrl: config.instanceUrl,
5909
5959
  getHeaders: config.getHeaders,
5910
5960
  fetch: config.fetch
5911
5961
  });
5912
- const discovered = await discovery.discover(`gid://gitlab/Group/${namespaceId}`);
5962
+ let discovered;
5963
+ try {
5964
+ discovered = await discovery.discover(scope);
5965
+ } catch {
5966
+ return { models: [], project };
5967
+ }
5913
5968
  cache.saveDiscovery(discovered, project);
5914
5969
  if (!cache.getSelectedModelRef()) {
5915
5970
  const pick = discovered.pinnedModel ?? discovered.defaultModel;