@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.mjs CHANGED
@@ -4757,7 +4757,8 @@ var AGENT_CONFIG_KEYS = [
4757
4757
  "temporal",
4758
4758
  "memory",
4759
4759
  "sandbox",
4760
- "tenancyStrategy"
4760
+ "tenancyStrategy",
4761
+ "durability"
4761
4762
  ];
4762
4763
  var AGENT_CONFIG_KEY_LIST = [...AGENT_CONFIG_KEYS].sort();
4763
4764
  function isPlainObject2(value) {
@@ -6327,10 +6328,151 @@ var Runtype = class {
6327
6328
 
6328
6329
  // src/version.ts
6329
6330
  var FALLBACK_VERSION = "0.0.0";
6330
- var SDK_VERSION = "9.2.1".length > 0 ? "9.2.1" : FALLBACK_VERSION;
6331
+ var SDK_VERSION = "9.3.0".length > 0 ? "9.3.0" : FALLBACK_VERSION;
6331
6332
  var RUNTYPE_CLIENT_KIND = "sdk";
6332
6333
  var SDK_USER_AGENT = `runtype-sdk/${SDK_VERSION} (typescript)`;
6333
6334
 
6335
+ // src/detached-reconnect.ts
6336
+ var TERMINAL_EVENTS = /* @__PURE__ */ new Set(["execution_complete", "execution_error"]);
6337
+ var DEFAULT_MAX_DETACHED_RECONNECTS = 12;
6338
+ function observeBlock(block, into) {
6339
+ let eventName = null;
6340
+ for (const line of block.split("\n")) {
6341
+ if (line.startsWith("id:")) {
6342
+ into.lastId = line.slice(3).trim();
6343
+ continue;
6344
+ }
6345
+ if (line.startsWith("event:")) {
6346
+ eventName = line.slice(6).trim();
6347
+ continue;
6348
+ }
6349
+ if (!line.startsWith("data:")) continue;
6350
+ const raw = line.slice(5).trim();
6351
+ if (!raw || raw === "[DONE]") continue;
6352
+ let payload;
6353
+ try {
6354
+ const parsed = JSON.parse(raw);
6355
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) continue;
6356
+ payload = parsed;
6357
+ } catch {
6358
+ continue;
6359
+ }
6360
+ if (typeof payload.executionId === "string") into.executionId = payload.executionId;
6361
+ const type = typeof payload.type === "string" ? payload.type : eventName;
6362
+ if (type && TERMINAL_EVENTS.has(type)) into.sawTerminal = true;
6363
+ if (type === "await" && payload.awaitReason === "detached") into.sawDetach = true;
6364
+ }
6365
+ }
6366
+ function withDetachedReconnect(response, reattach, options = {}) {
6367
+ if (options.autoReconnect === false) return response;
6368
+ if (!response.ok || !response.body) return response;
6369
+ const contentType = response.headers.get("content-type") ?? "";
6370
+ if (!contentType.includes("text/event-stream")) return response;
6371
+ const maxReconnects = options.maxReconnects ?? DEFAULT_MAX_DETACHED_RECONNECTS;
6372
+ const reconnectDelayMs = options.reconnectDelayMs ?? 0;
6373
+ const encoder = new TextEncoder();
6374
+ const decoder = new TextDecoder();
6375
+ const abort = new AbortController();
6376
+ let cancelled = false;
6377
+ let activeReader = null;
6378
+ const stream = new ReadableStream({
6379
+ async start(controller) {
6380
+ const observed = {
6381
+ lastId: null,
6382
+ executionId: null,
6383
+ sawTerminal: false,
6384
+ sawDetach: false
6385
+ };
6386
+ let current = response;
6387
+ let legs = 0;
6388
+ const enqueue = (chunk) => {
6389
+ if (cancelled) return;
6390
+ controller.enqueue(chunk);
6391
+ };
6392
+ try {
6393
+ while (!cancelled && current?.body) {
6394
+ const reader = current.body.getReader();
6395
+ activeReader = reader;
6396
+ let buffer = "";
6397
+ observed.sawDetach = false;
6398
+ for (; ; ) {
6399
+ const { value, done } = await reader.read();
6400
+ if (cancelled) break;
6401
+ if (value) {
6402
+ enqueue(value);
6403
+ buffer += decoder.decode(value, { stream: !done });
6404
+ let boundary = buffer.indexOf("\n\n");
6405
+ while (boundary >= 0) {
6406
+ observeBlock(buffer.slice(0, boundary), observed);
6407
+ buffer = buffer.slice(boundary + 2);
6408
+ boundary = buffer.indexOf("\n\n");
6409
+ }
6410
+ }
6411
+ if (done) break;
6412
+ }
6413
+ activeReader = null;
6414
+ if (cancelled) break;
6415
+ if (buffer.trim()) observeBlock(buffer, observed);
6416
+ if (observed.sawTerminal || !observed.sawDetach) break;
6417
+ if (!observed.executionId || legs >= maxReconnects) break;
6418
+ legs += 1;
6419
+ if (reconnectDelayMs > 0) {
6420
+ await new Promise((resolve) => setTimeout(resolve, reconnectDelayMs));
6421
+ }
6422
+ if (cancelled) break;
6423
+ current = await reattach({
6424
+ executionId: observed.executionId,
6425
+ // `'0'` replays the whole turn — the fail-open answer for a leg
6426
+ // that produced no cursor at all.
6427
+ after: observed.lastId ?? "0",
6428
+ signal: abort.signal
6429
+ });
6430
+ if (cancelled) {
6431
+ await current?.body?.cancel().catch(() => {
6432
+ });
6433
+ break;
6434
+ }
6435
+ if (!current || !current.ok || !current.body) {
6436
+ enqueue(
6437
+ encoder.encode(
6438
+ `event: error
6439
+ data: ${JSON.stringify({
6440
+ type: "error",
6441
+ executionId: observed.executionId,
6442
+ error: {
6443
+ code: "detached_reconnect_failed",
6444
+ message: "The agent detached and this client could not rejoin its stream. The execution is still running; reattach with the events route."
6445
+ },
6446
+ recoverable: false
6447
+ })}
6448
+
6449
+ `
6450
+ )
6451
+ );
6452
+ break;
6453
+ }
6454
+ }
6455
+ } finally {
6456
+ activeReader = null;
6457
+ if (!cancelled) controller.close();
6458
+ }
6459
+ },
6460
+ async cancel(reason) {
6461
+ cancelled = true;
6462
+ abort.abort(reason);
6463
+ const reader = activeReader;
6464
+ activeReader = null;
6465
+ if (reader) await reader.cancel(reason).catch(() => {
6466
+ });
6467
+ }
6468
+ });
6469
+ return new Response(stream, {
6470
+ status: response.status,
6471
+ statusText: response.statusText,
6472
+ headers: response.headers
6473
+ });
6474
+ }
6475
+
6334
6476
  // src/generated-tool-gate.ts
6335
6477
  var TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{1,63}$/;
6336
6478
  var DEFAULT_MAX_CODE_LENGTH = 12e3;
@@ -9711,6 +9853,14 @@ function appendRuntimeToolsToAgentRequest(request6, runtimeTools) {
9711
9853
  }
9712
9854
  };
9713
9855
  }
9856
+ function buildAgentAdmissionHeaders(options) {
9857
+ if (!options) return {};
9858
+ const headers = {};
9859
+ if (options.concurrency) headers["x-runtype-concurrency"] = options.concurrency;
9860
+ if (options.coalesce) headers["x-runtype-coalesce"] = "true";
9861
+ if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
9862
+ return headers;
9863
+ }
9714
9864
  var _AgentsEndpoint = class _AgentsEndpoint {
9715
9865
  constructor(client) {
9716
9866
  this.client = client;
@@ -9802,18 +9952,22 @@ var _AgentsEndpoint = class _AgentsEndpoint {
9802
9952
  /**
9803
9953
  * Execute an agent (non-streaming)
9804
9954
  */
9805
- async execute(id, data) {
9806
- return this.client.post(`/agents/${id}/execute`, {
9807
- ...data,
9808
- streamResponse: false
9809
- });
9955
+ async execute(id, data, options) {
9956
+ return this.client.post(
9957
+ `/agents/${id}/execute`,
9958
+ {
9959
+ ...data,
9960
+ streamResponse: false
9961
+ },
9962
+ buildAgentAdmissionHeaders(options)
9963
+ );
9810
9964
  }
9811
9965
  /** Start an agent execution and return its durable handle immediately. */
9812
- async executeAsync(id, data) {
9966
+ async executeAsync(id, data, options) {
9813
9967
  return this.client.post(
9814
9968
  `/agents/${id}/execute`,
9815
9969
  { ...data, streamResponse: false },
9816
- { Prefer: "respond-async" }
9970
+ { Prefer: "respond-async", ...buildAgentAdmissionHeaders(options) }
9817
9971
  );
9818
9972
  }
9819
9973
  /**
@@ -9835,14 +9989,42 @@ var _AgentsEndpoint = class _AgentsEndpoint {
9835
9989
  * ```
9836
9990
  */
9837
9991
  async executeStream(id, data, init) {
9838
- return this.client.requestStream(`/agents/${id}/execute`, {
9992
+ const admissionHeaders = buildAgentAdmissionHeaders(init);
9993
+ const response = await this.client.requestStream(`/agents/${id}/execute`, {
9839
9994
  method: "POST",
9840
9995
  body: JSON.stringify({
9841
9996
  ...data,
9842
9997
  streamResponse: true
9843
9998
  }),
9999
+ ...Object.keys(admissionHeaders).length > 0 ? { headers: admissionHeaders } : {},
9844
10000
  ...init?.signal ? { signal: init.signal } : {}
9845
10001
  });
10002
+ return withDetachedReconnect(
10003
+ response,
10004
+ this.buildDetachedReattach(id, data, init?.signal),
10005
+ init ?? {}
10006
+ );
10007
+ }
10008
+ /**
10009
+ * The `?after=` reattach leg every durable agent stream is followed with.
10010
+ *
10011
+ * Shared by `executeStream` and by the local-tool loop's RESUME leg: a
10012
+ * durable resume answers on the same watch-lease contract as the original
10013
+ * execute, so its socket can close on `awaitReason: 'detached'` too. Building
10014
+ * it once is what keeps the two from drifting into different cursor or
10015
+ * conversation-key behavior.
10016
+ */
10017
+ buildDetachedReattach(id, data, signal) {
10018
+ return async ({ executionId, after }) => {
10019
+ const named = data?.conversationId;
10020
+ const conversationId = typeof named === "string" ? named : "";
10021
+ const query = new URLSearchParams({ after });
10022
+ if (conversationId) query.set("conversationId", conversationId);
10023
+ return this.client.requestStream(`/agents/${id}/executions/${executionId}/events?${query.toString()}`, {
10024
+ method: "GET",
10025
+ ...signal ? { signal } : {}
10026
+ }).catch(() => null);
10027
+ };
9846
10028
  }
9847
10029
  /**
9848
10030
  * Execute an agent with streaming and callbacks
@@ -10006,7 +10188,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
10006
10188
  callbacks?.onTurnComplete?.(event);
10007
10189
  },
10008
10190
  onAgentPaused: (event) => {
10009
- pausedEvent = event;
10191
+ if (event.awaitReason !== "detached") pausedEvent = event;
10010
10192
  callbacks?.onAgentPaused?.(event);
10011
10193
  },
10012
10194
  onAgentComplete: (event) => {
@@ -10187,7 +10369,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
10187
10369
  }
10188
10370
  let resumeResponse;
10189
10371
  try {
10190
- resumeResponse = await this.client.requestStream(`/agents/${id}/resume`, {
10372
+ const rawResumeResponse = await this.client.requestStream(`/agents/${id}/resume`, {
10191
10373
  method: "POST",
10192
10374
  body: JSON.stringify({
10193
10375
  executionId,
@@ -10197,6 +10379,11 @@ var _AgentsEndpoint = class _AgentsEndpoint {
10197
10379
  }),
10198
10380
  ...abortSignal ? { signal: abortSignal } : {}
10199
10381
  });
10382
+ resumeResponse = withDetachedReconnect(
10383
+ rawResumeResponse,
10384
+ this.buildDetachedReattach(id, data, abortSignal),
10385
+ {}
10386
+ );
10200
10387
  } catch (error) {
10201
10388
  if (abortSignal?.aborted) return finishAborted(executionId);
10202
10389
  throw error;
@@ -14258,6 +14445,7 @@ export {
14258
14445
  CollectionsEndpoint,
14259
14446
  ContextTemplatesEndpoint,
14260
14447
  ConversationsEndpoint,
14448
+ DEFAULT_MAX_DETACHED_RECONNECTS,
14261
14449
  DEFAULT_RECOVERY_AFTER_EMPTY_SESSIONS,
14262
14450
  DEFAULT_STALL_STOP_AFTER,
14263
14451
  DispatchEndpoint,
@@ -14316,6 +14504,7 @@ export {
14316
14504
  UsersEndpoint,
14317
14505
  applyGeneratedRuntimeToolProposalToDispatchRequest,
14318
14506
  attachRuntimeToolsToDispatchRequest,
14507
+ buildAgentAdmissionHeaders,
14319
14508
  buildEmptySessionNudge,
14320
14509
  buildGeneratedRuntimeToolGateOutput,
14321
14510
  buildLedgerOffloadReference,
@@ -14405,5 +14594,6 @@ export {
14405
14594
  unregisterWorkflowHook,
14406
14595
  usedNoTools,
14407
14596
  validJson,
14597
+ withDetachedReconnect,
14408
14598
  withUnifiedEvents
14409
14599
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/sdk",
3
- "version": "9.2.1",
3
+ "version": "9.3.0",
4
4
  "type": "module",
5
5
  "description": "TypeScript SDK for the Runtype API with fluent methods. Use it to quickly realize AI products, agents, and workflows.",
6
6
  "main": "dist/index.cjs",