@moxt-ai/mobius-sdk 0.0.23 → 0.0.26

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.
@@ -1,12 +1,8 @@
1
- import { decodeAgentSettingsDiscoveredEvent, decodeNativeMessagePage, decodeNativeSessionResult, decodeSessionConfigUpdatedEvent, decodeSessionReadyEvent, decodeWorkspaceEntriesEvent, MOBIUS_API_PREFIX, MOBIUS_EVENT_FORMAT_VERSION, MOBIUS_SESSION_EVENT_STREAM_EVENT, MOBIUS_SESSION_EVENT_STREAM_MEDIA_TYPE, SessionPromptDisposition, } from "@moxt-ai/mobius-protocol";
1
+ import { decodeAgentSettingsDiscoveredEvent, decodeNativeSessionResult, decodeWorkspaceEntriesEvent, MOBIUS_API_PREFIX, } from "@moxt-ai/mobius-protocol";
2
2
  import { MobiusApiError, MobiusCancellationError, MobiusConnectionError, MobiusError, MobiusProtocolError, MobiusTimeoutError, MobiusValidationError, } from "./errors.js";
3
- import { decodeMobiusAttempt, decodeMobiusBrowserCredential, decodeMobiusEventPage, decodeMobiusInteraction, decodeMobiusInteractionList, decodeMobiusMessagePage, decodeMobiusRuntimeList, decodeMobiusRuntimePairing, decodeMobiusRuntimePairingStatus, decodeMobiusServiceCapabilities, decodeMobiusSession, decodeMobiusSessionEvent, decodeMobiusSessionExport, decodeMobiusSessionPage, decodeMobiusSessionState, decodeMobiusToolCallDetail, decodeMobiusTurn, MobiusPageRequest, MobiusPairedRuntimePairingStatus, MobiusRuntimePairingLifecycle, MobiusSessionAccess, } from "./resources.js";
4
- import { HttpTransport, isMobiusLiveEventTransport, } from "./transport/http-transport.js";
3
+ import { decodeMobiusBrowserCredential, decodeMobiusRuntimeList, decodeMobiusRuntimePairing, decodeMobiusRuntimePairingStatus, MobiusPairedRuntimePairingStatus, MobiusRuntimePairingLifecycle, } from "./resources.js";
4
+ import { HttpTransport } from "./transport/http-transport.js";
5
5
  const DEFAULT_OPERATION_TIMEOUT_MILLISECONDS = 20_000;
6
- const DEFAULT_STREAM_CONNECTION_TIMEOUT_MILLISECONDS = 15_000;
7
- const DEFAULT_STREAM_LIVENESS_TIMEOUT_MILLISECONDS = 45_000;
8
- const DEFAULT_STREAM_RECONNECT_ATTEMPTS = 4;
9
- const MAX_EVENT_BLOCK_LENGTH = 9 * 1024 * 1024;
10
6
  export class StaticMobiusCredentialProvider {
11
7
  #token;
12
8
  constructor(token) {
@@ -64,12 +60,6 @@ function projectPath(projectId, resourcePath) {
64
60
  }
65
61
  return `v1/projects/${encodeURIComponent(projectId)}/${resourcePath}`;
66
62
  }
67
- function sessionPath(projectId, sessionId, resourcePath) {
68
- if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/.test(sessionId)) {
69
- throw new MobiusValidationError("The Mobius Session identifier is invalid");
70
- }
71
- return projectPath(projectId, `sessions/${encodeURIComponent(sessionId)}/${resourcePath}`);
72
- }
73
63
  function requestUrl(endpoint, path) {
74
64
  return new URL(path, endpoint);
75
65
  }
@@ -148,82 +138,12 @@ class RequestDeadline {
148
138
  this.#controller.abort(new Error("The Mobius operation timed out"));
149
139
  };
150
140
  }
151
- class StreamConnection {
152
- #closed = false;
153
- #controller;
154
- #hasActivity = false;
155
- #livenessTimedOut = false;
156
- #livenessTimeoutId;
157
- #livenessTimeoutMilliseconds;
158
- #release;
159
- response;
160
- constructor(response, controller, release, livenessTimeoutMilliseconds) {
161
- this.#controller = controller;
162
- this.#livenessTimeoutMilliseconds = livenessTimeoutMilliseconds;
163
- this.#release = release;
164
- this.response = response;
165
- this.#livenessTimeoutId = globalThis.setTimeout(this.abortFromLivenessTimeout, this.#livenessTimeoutMilliseconds);
166
- }
167
- get hasActivity() {
168
- return this.#hasActivity;
169
- }
170
- close = () => {
171
- if (this.#closed) {
172
- return;
173
- }
174
- this.#closed = true;
175
- globalThis.clearTimeout(this.#livenessTimeoutId);
176
- this.#controller.abort(new Error("The Mobius event connection closed"));
177
- this.#release();
178
- };
179
- touch = () => {
180
- if (this.#closed) {
181
- return;
182
- }
183
- this.#hasActivity = true;
184
- globalThis.clearTimeout(this.#livenessTimeoutId);
185
- this.#livenessTimeoutId = globalThis.setTimeout(this.abortFromLivenessTimeout, this.#livenessTimeoutMilliseconds);
186
- };
187
- translateFailure = (cause) => {
188
- if (this.#livenessTimedOut) {
189
- throw new MobiusTimeoutError({ cause });
190
- }
191
- throw new MobiusConnectionError("The Mobius event stream was interrupted", {
192
- cause,
193
- });
194
- };
195
- abortFromLivenessTimeout = () => {
196
- this.#livenessTimedOut = true;
197
- this.#controller.abort(new Error("The Mobius event stream became inactive"));
198
- };
199
- }
200
- class LiveEventConnection {
201
- events;
202
- #controller;
203
- #release;
204
- #closed = false;
205
- constructor(events, controller, release) {
206
- this.events = events;
207
- this.#controller = controller;
208
- this.#release = release;
209
- }
210
- close = () => {
211
- if (this.#closed) {
212
- return;
213
- }
214
- this.#closed = true;
215
- this.#controller.abort(new Error("The live Session event connection closed"));
216
- this.#release();
217
- };
218
- }
219
141
  export class MobiusRequestExecutor {
220
142
  #authenticationProviderId;
221
143
  #credentialProvider;
222
144
  #endpoint;
223
145
  #operationTimeoutMilliseconds;
224
146
  #observeResponse;
225
- #streamConnectionTimeoutMilliseconds;
226
- #streamLivenessTimeoutMilliseconds;
227
147
  #transport;
228
148
  constructor(options) {
229
149
  const authenticationProviderId = options.authenticationProviderId ?? "";
@@ -237,61 +157,9 @@ export class MobiusRequestExecutor {
237
157
  this.#endpoint = normalizeEndpoint(options.endpoint);
238
158
  this.#operationTimeoutMilliseconds = validatePositiveInteger(options.operationTimeoutMilliseconds ?? DEFAULT_OPERATION_TIMEOUT_MILLISECONDS, "operation timeout");
239
159
  this.#observeResponse = options.observeResponse;
240
- this.#streamConnectionTimeoutMilliseconds = validatePositiveInteger(options.streamConnectionTimeoutMilliseconds ?? DEFAULT_STREAM_CONNECTION_TIMEOUT_MILLISECONDS, "stream connection timeout");
241
- this.#streamLivenessTimeoutMilliseconds = validatePositiveInteger(options.streamLivenessTimeoutMilliseconds ?? DEFAULT_STREAM_LIVENESS_TIMEOUT_MILLISECONDS, "stream liveness timeout");
242
160
  this.#transport = options.transport ?? new HttpTransport();
243
161
  }
244
162
  get = async (path, signal) => await this.request(path, "GET", "", false, signal);
245
- getFile = async (path, maximumBytes, signal) => {
246
- const deadline = new RequestDeadline(signal, this.#operationTimeoutMilliseconds);
247
- try {
248
- const credential = validateCredential(await this.#credentialProvider.credential(deadline.signal));
249
- const response = await this.#transport.fetch(new Request(requestUrl(this.#endpoint, path), {
250
- headers: this.authenticationHeaders(credential, "application/octet-stream"),
251
- signal: deadline.signal,
252
- }));
253
- this.#observeResponse?.({
254
- serverTiming: response.headers.get("server-timing"),
255
- status: response.status,
256
- });
257
- if (!response.ok) {
258
- await throwApiError(response);
259
- }
260
- const mediaType = response.headers.get("content-type") ?? "";
261
- const modifiedAt = response.headers.get("last-modified") ?? "";
262
- const encodedName = response.headers.get("x-mobius-file-name") ?? "";
263
- const encodedLength = response.headers.get("x-mobius-byte-length") ?? "";
264
- if (mediaType.length === 0 ||
265
- mediaType.length > 255 ||
266
- modifiedAt.length === 0 ||
267
- !/^\d+$/.test(encodedLength)) {
268
- throw new MobiusProtocolError("The service returned invalid file metadata");
269
- }
270
- const byteLength = Number(encodedLength);
271
- if (!Number.isSafeInteger(byteLength) || byteLength < 1 || byteLength > maximumBytes) {
272
- throw new MobiusProtocolError("The service returned an invalid file size");
273
- }
274
- let name;
275
- try {
276
- name = decodeURIComponent(encodedName);
277
- }
278
- catch (cause) {
279
- throw new MobiusProtocolError("The service returned an invalid file name", { cause });
280
- }
281
- return {
282
- bytes: await readExactResponseBytes(response, byteLength, deadline.signal, false),
283
- mediaType,
284
- modifiedAt,
285
- name,
286
- };
287
- }
288
- catch (cause) {
289
- return deadline.translate(cause);
290
- }
291
- finally {
292
- deadline.finish();
293
- }
294
- };
295
163
  post = async (path, body, signal) => {
296
164
  const encoded = JSON.stringify(body);
297
165
  if (typeof encoded !== "string") {
@@ -310,103 +178,6 @@ export class MobiusRequestExecutor {
310
178
  deleteEmpty = async (path, signal) => {
311
179
  await this.requestEmpty(path, "DELETE", signal);
312
180
  };
313
- openEventStream = async (path, signal) => {
314
- const controller = new AbortController();
315
- let timedOut = false;
316
- const abortFromRequest = () => controller.abort(signal.reason);
317
- if (signal.aborted) {
318
- abortFromRequest();
319
- }
320
- else {
321
- signal.addEventListener("abort", abortFromRequest, { once: true });
322
- }
323
- const timeoutId = globalThis.setTimeout(() => {
324
- timedOut = true;
325
- controller.abort(new Error("The Mobius event stream timed out"));
326
- }, this.#streamConnectionTimeoutMilliseconds);
327
- const release = () => {
328
- globalThis.clearTimeout(timeoutId);
329
- signal.removeEventListener("abort", abortFromRequest);
330
- };
331
- let response;
332
- try {
333
- const credential = validateCredential(await this.#credentialProvider.credential(controller.signal));
334
- response = await this.#transport.fetch(new Request(requestUrl(this.#endpoint, path), {
335
- headers: this.authenticationHeaders(credential, MOBIUS_SESSION_EVENT_STREAM_MEDIA_TYPE),
336
- signal: controller.signal,
337
- }));
338
- }
339
- catch (cause) {
340
- release();
341
- if (signal.aborted) {
342
- throw new MobiusCancellationError({ cause });
343
- }
344
- if (timedOut) {
345
- throw new MobiusTimeoutError({ cause });
346
- }
347
- if (cause instanceof MobiusError) {
348
- throw cause;
349
- }
350
- throw new MobiusConnectionError("The Mobius event stream could not connect", { cause });
351
- }
352
- if (!response.ok) {
353
- try {
354
- await throwApiError(response);
355
- }
356
- catch (cause) {
357
- release();
358
- if (signal.aborted) {
359
- throw new MobiusCancellationError({ cause });
360
- }
361
- if (timedOut) {
362
- throw new MobiusTimeoutError({ cause });
363
- }
364
- throw cause;
365
- }
366
- }
367
- globalThis.clearTimeout(timeoutId);
368
- const contentType = response.headers.get("content-type") ?? "";
369
- if (!contentType.startsWith(MOBIUS_SESSION_EVENT_STREAM_MEDIA_TYPE) ||
370
- response.headers.get("x-mobius-event-format") !== MOBIUS_EVENT_FORMAT_VERSION ||
371
- response.body === null) {
372
- controller.abort(new Error("The Mobius event stream is invalid"));
373
- release();
374
- throw new MobiusProtocolError("The service returned an invalid Session event stream");
375
- }
376
- return new StreamConnection(response, controller, release, this.#streamLivenessTimeoutMilliseconds);
377
- };
378
- openLiveEventStream = async (path, signal) => {
379
- if (!isMobiusLiveEventTransport(this.#transport)) {
380
- throw new MobiusValidationError("The configured transport does not support live Session events");
381
- }
382
- const controller = new AbortController();
383
- const abort = () => controller.abort(signal.reason);
384
- signal.addEventListener("abort", abort, { once: true });
385
- const timeoutId = globalThis.setTimeout(() => controller.abort(new Error("The live Session event connection timed out")), this.#streamConnectionTimeoutMilliseconds);
386
- const release = () => {
387
- globalThis.clearTimeout(timeoutId);
388
- signal.removeEventListener("abort", abort);
389
- };
390
- try {
391
- const credential = validateCredential(await this.#credentialProvider.credential(controller.signal));
392
- const events = await this.#transport.openLiveSessionEvents(new Request(requestUrl(this.#endpoint, path), {
393
- headers: this.authenticationHeaders(credential, "application/json"),
394
- signal: controller.signal,
395
- }));
396
- globalThis.clearTimeout(timeoutId);
397
- return new LiveEventConnection(events, controller, release);
398
- }
399
- catch (cause) {
400
- release();
401
- if (signal.aborted) {
402
- throw new MobiusCancellationError({ cause });
403
- }
404
- if (cause instanceof MobiusError) {
405
- throw cause;
406
- }
407
- throw new MobiusConnectionError("The live Session event stream could not connect", { cause });
408
- }
409
- };
410
181
  request = async (path, method, body, hasBody, signal) => {
411
182
  const deadline = new RequestDeadline(signal, this.#operationTimeoutMilliseconds);
412
183
  try {
@@ -472,63 +243,6 @@ export class MobiusRequestExecutor {
472
243
  return headers;
473
244
  };
474
245
  }
475
- export async function readExactResponseBytes(response, expectedByteLength, signal, verifyDigest = true) {
476
- const declaredLength = response.headers.get("content-length");
477
- if (declaredLength !== null &&
478
- (!/^(0|[1-9][0-9]*)$/.test(declaredLength) || Number(declaredLength) !== expectedByteLength)) {
479
- throw new MobiusProtocolError("The service returned an invalid artifact length");
480
- }
481
- const reader = response.body?.getReader();
482
- if (reader === undefined) {
483
- throw new MobiusProtocolError("The service returned no artifact content");
484
- }
485
- const chunks = [];
486
- let byteLength = 0;
487
- try {
488
- while (true) {
489
- signal.throwIfAborted();
490
- const chunk = await reader.read();
491
- signal.throwIfAborted();
492
- if (chunk.done) {
493
- break;
494
- }
495
- byteLength += chunk.value.byteLength;
496
- if (byteLength > expectedByteLength) {
497
- await reader.cancel("Artifact response exceeded its metadata");
498
- throw new MobiusProtocolError("The service returned an oversized artifact");
499
- }
500
- chunks.push(chunk.value);
501
- }
502
- }
503
- finally {
504
- reader.releaseLock();
505
- }
506
- if (byteLength !== expectedByteLength) {
507
- throw new MobiusProtocolError("The service returned an incomplete artifact");
508
- }
509
- const bytes = new Uint8Array(byteLength);
510
- let offset = 0;
511
- for (const chunk of chunks) {
512
- bytes.set(chunk, offset);
513
- offset += chunk.byteLength;
514
- }
515
- if (verifyDigest) {
516
- const digest = await crypto.subtle.digest("SHA-256", bytes);
517
- signal.throwIfAborted();
518
- const sha256 = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
519
- if (sha256 !== response.headers.get("x-mobius-sha256")) {
520
- throw new MobiusProtocolError("The service returned artifact content with an invalid digest");
521
- }
522
- }
523
- return bytes;
524
- }
525
- export class MobiusCapabilityResources {
526
- #executor;
527
- constructor(executor) {
528
- this.#executor = executor;
529
- }
530
- read = async (signal) => decodeMobiusServiceCapabilities(await this.#executor.get("capabilities", signal));
531
- }
532
246
  export class MobiusCredentialResources {
533
247
  #executor;
534
248
  #projectId;
@@ -623,599 +337,15 @@ function waitForPairingPoll(milliseconds, signal) {
623
337
  signal.addEventListener("abort", cancel, { once: true });
624
338
  });
625
339
  }
626
- export class MobiusSessionResources {
627
- #executor;
628
- #projectId;
629
- constructor(executor, projectId) {
630
- this.#executor = executor;
631
- this.#projectId = projectId;
632
- }
633
- create = async (request, signal) => decodeMobiusSession(await this.#executor.post(projectPath(this.#projectId, "sessions"), request, signal));
634
- createAccess = async (sessionId, expiresInSeconds, signal) => new MobiusSessionAccess(await this.#executor.post(sessionPath(this.#projectId, sessionId, "access-credentials"), { expiresInSeconds }, signal));
635
- prepare = async (sessionId, signal) => decodeSessionReadyEvent(JSON.stringify(await this.#executor.post(sessionPath(this.#projectId, sessionId, "prepare"), {}, signal)));
636
- updateConfiguration = async (sessionId, request, signal) => decodeSessionConfigUpdatedEvent(JSON.stringify(await this.#executor.post(sessionPath(this.#projectId, sessionId, "configuration"), request, signal)));
637
- configure = async (sessionId, request, signal) => decodeMobiusSession(await this.#executor.put(sessionPath(this.#projectId, sessionId, "binding"), request, signal));
638
- delete = async (sessionId, signal) => decodeMobiusSessionState(await this.#executor.delete(sessionPath(this.#projectId, sessionId, "").slice(0, -1), signal));
639
- export = async (sessionId, signal) => decodeMobiusSessionExport(await this.#executor.get(sessionPath(this.#projectId, sessionId, "export"), signal));
640
- import = async (exported, targetBinding, signal) => decodeMobiusSession(await this.#executor.post(projectPath(this.#projectId, "session-imports"), {
641
- formatVersion: exported.formatVersion,
642
- session: exported.session,
643
- targetBinding,
644
- }, signal));
645
- read = async (sessionId, signal) => decodeMobiusSession(await this.#executor.get(sessionPath(this.#projectId, sessionId, "").slice(0, -1), signal));
646
- readFile = async (sessionId, path, signal, maximumBytes = 32 * 1024 * 1024) => {
647
- if (path.length === 0 || path.length > 4_096) {
648
- throw new MobiusValidationError("The file path is invalid");
649
- }
650
- if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 1 || maximumBytes > 128 * 1024 * 1024) {
651
- throw new MobiusValidationError("The file size limit is invalid");
652
- }
653
- const query = new URLSearchParams({ maxBytes: maximumBytes.toString(), path });
654
- return await this.#executor.getFile(`${sessionPath(this.#projectId, sessionId, "files/content")}?${query.toString()}`, maximumBytes, signal);
655
- };
656
- list = async (page, signal) => {
657
- const path = requestUrl(new URL("https://mobius.invalid/"), projectPath(this.#projectId, "sessions"));
658
- path.searchParams.set("limit", page.limit.toString());
659
- if (page.cursor.length > 0) {
660
- path.searchParams.set("cursor", page.cursor);
661
- }
662
- return decodeMobiusSessionPage(await this.#executor.get(`${path.pathname.slice(1)}${path.search}`, signal));
663
- };
664
- readMessages = async (sessionId, page, signal) => {
665
- const path = requestUrl(new URL("https://mobius.invalid/"), sessionPath(this.#projectId, sessionId, "messages"));
666
- path.searchParams.set("limit", page.limit.toString());
667
- if (page.cursor.length > 0) {
668
- path.searchParams.set("cursor", page.cursor);
669
- }
670
- return decodeMobiusMessagePage(await this.#executor.get(`${path.pathname.slice(1)}${path.search}`, signal));
671
- };
672
- queryNativeMessages = async (...request) => {
673
- const [sessionId, page, signal] = request.length === 2
674
- ? [request[0], new MobiusPageRequest(50, ""), request[1]]
675
- : [request[0], request[1], request[2]];
676
- try {
677
- const path = requestUrl(new URL("https://mobius.invalid/"), sessionPath(this.#projectId, sessionId, "native-messages"));
678
- path.searchParams.set("limit", page.limit.toString());
679
- if (page.cursor.length > 0) {
680
- path.searchParams.set("cursor", page.cursor);
681
- }
682
- return decodeNativeMessagePage(await this.#executor.get(`${path.pathname.slice(1)}${path.search}`, signal));
683
- }
684
- catch (cause) {
685
- if (cause instanceof MobiusError) {
686
- throw cause;
687
- }
688
- throw new MobiusProtocolError("The service returned invalid native messages", { cause });
689
- }
690
- };
691
- }
692
- export class MobiusTurnResources {
693
- #executor;
694
- #projectId;
695
- constructor(executor, projectId) {
696
- this.#executor = executor;
697
- this.#projectId = projectId;
698
- }
699
- submit = async (sessionId, request, signal) => decodeMobiusTurn(await this.#executor.post(sessionPath(this.#projectId, sessionId, "turns"), request, signal));
700
- read = async (sessionId, turnId, signal) => decodeMobiusTurn(await this.#executor.get(sessionPath(this.#projectId, sessionId, `turns/${encodeURIComponent(turnId)}`), signal));
701
- cancel = async (sessionId, turnId, request, signal) => decodeMobiusTurn(await this.#executor.post(sessionPath(this.#projectId, sessionId, `turns/${encodeURIComponent(turnId)}/cancel`), request, signal));
702
- steer = async (sessionId, turnId, signal) => decodeMobiusTurn(await this.#executor.put(sessionPath(this.#projectId, sessionId, `turns/${encodeURIComponent(turnId)}/disposition`), { disposition: SessionPromptDisposition.Steer }, signal));
703
- }
704
- export class MobiusAttemptResources {
705
- #executor;
706
- #projectId;
707
- constructor(executor, projectId) {
708
- this.#executor = executor;
709
- this.#projectId = projectId;
710
- }
711
- read = async (sessionId, attemptId, signal) => decodeMobiusAttempt(await this.#executor.get(sessionPath(this.#projectId, sessionId, `attempts/${encodeURIComponent(attemptId)}`), signal));
712
- }
713
- export class MobiusToolCallResources {
714
- #executor;
715
- #projectId;
716
- constructor(executor, projectId) {
717
- this.#executor = executor;
718
- this.#projectId = projectId;
719
- }
720
- readDetail = async (sessionId, attemptId, toolCallId, signal) => decodeMobiusToolCallDetail(await this.#executor.get(sessionPath(this.#projectId, sessionId, `attempts/${encodeURIComponent(attemptId)}/tool-calls/${encodeURIComponent(toolCallId)}`), signal));
721
- }
722
- export class MobiusInteractionResources {
723
- #executor;
724
- #projectId;
725
- constructor(executor, projectId) {
726
- this.#executor = executor;
727
- this.#projectId = projectId;
728
- }
729
- listPending = async (sessionId, signal) => decodeMobiusInteractionList(await this.#executor.get(sessionPath(this.#projectId, sessionId, "interactions"), signal));
730
- read = async (sessionId, interactionId, signal) => decodeMobiusInteraction(await this.#executor.get(sessionPath(this.#projectId, sessionId, `interactions/${encodeURIComponent(interactionId)}`), signal));
731
- respond = async (sessionId, interactionId, request, signal) => decodeMobiusInteraction(await this.#executor.post(sessionPath(this.#projectId, sessionId, `interactions/${encodeURIComponent(interactionId)}/responses`), request, signal));
732
- }
733
- export class MobiusEventSubscription {
734
- #controller;
735
- #executor;
736
- #externalSignal;
737
- #maximumReconnectAttempts;
738
- #observer;
739
- #path;
740
- #closed = false;
741
- #cursor;
742
- #hasSequence = false;
743
- #lastSequence = 0n;
744
- completed;
745
- constructor(executor, path, cursor, observer, signal, controller, firstConnection, maximumReconnectAttempts) {
746
- this.#cursor = cursor;
747
- this.#controller = controller;
748
- this.#executor = executor;
749
- this.#externalSignal = signal;
750
- this.#maximumReconnectAttempts = maximumReconnectAttempts;
751
- this.#observer = observer;
752
- this.#path = path;
753
- if (this.#externalSignal.aborted) {
754
- this.abortFromExternal();
755
- }
756
- else {
757
- this.#externalSignal.addEventListener("abort", this.abortFromExternal, {
758
- once: true,
759
- });
760
- }
761
- this.completed = this.run(firstConnection).finally(() => {
762
- this.#externalSignal.removeEventListener("abort", this.abortFromExternal);
763
- });
764
- }
765
- get cursor() {
766
- return this.#cursor;
767
- }
768
- close = () => {
769
- if (this.#closed) {
770
- return;
771
- }
772
- this.#closed = true;
773
- this.#controller.abort(new Error("The Mobius event subscription closed"));
774
- };
775
- abortFromExternal = () => {
776
- this.#controller.abort(this.#externalSignal.reason);
777
- };
778
- run = async (firstConnection) => {
779
- let connection = firstConnection;
780
- let reconnectAttempts = 0;
781
- while (!this.#closed) {
782
- try {
783
- const stopped = await this.consume(connection);
784
- connection.close();
785
- if (stopped || this.#closed) {
786
- this.#closed = true;
787
- return;
788
- }
789
- throw new MobiusConnectionError("The Mobius event stream closed before the observer stopped it");
790
- }
791
- catch (cause) {
792
- connection.close();
793
- if (this.#closed) {
794
- return;
795
- }
796
- if (connection.hasActivity) {
797
- reconnectAttempts = 0;
798
- }
799
- try {
800
- const recovery = await this.recover(cause, reconnectAttempts);
801
- reconnectAttempts = recovery.attempts;
802
- connection = recovery.connection;
803
- }
804
- catch (recoveryCause) {
805
- if (this.#closed) {
806
- return;
807
- }
808
- throw recoveryCause;
809
- }
810
- }
811
- }
812
- };
813
- consume = async (connection) => {
814
- const body = connection.response.body;
815
- if (body === null) {
816
- throw new MobiusProtocolError("The Mobius event stream has no body");
817
- }
818
- const reader = body.getReader();
819
- const decoder = new TextDecoder();
820
- let buffered = "";
821
- try {
822
- while (!this.#closed) {
823
- const result = await reader.read();
824
- if (result.done) {
825
- buffered += decoder.decode();
826
- if (buffered.trim().length > 0) {
827
- throw new MobiusProtocolError("The Mobius event stream ended with an incomplete event");
828
- }
829
- return false;
830
- }
831
- connection.touch();
832
- buffered += decoder.decode(result.value, { stream: true });
833
- if (buffered.length > MAX_EVENT_BLOCK_LENGTH) {
834
- throw new MobiusProtocolError("The Mobius event stream exceeded its event limit");
835
- }
836
- let boundary = buffered.indexOf("\n\n");
837
- let boundaryLength = 2;
838
- const carriageReturnBoundary = buffered.indexOf("\r\n\r\n");
839
- if (carriageReturnBoundary >= 0 && (boundary < 0 || carriageReturnBoundary < boundary)) {
840
- boundary = carriageReturnBoundary;
841
- boundaryLength = 4;
842
- }
843
- while (boundary >= 0) {
844
- const block = buffered.slice(0, boundary);
845
- buffered = buffered.slice(boundary + boundaryLength);
846
- if (await this.consumeBlock(block.replaceAll("\r\n", "\n"))) {
847
- return true;
848
- }
849
- boundary = buffered.indexOf("\n\n");
850
- boundaryLength = 2;
851
- const nextCarriageReturnBoundary = buffered.indexOf("\r\n\r\n");
852
- if (nextCarriageReturnBoundary >= 0 && (boundary < 0 || nextCarriageReturnBoundary < boundary)) {
853
- boundary = nextCarriageReturnBoundary;
854
- boundaryLength = 4;
855
- }
856
- }
857
- }
858
- return true;
859
- }
860
- catch (cause) {
861
- if (this.#closed || this.#controller.signal.aborted) {
862
- throw cause;
863
- }
864
- if (cause instanceof MobiusError) {
865
- throw cause;
866
- }
867
- return connection.translateFailure(cause);
868
- }
869
- finally {
870
- reader.releaseLock();
871
- }
872
- };
873
- consumeBlock = async (block) => {
874
- if (block.length === 0 || block.startsWith(":")) {
875
- return false;
876
- }
877
- let eventName = "";
878
- let eventId = "";
879
- let data = "";
880
- for (const line of block.split("\n")) {
881
- if (line.startsWith("event:")) {
882
- eventName = line.slice("event:".length).trimStart();
883
- }
884
- else if (line.startsWith("id:")) {
885
- eventId = line.slice("id:".length).trimStart();
886
- }
887
- else if (line.startsWith("data:")) {
888
- data += `${line.slice("data:".length).trimStart()}\n`;
889
- }
890
- else if (!line.startsWith(":")) {
891
- throw new MobiusProtocolError("The Mobius event stream contains an invalid field");
892
- }
893
- }
894
- if (eventName !== MOBIUS_SESSION_EVENT_STREAM_EVENT || data.length === 0) {
895
- throw new MobiusProtocolError("The Mobius event stream contains an unsupported event");
896
- }
897
- let value;
898
- try {
899
- value = JSON.parse(data.slice(0, -1));
900
- }
901
- catch (cause) {
902
- throw new MobiusProtocolError("The Mobius event stream contains invalid JSON", { cause });
903
- }
904
- const event = decodeMobiusSessionEvent(value);
905
- if (event.cursor !== eventId) {
906
- throw new MobiusProtocolError("The Mobius event identifier does not match its cursor");
907
- }
908
- const sequence = BigInt(event.sequence);
909
- if (this.#hasSequence && sequence <= this.#lastSequence) {
910
- return false;
911
- }
912
- if (this.#hasSequence && sequence !== this.#lastSequence + 1n) {
913
- throw new MobiusProtocolError("The Mobius event stream contains a gap");
914
- }
915
- let shouldContinue;
916
- try {
917
- shouldContinue = await this.#observer.receive(event);
918
- }
919
- catch (cause) {
920
- throw new MobiusError("event_observer_failed", "The Mobius event observer failed", { cause });
921
- }
922
- this.#cursor = event.cursor;
923
- this.#hasSequence = true;
924
- this.#lastSequence = sequence;
925
- return !shouldContinue;
926
- };
927
- pathWithCursor = () => {
928
- if (this.#cursor.length === 0) {
929
- return this.#path;
930
- }
931
- return `${this.#path}${this.#path.includes("?") ? "&" : "?"}cursor=${encodeURIComponent(this.#cursor)}`;
932
- };
933
- recover = async (initialCause, completedAttempts) => {
934
- this.throwIfTerminal(initialCause);
935
- let attempts = completedAttempts;
936
- let latestCause = initialCause;
937
- while (attempts < this.#maximumReconnectAttempts) {
938
- attempts += 1;
939
- await this.reconnectDelay(attempts);
940
- try {
941
- return new MobiusEventStreamRecovery(await this.#executor.openEventStream(this.pathWithCursor(), this.#controller.signal), attempts);
942
- }
943
- catch (cause) {
944
- latestCause = cause;
945
- this.throwIfTerminal(cause);
946
- }
947
- }
948
- throw new MobiusConnectionError("The Mobius event stream could not recover", { cause: latestCause });
949
- };
950
- throwIfTerminal = (cause) => {
951
- if (this.#externalSignal.aborted) {
952
- throw new MobiusCancellationError({ cause });
953
- }
954
- if (cause instanceof MobiusProtocolError ||
955
- (cause instanceof MobiusApiError && cause.status < 500) ||
956
- (cause instanceof MobiusError &&
957
- !(cause instanceof MobiusConnectionError) &&
958
- !(cause instanceof MobiusTimeoutError) &&
959
- !(cause instanceof MobiusApiError))) {
960
- throw cause;
961
- }
962
- };
963
- reconnectDelay = async (attempt) => {
964
- const delayMilliseconds = Math.min(250 * 2 ** (attempt - 1), 2_000);
965
- await new Promise((resolve, reject) => {
966
- const timeoutId = globalThis.setTimeout(() => {
967
- this.#controller.signal.removeEventListener("abort", abort);
968
- resolve();
969
- }, delayMilliseconds);
970
- const abort = () => {
971
- globalThis.clearTimeout(timeoutId);
972
- reject(new MobiusCancellationError());
973
- };
974
- this.#controller.signal.addEventListener("abort", abort, { once: true });
975
- });
976
- };
977
- }
978
- class MobiusEventStreamRecovery {
979
- attempts;
980
- connection;
981
- constructor(connection, attempts) {
982
- this.attempts = attempts;
983
- this.connection = connection;
984
- }
985
- }
986
- export class MobiusLiveEventSubscription {
987
- #controller = new AbortController();
988
- #executor;
989
- #maximumReconnectAttempts;
990
- #observer;
991
- #path;
992
- #closed = false;
993
- connected;
994
- completed;
995
- constructor(executor, path, observer, signal, maximumReconnectAttempts) {
996
- this.#executor = executor;
997
- this.#maximumReconnectAttempts = maximumReconnectAttempts;
998
- this.#observer = observer;
999
- this.#path = path;
1000
- const abort = () => this.#controller.abort(signal.reason);
1001
- if (signal.aborted) {
1002
- abort();
1003
- }
1004
- else {
1005
- signal.addEventListener("abort", abort, { once: true });
1006
- }
1007
- let resolveConnected = () => undefined;
1008
- let rejectConnected = (_cause) => undefined;
1009
- this.connected = new Promise((resolve, reject) => {
1010
- resolveConnected = resolve;
1011
- rejectConnected = reject;
1012
- });
1013
- this.completed = this.run(resolveConnected, rejectConnected).finally(() => signal.removeEventListener("abort", abort));
1014
- }
1015
- close = () => {
1016
- if (this.#closed) {
1017
- return;
1018
- }
1019
- this.#closed = true;
1020
- this.#controller.abort(new Error("The live Session event subscription closed"));
1021
- };
1022
- run = async (resolveConnected, rejectConnected) => {
1023
- let attempts = 0;
1024
- let connectionState = { kind: "initial" };
1025
- while (!this.#closed) {
1026
- let connection = null;
1027
- try {
1028
- connection = await this.#executor.openLiveEventStream(this.#path, this.#controller.signal);
1029
- await this.#observer.reconcile(connectionState);
1030
- if (connectionState.kind === "initial") {
1031
- resolveConnected();
1032
- connectionState = { kind: "reconnected" };
1033
- }
1034
- attempts = 0;
1035
- const reader = connection.events.getReader();
1036
- try {
1037
- while (!this.#closed) {
1038
- const result = await reader.read();
1039
- if (result.done) {
1040
- throw new MobiusConnectionError("The live Session event stream disconnected");
1041
- }
1042
- let value;
1043
- try {
1044
- value = JSON.parse(result.value.data);
1045
- }
1046
- catch (cause) {
1047
- throw new MobiusProtocolError("The live Session event stream contains invalid JSON", {
1048
- cause,
1049
- });
1050
- }
1051
- const event = decodeMobiusSessionEvent(value);
1052
- if (event.cursor !== result.value.eventId) {
1053
- throw new MobiusProtocolError("The live Session event identifier is invalid");
1054
- }
1055
- if (!(await this.#observer.receive(event))) {
1056
- this.close();
1057
- }
1058
- }
1059
- }
1060
- finally {
1061
- reader.releaseLock();
1062
- }
1063
- }
1064
- catch (cause) {
1065
- if (this.#closed || this.#controller.signal.aborted) {
1066
- if (connectionState.kind === "initial") {
1067
- rejectConnected(new MobiusCancellationError({ cause }));
1068
- }
1069
- return;
1070
- }
1071
- if (cause instanceof MobiusProtocolError ||
1072
- (cause instanceof MobiusApiError && cause.status < 500) ||
1073
- (cause instanceof MobiusError &&
1074
- !(cause instanceof MobiusConnectionError) &&
1075
- !(cause instanceof MobiusTimeoutError) &&
1076
- !(cause instanceof MobiusApiError))) {
1077
- if (connectionState.kind === "initial") {
1078
- rejectConnected(cause);
1079
- }
1080
- throw cause;
1081
- }
1082
- attempts += 1;
1083
- if (attempts > this.#maximumReconnectAttempts) {
1084
- const failure = new MobiusConnectionError("The live Session event stream could not recover", {
1085
- cause,
1086
- });
1087
- if (connectionState.kind === "initial") {
1088
- rejectConnected(failure);
1089
- }
1090
- throw failure;
1091
- }
1092
- await this.reconnectDelay(attempts);
1093
- }
1094
- finally {
1095
- connection?.close();
1096
- }
1097
- }
1098
- };
1099
- reconnectDelay = async (attempt) => {
1100
- const delayMilliseconds = Math.min(250 * 2 ** (attempt - 1), 2_000);
1101
- await new Promise((resolve, reject) => {
1102
- const timeoutId = globalThis.setTimeout(() => {
1103
- this.#controller.signal.removeEventListener("abort", abort);
1104
- resolve();
1105
- }, delayMilliseconds);
1106
- const abort = () => {
1107
- globalThis.clearTimeout(timeoutId);
1108
- reject(new MobiusCancellationError());
1109
- };
1110
- this.#controller.signal.addEventListener("abort", abort, { once: true });
1111
- });
1112
- };
1113
- }
1114
- export class MobiusEventResources {
1115
- #executor;
1116
- #maximumReconnectAttempts;
1117
- #projectId;
1118
- #subscriptions = new Set();
1119
- #liveSubscriptions = new Set();
1120
- constructor(executor, projectId, maximumReconnectAttempts) {
1121
- this.#executor = executor;
1122
- this.#maximumReconnectAttempts = maximumReconnectAttempts;
1123
- this.#projectId = projectId;
1124
- }
1125
- subscribe = async (sessionId, cursor, observer, signal) => {
1126
- if (cursor.length > 512) {
1127
- throw new MobiusValidationError("The Session event cursor is invalid");
1128
- }
1129
- const path = sessionPath(this.#projectId, sessionId, "events");
1130
- const streamPath = cursor.length === 0 ? path : `${path}?cursor=${encodeURIComponent(cursor)}`;
1131
- const controller = new AbortController();
1132
- const abortFromExternal = () => controller.abort(signal.reason);
1133
- if (signal.aborted) {
1134
- abortFromExternal();
1135
- }
1136
- else {
1137
- signal.addEventListener("abort", abortFromExternal, { once: true });
1138
- }
1139
- let firstConnection;
1140
- try {
1141
- firstConnection = await this.#executor.openEventStream(streamPath, controller.signal);
1142
- }
1143
- finally {
1144
- signal.removeEventListener("abort", abortFromExternal);
1145
- }
1146
- const subscription = new MobiusEventSubscription(this.#executor, path, cursor, observer, signal, controller, firstConnection, this.#maximumReconnectAttempts);
1147
- this.#subscriptions.add(subscription);
1148
- const remove = subscription.completed.finally(() => {
1149
- this.#subscriptions.delete(subscription);
1150
- });
1151
- void remove.catch(() => { });
1152
- return subscription;
1153
- };
1154
- subscribeLive = async (sessionId, observer, signal) => {
1155
- const subscription = new MobiusLiveEventSubscription(this.#executor, sessionPath(this.#projectId, sessionId, "events"), observer, signal, this.#maximumReconnectAttempts);
1156
- this.#liveSubscriptions.add(subscription);
1157
- void subscription.completed.finally(() => this.#liveSubscriptions.delete(subscription)).catch(() => undefined);
1158
- await subscription.connected;
1159
- return subscription;
1160
- };
1161
- list = async (sessionId, page, signal) => {
1162
- const path = requestUrl(new URL("https://mobius.invalid/"), sessionPath(this.#projectId, sessionId, "events"));
1163
- path.searchParams.set("limit", page.limit.toString());
1164
- if (page.cursor.length > 0) {
1165
- path.searchParams.set("cursor", page.cursor);
1166
- }
1167
- return decodeMobiusEventPage(await this.#executor.get(`${path.pathname.slice(1)}${path.search}`, signal));
1168
- };
1169
- wait = async (sessionId, page, waitMilliseconds, signal) => {
1170
- if (!Number.isSafeInteger(waitMilliseconds) || waitMilliseconds < 1 || waitMilliseconds > 10_000) {
1171
- throw new MobiusValidationError("The Session event wait is invalid");
1172
- }
1173
- const path = requestUrl(new URL("https://mobius.invalid/"), sessionPath(this.#projectId, sessionId, "events"));
1174
- path.searchParams.set("limit", page.limit.toString());
1175
- path.searchParams.set("wait", waitMilliseconds.toString());
1176
- if (page.cursor.length > 0) {
1177
- path.searchParams.set("cursor", page.cursor);
1178
- }
1179
- return decodeMobiusEventPage(await this.#executor.get(`${path.pathname.slice(1)}${path.search}`, signal));
1180
- };
1181
- close = () => {
1182
- for (const subscription of this.#subscriptions) {
1183
- subscription.close();
1184
- }
1185
- this.#subscriptions.clear();
1186
- for (const subscription of this.#liveSubscriptions) {
1187
- subscription.close();
1188
- }
1189
- this.#liveSubscriptions.clear();
1190
- };
1191
- }
1192
340
  export class MobiusClient {
1193
- attempts;
1194
- capabilities;
1195
341
  credentials;
1196
- events;
1197
- interactions;
1198
342
  runtimePairings;
1199
343
  runtimes;
1200
- sessions;
1201
- turns;
1202
- toolCalls;
1203
344
  constructor(options) {
1204
345
  const executor = new MobiusRequestExecutor(options);
1205
- const reconnectAttempts = validatePositiveInteger(options.maxStreamReconnectAttempts ?? DEFAULT_STREAM_RECONNECT_ATTEMPTS, "stream reconnect attempt limit");
1206
- this.attempts = new MobiusAttemptResources(executor, options.projectId);
1207
- this.capabilities = new MobiusCapabilityResources(executor);
1208
346
  this.credentials = new MobiusCredentialResources(executor, options.projectId);
1209
- this.events = new MobiusEventResources(executor, options.projectId, reconnectAttempts);
1210
- this.interactions = new MobiusInteractionResources(executor, options.projectId);
1211
347
  this.runtimePairings = new MobiusRuntimePairingResources(executor, options.projectId);
1212
348
  this.runtimes = new MobiusRuntimeResources(executor, options.projectId);
1213
- this.sessions = new MobiusSessionResources(executor, options.projectId);
1214
- this.turns = new MobiusTurnResources(executor, options.projectId);
1215
- this.toolCalls = new MobiusToolCallResources(executor, options.projectId);
1216
349
  }
1217
- close = () => {
1218
- this.events.close();
1219
- };
1220
350
  }
1221
351
  //# sourceMappingURL=service-client.js.map