gitlab-ai-provider 6.12.1 → 6.12.2

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
@@ -403,6 +403,10 @@ var WorkflowType = /* @__PURE__ */ ((WorkflowType2) => {
403
403
  })(WorkflowType || {});
404
404
  var WS_KEEPALIVE_PING_INTERVAL_MS = 45e3;
405
405
  var WS_HEARTBEAT_INTERVAL_MS = 6e4;
406
+ var WS_CLOSE_WORKFLOW_LOCKED = 1013;
407
+ var WS_CLOSE_WAIT_TIMEOUT_MS = 3e3;
408
+ var WS_WORKFLOW_LOCK_RETRY_DELAY_MS = 1500;
409
+ var WS_WORKFLOW_LOCK_MAX_RETRIES = 8;
406
410
  var DEFAULT_WORKFLOW_DEFINITION = "chat" /* CHAT */;
407
411
  var DEFAULT_CLIENT_CAPABILITIES = ["shell_command"];
408
412
  var CLIENT_VERSION = "8.51.0";
@@ -2447,7 +2451,7 @@ import { AsyncResource } from "async_hooks";
2447
2451
  import WebSocket from "isomorphic-ws";
2448
2452
 
2449
2453
  // src/version.ts
2450
- var VERSION = true ? "6.12.0" : "0.0.0-dev";
2454
+ var VERSION = true ? "6.12.1" : "0.0.0-dev";
2451
2455
 
2452
2456
  // src/gitlab-workflow-client.ts
2453
2457
  var WS_CONNECT_TIMEOUT_MS = 3e4;
@@ -2597,6 +2601,48 @@ var GitLabWorkflowClient = class {
2597
2601
  }
2598
2602
  }
2599
2603
  }
2604
+ /**
2605
+ * Close the socket and wait for the close handshake to actually land.
2606
+ *
2607
+ * `close()` returns as soon as the close frame is queued. That is too early
2608
+ * when the caller is about to reconnect to the same workflow, because
2609
+ * Workhorse releases the workflow lock only once its own stop handshake with
2610
+ * DWS completes. Awaiting `onclose` (or the timeout) keeps the reconnect from
2611
+ * racing the lock and getting rejected with close code 1013.
2612
+ *
2613
+ * Best effort: never rejects, and resolves after `timeoutMs` if the server
2614
+ * never answers.
2615
+ *
2616
+ * @param timeoutMs - Maximum time to wait for the close event
2617
+ * @default WS_CLOSE_WAIT_TIMEOUT_MS
2618
+ */
2619
+ closeAndWait(timeoutMs = WS_CLOSE_WAIT_TIMEOUT_MS) {
2620
+ const sock = this.socket;
2621
+ const pending = sock && (sock.readyState === WebSocket.OPEN || sock.readyState === WebSocket.CONNECTING);
2622
+ if (this.closed || !pending) {
2623
+ this.close();
2624
+ return Promise.resolve();
2625
+ }
2626
+ return new Promise((resolve3) => {
2627
+ let settled = false;
2628
+ const finish = () => {
2629
+ if (settled) return;
2630
+ settled = true;
2631
+ clearTimeout(timer);
2632
+ resolve3();
2633
+ };
2634
+ const timer = setTimeout(finish, timeoutMs);
2635
+ const previous = sock.onclose;
2636
+ sock.onclose = (event) => {
2637
+ try {
2638
+ previous?.call(sock, event);
2639
+ } finally {
2640
+ finish();
2641
+ }
2642
+ };
2643
+ this.close();
2644
+ });
2645
+ }
2600
2646
  /**
2601
2647
  * Check if the WebSocket is currently connected.
2602
2648
  */
@@ -4556,19 +4602,20 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
4556
4602
  }
4557
4603
  ss.approvalPending = false;
4558
4604
  ss.deferredClose = null;
4559
- this.cleanupClient(ss, false);
4560
4605
  const approval = decision.approved ? { approval: { tool_name: tools[0]?.name, tool_args_json: tools[0]?.args } } : { rejection: { message: decision.message ?? "User rejected" } };
4561
4606
  const newStartReq = {
4562
4607
  ...startReq,
4563
4608
  approval,
4564
4609
  preapproved_tools: decision.approved ? [...startReq.preapproved_tools ?? [], ...tools.map((t) => t.name)] : startReq.preapproved_tools ?? []
4565
4610
  };
4566
- const newClient = new GitLabWorkflowClient();
4567
- this.activeClients.add(newClient);
4568
- ss.activeClient = newClient;
4569
- const modelRef = await this.resolveModelRef();
4570
- try {
4571
- await newClient.connect(
4611
+ const reconnect = async () => {
4612
+ if (ss.streamClosed) return;
4613
+ await this.closeClientAndWait(ss);
4614
+ const client = new GitLabWorkflowClient();
4615
+ this.activeClients.add(client);
4616
+ ss.activeClient = client;
4617
+ const modelRef = await this.resolveModelRef();
4618
+ await client.connect(
4572
4619
  {
4573
4620
  instanceUrl: this.config.instanceUrl,
4574
4621
  modelRef,
@@ -4579,23 +4626,63 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
4579
4626
  aiCatalogItemVersionId: wsExtras?.aiCatalogItemVersionId,
4580
4627
  workflowDefinition: wsExtras?.workflowDefinition
4581
4628
  },
4582
- (event) => this.handleWorkflowEvent(
4629
+ (event) => {
4630
+ if (event.type === "closed" && event.code === WS_CLOSE_WORKFLOW_LOCKED) {
4631
+ void retryWhileLocked();
4632
+ return;
4633
+ }
4634
+ this.handleWorkflowEvent(
4635
+ ss,
4636
+ event,
4637
+ controller,
4638
+ client,
4639
+ toolExecutor,
4640
+ nextTextId,
4641
+ availableToolNames,
4642
+ newStartReq,
4643
+ wsExtras
4644
+ );
4645
+ }
4646
+ );
4647
+ client.sendStartRequest(newStartReq);
4648
+ };
4649
+ let lockRetries = 0;
4650
+ const retryWhileLocked = async () => {
4651
+ if (ss.streamClosed) return;
4652
+ if (++lockRetries > WS_WORKFLOW_LOCK_MAX_RETRIES) {
4653
+ this.failStream(
4583
4654
  ss,
4584
- event,
4585
4655
  controller,
4586
- newClient,
4587
- toolExecutor,
4588
- nextTextId,
4589
- availableToolNames,
4590
- newStartReq,
4591
- wsExtras
4592
- )
4593
- );
4594
- newClient.sendStartRequest(newStartReq);
4595
- } catch (err) {
4596
- this.cleanupClient(ss, true);
4597
- if (!ss.streamClosed) controller.error(err);
4598
- }
4656
+ new GitLabError({
4657
+ message: "Failed to acquire lock on workflow after reconnect retries",
4658
+ statusCode: WS_CLOSE_WORKFLOW_LOCKED
4659
+ })
4660
+ );
4661
+ return;
4662
+ }
4663
+ await new Promise((r) => setTimeout(r, WS_WORKFLOW_LOCK_RETRY_DELAY_MS * lockRetries));
4664
+ await reconnect().catch((err) => this.failStream(ss, controller, err));
4665
+ };
4666
+ await reconnect().catch((err) => this.failStream(ss, controller, err));
4667
+ }
4668
+ /**
4669
+ * Close the active client and wait for its close handshake, so a following
4670
+ * reconnect to the same workflow does not race the Workhorse lock.
4671
+ */
4672
+ async closeClientAndWait(ss) {
4673
+ const client = ss.activeClient;
4674
+ if (!client) return;
4675
+ this.activeClients.delete(client);
4676
+ ss.activeClient = null;
4677
+ await client.closeAndWait();
4678
+ }
4679
+ /**
4680
+ * Surface a terminal error on the stream and drop the workflow.
4681
+ */
4682
+ failStream(ss, controller, error) {
4683
+ this.cleanupClient(ss, true);
4684
+ if (ss.streamClosed) return;
4685
+ controller.error(error);
4599
4686
  }
4600
4687
  // ---------------------------------------------------------------------------
4601
4688
  // Workflow metadata