gitlab-ai-provider 6.4.0 → 6.4.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,10 @@
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.4.1 (2026-04-06)</small>
6
+
7
+ - fix(workflow): surface server-side MCP tool calls as structured AI SDK events ([fcae357](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/fcae357))
8
+
5
9
  ## 6.4.0 (2026-04-02)
6
10
 
7
11
  - chore: regenerate dist ([c6833c4](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/c6833c4))
package/README.md CHANGED
@@ -334,6 +334,37 @@ The workflow service automatically maps DWS built-in tools to consumer tool name
334
334
  | `mkdir` | `bash` | Create directory |
335
335
  | `runHTTPRequest` | `bash` | Execute HTTP request |
336
336
 
337
+ **Server-Side Tool Visibility:**
338
+
339
+ When DWS executes tools on the server (e.g., MCP tools like Linear or Context7 that are configured in the agent's catalog), the provider automatically surfaces these as structured AI SDK stream events (`tool-input-start`, `tool-call`, `tool-result`) with `providerExecuted: true` and `dynamic: true`. This allows host applications to display server-side tool calls in their UI alongside locally-executed tools.
340
+
341
+ Server-side tool events are deduplicated across turns — the provider tracks emitted checkpoint tool entries in the persistent session state so that cumulative DWS checkpoints don't re-emit tools from previous turns. Tools that were already handled via `tool-request` (client-executed tools) are also excluded from checkpoint emission to avoid duplicates.
342
+
343
+ Since server-side MCP tools are not registered in the host's tool registry, the `dynamic: true` flag ensures the AI SDK's `parseToolCall` bypasses tool registry validation via the `parseProviderExecutedDynamicToolCall` path.
344
+
345
+ **Tool Call Approval:**
346
+
347
+ The workflow model supports an approval flow for tool execution. When DWS requires approval before executing a tool, it sends a `TOOL_CALL_APPROVAL_REQUIRED` checkpoint. The provider exposes this via the `approvalHandler` callback:
348
+
349
+ ```typescript
350
+ const model = gitlab.workflowChat('duo-workflow');
351
+
352
+ // Set approval handler — called when DWS requests tool execution approval
353
+ model.approvalHandler = async (tools) => {
354
+ // tools: Array<{ name: string, args: string }>
355
+ console.log(
356
+ 'Approval requested for:',
357
+ tools.map((t) => t.name)
358
+ );
359
+ return { approved: true }; // or { approved: false }
360
+ };
361
+
362
+ // Pre-approve specific tools to skip approval prompts
363
+ model.sessionPreapprovedTools = ['read', 'glob', 'grep'];
364
+ ```
365
+
366
+ Approved tools are tracked per-session. When the user approves a tool via "allow always", the provider automatically pre-approves it on subsequent DWS reconnections within the same session.
367
+
337
368
  **Workflow Options:**
338
369
 
339
370
  ```typescript
@@ -499,6 +530,8 @@ Provides server-side agentic execution through GitLab Duo Workflow Service.
499
530
  - Dynamic model discovery via GraphQL (`aiChatAvailableModels`)
500
531
  - Automatic model selection (pinned → user-selected → default)
501
532
  - Built-in tool mapping and MCP tool support
533
+ - Server-side tool visibility — DWS-executed MCP tools are surfaced as structured AI SDK events
534
+ - Tool call approval flow with per-session pre-approval tracking
502
535
  - Per-stream state isolation for concurrent requests
503
536
  - Dual heartbeat (WebSocket ping + JSON heartbeat)
504
537
 
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.3.0" : "0.0.0-dev";
1656
+ var VERSION = true ? "6.4.0" : "0.0.0-dev";
1657
1657
 
1658
1658
  // src/gitlab-workflow-types.ts
1659
1659
  var WorkflowType = /* @__PURE__ */ ((WorkflowType2) => {
@@ -3490,7 +3490,8 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3490
3490
  this.sessionWorkflows.set(sessionKey, {
3491
3491
  workflowId,
3492
3492
  workflowDefinition: effectiveDefinition,
3493
- agentEmitted: /* @__PURE__ */ new Map()
3493
+ agentEmitted: /* @__PURE__ */ new Map(),
3494
+ toolEntries: /* @__PURE__ */ new Set()
3494
3495
  });
3495
3496
  sess = this.sessionWorkflows.get(sessionKey);
3496
3497
  }
@@ -3510,6 +3511,8 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3510
3511
  currentAgentMessageId: "",
3511
3512
  activeClient: wsClient,
3512
3513
  processedRequestIDs: /* @__PURE__ */ new Set(),
3514
+ emittedToolEntries: new Set(sess?.toolEntries),
3515
+ emittedToolKeys: /* @__PURE__ */ new Set(),
3513
3516
  sessionKey
3514
3517
  };
3515
3518
  for (const msg of options.prompt) {
@@ -3659,6 +3662,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3659
3662
  const { requestID, data } = event;
3660
3663
  if (ss.processedRequestIDs.has(requestID)) break;
3661
3664
  ss.processedRequestIDs.add(requestID);
3665
+ ss.emittedToolKeys.add(data.name);
3662
3666
  let parsedArgs;
3663
3667
  try {
3664
3668
  JSON.parse(data.args);
@@ -3708,6 +3712,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3708
3712
  if (ss.processedRequestIDs.has(event.requestID)) break;
3709
3713
  ss.processedRequestIDs.add(event.requestID);
3710
3714
  const mapped = mapBuiltinTool(event.toolName, event.data, availableToolNames);
3715
+ ss.emittedToolKeys.add(mapped.toolName);
3711
3716
  const mappedArgs = JSON.stringify(mapped.args);
3712
3717
  if (ss.activeTextBlockId) {
3713
3718
  controller.enqueue({ type: "text-end", id: ss.activeTextBlockId });
@@ -3889,6 +3894,51 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3889
3894
  }
3890
3895
  for (let i = 0; i < chatLog.length; i++) {
3891
3896
  const entry = chatLog[i];
3897
+ if (entry.message_type === "tool" && entry.tool_info) {
3898
+ const toolId = entry.message_id || entry.correlation_id || `tool-${i}`;
3899
+ if (ss.emittedToolEntries.has(toolId)) continue;
3900
+ const name = entry.tool_info.name;
3901
+ const args = JSON.stringify(entry.tool_info.args ?? {});
3902
+ if (ss.emittedToolKeys.has(name)) {
3903
+ ss.emittedToolEntries.add(toolId);
3904
+ const sess2 = this.sessionWorkflows.get(ss.sessionKey);
3905
+ if (sess2) sess2.toolEntries.add(toolId);
3906
+ continue;
3907
+ }
3908
+ if (ss.activeTextBlockId) {
3909
+ controller.enqueue({ type: "text-end", id: ss.activeTextBlockId });
3910
+ ss.activeTextBlockId = null;
3911
+ }
3912
+ controller.enqueue({
3913
+ type: "tool-input-start",
3914
+ id: toolId,
3915
+ toolName: name,
3916
+ providerExecuted: true
3917
+ });
3918
+ controller.enqueue({ type: "tool-input-delta", id: toolId, delta: args });
3919
+ controller.enqueue({ type: "tool-input-end", id: toolId });
3920
+ controller.enqueue({
3921
+ type: "tool-call",
3922
+ toolCallId: toolId,
3923
+ toolName: name,
3924
+ input: args,
3925
+ providerExecuted: true,
3926
+ dynamic: true
3927
+ });
3928
+ const response = entry.tool_info.tool_response;
3929
+ const output = typeof response === "string" ? response : response ? response.content : entry.content || "";
3930
+ controller.enqueue({
3931
+ type: "tool-result",
3932
+ toolCallId: toolId,
3933
+ toolName: name,
3934
+ result: { output, title: name, metadata: {} },
3935
+ isError: false
3936
+ });
3937
+ ss.emittedToolEntries.add(toolId);
3938
+ const sess = this.sessionWorkflows.get(ss.sessionKey);
3939
+ if (sess) sess.toolEntries.add(toolId);
3940
+ continue;
3941
+ }
3892
3942
  if (entry.message_type !== "agent") continue;
3893
3943
  const content = entry.content || "";
3894
3944
  const msgId = entry.message_id || `idx-${i}`;