gitlab-ai-provider 5.3.3 → 6.1.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/dist/index.mjs CHANGED
@@ -171,7 +171,7 @@ var GitLabDirectAccessClient = class {
171
171
 
172
172
  // src/gitlab-anthropic-language-model.ts
173
173
  var GitLabAnthropicLanguageModel = class {
174
- specificationVersion = "v2";
174
+ specificationVersion = "v3";
175
175
  modelId;
176
176
  supportedUrls = {};
177
177
  config;
@@ -330,10 +330,14 @@ ${message.content}` : message.content;
330
330
  resultContent = part.output.value;
331
331
  } else if (part.output.type === "json") {
332
332
  resultContent = JSON.stringify(part.output.value);
333
+ } else if (part.output.type === "content") {
334
+ resultContent = JSON.stringify(part.output.value);
333
335
  } else if (part.output.type === "error-text") {
334
336
  resultContent = part.output.value;
335
337
  } else if (part.output.type === "error-json") {
336
338
  resultContent = JSON.stringify(part.output.value);
339
+ } else if (part.output.type === "execution-denied") {
340
+ resultContent = part.output.reason ?? "Tool execution denied.";
337
341
  } else {
338
342
  resultContent = JSON.stringify(part.output);
339
343
  }
@@ -356,18 +360,41 @@ ${message.content}` : message.content;
356
360
  * Convert Anthropic finish reason to AI SDK format
357
361
  */
358
362
  convertFinishReason(stopReason) {
359
- switch (stopReason) {
360
- case "end_turn":
361
- return "stop";
362
- case "stop_sequence":
363
- return "stop";
364
- case "max_tokens":
365
- return "length";
366
- case "tool_use":
367
- return "tool-calls";
368
- default:
369
- return "unknown";
370
- }
363
+ const unified = (() => {
364
+ switch (stopReason) {
365
+ case "end_turn":
366
+ return "stop";
367
+ case "stop_sequence":
368
+ return "stop";
369
+ case "max_tokens":
370
+ return "length";
371
+ case "tool_use":
372
+ return "tool-calls";
373
+ default:
374
+ return "other";
375
+ }
376
+ })();
377
+ return { unified, raw: stopReason ?? void 0 };
378
+ }
379
+ createUsage(params) {
380
+ const inputTotal = params?.inputTotal;
381
+ const outputTotal = params?.outputTotal;
382
+ const cacheRead = params?.cacheRead;
383
+ const cacheWrite = params?.cacheWrite;
384
+ return {
385
+ inputTokens: {
386
+ total: inputTotal,
387
+ noCache: inputTotal == null ? void 0 : Math.max(0, inputTotal - (cacheRead ?? 0) - (cacheWrite ?? 0)),
388
+ cacheRead,
389
+ cacheWrite
390
+ },
391
+ outputTokens: {
392
+ total: outputTotal,
393
+ text: outputTotal,
394
+ reasoning: params?.outputReasoning
395
+ },
396
+ raw: params?.raw
397
+ };
371
398
  }
372
399
  async doGenerate(options) {
373
400
  return this.doGenerateWithRetry(options, false);
@@ -407,11 +434,13 @@ ${message.content}` : message.content;
407
434
  });
408
435
  }
409
436
  }
410
- const usage = {
411
- inputTokens: response.usage.input_tokens,
412
- outputTokens: response.usage.output_tokens,
413
- totalTokens: response.usage.input_tokens + response.usage.output_tokens
414
- };
437
+ const rawUsage = response.usage;
438
+ const usage = this.createUsage({
439
+ inputTotal: response.usage.input_tokens,
440
+ outputTotal: response.usage.output_tokens,
441
+ cacheRead: rawUsage.cache_read_input_tokens,
442
+ cacheWrite: rawUsage.cache_creation_input_tokens
443
+ });
415
444
  return {
416
445
  content,
417
446
  finishReason: this.convertFinishReason(response.stop_reason),
@@ -467,12 +496,8 @@ ${message.content}` : message.content;
467
496
  const stream = new ReadableStream({
468
497
  start: async (controller) => {
469
498
  const contentBlocks = {};
470
- const usage = {
471
- inputTokens: 0,
472
- outputTokens: 0,
473
- totalTokens: 0
474
- };
475
- let finishReason = "unknown";
499
+ let usage = self.createUsage();
500
+ let finishReason = { unified: "other", raw: void 0 };
476
501
  try {
477
502
  const anthropicStream = client.messages.stream(requestBody, {
478
503
  signal: options.abortSignal
@@ -487,7 +512,14 @@ ${message.content}` : message.content;
487
512
  switch (event.type) {
488
513
  case "message_start":
489
514
  if (event.message.usage) {
490
- usage.inputTokens = event.message.usage.input_tokens;
515
+ usage = self.createUsage({
516
+ inputTotal: event.message.usage.input_tokens,
517
+ outputTotal: usage.outputTokens.total,
518
+ outputReasoning: usage.outputTokens.reasoning,
519
+ cacheRead: usage.inputTokens.cacheRead,
520
+ cacheWrite: usage.inputTokens.cacheWrite,
521
+ raw: usage.raw
522
+ });
491
523
  }
492
524
  controller.enqueue({
493
525
  type: "response-metadata",
@@ -559,8 +591,14 @@ ${message.content}` : message.content;
559
591
  }
560
592
  case "message_delta":
561
593
  if (event.usage) {
562
- usage.outputTokens = event.usage.output_tokens;
563
- usage.totalTokens = (usage.inputTokens || 0) + event.usage.output_tokens;
594
+ usage = self.createUsage({
595
+ inputTotal: usage.inputTokens.total,
596
+ outputTotal: event.usage.output_tokens,
597
+ outputReasoning: usage.outputTokens.reasoning,
598
+ cacheRead: usage.inputTokens.cacheRead,
599
+ cacheWrite: usage.inputTokens.cacheWrite,
600
+ raw: usage.raw
601
+ });
564
602
  }
565
603
  if (event.delta.stop_reason) {
566
604
  finishReason = self.convertFinishReason(event.delta.stop_reason);
@@ -760,7 +798,7 @@ var MODEL_ID_TO_ANTHROPIC_MODEL = Object.fromEntries(
760
798
 
761
799
  // src/gitlab-openai-language-model.ts
762
800
  var GitLabOpenAILanguageModel = class {
763
- specificationVersion = "v2";
801
+ specificationVersion = "v3";
764
802
  modelId;
765
803
  supportedUrls = {};
766
804
  config;
@@ -906,10 +944,14 @@ var GitLabOpenAILanguageModel = class {
906
944
  resultContent = part.output.value;
907
945
  } else if (part.output.type === "json") {
908
946
  resultContent = JSON.stringify(part.output.value);
947
+ } else if (part.output.type === "content") {
948
+ resultContent = JSON.stringify(part.output.value);
909
949
  } else if (part.output.type === "error-text") {
910
950
  resultContent = part.output.value;
911
951
  } else if (part.output.type === "error-json") {
912
952
  resultContent = JSON.stringify(part.output.value);
953
+ } else if (part.output.type === "execution-denied") {
954
+ resultContent = part.output.reason ?? "Tool execution denied.";
913
955
  } else {
914
956
  resultContent = JSON.stringify(part.output);
915
957
  }
@@ -925,18 +967,40 @@ var GitLabOpenAILanguageModel = class {
925
967
  return messages;
926
968
  }
927
969
  convertFinishReason(finishReason) {
928
- switch (finishReason) {
929
- case "stop":
930
- return "stop";
931
- case "length":
932
- return "length";
933
- case "tool_calls":
934
- return "tool-calls";
935
- case "content_filter":
936
- return "content-filter";
937
- default:
938
- return "unknown";
939
- }
970
+ const unified = (() => {
971
+ switch (finishReason) {
972
+ case "stop":
973
+ return "stop";
974
+ case "length":
975
+ return "length";
976
+ case "tool_calls":
977
+ return "tool-calls";
978
+ case "content_filter":
979
+ return "content-filter";
980
+ default:
981
+ return "other";
982
+ }
983
+ })();
984
+ return { unified, raw: finishReason ?? void 0 };
985
+ }
986
+ createUsage(params) {
987
+ const inputTotal = params?.inputTotal;
988
+ const outputTotal = params?.outputTotal;
989
+ const cacheRead = params?.cacheRead;
990
+ return {
991
+ inputTokens: {
992
+ total: inputTotal,
993
+ noCache: inputTotal == null ? void 0 : Math.max(0, inputTotal - (cacheRead ?? 0)),
994
+ cacheRead,
995
+ cacheWrite: void 0
996
+ },
997
+ outputTokens: {
998
+ total: outputTotal,
999
+ text: outputTotal,
1000
+ reasoning: params?.outputReasoning
1001
+ },
1002
+ raw: params?.raw
1003
+ };
940
1004
  }
941
1005
  /**
942
1006
  * Convert tools to Responses API format
@@ -953,7 +1017,7 @@ var GitLabOpenAILanguageModel = class {
953
1017
  name: tool.name,
954
1018
  description: tool.description || "",
955
1019
  parameters: schema,
956
- strict: false
1020
+ strict: tool.strict
957
1021
  };
958
1022
  });
959
1023
  }
@@ -1004,10 +1068,14 @@ var GitLabOpenAILanguageModel = class {
1004
1068
  resultContent = part.output.value;
1005
1069
  } else if (part.output.type === "json") {
1006
1070
  resultContent = JSON.stringify(part.output.value);
1071
+ } else if (part.output.type === "content") {
1072
+ resultContent = JSON.stringify(part.output.value);
1007
1073
  } else if (part.output.type === "error-text") {
1008
1074
  resultContent = part.output.value;
1009
1075
  } else if (part.output.type === "error-json") {
1010
1076
  resultContent = JSON.stringify(part.output.value);
1077
+ } else if (part.output.type === "execution-denied") {
1078
+ resultContent = part.output.reason ?? "Tool execution denied.";
1011
1079
  } else {
1012
1080
  resultContent = JSON.stringify(part.output);
1013
1081
  }
@@ -1036,20 +1104,23 @@ var GitLabOpenAILanguageModel = class {
1036
1104
  */
1037
1105
  convertResponsesStatus(status, hasToolCalls = false) {
1038
1106
  if (hasToolCalls) {
1039
- return "tool-calls";
1040
- }
1041
- switch (status) {
1042
- case "completed":
1043
- return "stop";
1044
- case "incomplete":
1045
- return "length";
1046
- case "cancelled":
1047
- return "stop";
1048
- case "failed":
1049
- return "error";
1050
- default:
1051
- return "unknown";
1052
- }
1107
+ return { unified: "tool-calls", raw: status };
1108
+ }
1109
+ const unified = (() => {
1110
+ switch (status) {
1111
+ case "completed":
1112
+ return "stop";
1113
+ case "incomplete":
1114
+ return "length";
1115
+ case "cancelled":
1116
+ return "stop";
1117
+ case "failed":
1118
+ return "error";
1119
+ default:
1120
+ return "other";
1121
+ }
1122
+ })();
1123
+ return { unified, raw: status };
1053
1124
  }
1054
1125
  async doGenerate(options) {
1055
1126
  if (this.useResponsesApi) {
@@ -1092,11 +1163,12 @@ var GitLabOpenAILanguageModel = class {
1092
1163
  }
1093
1164
  }
1094
1165
  }
1095
- const usage = {
1096
- inputTokens: response.usage?.prompt_tokens || 0,
1097
- outputTokens: response.usage?.completion_tokens || 0,
1098
- totalTokens: response.usage?.total_tokens || 0
1099
- };
1166
+ const usage = this.createUsage({
1167
+ inputTotal: response.usage?.prompt_tokens,
1168
+ outputTotal: response.usage?.completion_tokens,
1169
+ cacheRead: response.usage?.prompt_tokens_details?.cached_tokens,
1170
+ raw: { total_tokens: response.usage?.total_tokens }
1171
+ });
1100
1172
  return {
1101
1173
  content,
1102
1174
  finishReason: this.convertFinishReason(choice?.finish_reason),
@@ -1163,11 +1235,13 @@ var GitLabOpenAILanguageModel = class {
1163
1235
  });
1164
1236
  }
1165
1237
  }
1166
- const usage = {
1167
- inputTokens: response.usage?.input_tokens || 0,
1168
- outputTokens: response.usage?.output_tokens || 0,
1169
- totalTokens: response.usage?.total_tokens || 0
1170
- };
1238
+ const usage = this.createUsage({
1239
+ inputTotal: response.usage?.input_tokens,
1240
+ outputTotal: response.usage?.output_tokens,
1241
+ outputReasoning: response.usage?.output_tokens_details?.reasoning_tokens,
1242
+ cacheRead: response.usage?.input_tokens_details?.cached_tokens,
1243
+ raw: { total_tokens: response.usage?.total_tokens }
1244
+ });
1171
1245
  return {
1172
1246
  content,
1173
1247
  finishReason: this.convertResponsesStatus(response.status, hasToolCalls),
@@ -1226,12 +1300,8 @@ var GitLabOpenAILanguageModel = class {
1226
1300
  const stream = new ReadableStream({
1227
1301
  start: async (controller) => {
1228
1302
  const toolCalls = {};
1229
- const usage = {
1230
- inputTokens: 0,
1231
- outputTokens: 0,
1232
- totalTokens: 0
1233
- };
1234
- let finishReason = "unknown";
1303
+ let usage = self.createUsage();
1304
+ let finishReason = { unified: "other", raw: void 0 };
1235
1305
  let textStarted = false;
1236
1306
  const textId = "text-0";
1237
1307
  try {
@@ -1289,9 +1359,12 @@ var GitLabOpenAILanguageModel = class {
1289
1359
  finishReason = self.convertFinishReason(choice.finish_reason);
1290
1360
  }
1291
1361
  if (chunk.usage) {
1292
- usage.inputTokens = chunk.usage.prompt_tokens || 0;
1293
- usage.outputTokens = chunk.usage.completion_tokens || 0;
1294
- usage.totalTokens = chunk.usage.total_tokens || 0;
1362
+ usage = self.createUsage({
1363
+ inputTotal: chunk.usage.prompt_tokens,
1364
+ outputTotal: chunk.usage.completion_tokens,
1365
+ cacheRead: chunk.usage.prompt_tokens_details?.cached_tokens,
1366
+ raw: { total_tokens: chunk.usage.total_tokens }
1367
+ });
1295
1368
  }
1296
1369
  }
1297
1370
  if (textStarted) {
@@ -1371,12 +1444,8 @@ var GitLabOpenAILanguageModel = class {
1371
1444
  const stream = new ReadableStream({
1372
1445
  start: async (controller) => {
1373
1446
  const toolCalls = {};
1374
- const usage = {
1375
- inputTokens: 0,
1376
- outputTokens: 0,
1377
- totalTokens: 0
1378
- };
1379
- let finishReason = "unknown";
1447
+ let usage = self.createUsage();
1448
+ let finishReason = { unified: "other", raw: void 0 };
1380
1449
  let textStarted = false;
1381
1450
  const textId = "text-0";
1382
1451
  try {
@@ -1438,9 +1507,13 @@ var GitLabOpenAILanguageModel = class {
1438
1507
  const hasToolCalls2 = Object.keys(toolCalls).length > 0;
1439
1508
  finishReason = self.convertResponsesStatus(event.response.status, hasToolCalls2);
1440
1509
  if (event.response.usage) {
1441
- usage.inputTokens = event.response.usage.input_tokens || 0;
1442
- usage.outputTokens = event.response.usage.output_tokens || 0;
1443
- usage.totalTokens = event.response.usage.total_tokens || 0;
1510
+ usage = self.createUsage({
1511
+ inputTotal: event.response.usage.input_tokens,
1512
+ outputTotal: event.response.usage.output_tokens,
1513
+ outputReasoning: event.response.usage.output_tokens_details?.reasoning_tokens,
1514
+ cacheRead: event.response.usage.input_tokens_details?.cached_tokens,
1515
+ raw: { total_tokens: event.response.usage.total_tokens }
1516
+ });
1444
1517
  }
1445
1518
  }
1446
1519
  }
@@ -1448,8 +1521,8 @@ var GitLabOpenAILanguageModel = class {
1448
1521
  controller.enqueue({ type: "text-end", id: textId });
1449
1522
  }
1450
1523
  const hasToolCalls = Object.keys(toolCalls).length > 0;
1451
- if (hasToolCalls && finishReason === "stop") {
1452
- finishReason = "tool-calls";
1524
+ if (hasToolCalls && finishReason.unified === "stop") {
1525
+ finishReason = { unified: "tool-calls", raw: finishReason.raw };
1453
1526
  }
1454
1527
  for (const tc of Object.values(toolCalls)) {
1455
1528
  controller.enqueue({ type: "tool-input-end", id: tc.callId });
@@ -1509,7 +1582,7 @@ var GitLabOpenAILanguageModel = class {
1509
1582
  import WebSocket from "isomorphic-ws";
1510
1583
 
1511
1584
  // src/version.ts
1512
- var VERSION = true ? "5.3.2" : "0.0.0-dev";
1585
+ var VERSION = true ? "6.0.0" : "0.0.0-dev";
1513
1586
 
1514
1587
  // src/gitlab-workflow-types.ts
1515
1588
  var WorkflowType = /* @__PURE__ */ ((WorkflowType2) => {
@@ -1735,7 +1808,9 @@ var GitLabWorkflowClient = class {
1735
1808
  });
1736
1809
  } else if (checkpoint.status === "STOPPED" || checkpoint.status === "CANCELLED") {
1737
1810
  this.emit({ type: "completed" });
1738
- } else if (checkpoint.status === "TOOL_CALL_APPROVAL_REQUIRED" || checkpoint.status === "PLAN_APPROVAL_REQUIRED") {
1811
+ } else if (checkpoint.status === "TOOL_CALL_APPROVAL_REQUIRED") {
1812
+ this.emit({ type: "approval-required", tools: this.extractApprovalTools(checkpoint) });
1813
+ } else if (checkpoint.status === "PLAN_APPROVAL_REQUIRED") {
1739
1814
  this.emit({ type: "completed" });
1740
1815
  }
1741
1816
  return;
@@ -1774,6 +1849,18 @@ var GitLabWorkflowClient = class {
1774
1849
  }
1775
1850
  }
1776
1851
  }
1852
+ extractApprovalTools(checkpoint) {
1853
+ if (!checkpoint.checkpoint) return [];
1854
+ let parsed;
1855
+ try {
1856
+ parsed = JSON.parse(checkpoint.checkpoint);
1857
+ } catch {
1858
+ return [];
1859
+ }
1860
+ return (parsed.channel_values?.ui_chat_log ?? []).filter(
1861
+ (e) => e.message_type === "request" && e.tool_info !== null
1862
+ ).map((e) => ({ name: e.tool_info.name, args: JSON.stringify(e.tool_info.args) }));
1863
+ }
1777
1864
  send(event) {
1778
1865
  if (this.socket?.readyState === WebSocket.OPEN) {
1779
1866
  const json = JSON.stringify(event);
@@ -2873,7 +2960,7 @@ function minimalSchema(schemaStr) {
2873
2960
  }
2874
2961
  }
2875
2962
  var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
2876
- specificationVersion = "v2";
2963
+ specificationVersion = "v3";
2877
2964
  modelId;
2878
2965
  supportedUrls = {};
2879
2966
  config;
@@ -2945,6 +3032,23 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
2945
3032
  * the AI SDK only surfaces usage via finish-step at stream end.
2946
3033
  */
2947
3034
  onUsageUpdate = null;
3035
+ /**
3036
+ * Tool names pre-approved for the current session.
3037
+ * Set by the host (e.g., opencode) and merged into preapproved_tools on each StartRequest.
3038
+ * Updated when the user chooses "always" in the approval prompt.
3039
+ */
3040
+ sessionPreapprovedTools = [];
3041
+ /**
3042
+ * Set the approval handler callback.
3043
+ * Called when DWS requires tool call approval. Host (e.g., opencode) wires this
3044
+ * to its permission system each stream call, similar to toolExecutor.
3045
+ */
3046
+ set approvalHandler(handler) {
3047
+ this.workflowOptions.approvalHandler = handler ?? void 0;
3048
+ }
3049
+ get approvalHandler() {
3050
+ return this.workflowOptions.approvalHandler ?? null;
3051
+ }
2948
3052
  /**
2949
3053
  * Optional callback invoked when multiple workflow models are available
2950
3054
  * and the user should pick one. Set per-stream by the host (e.g., OpenCode)
@@ -3174,16 +3278,34 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3174
3278
  get workflowId() {
3175
3279
  return this.currentWorkflowId;
3176
3280
  }
3281
+ createUsage(params) {
3282
+ return {
3283
+ inputTokens: {
3284
+ total: params?.inputTotal,
3285
+ noCache: params?.inputTotal,
3286
+ cacheRead: void 0,
3287
+ cacheWrite: void 0
3288
+ },
3289
+ outputTokens: {
3290
+ total: params?.outputTotal,
3291
+ text: params?.outputTotal,
3292
+ reasoning: void 0
3293
+ }
3294
+ };
3295
+ }
3296
+ createFinishReason(unified, raw) {
3297
+ return { unified, raw };
3298
+ }
3177
3299
  // ---------------------------------------------------------------------------
3178
- // LanguageModelV2 — doGenerate (non-streaming)
3300
+ // LanguageModelV3 — doGenerate (non-streaming)
3179
3301
  // ---------------------------------------------------------------------------
3180
3302
  async doGenerate(options) {
3181
3303
  const { stream } = await this.doStream(options);
3182
3304
  const reader = stream.getReader();
3183
3305
  const textParts = [];
3184
3306
  const toolCalls = [];
3185
- let finishReason = "unknown";
3186
- const usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
3307
+ let finishReason = { unified: "other", raw: void 0 };
3308
+ let usage = this.createUsage();
3187
3309
  try {
3188
3310
  while (true) {
3189
3311
  const { done, value } = await reader.read();
@@ -3203,9 +3325,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3203
3325
  case "finish":
3204
3326
  finishReason = value.finishReason;
3205
3327
  if (value.usage) {
3206
- usage.inputTokens = value.usage.inputTokens ?? 0;
3207
- usage.outputTokens = value.usage.outputTokens ?? 0;
3208
- usage.totalTokens = value.usage.totalTokens ?? 0;
3328
+ usage = value.usage;
3209
3329
  }
3210
3330
  break;
3211
3331
  case "error":
@@ -3224,13 +3344,16 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3224
3344
  return { content, finishReason, usage, warnings: [] };
3225
3345
  }
3226
3346
  // ---------------------------------------------------------------------------
3227
- // LanguageModelV2 — doStream (streaming)
3347
+ // LanguageModelV3 — doStream (streaming)
3228
3348
  // ---------------------------------------------------------------------------
3229
3349
  async doStream(options) {
3230
3350
  const goal = this.extractGoalFromPrompt(options.prompt);
3231
3351
  const modelRef = await this.resolveModelRef();
3232
3352
  const mcpTools = this.extractMcpTools(options);
3233
- const preapprovedTools = this.workflowOptions.preapprovedTools ?? mcpTools.map((t) => t.name);
3353
+ const preapprovedTools = [
3354
+ ...this.workflowOptions.preapprovedTools ?? mcpTools.map((t) => t.name),
3355
+ ...this.sessionPreapprovedTools
3356
+ ];
3234
3357
  const additionalContext = this.buildAdditionalContext(options.prompt);
3235
3358
  const toolExecutor = this.toolExecutor ?? null;
3236
3359
  const availableToolNames = new Set(options.tools?.map((t) => t.name) ?? []);
@@ -3246,7 +3369,8 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3246
3369
  workflowId = await this.tokenClient.createWorkflow(goal, {
3247
3370
  projectId,
3248
3371
  namespaceId: this.workflowOptions.namespaceId,
3249
- workflowDefinition: this.workflowOptions.workflowDefinition
3372
+ workflowDefinition: this.workflowOptions.workflowDefinition,
3373
+ agentPrivileges: this.workflowOptions.agentPrivileges
3250
3374
  });
3251
3375
  this.currentWorkflowId = workflowId;
3252
3376
  }
@@ -3258,11 +3382,13 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3258
3382
  streamedInputChars: 0,
3259
3383
  streamedOutputChars: 0,
3260
3384
  pendingToolCount: 0,
3385
+ approvalPending: false,
3261
3386
  deferredClose: null,
3262
3387
  activeTextBlockId: null,
3263
3388
  agentMessageEmitted: new Map(this.persistedAgentEmitted),
3264
3389
  currentAgentMessageId: "",
3265
- activeClient: wsClient
3390
+ activeClient: wsClient,
3391
+ processedRequestIDs: /* @__PURE__ */ new Set()
3266
3392
  };
3267
3393
  for (const msg of options.prompt) {
3268
3394
  if (msg.role === "system") {
@@ -3275,6 +3401,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3275
3401
  }
3276
3402
  }
3277
3403
  }
3404
+ let startReq;
3278
3405
  const stream = new ReadableStream({
3279
3406
  start: async (controller) => {
3280
3407
  try {
@@ -3295,7 +3422,8 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3295
3422
  wsClient,
3296
3423
  toolExecutor,
3297
3424
  () => `text-${textBlockCounter++}`,
3298
- availableToolNames
3425
+ availableToolNames,
3426
+ startReq
3299
3427
  );
3300
3428
  }
3301
3429
  );
@@ -3317,7 +3445,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3317
3445
  const trimmedPreapproved = preapprovedTools.filter(
3318
3446
  (name) => trimmed.mcpTools.some((t) => t.name === name)
3319
3447
  );
3320
- const startReq = {
3448
+ startReq = {
3321
3449
  workflowID: workflowId,
3322
3450
  clientVersion: CLIENT_VERSION,
3323
3451
  workflowDefinition: workflowDef,
@@ -3382,7 +3510,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3382
3510
  // ---------------------------------------------------------------------------
3383
3511
  // Event handling
3384
3512
  // ---------------------------------------------------------------------------
3385
- handleWorkflowEvent(ss, event, controller, wsClient, toolExecutor, nextTextId, availableToolNames) {
3513
+ handleWorkflowEvent(ss, event, controller, wsClient, toolExecutor, nextTextId, availableToolNames, startReq) {
3386
3514
  if (ss.streamClosed) {
3387
3515
  return;
3388
3516
  }
@@ -3393,6 +3521,8 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3393
3521
  }
3394
3522
  case "tool-request": {
3395
3523
  const { requestID, data } = event;
3524
+ if (ss.processedRequestIDs.has(requestID)) break;
3525
+ ss.processedRequestIDs.add(requestID);
3396
3526
  let parsedArgs;
3397
3527
  try {
3398
3528
  JSON.parse(data.args);
@@ -3439,6 +3569,8 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3439
3569
  break;
3440
3570
  }
3441
3571
  case "builtin-tool-request": {
3572
+ if (ss.processedRequestIDs.has(event.requestID)) break;
3573
+ ss.processedRequestIDs.add(event.requestID);
3442
3574
  const mapped = mapBuiltinTool(event.toolName, event.data, availableToolNames);
3443
3575
  const mappedArgs = JSON.stringify(mapped.args);
3444
3576
  if (ss.activeTextBlockId) {
@@ -3479,6 +3611,26 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3479
3611
  });
3480
3612
  break;
3481
3613
  }
3614
+ case "approval-required": {
3615
+ ss.approvalPending = true;
3616
+ this.approveAndResume(
3617
+ ss,
3618
+ event.tools,
3619
+ startReq,
3620
+ controller,
3621
+ toolExecutor,
3622
+ nextTextId,
3623
+ availableToolNames
3624
+ ).catch(() => {
3625
+ ss.approvalPending = false;
3626
+ if (ss.deferredClose) {
3627
+ const close = ss.deferredClose;
3628
+ ss.deferredClose = null;
3629
+ close();
3630
+ }
3631
+ });
3632
+ break;
3633
+ }
3482
3634
  case "completed": {
3483
3635
  if (ss.activeTextBlockId) {
3484
3636
  controller.enqueue({ type: "text-end", id: ss.activeTextBlockId });
@@ -3490,14 +3642,14 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3490
3642
  const outputTokens = Math.ceil(ss.streamedOutputChars / 4);
3491
3643
  controller.enqueue({
3492
3644
  type: "finish",
3493
- finishReason: "stop",
3494
- usage: { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens }
3645
+ finishReason: this.createFinishReason("stop", "completed"),
3646
+ usage: this.createUsage({ inputTotal: inputTokens, outputTotal: outputTokens })
3495
3647
  });
3496
3648
  ss.streamClosed = true;
3497
3649
  controller.close();
3498
3650
  this.cleanupClient(ss);
3499
3651
  };
3500
- if (ss.pendingToolCount > 0) {
3652
+ if (ss.pendingToolCount > 0 || ss.approvalPending) {
3501
3653
  ss.deferredClose = doCompleteClose;
3502
3654
  } else {
3503
3655
  ss.deferredClose = null;
@@ -3548,15 +3700,15 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3548
3700
  const outTok = Math.ceil(ss.streamedOutputChars / 4);
3549
3701
  controller.enqueue({
3550
3702
  type: "finish",
3551
- finishReason: "stop",
3552
- usage: { inputTokens: inTok, outputTokens: outTok, totalTokens: inTok + outTok }
3703
+ finishReason: this.createFinishReason("stop", "closed"),
3704
+ usage: this.createUsage({ inputTotal: inTok, outputTotal: outTok })
3553
3705
  });
3554
3706
  ss.streamClosed = true;
3555
3707
  controller.close();
3556
3708
  this.cleanupClient(ss);
3557
3709
  }
3558
3710
  };
3559
- if (ss.pendingToolCount > 0) {
3711
+ if (ss.pendingToolCount > 0 || ss.approvalPending) {
3560
3712
  ss.deferredClose = doClose;
3561
3713
  } else {
3562
3714
  ss.deferredClose = null;
@@ -3666,8 +3818,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3666
3818
  toolCallId: requestID,
3667
3819
  toolName,
3668
3820
  result: errorText,
3669
- isError: true,
3670
- providerExecuted: true
3821
+ isError: true
3671
3822
  });
3672
3823
  } else {
3673
3824
  safeEnqueue({
@@ -3679,8 +3830,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3679
3830
  title: toolTitle,
3680
3831
  metadata: toolMetadata
3681
3832
  },
3682
- isError: false,
3683
- providerExecuted: true
3833
+ isError: false
3684
3834
  });
3685
3835
  }
3686
3836
  } else {
@@ -3691,8 +3841,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3691
3841
  toolCallId: requestID,
3692
3842
  toolName,
3693
3843
  result: errorMsg,
3694
- isError: true,
3695
- providerExecuted: true
3844
+ isError: true
3696
3845
  });
3697
3846
  }
3698
3847
  } catch (error) {
@@ -3704,8 +3853,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3704
3853
  toolCallId: requestID,
3705
3854
  toolName,
3706
3855
  result: errorMsg,
3707
- isError: true,
3708
- providerExecuted: true
3856
+ isError: true
3709
3857
  });
3710
3858
  } finally {
3711
3859
  ss.pendingToolCount--;
@@ -3736,6 +3884,60 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3736
3884
  this.persistedAgentEmitted.clear();
3737
3885
  }
3738
3886
  }
3887
+ async approveAndResume(ss, tools, startReq, controller, toolExecutor, nextTextId, availableToolNames) {
3888
+ const handler = this.workflowOptions.approvalHandler;
3889
+ if (!handler || !startReq) {
3890
+ ss.approvalPending = false;
3891
+ if (ss.deferredClose) {
3892
+ const close = ss.deferredClose;
3893
+ ss.deferredClose = null;
3894
+ close();
3895
+ }
3896
+ return;
3897
+ }
3898
+ let decision;
3899
+ try {
3900
+ decision = await handler(tools);
3901
+ } catch (err) {
3902
+ ss.approvalPending = false;
3903
+ if (!ss.streamClosed) controller.error(err);
3904
+ return;
3905
+ }
3906
+ ss.approvalPending = false;
3907
+ this.cleanupClient(ss, false);
3908
+ const approval = decision.approved ? { approval: { tool_name: tools[0]?.name, tool_args_json: tools[0]?.args } } : { rejection: { message: decision.message ?? "User rejected" } };
3909
+ const newStartReq = { ...startReq, approval };
3910
+ const newClient = new GitLabWorkflowClient();
3911
+ this.activeClients.add(newClient);
3912
+ ss.activeClient = newClient;
3913
+ const modelRef = await this.resolveModelRef();
3914
+ try {
3915
+ await newClient.connect(
3916
+ {
3917
+ instanceUrl: this.config.instanceUrl,
3918
+ modelRef,
3919
+ headers: this.config.getHeaders(),
3920
+ projectId: this.workflowOptions.projectId,
3921
+ namespaceId: this.workflowOptions.namespaceId,
3922
+ rootNamespaceId: this.workflowOptions.rootNamespaceId
3923
+ },
3924
+ (event) => this.handleWorkflowEvent(
3925
+ ss,
3926
+ event,
3927
+ controller,
3928
+ newClient,
3929
+ toolExecutor,
3930
+ nextTextId,
3931
+ availableToolNames,
3932
+ newStartReq
3933
+ )
3934
+ );
3935
+ newClient.sendStartRequest(newStartReq);
3936
+ } catch (err) {
3937
+ this.cleanupClient(ss, true);
3938
+ if (!ss.streamClosed) controller.error(err);
3939
+ }
3940
+ }
3739
3941
  // ---------------------------------------------------------------------------
3740
3942
  // Workflow metadata
3741
3943
  // ---------------------------------------------------------------------------
@@ -4304,13 +4506,13 @@ function createGitLab(options = {}) {
4304
4506
  return createAgenticChatModel(modelId);
4305
4507
  };
4306
4508
  const provider = Object.assign((modelId) => createDefaultModel(modelId), {
4307
- specificationVersion: "v2",
4509
+ specificationVersion: "v3",
4308
4510
  languageModel: createDefaultModel,
4309
4511
  chat: createDefaultModel,
4310
4512
  agenticChat: createAgenticChatModel,
4311
4513
  workflowChat: createWorkflowChatModel
4312
4514
  });
4313
- provider.textEmbeddingModel = (modelId) => {
4515
+ provider.embeddingModel = (modelId) => {
4314
4516
  throw new GitLabError({
4315
4517
  message: `GitLab provider does not support text embedding models. Model ID: ${modelId}`
4316
4518
  });