@gitlab/gitlab-ai-provider 3.1.2 → 3.1.3

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>3.1.3 (2026-01-21)</small>
6
+
7
+ - Merge branch 'fix/streaming-tool-call-handling' into 'main' ([22279b1](https://gitlab.com/gitlab-org/editor-extensions/gitlab-ai-provider/commit/22279b1))
8
+ - fix: refactor streaming to handle tool calls properly ([88fb513](https://gitlab.com/gitlab-org/editor-extensions/gitlab-ai-provider/commit/88fb513))
9
+
5
10
  ## <small>3.1.2 (2026-01-18)</small>
6
11
 
7
12
  - fix: removed API tools and added new env variable ([a64ef6d](https://gitlab.com/gitlab-org/editor-extensions/gitlab-ai-provider/commit/a64ef6d))
package/dist/index.js CHANGED
@@ -192,8 +192,6 @@ var GitLabDirectAccessClient = class {
192
192
  };
193
193
 
194
194
  // src/gitlab-agentic-language-model.ts
195
- var debugLog = (..._args) => {
196
- };
197
195
  var GitLabAgenticLanguageModel = class {
198
196
  specificationVersion = "v2";
199
197
  modelId;
@@ -222,14 +220,7 @@ var GitLabAgenticLanguageModel = class {
222
220
  */
223
221
  async getAnthropicClient(forceRefresh = false) {
224
222
  const tokenData = await this.directAccessClient.getDirectAccessToken(forceRefresh);
225
- debugLog("[gitlab-ai-provider] Token headers from GitLab:", tokenData.headers);
226
- debugLog("[gitlab-ai-provider] Proxy URL:", this.directAccessClient.getAnthropicProxyUrl());
227
223
  const { "x-api-key": _removed, ...filteredHeaders } = tokenData.headers;
228
- if (_removed) {
229
- debugLog(
230
- "[gitlab-ai-provider] Filtered out x-api-key from headers (using authToken instead)"
231
- );
232
- }
233
224
  this.anthropicClient = new import_sdk.default({
234
225
  apiKey: null,
235
226
  authToken: tokenData.token,
@@ -468,114 +459,155 @@ var GitLabAgenticLanguageModel = class {
468
459
  const self = this;
469
460
  const stream = new ReadableStream({
470
461
  start: async (controller) => {
462
+ const contentBlocks = {};
463
+ const usage = {
464
+ inputTokens: 0,
465
+ outputTokens: 0,
466
+ totalTokens: 0
467
+ };
468
+ let finishReason = "unknown";
471
469
  try {
472
- const anthropicStream = client.messages.stream(requestBody);
473
- let currentTextBlockId = null;
474
- let currentToolBlockId = null;
475
- let currentToolName = null;
476
- const usage = {
477
- inputTokens: 0,
478
- outputTokens: 0,
479
- totalTokens: 0
480
- };
481
- let finishReason = "unknown";
470
+ const anthropicStream = client.messages.stream(requestBody, {
471
+ signal: options.abortSignal
472
+ });
482
473
  controller.enqueue({
483
474
  type: "stream-start",
484
475
  warnings: []
485
476
  });
486
- for await (const event of anthropicStream) {
487
- switch (event.type) {
488
- case "message_start":
489
- if (event.message.usage) {
490
- usage.inputTokens = event.message.usage.input_tokens;
491
- }
492
- controller.enqueue({
493
- type: "response-metadata",
494
- id: event.message.id,
495
- modelId: event.message.model
496
- });
497
- break;
498
- case "content_block_start":
499
- if (event.content_block.type === "text") {
500
- currentTextBlockId = `text-${event.index}`;
501
- controller.enqueue({
502
- type: "text-start",
503
- id: currentTextBlockId
504
- });
505
- } else if (event.content_block.type === "tool_use") {
506
- currentToolBlockId = event.content_block.id;
507
- currentToolName = event.content_block.name;
508
- controller.enqueue({
509
- type: "tool-input-start",
510
- id: currentToolBlockId,
511
- toolName: currentToolName
512
- });
513
- }
514
- break;
515
- case "content_block_delta":
516
- if (event.delta.type === "text_delta" && currentTextBlockId) {
517
- controller.enqueue({
518
- type: "text-delta",
519
- id: currentTextBlockId,
520
- delta: event.delta.text
521
- });
522
- } else if (event.delta.type === "input_json_delta" && currentToolBlockId) {
523
- controller.enqueue({
524
- type: "tool-input-delta",
525
- id: currentToolBlockId,
526
- delta: event.delta.partial_json
527
- });
528
- }
529
- break;
530
- case "content_block_stop":
531
- if (currentTextBlockId) {
532
- controller.enqueue({
533
- type: "text-end",
534
- id: currentTextBlockId
535
- });
536
- currentTextBlockId = null;
537
- }
538
- if (currentToolBlockId) {
539
- controller.enqueue({
540
- type: "tool-input-end",
541
- id: currentToolBlockId
542
- });
543
- currentToolBlockId = null;
544
- currentToolName = null;
545
- }
546
- break;
547
- case "message_delta":
548
- if (event.usage) {
549
- usage.outputTokens = event.usage.output_tokens;
550
- usage.totalTokens = (usage.inputTokens || 0) + event.usage.output_tokens;
551
- }
552
- if (event.delta.stop_reason) {
553
- finishReason = self.convertFinishReason(event.delta.stop_reason);
554
- }
555
- break;
556
- case "message_stop": {
557
- const finalMessage = await anthropicStream.finalMessage();
558
- for (const block of finalMessage.content) {
559
- if (block.type === "tool_use") {
477
+ await new Promise((resolve2, reject) => {
478
+ anthropicStream.on("streamEvent", (event) => {
479
+ try {
480
+ switch (event.type) {
481
+ case "message_start":
482
+ if (event.message.usage) {
483
+ usage.inputTokens = event.message.usage.input_tokens;
484
+ }
560
485
  controller.enqueue({
561
- type: "tool-call",
562
- toolCallId: block.id,
563
- toolName: block.name,
564
- input: JSON.stringify(block.input)
486
+ type: "response-metadata",
487
+ id: event.message.id,
488
+ modelId: event.message.model
565
489
  });
490
+ break;
491
+ case "content_block_start":
492
+ if (event.content_block.type === "text") {
493
+ const textId = `text-${event.index}`;
494
+ contentBlocks[event.index] = { type: "text", id: textId };
495
+ controller.enqueue({
496
+ type: "text-start",
497
+ id: textId
498
+ });
499
+ } else if (event.content_block.type === "tool_use") {
500
+ contentBlocks[event.index] = {
501
+ type: "tool-call",
502
+ toolCallId: event.content_block.id,
503
+ toolName: event.content_block.name,
504
+ input: ""
505
+ };
506
+ controller.enqueue({
507
+ type: "tool-input-start",
508
+ id: event.content_block.id,
509
+ toolName: event.content_block.name
510
+ });
511
+ }
512
+ break;
513
+ case "content_block_delta": {
514
+ const block = contentBlocks[event.index];
515
+ if (event.delta.type === "text_delta" && block?.type === "text") {
516
+ controller.enqueue({
517
+ type: "text-delta",
518
+ id: block.id,
519
+ delta: event.delta.text
520
+ });
521
+ } else if (event.delta.type === "input_json_delta" && block?.type === "tool-call") {
522
+ block.input += event.delta.partial_json;
523
+ controller.enqueue({
524
+ type: "tool-input-delta",
525
+ id: block.toolCallId,
526
+ delta: event.delta.partial_json
527
+ });
528
+ }
529
+ break;
530
+ }
531
+ case "content_block_stop": {
532
+ const block = contentBlocks[event.index];
533
+ if (block?.type === "text") {
534
+ controller.enqueue({
535
+ type: "text-end",
536
+ id: block.id
537
+ });
538
+ } else if (block?.type === "tool-call") {
539
+ controller.enqueue({
540
+ type: "tool-input-end",
541
+ id: block.toolCallId
542
+ });
543
+ controller.enqueue({
544
+ type: "tool-call",
545
+ toolCallId: block.toolCallId,
546
+ toolName: block.toolName,
547
+ input: block.input === "" ? "{}" : block.input
548
+ });
549
+ }
550
+ delete contentBlocks[event.index];
551
+ break;
552
+ }
553
+ case "message_delta":
554
+ if (event.usage) {
555
+ usage.outputTokens = event.usage.output_tokens;
556
+ usage.totalTokens = (usage.inputTokens || 0) + event.usage.output_tokens;
557
+ }
558
+ if (event.delta.stop_reason) {
559
+ finishReason = self.convertFinishReason(event.delta.stop_reason);
560
+ }
561
+ break;
562
+ case "message_stop": {
563
+ controller.enqueue({
564
+ type: "finish",
565
+ finishReason,
566
+ usage
567
+ });
568
+ break;
566
569
  }
567
570
  }
568
- controller.enqueue({
569
- type: "finish",
570
- finishReason,
571
- usage
572
- });
573
- break;
571
+ } catch {
574
572
  }
573
+ });
574
+ anthropicStream.on("end", () => {
575
+ resolve2();
576
+ });
577
+ anthropicStream.on("error", (error) => {
578
+ reject(error);
579
+ });
580
+ });
581
+ for (const [, block] of Object.entries(contentBlocks)) {
582
+ if (block.type === "tool-call") {
583
+ controller.enqueue({
584
+ type: "tool-input-end",
585
+ id: block.toolCallId
586
+ });
587
+ controller.enqueue({
588
+ type: "tool-call",
589
+ toolCallId: block.toolCallId,
590
+ toolName: block.toolName,
591
+ input: block.input === "" ? "{}" : block.input
592
+ });
575
593
  }
576
594
  }
577
595
  controller.close();
578
596
  } catch (error) {
597
+ for (const [, block] of Object.entries(contentBlocks)) {
598
+ if (block.type === "tool-call") {
599
+ controller.enqueue({
600
+ type: "tool-input-end",
601
+ id: block.toolCallId
602
+ });
603
+ controller.enqueue({
604
+ type: "tool-call",
605
+ toolCallId: block.toolCallId,
606
+ toolName: block.toolName,
607
+ input: block.input === "" ? "{}" : block.input
608
+ });
609
+ }
610
+ }
579
611
  if (!isRetry && self.isTokenError(error)) {
580
612
  self.directAccessClient.invalidateToken();
581
613
  controller.enqueue({
@@ -1045,7 +1077,7 @@ var GitLabProjectCache = class {
1045
1077
  };
1046
1078
 
1047
1079
  // src/gitlab-project-detector.ts
1048
- var debugLog2 = (..._args) => {
1080
+ var debugLog = (..._args) => {
1049
1081
  };
1050
1082
  var GitLabProjectDetector = class {
1051
1083
  config;
@@ -1074,35 +1106,35 @@ var GitLabProjectDetector = class {
1074
1106
  return cached;
1075
1107
  }
1076
1108
  try {
1077
- debugLog2(`[GitLabProjectDetector] Getting git remote URL from: ${workingDirectory}`);
1109
+ debugLog(`[GitLabProjectDetector] Getting git remote URL from: ${workingDirectory}`);
1078
1110
  const remoteUrl = await this.getGitRemoteUrl(workingDirectory, remoteName);
1079
1111
  if (!remoteUrl) {
1080
- debugLog2(`[GitLabProjectDetector] No git remote URL found`);
1112
+ debugLog(`[GitLabProjectDetector] No git remote URL found`);
1081
1113
  return null;
1082
1114
  }
1083
- debugLog2(`[GitLabProjectDetector] Git remote URL: ${remoteUrl}`);
1084
- debugLog2(
1115
+ debugLog(`[GitLabProjectDetector] Git remote URL: ${remoteUrl}`);
1116
+ debugLog(
1085
1117
  `[GitLabProjectDetector] Parsing project path from URL (instance: ${this.config.instanceUrl})`
1086
1118
  );
1087
1119
  const projectPath = this.parseGitRemoteUrl(remoteUrl, this.config.instanceUrl);
1088
1120
  if (!projectPath) {
1089
- debugLog2(
1121
+ debugLog(
1090
1122
  `[GitLabProjectDetector] Could not parse project path from URL (remote doesn't match instance)`
1091
1123
  );
1092
1124
  return null;
1093
1125
  }
1094
- debugLog2(`[GitLabProjectDetector] Parsed project path: ${projectPath}`);
1095
- debugLog2(`[GitLabProjectDetector] Fetching project from GitLab API: ${projectPath}`);
1126
+ debugLog(`[GitLabProjectDetector] Parsed project path: ${projectPath}`);
1127
+ debugLog(`[GitLabProjectDetector] Fetching project from GitLab API: ${projectPath}`);
1096
1128
  const project = await this.getProjectByPath(projectPath);
1097
- debugLog2(`[GitLabProjectDetector] \u2713 Project fetched successfully:`, project);
1129
+ debugLog(`[GitLabProjectDetector] \u2713 Project fetched successfully:`, project);
1098
1130
  this.cache.set(cacheKey, project);
1099
1131
  return project;
1100
1132
  } catch (error) {
1101
1133
  if (error instanceof GitLabError) {
1102
- debugLog2(`[GitLabProjectDetector] GitLab API error:`, error.message || error);
1134
+ debugLog(`[GitLabProjectDetector] GitLab API error:`, error.message || error);
1103
1135
  return null;
1104
1136
  }
1105
- debugLog2(`[GitLabProjectDetector] Unexpected error:`, error);
1137
+ debugLog(`[GitLabProjectDetector] Unexpected error:`, error);
1106
1138
  console.warn(`Failed to auto-detect GitLab project: ${error}`);
1107
1139
  return null;
1108
1140
  }