@runtypelabs/sdk 9.2.1 → 9.3.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.cjs CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  CollectionsEndpoint: () => CollectionsEndpoint,
40
40
  ContextTemplatesEndpoint: () => ContextTemplatesEndpoint,
41
41
  ConversationsEndpoint: () => ConversationsEndpoint,
42
+ DEFAULT_MAX_DETACHED_RECONNECTS: () => DEFAULT_MAX_DETACHED_RECONNECTS,
42
43
  DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS: () => DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS,
43
44
  DEFAULT_STALL_STOP_AFTER: () => DEFAULT_STALL_STOP_AFTER,
44
45
  DispatchEndpoint: () => DispatchEndpoint,
@@ -97,6 +98,7 @@ __export(index_exports, {
97
98
  UsersEndpoint: () => UsersEndpoint,
98
99
  applyGeneratedRuntimeToolProposalToDispatchRequest: () => applyGeneratedRuntimeToolProposalToDispatchRequest,
99
100
  attachRuntimeToolsToDispatchRequest: () => attachRuntimeToolsToDispatchRequest,
101
+ buildAgentAdmissionHeaders: () => buildAgentAdmissionHeaders,
100
102
  buildEmptySessionNudge: () => buildEmptySessionNudge,
101
103
  buildGeneratedRuntimeToolGateOutput: () => buildGeneratedRuntimeToolGateOutput,
102
104
  buildLedgerOffloadReference: () => buildLedgerOffloadReference,
@@ -186,6 +188,7 @@ __export(index_exports, {
186
188
  unregisterWorkflowHook: () => unregisterWorkflowHook,
187
189
  usedNoTools: () => usedNoTools,
188
190
  validJson: () => validJson,
191
+ withDetachedReconnect: () => withDetachedReconnect,
189
192
  withUnifiedEvents: () => withUnifiedEvents
190
193
  });
191
194
  module.exports = __toCommonJS(index_exports);
@@ -4949,7 +4952,8 @@ var AGENT_CONFIG_KEYS = [
4949
4952
  "temporal",
4950
4953
  "memory",
4951
4954
  "sandbox",
4952
- "tenancyStrategy"
4955
+ "tenancyStrategy",
4956
+ "durability"
4953
4957
  ];
4954
4958
  var AGENT_CONFIG_KEY_LIST = [...AGENT_CONFIG_KEYS].sort();
4955
4959
  function isPlainObject2(value) {
@@ -6519,10 +6523,151 @@ var Runtype = class {
6519
6523
 
6520
6524
  // src/version.ts
6521
6525
  var FALLBACK_VERSION = "0.0.0";
6522
- var SDK_VERSION = "9.2.1".length > 0 ? "9.2.1" : FALLBACK_VERSION;
6526
+ var SDK_VERSION = "9.3.0".length > 0 ? "9.3.0" : FALLBACK_VERSION;
6523
6527
  var RUNTYPE_CLIENT_KIND = "sdk";
6524
6528
  var SDK_USER_AGENT = `runtype-sdk/${SDK_VERSION} (typescript)`;
6525
6529
 
6530
+ // src/detached-reconnect.ts
6531
+ var TERMINAL_EVENTS = /* @__PURE__ */ new Set(["execution_complete", "execution_error"]);
6532
+ var DEFAULT_MAX_DETACHED_RECONNECTS = 12;
6533
+ function observeBlock(block, into) {
6534
+ let eventName = null;
6535
+ for (const line of block.split("\n")) {
6536
+ if (line.startsWith("id:")) {
6537
+ into.lastId = line.slice(3).trim();
6538
+ continue;
6539
+ }
6540
+ if (line.startsWith("event:")) {
6541
+ eventName = line.slice(6).trim();
6542
+ continue;
6543
+ }
6544
+ if (!line.startsWith("data:")) continue;
6545
+ const raw = line.slice(5).trim();
6546
+ if (!raw || raw === "[DONE]") continue;
6547
+ let payload;
6548
+ try {
6549
+ const parsed = JSON.parse(raw);
6550
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) continue;
6551
+ payload = parsed;
6552
+ } catch {
6553
+ continue;
6554
+ }
6555
+ if (typeof payload.executionId === "string") into.executionId = payload.executionId;
6556
+ const type = typeof payload.type === "string" ? payload.type : eventName;
6557
+ if (type && TERMINAL_EVENTS.has(type)) into.sawTerminal = true;
6558
+ if (type === "await" && payload.awaitReason === "detached") into.sawDetach = true;
6559
+ }
6560
+ }
6561
+ function withDetachedReconnect(response, reattach, options = {}) {
6562
+ if (options.autoReconnect === false) return response;
6563
+ if (!response.ok || !response.body) return response;
6564
+ const contentType = response.headers.get("content-type") ?? "";
6565
+ if (!contentType.includes("text/event-stream")) return response;
6566
+ const maxReconnects = options.maxReconnects ?? DEFAULT_MAX_DETACHED_RECONNECTS;
6567
+ const reconnectDelayMs = options.reconnectDelayMs ?? 0;
6568
+ const encoder = new TextEncoder();
6569
+ const decoder = new TextDecoder();
6570
+ const abort = new AbortController();
6571
+ let cancelled = false;
6572
+ let activeReader = null;
6573
+ const stream = new ReadableStream({
6574
+ async start(controller) {
6575
+ const observed = {
6576
+ lastId: null,
6577
+ executionId: null,
6578
+ sawTerminal: false,
6579
+ sawDetach: false
6580
+ };
6581
+ let current = response;
6582
+ let legs = 0;
6583
+ const enqueue = (chunk) => {
6584
+ if (cancelled) return;
6585
+ controller.enqueue(chunk);
6586
+ };
6587
+ try {
6588
+ while (!cancelled && current?.body) {
6589
+ const reader = current.body.getReader();
6590
+ activeReader = reader;
6591
+ let buffer = "";
6592
+ observed.sawDetach = false;
6593
+ for (; ; ) {
6594
+ const { value, done } = await reader.read();
6595
+ if (cancelled) break;
6596
+ if (value) {
6597
+ enqueue(value);
6598
+ buffer += decoder.decode(value, { stream: !done });
6599
+ let boundary = buffer.indexOf("\n\n");
6600
+ while (boundary >= 0) {
6601
+ observeBlock(buffer.slice(0, boundary), observed);
6602
+ buffer = buffer.slice(boundary + 2);
6603
+ boundary = buffer.indexOf("\n\n");
6604
+ }
6605
+ }
6606
+ if (done) break;
6607
+ }
6608
+ activeReader = null;
6609
+ if (cancelled) break;
6610
+ if (buffer.trim()) observeBlock(buffer, observed);
6611
+ if (observed.sawTerminal || !observed.sawDetach) break;
6612
+ if (!observed.executionId || legs >= maxReconnects) break;
6613
+ legs += 1;
6614
+ if (reconnectDelayMs > 0) {
6615
+ await new Promise((resolve) => setTimeout(resolve, reconnectDelayMs));
6616
+ }
6617
+ if (cancelled) break;
6618
+ current = await reattach({
6619
+ executionId: observed.executionId,
6620
+ // `'0'` replays the whole turn — the fail-open answer for a leg
6621
+ // that produced no cursor at all.
6622
+ after: observed.lastId ?? "0",
6623
+ signal: abort.signal
6624
+ });
6625
+ if (cancelled) {
6626
+ await current?.body?.cancel().catch(() => {
6627
+ });
6628
+ break;
6629
+ }
6630
+ if (!current || !current.ok || !current.body) {
6631
+ enqueue(
6632
+ encoder.encode(
6633
+ `event: error
6634
+ data: ${JSON.stringify({
6635
+ type: "error",
6636
+ executionId: observed.executionId,
6637
+ error: {
6638
+ code: "detached_reconnect_failed",
6639
+ message: "The agent detached and this client could not rejoin its stream. The execution is still running; reattach with the events route."
6640
+ },
6641
+ recoverable: false
6642
+ })}
6643
+
6644
+ `
6645
+ )
6646
+ );
6647
+ break;
6648
+ }
6649
+ }
6650
+ } finally {
6651
+ activeReader = null;
6652
+ if (!cancelled) controller.close();
6653
+ }
6654
+ },
6655
+ async cancel(reason) {
6656
+ cancelled = true;
6657
+ abort.abort(reason);
6658
+ const reader = activeReader;
6659
+ activeReader = null;
6660
+ if (reader) await reader.cancel(reason).catch(() => {
6661
+ });
6662
+ }
6663
+ });
6664
+ return new Response(stream, {
6665
+ status: response.status,
6666
+ statusText: response.statusText,
6667
+ headers: response.headers
6668
+ });
6669
+ }
6670
+
6526
6671
  // src/generated-tool-gate.ts
6527
6672
  var TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{1,63}$/;
6528
6673
  var DEFAULT_MAX_CODE_LENGTH = 12e3;
@@ -9903,6 +10048,14 @@ function appendRuntimeToolsToAgentRequest(request6, runtimeTools) {
9903
10048
  }
9904
10049
  };
9905
10050
  }
10051
+ function buildAgentAdmissionHeaders(options) {
10052
+ if (!options) return {};
10053
+ const headers = {};
10054
+ if (options.concurrency) headers["x-runtype-concurrency"] = options.concurrency;
10055
+ if (options.coalesce) headers["x-runtype-coalesce"] = "true";
10056
+ if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
10057
+ return headers;
10058
+ }
9906
10059
  var _AgentsEndpoint = class _AgentsEndpoint {
9907
10060
  constructor(client) {
9908
10061
  this.client = client;
@@ -9994,18 +10147,22 @@ var _AgentsEndpoint = class _AgentsEndpoint {
9994
10147
  /**
9995
10148
  * Execute an agent (non-streaming)
9996
10149
  */
9997
- async execute(id, data) {
9998
- return this.client.post(`/agents/${id}/execute`, {
9999
- ...data,
10000
- streamResponse: false
10001
- });
10150
+ async execute(id, data, options) {
10151
+ return this.client.post(
10152
+ `/agents/${id}/execute`,
10153
+ {
10154
+ ...data,
10155
+ streamResponse: false
10156
+ },
10157
+ buildAgentAdmissionHeaders(options)
10158
+ );
10002
10159
  }
10003
10160
  /** Start an agent execution and return its durable handle immediately. */
10004
- async executeAsync(id, data) {
10161
+ async executeAsync(id, data, options) {
10005
10162
  return this.client.post(
10006
10163
  `/agents/${id}/execute`,
10007
10164
  { ...data, streamResponse: false },
10008
- { Prefer: "respond-async" }
10165
+ { Prefer: "respond-async", ...buildAgentAdmissionHeaders(options) }
10009
10166
  );
10010
10167
  }
10011
10168
  /**
@@ -10027,14 +10184,42 @@ var _AgentsEndpoint = class _AgentsEndpoint {
10027
10184
  * ```
10028
10185
  */
10029
10186
  async executeStream(id, data, init) {
10030
- return this.client.requestStream(`/agents/${id}/execute`, {
10187
+ const admissionHeaders = buildAgentAdmissionHeaders(init);
10188
+ const response = await this.client.requestStream(`/agents/${id}/execute`, {
10031
10189
  method: "POST",
10032
10190
  body: JSON.stringify({
10033
10191
  ...data,
10034
10192
  streamResponse: true
10035
10193
  }),
10194
+ ...Object.keys(admissionHeaders).length > 0 ? { headers: admissionHeaders } : {},
10036
10195
  ...init?.signal ? { signal: init.signal } : {}
10037
10196
  });
10197
+ return withDetachedReconnect(
10198
+ response,
10199
+ this.buildDetachedReattach(id, data, init?.signal),
10200
+ init ?? {}
10201
+ );
10202
+ }
10203
+ /**
10204
+ * The `?after=` reattach leg every durable agent stream is followed with.
10205
+ *
10206
+ * Shared by `executeStream` and by the local-tool loop's RESUME leg: a
10207
+ * durable resume answers on the same watch-lease contract as the original
10208
+ * execute, so its socket can close on `awaitReason: 'detached'` too. Building
10209
+ * it once is what keeps the two from drifting into different cursor or
10210
+ * conversation-key behavior.
10211
+ */
10212
+ buildDetachedReattach(id, data, signal) {
10213
+ return async ({ executionId, after }) => {
10214
+ const named = data?.conversationId;
10215
+ const conversationId = typeof named === "string" ? named : "";
10216
+ const query = new URLSearchParams({ after });
10217
+ if (conversationId) query.set("conversationId", conversationId);
10218
+ return this.client.requestStream(`/agents/${id}/executions/${executionId}/events?${query.toString()}`, {
10219
+ method: "GET",
10220
+ ...signal ? { signal } : {}
10221
+ }).catch(() => null);
10222
+ };
10038
10223
  }
10039
10224
  /**
10040
10225
  * Execute an agent with streaming and callbacks
@@ -10198,7 +10383,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
10198
10383
  callbacks?.onTurnComplete?.(event);
10199
10384
  },
10200
10385
  onAgentPaused: (event) => {
10201
- pausedEvent = event;
10386
+ if (event.awaitReason !== "detached") pausedEvent = event;
10202
10387
  callbacks?.onAgentPaused?.(event);
10203
10388
  },
10204
10389
  onAgentComplete: (event) => {
@@ -10379,7 +10564,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
10379
10564
  }
10380
10565
  let resumeResponse;
10381
10566
  try {
10382
- resumeResponse = await this.client.requestStream(`/agents/${id}/resume`, {
10567
+ const rawResumeResponse = await this.client.requestStream(`/agents/${id}/resume`, {
10383
10568
  method: "POST",
10384
10569
  body: JSON.stringify({
10385
10570
  executionId,
@@ -10389,6 +10574,11 @@ var _AgentsEndpoint = class _AgentsEndpoint {
10389
10574
  }),
10390
10575
  ...abortSignal ? { signal: abortSignal } : {}
10391
10576
  });
10577
+ resumeResponse = withDetachedReconnect(
10578
+ rawResumeResponse,
10579
+ this.buildDetachedReattach(id, data, abortSignal),
10580
+ {}
10581
+ );
10392
10582
  } catch (error) {
10393
10583
  if (abortSignal?.aborted) return finishAborted(executionId);
10394
10584
  throw error;
@@ -14451,6 +14641,7 @@ var STEP_TYPE_TO_METHOD = {
14451
14641
  CollectionsEndpoint,
14452
14642
  ContextTemplatesEndpoint,
14453
14643
  ConversationsEndpoint,
14644
+ DEFAULT_MAX_DETACHED_RECONNECTS,
14454
14645
  DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS,
14455
14646
  DEFAULT_STALL_STOP_AFTER,
14456
14647
  DispatchEndpoint,
@@ -14509,6 +14700,7 @@ var STEP_TYPE_TO_METHOD = {
14509
14700
  UsersEndpoint,
14510
14701
  applyGeneratedRuntimeToolProposalToDispatchRequest,
14511
14702
  attachRuntimeToolsToDispatchRequest,
14703
+ buildAgentAdmissionHeaders,
14512
14704
  buildEmptySessionNudge,
14513
14705
  buildGeneratedRuntimeToolGateOutput,
14514
14706
  buildLedgerOffloadReference,
@@ -14598,5 +14790,6 @@ var STEP_TYPE_TO_METHOD = {
14598
14790
  unregisterWorkflowHook,
14599
14791
  usedNoTools,
14600
14792
  validJson,
14793
+ withDetachedReconnect,
14601
14794
  withUnifiedEvents
14602
14795
  });