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