gitlab-ai-provider 6.2.1 → 6.4.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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
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
+ ## 6.4.0 (2026-04-02)
6
+
7
+ - chore: regenerate dist ([c6833c4](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/c6833c4))
8
+ - feat: pass ai_catalog_item_version_id to WebSocket for MCP tool injection ([19cd4e5](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/19cd4e5))
9
+
10
+ ## 6.3.0 (2026-03-31)
11
+
12
+ - fix(model): clear deferredClose before reconnect to prevent stream dying after batch approval ([017f8fe](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/017f8fe))
13
+ - fix(tests): use class mock for GitLabWorkflowTokenClient to support new in all environments ([e3f0e24](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/e3f0e24))
14
+ - chore: rebuild dist ([71bdc51](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/71bdc51))
15
+ - feat(model): per-session DWS workflow lifecycle and tool call approval ([aa7a09a](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/aa7a09a))
16
+
5
17
  ## <small>6.2.1 (2026-03-31)</small>
6
18
 
7
19
  - fix(model): pre-approve all batch tools on approval reconnect to prevent re-triggering ([d7f80e2](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/d7f80e2))
package/dist/index.d.mts CHANGED
@@ -703,6 +703,7 @@ declare class GitLabWorkflowLanguageModel implements LanguageModelV3 {
703
703
  private readonly modelDiscovery;
704
704
  private readonly modelCache;
705
705
  private detectedProjectPath;
706
+ private readonly sessionWorkflows;
706
707
  private currentWorkflowId;
707
708
  private currentWorkflowDefinition;
708
709
  private persistedAgentEmitted;
@@ -756,6 +757,11 @@ declare class GitLabWorkflowLanguageModel implements LanguageModelV3 {
756
757
  * Updated when the user chooses "always" in the approval prompt.
757
758
  */
758
759
  sessionPreapprovedTools: string[];
760
+ /**
761
+ * The opencode session ID. Set per-stream by the host to key per-session
762
+ * DWS workflows. Different sessions get different DWS workflows.
763
+ */
764
+ sessionID: string;
759
765
  /**
760
766
  * Set the approval handler callback.
761
767
  * Called when DWS requires tool call approval. Host (e.g., opencode) wires this
@@ -843,7 +849,7 @@ declare class GitLabWorkflowLanguageModel implements LanguageModelV3 {
843
849
  * Reset the workflow state, forcing a new workflow to be created on the
844
850
  * next doStream() call. Call this when starting a new conversation.
845
851
  */
846
- resetWorkflow(): void;
852
+ resetWorkflow(sessionKey?: string): void;
847
853
  /**
848
854
  * Get the current workflow ID (if any).
849
855
  * Useful for consumers that need to track workflow state.
@@ -1466,6 +1472,10 @@ interface WorkflowWebSocketOptions {
1466
1472
  projectId?: string;
1467
1473
  namespaceId?: string;
1468
1474
  rootNamespaceId?: string;
1475
+ /** AI Catalog item version ID — required for Workhorse to inject MCP tools */
1476
+ aiCatalogItemVersionId?: number;
1477
+ /** Workflow definition type (e.g. 'chat') — used by Workhorse for MCP config */
1478
+ workflowDefinition?: string;
1469
1479
  }
1470
1480
  type EventCallback = (event: WorkflowClientEvent) => void;
1471
1481
  declare class GitLabWorkflowClient {
package/dist/index.d.ts CHANGED
@@ -703,6 +703,7 @@ declare class GitLabWorkflowLanguageModel implements LanguageModelV3 {
703
703
  private readonly modelDiscovery;
704
704
  private readonly modelCache;
705
705
  private detectedProjectPath;
706
+ private readonly sessionWorkflows;
706
707
  private currentWorkflowId;
707
708
  private currentWorkflowDefinition;
708
709
  private persistedAgentEmitted;
@@ -756,6 +757,11 @@ declare class GitLabWorkflowLanguageModel implements LanguageModelV3 {
756
757
  * Updated when the user chooses "always" in the approval prompt.
757
758
  */
758
759
  sessionPreapprovedTools: string[];
760
+ /**
761
+ * The opencode session ID. Set per-stream by the host to key per-session
762
+ * DWS workflows. Different sessions get different DWS workflows.
763
+ */
764
+ sessionID: string;
759
765
  /**
760
766
  * Set the approval handler callback.
761
767
  * Called when DWS requires tool call approval. Host (e.g., opencode) wires this
@@ -843,7 +849,7 @@ declare class GitLabWorkflowLanguageModel implements LanguageModelV3 {
843
849
  * Reset the workflow state, forcing a new workflow to be created on the
844
850
  * next doStream() call. Call this when starting a new conversation.
845
851
  */
846
- resetWorkflow(): void;
852
+ resetWorkflow(sessionKey?: string): void;
847
853
  /**
848
854
  * Get the current workflow ID (if any).
849
855
  * Useful for consumers that need to track workflow state.
@@ -1466,6 +1472,10 @@ interface WorkflowWebSocketOptions {
1466
1472
  projectId?: string;
1467
1473
  namespaceId?: string;
1468
1474
  rootNamespaceId?: string;
1475
+ /** AI Catalog item version ID — required for Workhorse to inject MCP tools */
1476
+ aiCatalogItemVersionId?: number;
1477
+ /** Workflow definition type (e.g. 'chat') — used by Workhorse for MCP config */
1478
+ workflowDefinition?: string;
1469
1479
  }
1470
1480
  type EventCallback = (event: WorkflowClientEvent) => void;
1471
1481
  declare class GitLabWorkflowClient {
package/dist/index.js CHANGED
@@ -1653,7 +1653,7 @@ var GitLabOpenAILanguageModel = class {
1653
1653
  var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
1654
1654
 
1655
1655
  // src/version.ts
1656
- var VERSION = true ? "6.2.1" : "0.0.0-dev";
1656
+ var VERSION = true ? "6.3.0" : "0.0.0-dev";
1657
1657
 
1658
1658
  // src/gitlab-workflow-types.ts
1659
1659
  var WorkflowType = /* @__PURE__ */ ((WorkflowType2) => {
@@ -1837,6 +1837,12 @@ var GitLabWorkflowClient = class {
1837
1837
  if (options.modelRef && options.modelRef !== "default") {
1838
1838
  url.searchParams.set("user_selected_model_identifier", options.modelRef);
1839
1839
  }
1840
+ if (options.aiCatalogItemVersionId) {
1841
+ url.searchParams.set("ai_catalog_item_version_id", String(options.aiCatalogItemVersionId));
1842
+ }
1843
+ if (options.workflowDefinition) {
1844
+ url.searchParams.set("workflow_definition", options.workflowDefinition);
1845
+ }
1840
1846
  return url.toString();
1841
1847
  }
1842
1848
  buildWebSocketHeaders(options) {
@@ -3046,14 +3052,12 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3046
3052
  // Cached detected project path
3047
3053
  detectedProjectPath = null;
3048
3054
  // Workflow ID persisted across turns for multi-turn conversations.
3049
- // When DWS sends INPUT_REQUIRED, the workflow stays alive server-side.
3050
- // On the next doStream() call we reuse this ID (skip createWorkflow).
3055
+ // Per-session workflow state. Keyed by opencode sessionID (x-opencode-session header).
3056
+ // Each opencode session gets its own DWS workflow, reused across turns within that session.
3057
+ sessionWorkflows = /* @__PURE__ */ new Map();
3058
+ // Fallback for callers that don't pass x-opencode-session (e.g. direct SDK use).
3051
3059
  currentWorkflowId = null;
3052
- // Track which workflowDefinition/flowConfig the current workflow was created with.
3053
- // When the agent changes, reset the workflow so a new one is created correctly.
3054
3060
  currentWorkflowDefinition = null;
3055
- // Persisted across turns so that cumulative DWS chat logs don't re-emit
3056
- // messages that were already streamed in a previous doStream() call.
3057
3061
  persistedAgentEmitted = /* @__PURE__ */ new Map();
3058
3062
  // Track all active stream clients so stopWorkflow() can stop them all.
3059
3063
  activeClients = /* @__PURE__ */ new Set();
@@ -3115,6 +3119,11 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3115
3119
  * Updated when the user chooses "always" in the approval prompt.
3116
3120
  */
3117
3121
  sessionPreapprovedTools = [];
3122
+ /**
3123
+ * The opencode session ID. Set per-stream by the host to key per-session
3124
+ * DWS workflows. Different sessions get different DWS workflows.
3125
+ */
3126
+ sessionID = "";
3118
3127
  /**
3119
3128
  * Set the approval handler callback.
3120
3129
  * Called when DWS requires tool call approval. Host (e.g., opencode) wires this
@@ -3344,9 +3353,14 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3344
3353
  * Reset the workflow state, forcing a new workflow to be created on the
3345
3354
  * next doStream() call. Call this when starting a new conversation.
3346
3355
  */
3347
- resetWorkflow() {
3348
- this.currentWorkflowId = null;
3349
- this.persistedAgentEmitted.clear();
3356
+ resetWorkflow(sessionKey) {
3357
+ if (sessionKey) {
3358
+ this.sessionWorkflows.delete(sessionKey);
3359
+ } else {
3360
+ this.sessionWorkflows.clear();
3361
+ this.currentWorkflowId = null;
3362
+ this.persistedAgentEmitted.clear();
3363
+ }
3350
3364
  }
3351
3365
  /**
3352
3366
  * Get the current workflow ID (if any).
@@ -3445,12 +3459,21 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3445
3459
  );
3446
3460
  const projectId = await this.resolveProjectId();
3447
3461
  const effectiveDefinition = callFlowConfig ?? callWorkflowDefinition ?? DEFAULT_WORKFLOW_DEFINITION;
3448
- if (this.currentWorkflowId && this.currentWorkflowDefinition !== effectiveDefinition) {
3462
+ const sessionKey = this.sessionID;
3463
+ let sess = this.sessionWorkflows.get(sessionKey);
3464
+ if (sess && sess.workflowDefinition !== effectiveDefinition) {
3465
+ this.sessionWorkflows.delete(sessionKey);
3466
+ sess = void 0;
3467
+ }
3468
+ const useLegacy = sessionKey === "";
3469
+ if (useLegacy && this.currentWorkflowId && this.currentWorkflowDefinition !== effectiveDefinition) {
3449
3470
  this.currentWorkflowId = null;
3450
3471
  this.currentWorkflowDefinition = null;
3451
3472
  }
3452
3473
  let workflowId;
3453
- if (this.currentWorkflowId) {
3474
+ if (sess) {
3475
+ workflowId = sess.workflowId;
3476
+ } else if (useLegacy && this.currentWorkflowId) {
3454
3477
  workflowId = this.currentWorkflowId;
3455
3478
  } else {
3456
3479
  workflowId = await this.tokenClient.createWorkflow(goal, {
@@ -3460,8 +3483,17 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3460
3483
  agentPrivileges: this.workflowOptions.agentPrivileges,
3461
3484
  aiCatalogItemVersionId: callAiCatalogItemVersionId
3462
3485
  });
3463
- this.currentWorkflowId = workflowId;
3464
- this.currentWorkflowDefinition = effectiveDefinition;
3486
+ if (useLegacy) {
3487
+ this.currentWorkflowId = workflowId;
3488
+ this.currentWorkflowDefinition = effectiveDefinition;
3489
+ } else {
3490
+ this.sessionWorkflows.set(sessionKey, {
3491
+ workflowId,
3492
+ workflowDefinition: effectiveDefinition,
3493
+ agentEmitted: /* @__PURE__ */ new Map()
3494
+ });
3495
+ sess = this.sessionWorkflows.get(sessionKey);
3496
+ }
3465
3497
  }
3466
3498
  const wsClient = new GitLabWorkflowClient();
3467
3499
  this.activeClients.add(wsClient);
@@ -3474,10 +3506,11 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3474
3506
  approvalPending: false,
3475
3507
  deferredClose: null,
3476
3508
  activeTextBlockId: null,
3477
- agentMessageEmitted: new Map(this.persistedAgentEmitted),
3509
+ agentMessageEmitted: new Map(sess?.agentEmitted ?? this.persistedAgentEmitted),
3478
3510
  currentAgentMessageId: "",
3479
3511
  activeClient: wsClient,
3480
- processedRequestIDs: /* @__PURE__ */ new Set()
3512
+ processedRequestIDs: /* @__PURE__ */ new Set(),
3513
+ sessionKey
3481
3514
  };
3482
3515
  for (const msg of options.prompt) {
3483
3516
  if (msg.role === "system") {
@@ -3501,7 +3534,9 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3501
3534
  headers: this.config.getHeaders(),
3502
3535
  projectId: this.workflowOptions.projectId,
3503
3536
  namespaceId: this.workflowOptions.namespaceId,
3504
- rootNamespaceId: this.workflowOptions.rootNamespaceId
3537
+ rootNamespaceId: this.workflowOptions.rootNamespaceId,
3538
+ aiCatalogItemVersionId: callAiCatalogItemVersionId,
3539
+ workflowDefinition: callWorkflowDefinition ?? this.workflowOptions.workflowDefinition
3505
3540
  },
3506
3541
  (event) => {
3507
3542
  this.handleWorkflowEvent(
@@ -3512,7 +3547,11 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3512
3547
  toolExecutor,
3513
3548
  () => `text-${textBlockCounter++}`,
3514
3549
  availableToolNames,
3515
- startReq
3550
+ startReq,
3551
+ {
3552
+ aiCatalogItemVersionId: callAiCatalogItemVersionId,
3553
+ workflowDefinition: callWorkflowDefinition ?? this.workflowOptions.workflowDefinition
3554
+ }
3516
3555
  );
3517
3556
  }
3518
3557
  );
@@ -3588,7 +3627,13 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3588
3627
  wsClient.close();
3589
3628
  this.activeClients.delete(wsClient);
3590
3629
  ss.activeClient = null;
3591
- this.currentWorkflowId = null;
3630
+ if (!ss.streamClosed) {
3631
+ if (ss.sessionKey) {
3632
+ this.sessionWorkflows.delete(ss.sessionKey);
3633
+ } else {
3634
+ this.currentWorkflowId = null;
3635
+ }
3636
+ }
3592
3637
  }
3593
3638
  });
3594
3639
  return {
@@ -3601,7 +3646,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3601
3646
  // ---------------------------------------------------------------------------
3602
3647
  // Event handling
3603
3648
  // ---------------------------------------------------------------------------
3604
- handleWorkflowEvent(ss, event, controller, wsClient, toolExecutor, nextTextId, availableToolNames, startReq) {
3649
+ handleWorkflowEvent(ss, event, controller, wsClient, toolExecutor, nextTextId, availableToolNames, startReq, wsExtras) {
3605
3650
  if (ss.streamClosed) {
3606
3651
  return;
3607
3652
  }
@@ -3711,8 +3756,9 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3711
3756
  controller,
3712
3757
  toolExecutor,
3713
3758
  nextTextId,
3714
- availableToolNames
3715
- ).catch(() => {
3759
+ availableToolNames,
3760
+ wsExtras
3761
+ ).catch((_err) => {
3716
3762
  ss.approvalPending = false;
3717
3763
  if (ss.deferredClose) {
3718
3764
  const close = ss.deferredClose;
@@ -3738,7 +3784,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3738
3784
  });
3739
3785
  ss.streamClosed = true;
3740
3786
  controller.close();
3741
- this.cleanupClient(ss);
3787
+ this.cleanupClient(ss, false);
3742
3788
  };
3743
3789
  if (ss.pendingToolCount > 0 || ss.approvalPending) {
3744
3790
  ss.deferredClose = doCompleteClose;
@@ -3796,7 +3842,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3796
3842
  });
3797
3843
  ss.streamClosed = true;
3798
3844
  controller.close();
3799
- this.cleanupClient(ss);
3845
+ this.cleanupClient(ss, false);
3800
3846
  }
3801
3847
  };
3802
3848
  if (ss.pendingToolCount > 0 || ss.approvalPending) {
@@ -3865,7 +3911,8 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3865
3911
  });
3866
3912
  ss.streamedOutputChars += delta.length;
3867
3913
  ss.agentMessageEmitted.set(msgId, content.length);
3868
- this.persistedAgentEmitted.set(msgId, content.length);
3914
+ const target = this.sessionWorkflows.get(ss.sessionKey)?.agentEmitted ?? this.persistedAgentEmitted;
3915
+ target.set(msgId, content.length);
3869
3916
  ss.currentAgentMessageId = msgId;
3870
3917
  }
3871
3918
  }
@@ -3971,11 +4018,15 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3971
4018
  ss.activeClient = null;
3972
4019
  }
3973
4020
  if (clearWorkflow) {
3974
- this.currentWorkflowId = null;
3975
- this.persistedAgentEmitted.clear();
4021
+ if (ss.sessionKey) {
4022
+ this.sessionWorkflows.delete(ss.sessionKey);
4023
+ } else {
4024
+ this.currentWorkflowId = null;
4025
+ this.persistedAgentEmitted.clear();
4026
+ }
3976
4027
  }
3977
4028
  }
3978
- async approveAndResume(ss, tools, startReq, controller, toolExecutor, nextTextId, availableToolNames) {
4029
+ async approveAndResume(ss, tools, startReq, controller, toolExecutor, nextTextId, availableToolNames, wsExtras) {
3979
4030
  const handler = this.workflowOptions.approvalHandler;
3980
4031
  if (!handler || !startReq) {
3981
4032
  ss.approvalPending = false;
@@ -3995,6 +4046,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3995
4046
  return;
3996
4047
  }
3997
4048
  ss.approvalPending = false;
4049
+ ss.deferredClose = null;
3998
4050
  this.cleanupClient(ss, false);
3999
4051
  const approval = decision.approved ? { approval: { tool_name: tools[0]?.name, tool_args_json: tools[0]?.args } } : { rejection: { message: decision.message ?? "User rejected" } };
4000
4052
  const newStartReq = {
@@ -4014,7 +4066,9 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
4014
4066
  headers: this.config.getHeaders(),
4015
4067
  projectId: this.workflowOptions.projectId,
4016
4068
  namespaceId: this.workflowOptions.namespaceId,
4017
- rootNamespaceId: this.workflowOptions.rootNamespaceId
4069
+ rootNamespaceId: this.workflowOptions.rootNamespaceId,
4070
+ aiCatalogItemVersionId: wsExtras?.aiCatalogItemVersionId,
4071
+ workflowDefinition: wsExtras?.workflowDefinition
4018
4072
  },
4019
4073
  (event) => this.handleWorkflowEvent(
4020
4074
  ss,
@@ -4024,7 +4078,8 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
4024
4078
  toolExecutor,
4025
4079
  nextTextId,
4026
4080
  availableToolNames,
4027
- newStartReq
4081
+ newStartReq,
4082
+ wsExtras
4028
4083
  )
4029
4084
  );
4030
4085
  newClient.sendStartRequest(newStartReq);