@opengeni/sdk 0.13.0 → 0.20.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/README.md +66 -17
- package/dist/index.d.ts +610 -85
- package/dist/index.js +730 -221
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +384 -71
- package/src/errors.ts +13 -0
- package/src/index.ts +77 -2
- package/src/stream.ts +3 -0
- package/src/transcription.ts +496 -0
- package/src/types.ts +465 -60
- package/src/workspace-control-stream.ts +101 -0
package/dist/index.js
CHANGED
|
@@ -9,6 +9,16 @@ var OpenGeniApiError = class extends Error {
|
|
|
9
9
|
this.body = body;
|
|
10
10
|
}
|
|
11
11
|
};
|
|
12
|
+
var OpenGeniApiContractMismatchError = class extends Error {
|
|
13
|
+
expected;
|
|
14
|
+
actual;
|
|
15
|
+
constructor(expected, actual) {
|
|
16
|
+
super(`OpenGeni API contract mismatch: client expects ${expected}, API serves ${actual}`);
|
|
17
|
+
this.name = "OpenGeniApiContractMismatchError";
|
|
18
|
+
this.expected = expected;
|
|
19
|
+
this.actual = actual;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
12
22
|
var OpenGeniStreamError = class extends Error {
|
|
13
23
|
constructor(message) {
|
|
14
24
|
super(message);
|
|
@@ -114,6 +124,7 @@ async function* streamSessionEvents(transport, options = {}) {
|
|
|
114
124
|
everConnected = true;
|
|
115
125
|
failedAttempts = 0;
|
|
116
126
|
delayMs = baseDelayMs;
|
|
127
|
+
await options.beforeLive?.();
|
|
117
128
|
options.onStateChange?.("live");
|
|
118
129
|
for await (const message of parseSseStream(body)) {
|
|
119
130
|
if (signal?.aborted) {
|
|
@@ -208,6 +219,251 @@ async function sleep(delayMs, signal) {
|
|
|
208
219
|
});
|
|
209
220
|
}
|
|
210
221
|
|
|
222
|
+
// src/workspace-control-stream.ts
|
|
223
|
+
async function* streamWorkspaceControlEvents(transport, options = {}) {
|
|
224
|
+
const signal = options.signal;
|
|
225
|
+
const reconnect = options.reconnect ?? true;
|
|
226
|
+
const baseDelayMs = options.reconnectDelayMs ?? 500;
|
|
227
|
+
const maxDelayMs = options.maxReconnectDelayMs ?? 1e4;
|
|
228
|
+
const maxAttempts = options.maxReconnectAttempts ?? Number.POSITIVE_INFINITY;
|
|
229
|
+
let cursor = options.after ?? 0;
|
|
230
|
+
let failures = 0;
|
|
231
|
+
let delayMs = baseDelayMs;
|
|
232
|
+
let everConnected = false;
|
|
233
|
+
for (; ; ) {
|
|
234
|
+
if (signal?.aborted) return;
|
|
235
|
+
options.onStateChange?.(everConnected || failures > 0 ? "reconnecting" : "connecting");
|
|
236
|
+
const cursorAtOpen = cursor;
|
|
237
|
+
try {
|
|
238
|
+
const body = await transport.openStream(cursor, signal);
|
|
239
|
+
everConnected = true;
|
|
240
|
+
failures = 0;
|
|
241
|
+
delayMs = baseDelayMs;
|
|
242
|
+
await options.beforeLive?.();
|
|
243
|
+
options.onStateChange?.("live");
|
|
244
|
+
for await (const message of parseSseStream(body)) {
|
|
245
|
+
if (signal?.aborted) return;
|
|
246
|
+
const event = parseWorkspaceControlEvent(message.data);
|
|
247
|
+
if (!event || event.sequence <= cursor) continue;
|
|
248
|
+
cursor = event.sequence;
|
|
249
|
+
yield event;
|
|
250
|
+
}
|
|
251
|
+
if (!reconnect) return;
|
|
252
|
+
if (cursor === cursorAtOpen) await sleep2(baseDelayMs, signal);
|
|
253
|
+
continue;
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (signal?.aborted || isAbortError(error)) return;
|
|
256
|
+
if (!reconnect || !isRetryableStreamError(error)) throw error;
|
|
257
|
+
failures += 1;
|
|
258
|
+
if (failures > maxAttempts) {
|
|
259
|
+
throw new OpenGeniStreamError(
|
|
260
|
+
`workspace control stream gave up after ${maxAttempts} reconnect attempts: ${error instanceof Error ? error.message : String(error)}`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
await sleep2(delayMs, signal);
|
|
265
|
+
delayMs = Math.min(Math.max(delayMs * 2, baseDelayMs), maxDelayMs);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function parseWorkspaceControlEvent(data) {
|
|
269
|
+
let value;
|
|
270
|
+
try {
|
|
271
|
+
value = JSON.parse(data);
|
|
272
|
+
} catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
if (typeof value !== "object" || value === null || value.type !== "workspace.control.changed" || typeof value.id !== "string" || typeof value.sequence !== "number") {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return value;
|
|
279
|
+
}
|
|
280
|
+
async function sleep2(delayMs, signal) {
|
|
281
|
+
if (signal?.aborted || delayMs <= 0) return;
|
|
282
|
+
await new Promise((resolve) => {
|
|
283
|
+
const timer = setTimeout(done, delayMs);
|
|
284
|
+
function done() {
|
|
285
|
+
clearTimeout(timer);
|
|
286
|
+
signal?.removeEventListener("abort", done);
|
|
287
|
+
resolve();
|
|
288
|
+
}
|
|
289
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/types.ts
|
|
294
|
+
var SESSION_EVENT_TYPES = [
|
|
295
|
+
"session.created",
|
|
296
|
+
// Defensive bounded projection for malformed/legacy oversized envelopes.
|
|
297
|
+
"session.event.envelope_omitted",
|
|
298
|
+
"session.status.changed",
|
|
299
|
+
"session.requiresAction",
|
|
300
|
+
"session.humanInput.requested",
|
|
301
|
+
"session.context.compaction.requested",
|
|
302
|
+
"session.context.compacted",
|
|
303
|
+
"session.context.compaction.skipped",
|
|
304
|
+
"session.context.cleared",
|
|
305
|
+
"user.message",
|
|
306
|
+
"user.pause",
|
|
307
|
+
"user.approvalDecision",
|
|
308
|
+
"user.humanInputResponse",
|
|
309
|
+
"turn.queued",
|
|
310
|
+
"turn.started",
|
|
311
|
+
"turn.completed",
|
|
312
|
+
"turn.failed",
|
|
313
|
+
"turn.cancelled",
|
|
314
|
+
"turn.superseded",
|
|
315
|
+
"turn.recovery.requested",
|
|
316
|
+
"turn.capacity_waiting",
|
|
317
|
+
"agent.message.delta",
|
|
318
|
+
"agent.message.completed",
|
|
319
|
+
"agent.reasoning.delta",
|
|
320
|
+
"agent.toolCall.created",
|
|
321
|
+
"agent.toolCall.output",
|
|
322
|
+
"agent.model.usage",
|
|
323
|
+
"tool.auth_needed",
|
|
324
|
+
"credential.auth_needed",
|
|
325
|
+
"agent.updated",
|
|
326
|
+
"rig.setup.started",
|
|
327
|
+
"rig.setup.completed",
|
|
328
|
+
"rig.setup.skipped",
|
|
329
|
+
"rig.setup.failed",
|
|
330
|
+
"sandbox.operation.started",
|
|
331
|
+
"sandbox.operation.completed",
|
|
332
|
+
"sandbox.operation.failed",
|
|
333
|
+
"sandbox.command.output.delta",
|
|
334
|
+
"artifact.created",
|
|
335
|
+
"goal.set",
|
|
336
|
+
"goal.updated",
|
|
337
|
+
"goal.completed",
|
|
338
|
+
"goal.paused",
|
|
339
|
+
"goal.resumed",
|
|
340
|
+
"goal.cleared",
|
|
341
|
+
"goal.continuation",
|
|
342
|
+
"system.update.pending",
|
|
343
|
+
"system.update.delivered",
|
|
344
|
+
"session.control.paused",
|
|
345
|
+
"session.control.resumed",
|
|
346
|
+
"session.control.steer_requested",
|
|
347
|
+
"workspace.inference.paused",
|
|
348
|
+
"workspace.inference.resumed",
|
|
349
|
+
"session.queue.changed",
|
|
350
|
+
"session.queue.prompt.cancelled",
|
|
351
|
+
"session.queue.history",
|
|
352
|
+
"turn.event.rejected_late",
|
|
353
|
+
"memory.saved",
|
|
354
|
+
"memory.corrected",
|
|
355
|
+
// Channel-B desktop pixel-plane signals (mirror of contracts SessionEventType;
|
|
356
|
+
// the contract-parity test asserts sorted equality).
|
|
357
|
+
"stream.url.rotated",
|
|
358
|
+
"stream.opened",
|
|
359
|
+
"stream.closed",
|
|
360
|
+
"stream.revoked",
|
|
361
|
+
// Channel-B recording signals (P4.3 — "agent films itself proving the fix").
|
|
362
|
+
"recording.started",
|
|
363
|
+
"recording.available",
|
|
364
|
+
"recording.failed",
|
|
365
|
+
// Channel-A structured-service notifications (P4.4; mirror of contracts
|
|
366
|
+
// SessionEventType — the contract-parity test asserts sorted equality).
|
|
367
|
+
"fs.changed",
|
|
368
|
+
"git.changed",
|
|
369
|
+
"terminal.pty.started",
|
|
370
|
+
"terminal.pty.output.delta",
|
|
371
|
+
"terminal.pty.exited",
|
|
372
|
+
"session.title_set",
|
|
373
|
+
// Multi-account Codex (P1): the session's inference account changed.
|
|
374
|
+
"codex.account.switched",
|
|
375
|
+
// credential allocator metadata-only per-turn credential selection audit.
|
|
376
|
+
"codex.credential.selected",
|
|
377
|
+
// credential allocator durable zero-capacity wait lifecycle. These are system/runtime
|
|
378
|
+
// events, never synthetic user messages.
|
|
379
|
+
"codex.capacity.waiting",
|
|
380
|
+
"codex.capacity.resumed",
|
|
381
|
+
"codex.capacity.superseded",
|
|
382
|
+
// Sandbox durability observability (mirror of contracts SessionEventType):
|
|
383
|
+
// box lifecycle + manifest-env drift, attributable from the DB alone.
|
|
384
|
+
"sandbox.box.created",
|
|
385
|
+
"sandbox.box.lost",
|
|
386
|
+
"sandbox.box.terminated",
|
|
387
|
+
"sandbox.box.snapshot",
|
|
388
|
+
"sandbox.env.drift",
|
|
389
|
+
// Active-sandbox pointer reconcile (issue #341; announce-only; mirror of contracts
|
|
390
|
+
// SessionEventType — the contract-parity test asserts sorted equality).
|
|
391
|
+
"session.route.reconciled",
|
|
392
|
+
// Workbench v2 turn-end workspace capture (announce-only; mirror of contracts
|
|
393
|
+
// SessionEventType — the contract-parity test asserts sorted equality).
|
|
394
|
+
"workspace.revision.captured",
|
|
395
|
+
"workspace.revision.degraded",
|
|
396
|
+
// Connected Machine op-outcome observability (announce-only, quiet; mirror of
|
|
397
|
+
// contracts SessionEventType — the contract-parity test asserts sorted equality).
|
|
398
|
+
"machine.op.failed",
|
|
399
|
+
"machine.op.recovered",
|
|
400
|
+
// Connected Machine link-plane observability (announce-only, quiet; mirror of
|
|
401
|
+
// contracts SessionEventType — the contract-parity test asserts sorted equality).
|
|
402
|
+
"machine.link.lost",
|
|
403
|
+
"machine.link.restored",
|
|
404
|
+
"machine.runner.restarted"
|
|
405
|
+
];
|
|
406
|
+
var KNOWN_PERMISSIONS = [
|
|
407
|
+
"account:read",
|
|
408
|
+
"account:admin",
|
|
409
|
+
"members:manage",
|
|
410
|
+
"workspace:create",
|
|
411
|
+
"billing:read",
|
|
412
|
+
"billing:manage",
|
|
413
|
+
"workspace:read",
|
|
414
|
+
"workspace:admin",
|
|
415
|
+
"sessions:create",
|
|
416
|
+
"sessions:read",
|
|
417
|
+
"sessions:control",
|
|
418
|
+
// sandbox workspace (mirror of @opengeni/contracts Permission). stream:view is
|
|
419
|
+
// strictly broader than sessions:read (un-redacted pixels); stream:control is
|
|
420
|
+
// the never-granted-v1 raw-input plane; stream:acknowledge is the secret-leak
|
|
421
|
+
// consent gate.
|
|
422
|
+
"stream:view",
|
|
423
|
+
"stream:control",
|
|
424
|
+
"stream:acknowledge",
|
|
425
|
+
"files:upload",
|
|
426
|
+
"files:read",
|
|
427
|
+
"files:write",
|
|
428
|
+
"terminal:attach",
|
|
429
|
+
"documents:manage",
|
|
430
|
+
"documents:search",
|
|
431
|
+
"scheduled_tasks:manage",
|
|
432
|
+
"scheduled_tasks:run",
|
|
433
|
+
"github:manage",
|
|
434
|
+
"github:use",
|
|
435
|
+
"api_keys:manage",
|
|
436
|
+
"connections:read",
|
|
437
|
+
"connections:write",
|
|
438
|
+
"environments:manage",
|
|
439
|
+
"environments:use",
|
|
440
|
+
"variable-sets:manage",
|
|
441
|
+
"variable-sets:use",
|
|
442
|
+
"mcp_servers:attach",
|
|
443
|
+
"toolspace:call",
|
|
444
|
+
"goals:manage",
|
|
445
|
+
"enrollments:read",
|
|
446
|
+
"enrollments:manage",
|
|
447
|
+
"rigs:use",
|
|
448
|
+
"rigs:manage"
|
|
449
|
+
];
|
|
450
|
+
var OPENGENI_API_CONTRACT_REVISION = "2026-07-turn-instructions-v1";
|
|
451
|
+
var OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract";
|
|
452
|
+
var KNOWN_USAGE_EVENT_TYPES = [
|
|
453
|
+
"agent_run.created",
|
|
454
|
+
"agent_run.completed",
|
|
455
|
+
"model.tokens",
|
|
456
|
+
"model.cost",
|
|
457
|
+
"file.uploaded",
|
|
458
|
+
"file.deleted",
|
|
459
|
+
"document.indexed",
|
|
460
|
+
"scheduled_task.fired",
|
|
461
|
+
"api_key.request",
|
|
462
|
+
// sandbox warm-time metering (P2.1) — mirrors contracts UsageEventType.
|
|
463
|
+
"sandbox.warm_seconds",
|
|
464
|
+
"sandbox.warm_cost"
|
|
465
|
+
];
|
|
466
|
+
|
|
211
467
|
// src/client.ts
|
|
212
468
|
var OpenGeniClient = class {
|
|
213
469
|
baseUrl;
|
|
@@ -255,7 +511,7 @@ var OpenGeniClient = class {
|
|
|
255
511
|
}
|
|
256
512
|
/** Pin-aware ordinary-session page with a stable keyset cursor. */
|
|
257
513
|
async listSessionPage(workspaceId, options = {}) {
|
|
258
|
-
|
|
514
|
+
return await this.requestJson(
|
|
259
515
|
"GET",
|
|
260
516
|
`/v1/workspaces/${workspaceId}/sessions`,
|
|
261
517
|
void 0,
|
|
@@ -269,16 +525,6 @@ var OpenGeniClient = class {
|
|
|
269
525
|
} : {}
|
|
270
526
|
}
|
|
271
527
|
);
|
|
272
|
-
if (Array.isArray(response)) {
|
|
273
|
-
if (options.cursor) {
|
|
274
|
-
throw new Error("The connected OpenGeni API does not support stable session-page cursors");
|
|
275
|
-
}
|
|
276
|
-
if (options.search?.trim()) {
|
|
277
|
-
throw new Error("The connected OpenGeni API does not support session search");
|
|
278
|
-
}
|
|
279
|
-
return { pinned: [], sessions: response, nextCursor: null };
|
|
280
|
-
}
|
|
281
|
-
return response;
|
|
282
528
|
}
|
|
283
529
|
/** Set this authenticated member's personal workspace pin for a session. */
|
|
284
530
|
async updateSessionPin(workspaceId, sessionId, request) {
|
|
@@ -318,7 +564,8 @@ var OpenGeniClient = class {
|
|
|
318
564
|
void 0,
|
|
319
565
|
{
|
|
320
566
|
...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {}
|
|
321
|
-
}
|
|
567
|
+
},
|
|
568
|
+
{ signal: options.signal }
|
|
322
569
|
);
|
|
323
570
|
}
|
|
324
571
|
/**
|
|
@@ -418,23 +665,75 @@ var OpenGeniClient = class {
|
|
|
418
665
|
}
|
|
419
666
|
// --- Events: replay, send, stream ----------------------------------------
|
|
420
667
|
/**
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
668
|
+
* Return the events from one bounded page. With no cursor, this uses the safe
|
|
669
|
+
* semantic monitoring tail; pass explicit forensic options and a cursor for
|
|
670
|
+
* retained audit replay. Use `listEventPage` when projection, coverage, or
|
|
671
|
+
* resume-cursor facts are required.
|
|
425
672
|
*/
|
|
426
673
|
async listEvents(workspaceId, sessionId, options = {}) {
|
|
427
|
-
return await this.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
674
|
+
return (await this.listEventPage(workspaceId, sessionId, options)).events;
|
|
675
|
+
}
|
|
676
|
+
/** Bounded durable/monitoring page plus exact projection and cursor facts. */
|
|
677
|
+
async listEventPage(workspaceId, sessionId, options = {}) {
|
|
678
|
+
if (options.latest && ["includeTypes", "excludeTypes", "includeClasses", "excludeClasses"].some(
|
|
679
|
+
(name) => Object.prototype.hasOwnProperty.call(options, name)
|
|
680
|
+
)) {
|
|
681
|
+
throw new TypeError("latest cannot be combined with event filters");
|
|
682
|
+
}
|
|
683
|
+
const response = await this.fetchImpl(
|
|
684
|
+
this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, {
|
|
432
685
|
...options.after !== void 0 ? { after: String(options.after) } : {},
|
|
433
686
|
...options.before !== void 0 ? { before: String(options.before) } : {},
|
|
434
687
|
...options.limit !== void 0 ? { limit: String(options.limit) } : {},
|
|
435
|
-
...options.compact ? { compact: "1" } : {}
|
|
688
|
+
...options.compact ? { compact: "1" } : {},
|
|
689
|
+
...options.mode ? { mode: options.mode } : {},
|
|
690
|
+
...options.direction ? { direction: options.direction } : {},
|
|
691
|
+
...options.payloadMode ? { payloadMode: options.payloadMode } : {},
|
|
692
|
+
...options.includeTypes?.length ? { includeTypes: options.includeTypes.join(",") } : {},
|
|
693
|
+
...options.excludeTypes?.length ? { excludeTypes: options.excludeTypes.join(",") } : {},
|
|
694
|
+
...options.includeClasses?.length ? { includeClasses: options.includeClasses.join(",") } : {},
|
|
695
|
+
...options.excludeClasses?.length ? { excludeClasses: options.excludeClasses.join(",") } : {},
|
|
696
|
+
...options.latest ? { latest: options.latest } : {}
|
|
697
|
+
}),
|
|
698
|
+
{
|
|
699
|
+
method: "GET",
|
|
700
|
+
headers: { ...this.headers(), Accept: "application/json" }
|
|
436
701
|
}
|
|
437
702
|
);
|
|
703
|
+
assertApiContractResponse(response);
|
|
704
|
+
if (!response.ok) throw new OpenGeniApiError(response.status, await safeText(response));
|
|
705
|
+
const events = await response.json();
|
|
706
|
+
const integerHeader = (name) => {
|
|
707
|
+
const raw = response.headers.get(name);
|
|
708
|
+
if (raw === null) return null;
|
|
709
|
+
const value = Number(raw);
|
|
710
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
711
|
+
};
|
|
712
|
+
const mode = response.headers.get("X-OpenGeni-Event-Mode") === "forensic" ? "forensic" : "monitoring";
|
|
713
|
+
const direction = response.headers.get("X-OpenGeni-Event-Direction") === "after" ? "after" : "before";
|
|
714
|
+
const payloadHeader = response.headers.get("X-OpenGeni-Payload-Mode");
|
|
715
|
+
const payloadMode = payloadHeader === "none" || payloadHeader === "full" ? payloadHeader : "summary";
|
|
716
|
+
const first = integerHeader("X-OpenGeni-Covered-First");
|
|
717
|
+
const last = integerHeader("X-OpenGeni-Covered-Last");
|
|
718
|
+
const bytes = integerHeader("X-OpenGeni-Page-Bytes") ?? new TextEncoder().encode(JSON.stringify(events)).byteLength;
|
|
719
|
+
const maxBytes = integerHeader("X-OpenGeni-Page-Max-Bytes") ?? 1024 * 1024;
|
|
720
|
+
const truncatedByHeader = response.headers.get("X-OpenGeni-Truncated-By");
|
|
721
|
+
const truncatedBy = truncatedByHeader === "count" || truncatedByHeader === "bytes" || truncatedByHeader === "http_bytes" ? truncatedByHeader : null;
|
|
722
|
+
return {
|
|
723
|
+
events,
|
|
724
|
+
mode,
|
|
725
|
+
payloadMode,
|
|
726
|
+
direction,
|
|
727
|
+
bytes,
|
|
728
|
+
maxBytes,
|
|
729
|
+
truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
|
|
730
|
+
hasMore: response.headers.get("X-OpenGeni-Has-More") === "true",
|
|
731
|
+
truncatedBy,
|
|
732
|
+
coveredSequence: first === null || last === null ? null : { first, last },
|
|
733
|
+
nextAfter: integerHeader("X-OpenGeni-Next-After"),
|
|
734
|
+
nextBefore: integerHeader("X-OpenGeni-Next-Before"),
|
|
735
|
+
forensicExact: response.headers.get("X-OpenGeni-Forensic-Exact") === "true"
|
|
736
|
+
};
|
|
438
737
|
}
|
|
439
738
|
/** POST a user/control event to the session. Returns the accepted event. */
|
|
440
739
|
async sendEvent(workspaceId, sessionId, event) {
|
|
@@ -454,10 +753,12 @@ var OpenGeniClient = class {
|
|
|
454
753
|
});
|
|
455
754
|
}
|
|
456
755
|
async pauseSession(workspaceId, sessionId, options = {}) {
|
|
457
|
-
return
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
756
|
+
return await this.controlSession(workspaceId, sessionId, {
|
|
757
|
+
action: "pause",
|
|
758
|
+
clientEventId: options.clientEventId ?? crypto.randomUUID(),
|
|
759
|
+
...options.reason ? { reason: options.reason } : {},
|
|
760
|
+
...options.expectedControlEtag ? { expectedControlEtag: options.expectedControlEtag } : {}
|
|
761
|
+
});
|
|
461
762
|
}
|
|
462
763
|
async sendApprovalDecision(workspaceId, sessionId, decision) {
|
|
463
764
|
const { clientEventId, ...payload } = decision;
|
|
@@ -467,6 +768,28 @@ var OpenGeniClient = class {
|
|
|
467
768
|
payload
|
|
468
769
|
});
|
|
469
770
|
}
|
|
771
|
+
async listHumanInputRequests(workspaceId, sessionId, options = {}) {
|
|
772
|
+
const result = await this.requestJson(
|
|
773
|
+
"GET",
|
|
774
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests`,
|
|
775
|
+
void 0,
|
|
776
|
+
options.status ? { status: options.status } : void 0
|
|
777
|
+
);
|
|
778
|
+
return result.requests;
|
|
779
|
+
}
|
|
780
|
+
async getHumanInputRequest(workspaceId, sessionId, requestId) {
|
|
781
|
+
return await this.requestJson(
|
|
782
|
+
"GET",
|
|
783
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests/${requestId}`
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
async submitHumanInputResponse(workspaceId, sessionId, requestId, response, options = {}) {
|
|
787
|
+
return await this.sendEvent(workspaceId, sessionId, {
|
|
788
|
+
type: "user.humanInputResponse",
|
|
789
|
+
...options.clientEventId ? { clientEventId: options.clientEventId } : {},
|
|
790
|
+
payload: { requestId, response }
|
|
791
|
+
});
|
|
792
|
+
}
|
|
470
793
|
/**
|
|
471
794
|
* Live-stream a session's events with automatic reconnect, resume from the
|
|
472
795
|
* last seen sequence, gap backfill, and duplicate suppression. See
|
|
@@ -495,6 +818,7 @@ var OpenGeniClient = class {
|
|
|
495
818
|
headers: { ...this.headers(), Accept: "text/event-stream" },
|
|
496
819
|
...options.signal ? { signal: options.signal } : {}
|
|
497
820
|
});
|
|
821
|
+
assertApiContractResponse(response);
|
|
498
822
|
if (!response.ok) {
|
|
499
823
|
throw new OpenGeniApiError(response.status, await safeText(response));
|
|
500
824
|
}
|
|
@@ -510,10 +834,44 @@ var OpenGeniClient = class {
|
|
|
510
834
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue`
|
|
511
835
|
);
|
|
512
836
|
}
|
|
513
|
-
async
|
|
837
|
+
async moveQueueItem(workspaceId, sessionId, turnId, request) {
|
|
838
|
+
return await this.requestJson(
|
|
839
|
+
"POST",
|
|
840
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/move`,
|
|
841
|
+
request
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
async editQueueItem(workspaceId, sessionId, turnId, request) {
|
|
845
|
+
return await this.requestJson(
|
|
846
|
+
"POST",
|
|
847
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/edit`,
|
|
848
|
+
request
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
async steerQueueItem(workspaceId, sessionId, turnId, request) {
|
|
514
852
|
return await this.requestJson(
|
|
515
853
|
"POST",
|
|
516
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/
|
|
854
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/steer`,
|
|
855
|
+
request
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
async deleteQueueItem(workspaceId, sessionId, turnId, request) {
|
|
859
|
+
return await this.requestJson(
|
|
860
|
+
"POST",
|
|
861
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/delete`,
|
|
862
|
+
request
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
async getComposerDraft(workspaceId, sessionId) {
|
|
866
|
+
return await this.requestJson(
|
|
867
|
+
"GET",
|
|
868
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
async saveComposerDraft(workspaceId, sessionId, request) {
|
|
872
|
+
return await this.requestJson(
|
|
873
|
+
"PUT",
|
|
874
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`,
|
|
517
875
|
request
|
|
518
876
|
);
|
|
519
877
|
}
|
|
@@ -525,7 +883,12 @@ var OpenGeniClient = class {
|
|
|
525
883
|
);
|
|
526
884
|
}
|
|
527
885
|
async resumeSession(workspaceId, sessionId, options = {}) {
|
|
528
|
-
return await this.controlSession(workspaceId, sessionId, {
|
|
886
|
+
return await this.controlSession(workspaceId, sessionId, {
|
|
887
|
+
action: "resume",
|
|
888
|
+
clientEventId: options.clientEventId ?? crypto.randomUUID(),
|
|
889
|
+
...options.reason ? { reason: options.reason } : {},
|
|
890
|
+
...options.expectedControlEtag ? { expectedControlEtag: options.expectedControlEtag } : {}
|
|
891
|
+
});
|
|
529
892
|
}
|
|
530
893
|
async setWorkspaceInferenceState(workspaceId, request) {
|
|
531
894
|
return await this.requestJson(
|
|
@@ -534,12 +897,65 @@ var OpenGeniClient = class {
|
|
|
534
897
|
request
|
|
535
898
|
);
|
|
536
899
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
900
|
+
async listWorkspaceControlEvents(workspaceId, options = {}) {
|
|
901
|
+
return (await this.listWorkspaceControlEventPage(workspaceId, options)).events;
|
|
902
|
+
}
|
|
903
|
+
/** Count/byte-bounded page plus an explicit continuation cursor. */
|
|
904
|
+
async listWorkspaceControlEventPage(workspaceId, options = {}) {
|
|
905
|
+
const response = await this.fetchImpl(
|
|
906
|
+
this.url(`/v1/workspaces/${workspaceId}/control-events`, {
|
|
907
|
+
...options.after !== void 0 ? { after: String(options.after) } : {},
|
|
908
|
+
...options.limit !== void 0 ? { limit: String(options.limit) } : {}
|
|
909
|
+
}),
|
|
910
|
+
{
|
|
911
|
+
method: "GET",
|
|
912
|
+
headers: { ...this.headers(), Accept: "application/json" }
|
|
913
|
+
}
|
|
542
914
|
);
|
|
915
|
+
assertApiContractResponse(response);
|
|
916
|
+
if (!response.ok) {
|
|
917
|
+
throw new OpenGeniApiError(response.status, await safeText(response));
|
|
918
|
+
}
|
|
919
|
+
const events = await response.json();
|
|
920
|
+
const bytesHeader = response.headers.get("X-OpenGeni-Page-Bytes");
|
|
921
|
+
const nextHeader = response.headers.get("X-OpenGeni-Next-After");
|
|
922
|
+
const parsedBytes = bytesHeader === null ? Number.NaN : Number(bytesHeader);
|
|
923
|
+
const parsedNext = nextHeader === null ? null : Number(nextHeader);
|
|
924
|
+
return {
|
|
925
|
+
events,
|
|
926
|
+
bytes: Number.isSafeInteger(parsedBytes) && parsedBytes >= 0 ? parsedBytes : new TextEncoder().encode(JSON.stringify(events)).byteLength,
|
|
927
|
+
truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
|
|
928
|
+
nextAfter: parsedNext !== null && Number.isSafeInteger(parsedNext) && parsedNext >= 0 ? parsedNext : null
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
streamWorkspaceControlEvents(workspaceId, options = {}) {
|
|
932
|
+
return streamWorkspaceControlEvents(this.workspaceControlStreamTransport(workspaceId), options);
|
|
933
|
+
}
|
|
934
|
+
workspaceControlStreamTransport(workspaceId) {
|
|
935
|
+
return {
|
|
936
|
+
openStream: async (after, signal) => await this.openWorkspaceControlEventStream(workspaceId, {
|
|
937
|
+
after,
|
|
938
|
+
...signal ? { signal } : {}
|
|
939
|
+
})
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
async openWorkspaceControlEventStream(workspaceId, options = {}) {
|
|
943
|
+
const response = await this.fetchImpl(
|
|
944
|
+
this.url(`/v1/workspaces/${workspaceId}/control-events/stream`, {
|
|
945
|
+
after: String(options.after ?? 0)
|
|
946
|
+
}),
|
|
947
|
+
{
|
|
948
|
+
method: "GET",
|
|
949
|
+
headers: { ...this.headers(), Accept: "text/event-stream" },
|
|
950
|
+
...options.signal ? { signal: options.signal } : {}
|
|
951
|
+
}
|
|
952
|
+
);
|
|
953
|
+
assertApiContractResponse(response);
|
|
954
|
+
if (!response.ok) throw new OpenGeniApiError(response.status, await safeText(response));
|
|
955
|
+
if (!response.body) {
|
|
956
|
+
throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
|
|
957
|
+
}
|
|
958
|
+
return response.body;
|
|
543
959
|
}
|
|
544
960
|
/**
|
|
545
961
|
* Steer: atomically put this prompt at the head and supersede the current
|
|
@@ -610,19 +1026,23 @@ var OpenGeniClient = class {
|
|
|
610
1026
|
// synchronous API-direct point query; the fs.changed/git.changed/terminal.pty.*
|
|
611
1027
|
// notifications + the PTY output stream arrive on the existing event SSE.
|
|
612
1028
|
/** FileSystem: list a directory tree (feeds the Pierre file tree). */
|
|
613
|
-
async fsList(workspaceId, sessionId, request = {}) {
|
|
1029
|
+
async fsList(workspaceId, sessionId, request = {}, options = {}) {
|
|
614
1030
|
return await this.requestJson(
|
|
615
1031
|
"POST",
|
|
616
1032
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list`,
|
|
617
|
-
request
|
|
1033
|
+
request,
|
|
1034
|
+
{},
|
|
1035
|
+
options
|
|
618
1036
|
);
|
|
619
1037
|
}
|
|
620
1038
|
/** FileSystem: read a file (text or base64; binary-safe, size-capped). */
|
|
621
|
-
async fsRead(workspaceId, sessionId, request) {
|
|
1039
|
+
async fsRead(workspaceId, sessionId, request, options = {}) {
|
|
622
1040
|
return await this.requestJson(
|
|
623
1041
|
"POST",
|
|
624
1042
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/read`,
|
|
625
|
-
request
|
|
1043
|
+
request,
|
|
1044
|
+
{},
|
|
1045
|
+
options
|
|
626
1046
|
);
|
|
627
1047
|
}
|
|
628
1048
|
/** FileSystem: write a file (last-writer-wins; emits fs.changed). */
|
|
@@ -658,19 +1078,23 @@ var OpenGeniClient = class {
|
|
|
658
1078
|
);
|
|
659
1079
|
}
|
|
660
1080
|
/** Git: working-tree/index status (the Pierre file-status feed). */
|
|
661
|
-
async gitStatus(workspaceId, sessionId, request = {}) {
|
|
1081
|
+
async gitStatus(workspaceId, sessionId, request = {}, options = {}) {
|
|
662
1082
|
return await this.requestJson(
|
|
663
1083
|
"POST",
|
|
664
1084
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/status`,
|
|
665
|
-
request
|
|
1085
|
+
request,
|
|
1086
|
+
{},
|
|
1087
|
+
options
|
|
666
1088
|
);
|
|
667
1089
|
}
|
|
668
1090
|
/** Git: structured diff hunks (the Pierre diff feed). */
|
|
669
|
-
async gitDiff(workspaceId, sessionId, request = {}) {
|
|
1091
|
+
async gitDiff(workspaceId, sessionId, request = {}, options = {}) {
|
|
670
1092
|
return await this.requestJson(
|
|
671
1093
|
"POST",
|
|
672
1094
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/diff`,
|
|
673
|
-
request
|
|
1095
|
+
request,
|
|
1096
|
+
{},
|
|
1097
|
+
options
|
|
674
1098
|
);
|
|
675
1099
|
}
|
|
676
1100
|
/** Git: commit log. */
|
|
@@ -693,23 +1117,27 @@ var OpenGeniClient = class {
|
|
|
693
1117
|
* (tree + per-repo diff + file after-image refs), served from durable storage
|
|
694
1118
|
* WITHOUT warming a machine — the workbench cold-paint source. Returns
|
|
695
1119
|
* `{available:false}` when no capture exists yet (fall back to the live path). */
|
|
696
|
-
async getWorkspaceCapture(workspaceId, sessionId) {
|
|
1120
|
+
async getWorkspaceCapture(workspaceId, sessionId, options = {}) {
|
|
697
1121
|
return await this.requestJson(
|
|
698
1122
|
"GET",
|
|
699
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture
|
|
1123
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture`,
|
|
1124
|
+
void 0,
|
|
1125
|
+
{},
|
|
1126
|
+
options
|
|
700
1127
|
);
|
|
701
1128
|
}
|
|
702
1129
|
/** Workspace capture: a single file's after-image from the capture (revision
|
|
703
1130
|
* pins a specific one; omitted → latest). Content is inline for small files,
|
|
704
1131
|
* else a short-TTL signed URL; a tooLarge file returns metadata only. */
|
|
705
|
-
async getWorkspaceCaptureFile(workspaceId, sessionId, path, revision) {
|
|
1132
|
+
async getWorkspaceCaptureFile(workspaceId, sessionId, path, revision, options = {}) {
|
|
706
1133
|
const query = { path };
|
|
707
1134
|
if (revision !== void 0) query.revision = String(revision);
|
|
708
1135
|
return await this.requestJson(
|
|
709
1136
|
"GET",
|
|
710
1137
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture/file`,
|
|
711
1138
|
void 0,
|
|
712
|
-
query
|
|
1139
|
+
query,
|
|
1140
|
+
options
|
|
713
1141
|
);
|
|
714
1142
|
}
|
|
715
1143
|
/** Terminal: run a bounded command, returning buffered stdout/stderr inline. */
|
|
@@ -764,10 +1192,13 @@ var OpenGeniClient = class {
|
|
|
764
1192
|
* liveness the client polls on while `cold`/`warming`. The desktop URL/token
|
|
765
1193
|
* are minted in-process only when the box is warm AND the principal has
|
|
766
1194
|
* acknowledged the un-redacted plane. */
|
|
767
|
-
async getStreamCapabilities(workspaceId, sessionId) {
|
|
1195
|
+
async getStreamCapabilities(workspaceId, sessionId, options = {}) {
|
|
768
1196
|
return await this.requestJson(
|
|
769
1197
|
"GET",
|
|
770
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities
|
|
1198
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`,
|
|
1199
|
+
void 0,
|
|
1200
|
+
{},
|
|
1201
|
+
options
|
|
771
1202
|
);
|
|
772
1203
|
}
|
|
773
1204
|
/** Record the calling principal's acknowledgment of the un-redacted desktop
|
|
@@ -821,7 +1252,14 @@ var OpenGeniClient = class {
|
|
|
821
1252
|
* knowledge of the host setup; safe to call before any auth is established.
|
|
822
1253
|
*/
|
|
823
1254
|
async getClientConfig() {
|
|
824
|
-
|
|
1255
|
+
const config = await this.requestJson("GET", "/v1/config/client");
|
|
1256
|
+
if (config.apiContractRevision !== OPENGENI_API_CONTRACT_REVISION) {
|
|
1257
|
+
throw new OpenGeniApiContractMismatchError(
|
|
1258
|
+
OPENGENI_API_CONTRACT_REVISION,
|
|
1259
|
+
String(config.apiContractRevision || "(missing)")
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
return config;
|
|
825
1263
|
}
|
|
826
1264
|
/** The caller's access context: subject, account + workspace grants, defaults. */
|
|
827
1265
|
async getAccessContext() {
|
|
@@ -1404,14 +1842,13 @@ var OpenGeniClient = class {
|
|
|
1404
1842
|
return logoAssetPath ? `${this.baseUrl}/v1/${logoAssetPath}` : null;
|
|
1405
1843
|
}
|
|
1406
1844
|
// --- GitHub ----------------------------------------------------------------------------------
|
|
1407
|
-
/** GitHub App configuration status
|
|
1845
|
+
/** GitHub App configuration status; install/link URLs are null while new binding is disabled. */
|
|
1408
1846
|
async getGitHubApp(workspaceId) {
|
|
1409
1847
|
return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/github/app`);
|
|
1410
1848
|
}
|
|
1411
1849
|
/**
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1414
|
-
* `getGitHubApp().installUrl` or a github_connect_link tool.
|
|
1850
|
+
* Compatibility URL for previously issued state. New installation binding is
|
|
1851
|
+
* disabled, so the endpoint validates state and terminates with HTTP 410.
|
|
1415
1852
|
*/
|
|
1416
1853
|
githubConnectUrl(workspaceId, state) {
|
|
1417
1854
|
return this.url(`/v1/workspaces/${workspaceId}/github/connect`, { state });
|
|
@@ -1429,6 +1866,13 @@ var OpenGeniClient = class {
|
|
|
1429
1866
|
`/v1/workspaces/${workspaceId}/github/repositories/sync`
|
|
1430
1867
|
);
|
|
1431
1868
|
}
|
|
1869
|
+
/** Remove one workspace binding without uninstalling the GitHub App itself. */
|
|
1870
|
+
async unlinkGitHubInstallation(workspaceId, installationId) {
|
|
1871
|
+
await this.requestVoid(
|
|
1872
|
+
"DELETE",
|
|
1873
|
+
`/v1/workspaces/${workspaceId}/github/installations/${installationId}`
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1432
1876
|
/** Build a GitHub App manifest + the GitHub URL to submit it to. */
|
|
1433
1877
|
async createGitHubAppManifest(workspaceId, request = {}) {
|
|
1434
1878
|
return await this.requestJson(
|
|
@@ -1491,7 +1935,8 @@ var OpenGeniClient = class {
|
|
|
1491
1935
|
const extra = typeof this.options.headers === "function" ? this.options.headers() : this.options.headers;
|
|
1492
1936
|
return {
|
|
1493
1937
|
...this.options.apiKey ? { Authorization: `Bearer ${this.options.apiKey}` } : {},
|
|
1494
|
-
...extra
|
|
1938
|
+
...extra,
|
|
1939
|
+
[OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION
|
|
1495
1940
|
};
|
|
1496
1941
|
}
|
|
1497
1942
|
url(path, query = {}) {
|
|
@@ -1591,7 +2036,7 @@ var OpenGeniClient = class {
|
|
|
1591
2036
|
{ target }
|
|
1592
2037
|
);
|
|
1593
2038
|
}
|
|
1594
|
-
async requestJson(method, path, body, query = {}) {
|
|
2039
|
+
async requestJson(method, path, body, query = {}, options = {}) {
|
|
1595
2040
|
const response = await this.fetchImpl(this.url(path, query), {
|
|
1596
2041
|
method,
|
|
1597
2042
|
headers: {
|
|
@@ -1599,8 +2044,10 @@ var OpenGeniClient = class {
|
|
|
1599
2044
|
Accept: "application/json",
|
|
1600
2045
|
...body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
1601
2046
|
},
|
|
1602
|
-
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
2047
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {},
|
|
2048
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1603
2049
|
});
|
|
2050
|
+
assertApiContractResponse(response);
|
|
1604
2051
|
if (!response.ok) {
|
|
1605
2052
|
throw new OpenGeniApiError(response.status, await safeText(response));
|
|
1606
2053
|
}
|
|
@@ -1617,11 +2064,18 @@ var OpenGeniClient = class {
|
|
|
1617
2064
|
},
|
|
1618
2065
|
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
1619
2066
|
});
|
|
2067
|
+
assertApiContractResponse(response);
|
|
1620
2068
|
if (!response.ok) {
|
|
1621
2069
|
throw new OpenGeniApiError(response.status, await safeText(response));
|
|
1622
2070
|
}
|
|
1623
2071
|
}
|
|
1624
2072
|
};
|
|
2073
|
+
function assertApiContractResponse(response) {
|
|
2074
|
+
const actual = response.headers.get(OPENGENI_API_CONTRACT_HEADER);
|
|
2075
|
+
if (actual && actual !== OPENGENI_API_CONTRACT_REVISION) {
|
|
2076
|
+
throw new OpenGeniApiContractMismatchError(OPENGENI_API_CONTRACT_REVISION, actual);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
1625
2079
|
async function safeText(response) {
|
|
1626
2080
|
try {
|
|
1627
2081
|
return await response.text();
|
|
@@ -1813,174 +2267,225 @@ function ttydResizeFrame(columns, rows) {
|
|
|
1813
2267
|
return TtydClientCommand.RESIZE + JSON.stringify({ columns, rows });
|
|
1814
2268
|
}
|
|
1815
2269
|
|
|
1816
|
-
// src/
|
|
1817
|
-
var
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
"
|
|
1825
|
-
|
|
1826
|
-
"
|
|
1827
|
-
"
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
"
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
"
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
"
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
"
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
"
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
"
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
"
|
|
1959
|
-
"
|
|
1960
|
-
"
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2270
|
+
// src/transcription.ts
|
|
2271
|
+
var DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY = {
|
|
2272
|
+
enabled: false,
|
|
2273
|
+
acceptanceId: null,
|
|
2274
|
+
primary: null,
|
|
2275
|
+
language: null,
|
|
2276
|
+
autoDetectLanguage: false,
|
|
2277
|
+
diarization: { enabled: false, maxSpeakers: null },
|
|
2278
|
+
retention: { mode: "none", maxDays: null },
|
|
2279
|
+
privacy: { allowProviderLogging: false, allowProviderTraining: false },
|
|
2280
|
+
fallback: { mode: "disabled", targets: [] },
|
|
2281
|
+
cost: { currency: "USD", maxPerHour: null, maxPerMonth: null }
|
|
2282
|
+
};
|
|
2283
|
+
function resolveWorkspaceTranscriptionPolicy(settings) {
|
|
2284
|
+
if (!isRecord(settings)) return cloneDefaultPolicy();
|
|
2285
|
+
const candidate = settings.transcription;
|
|
2286
|
+
if (!isWorkspaceTranscriptionPolicy(candidate)) return cloneDefaultPolicy();
|
|
2287
|
+
return {
|
|
2288
|
+
...candidate,
|
|
2289
|
+
primary: candidate.primary ? normalizeTarget(candidate.primary) : null,
|
|
2290
|
+
language: candidate.language?.trim() ?? null,
|
|
2291
|
+
diarization: { ...candidate.diarization },
|
|
2292
|
+
retention: { ...candidate.retention },
|
|
2293
|
+
privacy: { ...candidate.privacy },
|
|
2294
|
+
fallback: {
|
|
2295
|
+
mode: candidate.fallback.mode,
|
|
2296
|
+
targets: candidate.fallback.targets.map(normalizeTarget)
|
|
2297
|
+
},
|
|
2298
|
+
cost: { ...candidate.cost }
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
function authorizeTranscriptionAdapter(policy, descriptor, selection = { kind: "primary" }) {
|
|
2302
|
+
if (!isWorkspaceTranscriptionPolicy(policy)) {
|
|
2303
|
+
return { authorized: false, reason: "unaccepted" };
|
|
2304
|
+
}
|
|
2305
|
+
if (!policy.enabled) return { authorized: false, reason: "disabled" };
|
|
2306
|
+
if (!policy.acceptanceId) return { authorized: false, reason: "unaccepted" };
|
|
2307
|
+
let target;
|
|
2308
|
+
if (selection.kind === "primary") {
|
|
2309
|
+
target = policy.primary;
|
|
2310
|
+
} else {
|
|
2311
|
+
if (policy.fallback.mode !== "explicit") {
|
|
2312
|
+
return { authorized: false, reason: "fallback_disabled" };
|
|
2313
|
+
}
|
|
2314
|
+
target = policy.fallback.targets[selection.index];
|
|
2315
|
+
if (!target) return { authorized: false, reason: "fallback_unaccepted" };
|
|
2316
|
+
}
|
|
2317
|
+
if (!target) return { authorized: false, reason: "target_missing" };
|
|
2318
|
+
const acceptedTarget = normalizeTarget(target);
|
|
2319
|
+
if (acceptedTarget.provider !== descriptor.provider) {
|
|
2320
|
+
return { authorized: false, reason: "provider_mismatch" };
|
|
2321
|
+
}
|
|
2322
|
+
if (acceptedTarget.model !== descriptor.model) {
|
|
2323
|
+
return { authorized: false, reason: "model_mismatch" };
|
|
2324
|
+
}
|
|
2325
|
+
if (acceptedTarget.credentialMode !== descriptor.credentialMode) {
|
|
2326
|
+
return { authorized: false, reason: "credential_mode_mismatch" };
|
|
2327
|
+
}
|
|
2328
|
+
if (acceptedTarget.region !== descriptor.region) {
|
|
2329
|
+
return { authorized: false, reason: "region_mismatch" };
|
|
2330
|
+
}
|
|
2331
|
+
return {
|
|
2332
|
+
authorized: true,
|
|
2333
|
+
acceptanceId: policy.acceptanceId,
|
|
2334
|
+
target: acceptedTarget,
|
|
2335
|
+
selection
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
function createTranscriptionSessionRequest(input) {
|
|
2339
|
+
const sequenceFloor = input.sequenceFloor ?? 0;
|
|
2340
|
+
if (!Number.isSafeInteger(sequenceFloor) || sequenceFloor < 0) return null;
|
|
2341
|
+
const authorization = authorizeTranscriptionAdapter(
|
|
2342
|
+
input.policy,
|
|
2343
|
+
input.adapter.descriptor,
|
|
2344
|
+
input.selection
|
|
2345
|
+
);
|
|
2346
|
+
if (!authorization.authorized) return null;
|
|
2347
|
+
return {
|
|
2348
|
+
localSessionId: input.localSessionId,
|
|
2349
|
+
policyAcceptanceId: authorization.acceptanceId,
|
|
2350
|
+
selection: authorization.selection,
|
|
2351
|
+
target: { ...authorization.target },
|
|
2352
|
+
language: input.policy.language?.trim() ?? null,
|
|
2353
|
+
autoDetectLanguage: input.policy.autoDetectLanguage,
|
|
2354
|
+
diarization: { ...input.policy.diarization },
|
|
2355
|
+
retention: { ...input.policy.retention },
|
|
2356
|
+
privacy: { ...input.policy.privacy },
|
|
2357
|
+
cost: { ...input.policy.cost },
|
|
2358
|
+
sequenceFloor
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
function cloneDefaultPolicy() {
|
|
2362
|
+
return {
|
|
2363
|
+
...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
|
|
2364
|
+
diarization: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.diarization },
|
|
2365
|
+
retention: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.retention },
|
|
2366
|
+
privacy: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.privacy },
|
|
2367
|
+
fallback: { mode: "disabled", targets: [] },
|
|
2368
|
+
cost: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.cost }
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
function isWorkspaceTranscriptionPolicy(value) {
|
|
2372
|
+
if (!isRecord(value) || typeof value.enabled !== "boolean") return false;
|
|
2373
|
+
if (!hasOnlyKeys(value, [
|
|
2374
|
+
"enabled",
|
|
2375
|
+
"acceptanceId",
|
|
2376
|
+
"primary",
|
|
2377
|
+
"language",
|
|
2378
|
+
"autoDetectLanguage",
|
|
2379
|
+
"diarization",
|
|
2380
|
+
"retention",
|
|
2381
|
+
"privacy",
|
|
2382
|
+
"fallback",
|
|
2383
|
+
"cost"
|
|
2384
|
+
])) {
|
|
2385
|
+
return false;
|
|
2386
|
+
}
|
|
2387
|
+
if (!(value.acceptanceId === null || isUuid(value.acceptanceId))) return false;
|
|
2388
|
+
if (!(value.primary === null || isTarget(value.primary))) return false;
|
|
2389
|
+
if (!(value.language === null || isBoundedString(value.language, 64))) return false;
|
|
2390
|
+
if (typeof value.autoDetectLanguage !== "boolean") return false;
|
|
2391
|
+
if (!isRecord(value.diarization) || !hasOnlyKeys(value.diarization, ["enabled", "maxSpeakers"]) || typeof value.diarization.enabled !== "boolean" || !(value.diarization.maxSpeakers === null || isBoundedInteger(value.diarization.maxSpeakers, 100) && value.diarization.maxSpeakers >= 2)) {
|
|
2392
|
+
return false;
|
|
2393
|
+
}
|
|
2394
|
+
if (!value.diarization.enabled && value.diarization.maxSpeakers !== null) return false;
|
|
2395
|
+
if (!isRecord(value.retention) || !hasOnlyKeys(value.retention, ["mode", "maxDays"])) {
|
|
2396
|
+
return false;
|
|
2397
|
+
}
|
|
2398
|
+
if (value.retention.mode !== "none" && value.retention.mode !== "provider-policy") return false;
|
|
2399
|
+
if (!(value.retention.maxDays === null || isBoundedInteger(value.retention.maxDays, 3650))) {
|
|
2400
|
+
return false;
|
|
2401
|
+
}
|
|
2402
|
+
if (!isRecord(value.privacy) || !hasOnlyKeys(value.privacy, ["allowProviderLogging", "allowProviderTraining"]) || typeof value.privacy.allowProviderLogging !== "boolean" || typeof value.privacy.allowProviderTraining !== "boolean") {
|
|
2403
|
+
return false;
|
|
2404
|
+
}
|
|
2405
|
+
if (!isRecord(value.fallback) || !hasOnlyKeys(value.fallback, ["mode", "targets"])) {
|
|
2406
|
+
return false;
|
|
2407
|
+
}
|
|
2408
|
+
if (value.fallback.mode !== "disabled" && value.fallback.mode !== "explicit") return false;
|
|
2409
|
+
if (!Array.isArray(value.fallback.targets) || value.fallback.targets.length > 8 || !value.fallback.targets.every(isTarget)) {
|
|
2410
|
+
return false;
|
|
2411
|
+
}
|
|
2412
|
+
if (value.fallback.mode === "disabled" && value.fallback.targets.length !== 0) return false;
|
|
2413
|
+
if (value.fallback.mode === "explicit" && value.fallback.targets.length === 0) return false;
|
|
2414
|
+
if (!isRecord(value.cost) || !hasOnlyKeys(value.cost, ["currency", "maxPerHour", "maxPerMonth"]) || value.cost.currency !== "USD") {
|
|
2415
|
+
return false;
|
|
2416
|
+
}
|
|
2417
|
+
if (!isNullableBoundedNumber(value.cost.maxPerHour, 1e4)) return false;
|
|
2418
|
+
if (!isNullableBoundedNumber(value.cost.maxPerMonth, 1e6)) return false;
|
|
2419
|
+
if (value.enabled && (!value.acceptanceId || !value.primary)) return false;
|
|
2420
|
+
if (value.enabled && !value.autoDetectLanguage && value.language === null) return false;
|
|
2421
|
+
if (value.autoDetectLanguage && value.language !== null) return false;
|
|
2422
|
+
const targets = [value.primary, ...value.fallback.targets].filter(
|
|
2423
|
+
(target) => target !== null
|
|
2424
|
+
);
|
|
2425
|
+
if (new Set(targets.map(targetKey)).size !== targets.length) return false;
|
|
2426
|
+
return true;
|
|
2427
|
+
}
|
|
2428
|
+
function targetKey(target) {
|
|
2429
|
+
return [
|
|
2430
|
+
target.provider.trim(),
|
|
2431
|
+
target.model?.trim() ?? "",
|
|
2432
|
+
target.credentialMode,
|
|
2433
|
+
target.credentialConnectionId ?? "",
|
|
2434
|
+
target.region?.trim() ?? ""
|
|
2435
|
+
].join("\0");
|
|
2436
|
+
}
|
|
2437
|
+
function isTarget(value) {
|
|
2438
|
+
if (!isRecord(value)) return false;
|
|
2439
|
+
if (!hasOnlyKeys(value, ["provider", "model", "credentialMode", "credentialConnectionId", "region"])) {
|
|
2440
|
+
return false;
|
|
2441
|
+
}
|
|
2442
|
+
if (!isBoundedString(value.provider, 128)) return false;
|
|
2443
|
+
if (!(value.model === null || isBoundedString(value.model, 256))) return false;
|
|
2444
|
+
if (value.credentialMode !== "managed" && value.credentialMode !== "byok") return false;
|
|
2445
|
+
if (value.provider.trim() === "azure-speech" && value.credentialMode !== "byok") return false;
|
|
2446
|
+
if (!(value.credentialConnectionId === null || isUuid(value.credentialConnectionId))) {
|
|
2447
|
+
return false;
|
|
2448
|
+
}
|
|
2449
|
+
if (!(value.region === null || isBoundedString(value.region, 128))) return false;
|
|
2450
|
+
if (value.credentialMode === "byok" && value.credentialConnectionId === null) return false;
|
|
2451
|
+
if (value.credentialMode === "managed" && value.credentialConnectionId !== null) return false;
|
|
2452
|
+
return true;
|
|
2453
|
+
}
|
|
2454
|
+
function normalizeTarget(target) {
|
|
2455
|
+
return {
|
|
2456
|
+
provider: target.provider.trim(),
|
|
2457
|
+
model: target.model?.trim() ?? null,
|
|
2458
|
+
credentialMode: target.credentialMode,
|
|
2459
|
+
credentialConnectionId: target.credentialConnectionId,
|
|
2460
|
+
region: target.region?.trim() ?? null
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
function isRecord(value) {
|
|
2464
|
+
return typeof value === "object" && value !== null;
|
|
2465
|
+
}
|
|
2466
|
+
function hasOnlyKeys(value, keys) {
|
|
2467
|
+
const accepted = new Set(keys);
|
|
2468
|
+
return Object.keys(value).every((key) => accepted.has(key));
|
|
2469
|
+
}
|
|
2470
|
+
function isBoundedString(value, maximum) {
|
|
2471
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= maximum;
|
|
2472
|
+
}
|
|
2473
|
+
function isUuid(value) {
|
|
2474
|
+
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
2475
|
+
}
|
|
2476
|
+
function isBoundedInteger(value, maximum) {
|
|
2477
|
+
return Number.isInteger(value) && value >= 0 && value <= maximum;
|
|
2478
|
+
}
|
|
2479
|
+
function isNullableBoundedNumber(value, maximum) {
|
|
2480
|
+
return value === null || typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= maximum;
|
|
2481
|
+
}
|
|
1981
2482
|
export {
|
|
2483
|
+
DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
|
|
1982
2484
|
KNOWN_PERMISSIONS,
|
|
1983
2485
|
KNOWN_USAGE_EVENT_TYPES,
|
|
2486
|
+
OPENGENI_API_CONTRACT_HEADER,
|
|
2487
|
+
OPENGENI_API_CONTRACT_REVISION,
|
|
2488
|
+
OpenGeniApiContractMismatchError,
|
|
1984
2489
|
OpenGeniApiError,
|
|
1985
2490
|
OpenGeniClient,
|
|
1986
2491
|
OpenGeniStreamError,
|
|
@@ -1989,16 +2494,20 @@ export {
|
|
|
1989
2494
|
TtydClientCommand,
|
|
1990
2495
|
TtydServerCommand,
|
|
1991
2496
|
applyUrlRotation,
|
|
2497
|
+
authorizeTranscriptionAdapter,
|
|
2498
|
+
createTranscriptionSessionRequest,
|
|
1992
2499
|
desktopSocketUrl,
|
|
1993
2500
|
formatSseEvent,
|
|
1994
2501
|
isRetryableStreamError,
|
|
1995
2502
|
nextDesktopState,
|
|
1996
2503
|
parseSseStream,
|
|
1997
2504
|
proxySessionEventStream,
|
|
2505
|
+
resolveWorkspaceTranscriptionPolicy,
|
|
1998
2506
|
resumeSequenceFromRequest,
|
|
1999
2507
|
sessionEventsToSseResponse,
|
|
2000
2508
|
sessionEventsToSseStream,
|
|
2001
2509
|
streamSessionEvents,
|
|
2510
|
+
streamWorkspaceControlEvents,
|
|
2002
2511
|
terminalSocketUrl,
|
|
2003
2512
|
ttydAuthFrame,
|
|
2004
2513
|
ttydInputFrame,
|