@opengeni/sdk 0.32.1 → 0.33.1
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 +35 -0
- package/dist/artifact-client.d.ts +11 -0
- package/dist/chunk-YROWFD7R.js +2971 -0
- package/dist/chunk-YROWFD7R.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/core.d.ts +5 -0
- package/dist/core.js +23 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +45 -2916
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/workspace-artifacts.d.ts +94 -0
- package/package.json +5 -1
- package/src/artifact-client.ts +85 -0
- package/src/client.ts +1 -1
- package/src/core.ts +19 -0
- package/src/index.ts +14 -1
- package/src/types.ts +9 -2
- package/src/workspace-artifacts.ts +94 -0
package/dist/index.js
CHANGED
|
@@ -1,2724 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
const
|
|
33
|
-
const nested = record.error && typeof record.error === "object" && !Array.isArray(record.error) ? record.error : record;
|
|
34
|
-
const code = boundedApiField(nested.code);
|
|
35
|
-
const message = boundedApiField(nested.message);
|
|
36
|
-
const requestId = boundedCorrelationId(nested.requestId);
|
|
37
|
-
const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
|
|
38
|
-
if (!code && !message && !requestId && retryable === void 0) return null;
|
|
39
|
-
return {
|
|
40
|
-
code,
|
|
41
|
-
message,
|
|
42
|
-
requestId,
|
|
43
|
-
retryable
|
|
44
|
-
};
|
|
45
|
-
} catch {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
function boundedApiField(value) {
|
|
50
|
-
if (typeof value !== "string") return;
|
|
51
|
-
const bytes = new TextEncoder().encode(value);
|
|
52
|
-
return bytes.byteLength <= 512 ? value : new TextDecoder().decode(bytes.slice(0, 512));
|
|
53
|
-
}
|
|
54
|
-
function retryableApiStatus(status) {
|
|
55
|
-
return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
|
|
56
|
-
}
|
|
57
|
-
function boundedCorrelationId(value) {
|
|
58
|
-
if (typeof value !== "string" || value.length > 128 || !/^[\w.:-]+$/.test(value)) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
return value;
|
|
62
|
-
}
|
|
63
|
-
var OpenGeniSessionListCursorError = class extends OpenGeniApiError {
|
|
64
|
-
};
|
|
65
|
-
var OpenGeniApiContractMismatchError = class extends Error {
|
|
66
|
-
expected;
|
|
67
|
-
actual;
|
|
68
|
-
constructor(expected, actual) {
|
|
69
|
-
super(`OpenGeni API contract mismatch: client expects ${expected}, API serves ${actual}`);
|
|
70
|
-
this.name = "OpenGeniApiContractMismatchError";
|
|
71
|
-
this.expected = expected;
|
|
72
|
-
this.actual = actual;
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
var OpenGeniStreamError = class extends Error {
|
|
76
|
-
constructor(message) {
|
|
77
|
-
super(message);
|
|
78
|
-
this.name = "OpenGeniStreamError";
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
function isAbortError(error) {
|
|
82
|
-
return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
|
|
83
|
-
}
|
|
84
|
-
function isRetryableStreamError(error) {
|
|
85
|
-
if (error instanceof OpenGeniApiError) return error.retryable;
|
|
86
|
-
return error instanceof TypeError;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// src/sse.ts
|
|
90
|
-
async function* parseSseStream(stream) {
|
|
91
|
-
const reader = stream.getReader();
|
|
92
|
-
const decoder = new TextDecoder();
|
|
93
|
-
let buffer = "";
|
|
94
|
-
let id;
|
|
95
|
-
let event;
|
|
96
|
-
let dataLines = null;
|
|
97
|
-
const dispatch = () => {
|
|
98
|
-
const message = dataLines === null ? null : {
|
|
99
|
-
...id !== void 0 ? { id } : {},
|
|
100
|
-
...event !== void 0 ? { event } : {},
|
|
101
|
-
data: dataLines.join("\n")
|
|
102
|
-
};
|
|
103
|
-
id = void 0;
|
|
104
|
-
event = void 0;
|
|
105
|
-
dataLines = null;
|
|
106
|
-
return message;
|
|
107
|
-
};
|
|
108
|
-
const handleLine = (line) => {
|
|
109
|
-
if (line === "") {
|
|
110
|
-
return dispatch();
|
|
111
|
-
}
|
|
112
|
-
if (line.startsWith(":")) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
const colon = line.indexOf(":");
|
|
116
|
-
const field = colon === -1 ? line : line.slice(0, colon);
|
|
117
|
-
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
118
|
-
if (value.startsWith(" ")) {
|
|
119
|
-
value = value.slice(1);
|
|
120
|
-
}
|
|
121
|
-
if (field === "data") {
|
|
122
|
-
(dataLines ??= []).push(value);
|
|
123
|
-
} else if (field === "event") {
|
|
124
|
-
event = value;
|
|
125
|
-
} else if (field === "id") {
|
|
126
|
-
id = value;
|
|
127
|
-
}
|
|
128
|
-
return null;
|
|
129
|
-
};
|
|
130
|
-
try {
|
|
131
|
-
while (true) {
|
|
132
|
-
const { done, value } = await reader.read();
|
|
133
|
-
if (done) {
|
|
134
|
-
break;
|
|
135
|
-
}
|
|
136
|
-
buffer += decoder.decode(value, { stream: true });
|
|
137
|
-
let newline = buffer.indexOf("\n");
|
|
138
|
-
while (newline !== -1) {
|
|
139
|
-
let line = buffer.slice(0, newline);
|
|
140
|
-
buffer = buffer.slice(newline + 1);
|
|
141
|
-
if (line.endsWith("\r")) {
|
|
142
|
-
line = line.slice(0, -1);
|
|
143
|
-
}
|
|
144
|
-
const message = handleLine(line);
|
|
145
|
-
if (message) {
|
|
146
|
-
yield message;
|
|
147
|
-
}
|
|
148
|
-
newline = buffer.indexOf("\n");
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
} finally {
|
|
152
|
-
await reader.cancel().catch(() => {
|
|
153
|
-
});
|
|
154
|
-
reader.releaseLock();
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// src/stream.ts
|
|
159
|
-
async function* streamSessionEvents(transport, options = {}) {
|
|
160
|
-
const signal = options.signal;
|
|
161
|
-
const reconnect = options.reconnect ?? true;
|
|
162
|
-
const baseDelayMs = options.reconnectDelayMs ?? 500;
|
|
163
|
-
const maxDelayMs = options.maxReconnectDelayMs ?? 1e4;
|
|
164
|
-
const maxAttempts = options.maxReconnectAttempts ?? Number.POSITIVE_INFINITY;
|
|
165
|
-
let cursor = options.after ?? 0;
|
|
166
|
-
let failedAttempts = 0;
|
|
167
|
-
let delayMs = baseDelayMs;
|
|
168
|
-
let everConnected = false;
|
|
169
|
-
while (true) {
|
|
170
|
-
if (signal?.aborted) break;
|
|
171
|
-
options.onStateChange?.(everConnected || failedAttempts > 0 ? "reconnecting" : "connecting");
|
|
172
|
-
const cursorAtOpen = cursor;
|
|
173
|
-
try {
|
|
174
|
-
const body = await transport.openStream(cursor, signal);
|
|
175
|
-
everConnected = true;
|
|
176
|
-
failedAttempts = 0;
|
|
177
|
-
delayMs = baseDelayMs;
|
|
178
|
-
await options.beforeLive?.();
|
|
179
|
-
options.onStateChange?.("live");
|
|
180
|
-
for await (const message of parseSseStream(body)) {
|
|
181
|
-
if (signal?.aborted) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
const event = parseSessionEvent(message.data);
|
|
185
|
-
if (!event || event.sequence <= cursor) {
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (event.sequence > cursor + 1) {
|
|
189
|
-
for await (const missed of backfillEvents(transport, cursor, event.sequence - 1)) {
|
|
190
|
-
cursor = missed.sequence;
|
|
191
|
-
yield missed;
|
|
192
|
-
if (signal?.aborted) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
cursor = event.sequence;
|
|
198
|
-
yield event;
|
|
199
|
-
}
|
|
200
|
-
if (!reconnect) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (cursor === cursorAtOpen) {
|
|
204
|
-
await sleep(baseDelayMs, signal);
|
|
205
|
-
}
|
|
206
|
-
continue;
|
|
207
|
-
} catch (error) {
|
|
208
|
-
if (signal?.aborted || isAbortError(error)) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
if (!reconnect || !isRetryableStreamError(error)) {
|
|
212
|
-
throw error;
|
|
213
|
-
}
|
|
214
|
-
failedAttempts += 1;
|
|
215
|
-
if (failedAttempts > maxAttempts) {
|
|
216
|
-
throw new OpenGeniStreamError(
|
|
217
|
-
`event stream gave up after ${maxAttempts} consecutive failed reconnect attempts: ${error instanceof Error ? error.message : String(error)}`
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
await sleep(delayMs, signal);
|
|
222
|
-
delayMs = Math.min(Math.max(delayMs * 2, baseDelayMs), maxDelayMs);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
async function* backfillEvents(transport, fromExclusive, toInclusive) {
|
|
226
|
-
let cursor = fromExclusive;
|
|
227
|
-
while (cursor < toInclusive) {
|
|
228
|
-
const page = await transport.listEvents(cursor, Math.min(500, toInclusive - cursor));
|
|
229
|
-
const advancing = page.filter((event) => event.sequence > cursor && event.sequence <= toInclusive).sort((a, b) => a.sequence - b.sequence);
|
|
230
|
-
if (advancing.length === 0) {
|
|
231
|
-
throw new OpenGeniStreamError(
|
|
232
|
-
`event replay backfill stalled: expected sequences ${cursor + 1}..${toInclusive} but the replay endpoint returned none of them`
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
for (const event of advancing) {
|
|
236
|
-
if (event.sequence !== cursor + 1) {
|
|
237
|
-
throw new OpenGeniStreamError(
|
|
238
|
-
`event replay backfill is missing sequence ${cursor + 1} (replay endpoint skipped to ${event.sequence}); refusing to deliver with a gap`
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
cursor = event.sequence;
|
|
242
|
-
yield event;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
function parseSessionEvent(data) {
|
|
247
|
-
let parsed;
|
|
248
|
-
try {
|
|
249
|
-
parsed = JSON.parse(data);
|
|
250
|
-
} catch {
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
if (typeof parsed !== "object" || parsed === null || typeof parsed.sequence !== "number" || typeof parsed.type !== "string" || typeof parsed.id !== "string") {
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
return parsed;
|
|
257
|
-
}
|
|
258
|
-
async function sleep(delayMs, signal) {
|
|
259
|
-
if (signal?.aborted || delayMs <= 0) {
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
await new Promise((resolve) => {
|
|
263
|
-
const timer = setTimeout(done, delayMs);
|
|
264
|
-
function done() {
|
|
265
|
-
clearTimeout(timer);
|
|
266
|
-
signal?.removeEventListener("abort", done);
|
|
267
|
-
resolve();
|
|
268
|
-
}
|
|
269
|
-
signal?.addEventListener("abort", done, { once: true });
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// src/workspace-control-stream.ts
|
|
274
|
-
async function* streamWorkspaceControlEvents(transport, options = {}) {
|
|
275
|
-
const signal = options.signal;
|
|
276
|
-
const reconnect = options.reconnect ?? true;
|
|
277
|
-
const baseDelayMs = options.reconnectDelayMs ?? 500;
|
|
278
|
-
const maxDelayMs = options.maxReconnectDelayMs ?? 1e4;
|
|
279
|
-
const maxAttempts = options.maxReconnectAttempts ?? Number.POSITIVE_INFINITY;
|
|
280
|
-
let cursor = options.after ?? 0;
|
|
281
|
-
let failures = 0;
|
|
282
|
-
let delayMs = baseDelayMs;
|
|
283
|
-
let everConnected = false;
|
|
284
|
-
for (; ; ) {
|
|
285
|
-
if (signal?.aborted) return;
|
|
286
|
-
options.onStateChange?.(everConnected || failures > 0 ? "reconnecting" : "connecting");
|
|
287
|
-
const cursorAtOpen = cursor;
|
|
288
|
-
try {
|
|
289
|
-
const body = await transport.openStream(cursor, signal);
|
|
290
|
-
everConnected = true;
|
|
291
|
-
failures = 0;
|
|
292
|
-
delayMs = baseDelayMs;
|
|
293
|
-
await options.beforeLive?.();
|
|
294
|
-
options.onStateChange?.("live");
|
|
295
|
-
for await (const message of parseSseStream(body)) {
|
|
296
|
-
if (signal?.aborted) return;
|
|
297
|
-
const event = parseWorkspaceControlEvent(message.data);
|
|
298
|
-
if (!event || event.sequence <= cursor) continue;
|
|
299
|
-
cursor = event.sequence;
|
|
300
|
-
yield event;
|
|
301
|
-
}
|
|
302
|
-
if (!reconnect) return;
|
|
303
|
-
if (cursor === cursorAtOpen) await sleep2(baseDelayMs, signal);
|
|
304
|
-
continue;
|
|
305
|
-
} catch (error) {
|
|
306
|
-
if (signal?.aborted || isAbortError(error)) return;
|
|
307
|
-
if (!reconnect || !isRetryableStreamError(error)) throw error;
|
|
308
|
-
failures += 1;
|
|
309
|
-
if (failures > maxAttempts) {
|
|
310
|
-
throw new OpenGeniStreamError(
|
|
311
|
-
`workspace control stream gave up after ${maxAttempts} reconnect attempts: ${error instanceof Error ? error.message : String(error)}`
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
await sleep2(delayMs, signal);
|
|
316
|
-
delayMs = Math.min(Math.max(delayMs * 2, baseDelayMs), maxDelayMs);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
function parseWorkspaceControlEvent(data) {
|
|
320
|
-
let value;
|
|
321
|
-
try {
|
|
322
|
-
value = JSON.parse(data);
|
|
323
|
-
} catch {
|
|
324
|
-
return null;
|
|
325
|
-
}
|
|
326
|
-
if (typeof value !== "object" || value === null || value.type !== "workspace.control.changed" || typeof value.id !== "string" || typeof value.sequence !== "number") {
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
return value;
|
|
330
|
-
}
|
|
331
|
-
async function sleep2(delayMs, signal) {
|
|
332
|
-
if (signal?.aborted || delayMs <= 0) return;
|
|
333
|
-
await new Promise((resolve) => {
|
|
334
|
-
const timer = setTimeout(done, delayMs);
|
|
335
|
-
function done() {
|
|
336
|
-
clearTimeout(timer);
|
|
337
|
-
signal?.removeEventListener("abort", done);
|
|
338
|
-
resolve();
|
|
339
|
-
}
|
|
340
|
-
signal?.addEventListener("abort", done, { once: true });
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// src/types.ts
|
|
345
|
-
var SESSION_EVENT_TYPES = [
|
|
346
|
-
"session.created",
|
|
347
|
-
// Defensive bounded projection for malformed/legacy oversized envelopes.
|
|
348
|
-
"session.event.envelope_omitted",
|
|
349
|
-
"session.status.changed",
|
|
350
|
-
"session.requiresAction",
|
|
351
|
-
"session.humanInput.requested",
|
|
352
|
-
"session.context.compaction.requested",
|
|
353
|
-
"session.context.compaction.started",
|
|
354
|
-
"session.context.compacted",
|
|
355
|
-
"session.context.compaction.skipped",
|
|
356
|
-
"session.context.cleared",
|
|
357
|
-
"user.message",
|
|
358
|
-
"user.pause",
|
|
359
|
-
"user.approvalDecision",
|
|
360
|
-
"user.humanInputResponse",
|
|
361
|
-
"turn.queued",
|
|
362
|
-
"turn.started",
|
|
363
|
-
"turn.completed",
|
|
364
|
-
"turn.failed",
|
|
365
|
-
"turn.cancelled",
|
|
366
|
-
"turn.superseded",
|
|
367
|
-
"turn.recovery.requested",
|
|
368
|
-
"turn.capacity_waiting",
|
|
369
|
-
"agent.message.delta",
|
|
370
|
-
"agent.message.completed",
|
|
371
|
-
"agent.reasoning.delta",
|
|
372
|
-
"agent.toolCall.created",
|
|
373
|
-
"agent.toolCall.output",
|
|
374
|
-
"agent.model.request",
|
|
375
|
-
"agent.model.usage",
|
|
376
|
-
"tool.auth_needed",
|
|
377
|
-
"credential.auth_needed",
|
|
378
|
-
"agent.updated",
|
|
379
|
-
"rig.setup.started",
|
|
380
|
-
"rig.setup.completed",
|
|
381
|
-
"rig.setup.skipped",
|
|
382
|
-
"rig.setup.failed",
|
|
383
|
-
"sandbox.operation.started",
|
|
384
|
-
"sandbox.operation.completed",
|
|
385
|
-
"sandbox.operation.failed",
|
|
386
|
-
"sandbox.command.output.delta",
|
|
387
|
-
"artifact.created",
|
|
388
|
-
"goal.set",
|
|
389
|
-
"goal.updated",
|
|
390
|
-
"goal.completed",
|
|
391
|
-
"goal.paused",
|
|
392
|
-
"goal.resumed",
|
|
393
|
-
"goal.cleared",
|
|
394
|
-
"goal.continuation",
|
|
395
|
-
"system.update.pending",
|
|
396
|
-
"system.update.delivered",
|
|
397
|
-
"system.update.superseded",
|
|
398
|
-
"system.update.cancelled",
|
|
399
|
-
"system.update.settled",
|
|
400
|
-
"session.control.paused",
|
|
401
|
-
"session.control.resumed",
|
|
402
|
-
"session.control.steer_requested",
|
|
403
|
-
"workspace.inference.paused",
|
|
404
|
-
"workspace.inference.resumed",
|
|
405
|
-
"session.queue.changed",
|
|
406
|
-
"session.queue.prompt.cancelled",
|
|
407
|
-
"session.queue.history",
|
|
408
|
-
"turn.event.rejected_late",
|
|
409
|
-
"memory.saved",
|
|
410
|
-
"memory.corrected",
|
|
411
|
-
// Channel-B desktop pixel-plane signals (mirror of contracts SessionEventType;
|
|
412
|
-
// the contract-parity test asserts sorted equality).
|
|
413
|
-
"stream.url.rotated",
|
|
414
|
-
"stream.opened",
|
|
415
|
-
"stream.closed",
|
|
416
|
-
"stream.revoked",
|
|
417
|
-
// Channel-B recording signals (P4.3 — "agent films itself proving the fix").
|
|
418
|
-
"recording.started",
|
|
419
|
-
"recording.available",
|
|
420
|
-
"recording.failed",
|
|
421
|
-
// Channel-A structured-service notifications (P4.4; mirror of contracts
|
|
422
|
-
// SessionEventType — the contract-parity test asserts sorted equality).
|
|
423
|
-
"fs.changed",
|
|
424
|
-
"git.changed",
|
|
425
|
-
"terminal.pty.started",
|
|
426
|
-
"terminal.pty.output.delta",
|
|
427
|
-
"terminal.pty.exited",
|
|
428
|
-
"session.title_set",
|
|
429
|
-
"session.mcp.approval_policy.updated",
|
|
430
|
-
"session.tool_policy.updated",
|
|
431
|
-
// Multi-account Codex (P1): the session's inference account changed.
|
|
432
|
-
"codex.account.switched",
|
|
433
|
-
// credential allocator metadata-only per-turn credential selection audit.
|
|
434
|
-
"codex.credential.selected",
|
|
435
|
-
// Bounded, identity-free deterministic shadow/replay decision.
|
|
436
|
-
"codex.fleet.decision",
|
|
437
|
-
// credential allocator durable zero-capacity wait lifecycle. These are system/runtime
|
|
438
|
-
// events, never synthetic user messages.
|
|
439
|
-
"codex.capacity.waiting",
|
|
440
|
-
"codex.capacity.resumed",
|
|
441
|
-
"codex.capacity.superseded",
|
|
442
|
-
// Sandbox durability observability (mirror of contracts SessionEventType):
|
|
443
|
-
// box lifecycle + manifest-env drift, attributable from the DB alone.
|
|
444
|
-
"sandbox.box.created",
|
|
445
|
-
"sandbox.box.lost",
|
|
446
|
-
"sandbox.box.terminated",
|
|
447
|
-
"sandbox.box.snapshot",
|
|
448
|
-
"sandbox.env.drift",
|
|
449
|
-
// Active-sandbox pointer reconcile (issue #341; announce-only; mirror of contracts
|
|
450
|
-
// SessionEventType — the contract-parity test asserts sorted equality).
|
|
451
|
-
"session.route.reconciled",
|
|
452
|
-
// Workbench v2 turn-end workspace capture (announce-only; mirror of contracts
|
|
453
|
-
// SessionEventType — the contract-parity test asserts sorted equality).
|
|
454
|
-
"workspace.revision.captured",
|
|
455
|
-
"workspace.revision.degraded",
|
|
456
|
-
// Connected Machine op-outcome observability (announce-only, quiet; mirror of
|
|
457
|
-
// contracts SessionEventType — the contract-parity test asserts sorted equality).
|
|
458
|
-
"machine.op.failed",
|
|
459
|
-
"machine.op.recovered",
|
|
460
|
-
// Connected Machine link-plane observability (announce-only, quiet; mirror of
|
|
461
|
-
// contracts SessionEventType — the contract-parity test asserts sorted equality).
|
|
462
|
-
"machine.link.lost",
|
|
463
|
-
"machine.link.restored",
|
|
464
|
-
"machine.runner.restarted"
|
|
465
|
-
];
|
|
466
|
-
var KNOWN_PERMISSIONS = [
|
|
467
|
-
"account:read",
|
|
468
|
-
"account:admin",
|
|
469
|
-
"members:manage",
|
|
470
|
-
"workspace:create",
|
|
471
|
-
"billing:read",
|
|
472
|
-
"billing:manage",
|
|
473
|
-
"workspace:read",
|
|
474
|
-
"workspace:admin",
|
|
475
|
-
"sessions:create",
|
|
476
|
-
"sessions:read",
|
|
477
|
-
"sessions:control",
|
|
478
|
-
// sandbox workspace (mirror of @opengeni/contracts Permission). stream:view is
|
|
479
|
-
// strictly broader than sessions:read (un-redacted pixels); stream:control is
|
|
480
|
-
// the never-granted-v1 raw-input plane; stream:acknowledge is the secret-leak
|
|
481
|
-
// consent gate.
|
|
482
|
-
"stream:view",
|
|
483
|
-
"stream:control",
|
|
484
|
-
"stream:acknowledge",
|
|
485
|
-
"files:upload",
|
|
486
|
-
"files:read",
|
|
487
|
-
"files:write",
|
|
488
|
-
"terminal:attach",
|
|
489
|
-
"documents:manage",
|
|
490
|
-
"documents:search",
|
|
491
|
-
"scheduled_tasks:manage",
|
|
492
|
-
"scheduled_tasks:run",
|
|
493
|
-
"github:manage",
|
|
494
|
-
"github:use",
|
|
495
|
-
"api_keys:manage",
|
|
496
|
-
"connections:read",
|
|
497
|
-
"connections:write",
|
|
498
|
-
"environments:manage",
|
|
499
|
-
"environments:use",
|
|
500
|
-
"variable-sets:manage",
|
|
501
|
-
"variable-sets:use",
|
|
502
|
-
"mcp_servers:attach",
|
|
503
|
-
"toolspace:call",
|
|
504
|
-
"goals:manage",
|
|
505
|
-
"enrollments:read",
|
|
506
|
-
"enrollments:manage",
|
|
507
|
-
"rigs:use",
|
|
508
|
-
"rigs:manage"
|
|
509
|
-
];
|
|
510
|
-
var OPENGENI_API_CONTRACT_REVISION = "2026-07-turn-instructions-v1";
|
|
511
|
-
var OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract";
|
|
512
|
-
var OPENGENI_CORRELATION_HEADER = "x-opengeni-correlation-id";
|
|
513
|
-
var RETAINED_OUTPUT_DEFAULT_PAGE_BYTES = 256 * 1024;
|
|
514
|
-
var RETAINED_OUTPUT_MAX_PAGE_BYTES = 1024 * 1024;
|
|
515
|
-
var KNOWN_USAGE_EVENT_TYPES = [
|
|
516
|
-
"agent_run.created",
|
|
517
|
-
"agent_run.completed",
|
|
518
|
-
"model.tokens",
|
|
519
|
-
"model.cost",
|
|
520
|
-
"file.uploaded",
|
|
521
|
-
"file.deleted",
|
|
522
|
-
"document.indexed",
|
|
523
|
-
"scheduled_task.fired",
|
|
524
|
-
"api_key.request",
|
|
525
|
-
// sandbox warm-time metering (P2.1) — mirrors contracts UsageEventType.
|
|
526
|
-
"sandbox.warm_seconds",
|
|
527
|
-
"sandbox.warm_cost"
|
|
528
|
-
];
|
|
529
|
-
|
|
530
|
-
// src/client.ts
|
|
531
|
-
function sessionListQuery(options) {
|
|
532
|
-
const { limit, parentSessionId } = options;
|
|
533
|
-
return {
|
|
534
|
-
...limit === void 0 ? {} : { limit: String(limit) },
|
|
535
|
-
...parentSessionId === void 0 ? {} : { parentSessionId: parentSessionId ?? "null" }
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
var OpenGeniClient = class {
|
|
539
|
-
baseUrl;
|
|
540
|
-
options;
|
|
541
|
-
fetchImpl;
|
|
542
|
-
constructor(options) {
|
|
543
|
-
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
544
|
-
this.options = options;
|
|
545
|
-
this.fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
|
|
546
|
-
}
|
|
547
|
-
// --- Session lifecycle ---------------------------------------------------
|
|
548
|
-
/** Upload one ephemeral browser recording. This method never retries. */
|
|
549
|
-
async transcribeAudio(workspaceId, input) {
|
|
550
|
-
const correlationId = crypto.randomUUID();
|
|
551
|
-
const form = new FormData();
|
|
552
|
-
const filename = filenameForAudioMimeType(input.mimeType);
|
|
553
|
-
const audio = input.audio instanceof File ? input.audio : input.audio instanceof Uint8Array ? new File([Uint8Array.from(input.audio)], filename, { type: input.mimeType }) : new File([input.audio], filename, { type: input.mimeType || input.audio.type });
|
|
554
|
-
form.append("audio", audio, filename);
|
|
555
|
-
form.append("mimeType", input.mimeType);
|
|
556
|
-
if (input.durationSeconds !== void 0) {
|
|
557
|
-
form.append("durationSeconds", String(input.durationSeconds));
|
|
558
|
-
}
|
|
559
|
-
let response;
|
|
560
|
-
try {
|
|
561
|
-
response = await this.fetchImpl(this.url(`/v1/workspaces/${workspaceId}/transcriptions`), {
|
|
562
|
-
method: "POST",
|
|
563
|
-
headers: { ...this.headers(correlationId), Accept: "application/json" },
|
|
564
|
-
body: form,
|
|
565
|
-
...input.signal ? { signal: input.signal } : {}
|
|
566
|
-
});
|
|
567
|
-
} catch (error) {
|
|
568
|
-
if (input.signal?.aborted) throw error;
|
|
569
|
-
throw mutationTransportError(correlationId);
|
|
570
|
-
}
|
|
571
|
-
assertApiContractResponse(response);
|
|
572
|
-
if (!response.ok) throw await apiErrorFromResponse(response, { method: "POST", correlationId });
|
|
573
|
-
await assertJsonResponse(response, { method: "POST", correlationId });
|
|
574
|
-
let body;
|
|
575
|
-
try {
|
|
576
|
-
body = await response.json();
|
|
577
|
-
} catch {
|
|
578
|
-
throw new OpenGeniApiError(response.status, "Invalid transcription response.", {
|
|
579
|
-
code: "invalid_response",
|
|
580
|
-
mutation: true,
|
|
581
|
-
correlationId
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
if (!isTranscribeAudioResponse(body)) {
|
|
585
|
-
throw new OpenGeniApiError(response.status, "Invalid transcription response.", {
|
|
586
|
-
code: "invalid_response",
|
|
587
|
-
mutation: true,
|
|
588
|
-
correlationId
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
return body;
|
|
592
|
-
}
|
|
593
|
-
async createSession(workspaceId, request) {
|
|
594
|
-
return await this.requestJson(
|
|
595
|
-
"POST",
|
|
596
|
-
`/v1/workspaces/${workspaceId}/sessions`,
|
|
597
|
-
request
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
async getNewSessionDraft(workspaceId) {
|
|
601
|
-
return await this.requestJson(
|
|
602
|
-
"GET",
|
|
603
|
-
`/v1/workspaces/${workspaceId}/new-session-draft`
|
|
604
|
-
);
|
|
605
|
-
}
|
|
606
|
-
async saveNewSessionDraft(workspaceId, request) {
|
|
607
|
-
return await this.requestJson(
|
|
608
|
-
"PUT",
|
|
609
|
-
`/v1/workspaces/${workspaceId}/new-session-draft`,
|
|
610
|
-
request
|
|
611
|
-
);
|
|
612
|
-
}
|
|
613
|
-
async getSession(workspaceId, sessionId) {
|
|
614
|
-
return await this.requestJson(
|
|
615
|
-
"GET",
|
|
616
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}`
|
|
617
|
-
);
|
|
618
|
-
}
|
|
619
|
-
async updateSession(workspaceId, sessionId, request) {
|
|
620
|
-
return await this.requestJson(
|
|
621
|
-
"PATCH",
|
|
622
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}`,
|
|
623
|
-
request
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
/** Replace the durable tool policy or explicitly adopt workspace defaults. */
|
|
627
|
-
async updateSessionToolPolicy(workspaceId, sessionId, request) {
|
|
628
|
-
return await this.requestJson(
|
|
629
|
-
"PUT",
|
|
630
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/tool-policy`,
|
|
631
|
-
request
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Replace one attached MCP server's approval policy. The change is captured
|
|
636
|
-
* by the next claimed attempt; already-claimed work keeps its immutable
|
|
637
|
-
* policy snapshot.
|
|
638
|
-
*/
|
|
639
|
-
async updateSessionMcpApprovalPolicy(workspaceId, sessionId, serverId, request) {
|
|
640
|
-
return await this.requestJson(
|
|
641
|
-
"PATCH",
|
|
642
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/mcp-servers/${encodeURIComponent(serverId)}/approval-policy`,
|
|
643
|
-
request
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
|
-
async listSessions(workspaceId, options = {}) {
|
|
647
|
-
if (options.search?.trim()) {
|
|
648
|
-
const page = await this.listSessionPage(workspaceId, options);
|
|
649
|
-
return [...page.pinned, ...page.sessions];
|
|
650
|
-
}
|
|
651
|
-
return await this.requestJson(
|
|
652
|
-
"GET",
|
|
653
|
-
`/v1/workspaces/${workspaceId}/sessions`,
|
|
654
|
-
void 0,
|
|
655
|
-
sessionListQuery(options)
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
/** Pin-aware ordinary-session page with a stable keyset cursor. */
|
|
659
|
-
async listSessionPage(workspaceId, options = {}) {
|
|
660
|
-
const search = options.search?.trim();
|
|
661
|
-
let response;
|
|
662
|
-
try {
|
|
663
|
-
response = await this.requestJson(
|
|
664
|
-
"GET",
|
|
665
|
-
`/v1/workspaces/${workspaceId}/sessions`,
|
|
666
|
-
void 0,
|
|
667
|
-
{
|
|
668
|
-
view: "page",
|
|
669
|
-
...sessionListQuery(options),
|
|
670
|
-
...options.cursor !== void 0 ? { cursor: options.cursor } : {},
|
|
671
|
-
...search ? { search } : {},
|
|
672
|
-
...options.pinsOnly ? { pinsOnly: "true" } : {}
|
|
673
|
-
}
|
|
674
|
-
);
|
|
675
|
-
} catch (error) {
|
|
676
|
-
if (error instanceof OpenGeniApiError && error.status === 410) {
|
|
677
|
-
throw new OpenGeniSessionListCursorError(error.status, error.body, {
|
|
678
|
-
...error.code ? { code: error.code } : {},
|
|
679
|
-
retryable: error.retryable,
|
|
680
|
-
...error.correlationId ? { correlationId: error.correlationId } : {},
|
|
681
|
-
outcomeUnknown: error.outcomeUnknown,
|
|
682
|
-
displayMessage: "The session list changed \u2014 refresh and try again."
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
throw error;
|
|
686
|
-
}
|
|
687
|
-
if (Array.isArray(response)) {
|
|
688
|
-
if (options.cursor) {
|
|
689
|
-
throw new Error("The connected OpenGeni API does not support stable session-page cursors");
|
|
690
|
-
}
|
|
691
|
-
if (search) {
|
|
692
|
-
throw new Error("The connected OpenGeni API does not support session search");
|
|
693
|
-
}
|
|
694
|
-
if (options.pinsOnly) {
|
|
695
|
-
throw new Error("The connected OpenGeni API does not support pins-only session lists");
|
|
696
|
-
}
|
|
697
|
-
return { pinned: [], sessions: response, nextCursor: null };
|
|
698
|
-
}
|
|
699
|
-
return response;
|
|
700
|
-
}
|
|
701
|
-
/** Set this authenticated member's personal workspace pin for a session. */
|
|
702
|
-
async updateSessionPin(workspaceId, sessionId, request) {
|
|
703
|
-
return await this.requestJson(
|
|
704
|
-
"PUT",
|
|
705
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/pin`,
|
|
706
|
-
request
|
|
707
|
-
);
|
|
708
|
-
}
|
|
709
|
-
async getSessionLineage(workspaceId, sessionId) {
|
|
710
|
-
return await this.requestJson(
|
|
711
|
-
"GET",
|
|
712
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/lineage`
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
async listTurns(workspaceId, sessionId, options = {}) {
|
|
716
|
-
return await this.requestJson(
|
|
717
|
-
"GET",
|
|
718
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/turns`,
|
|
719
|
-
void 0,
|
|
720
|
-
{
|
|
721
|
-
...options.limit !== void 0 ? { limit: String(options.limit) } : {},
|
|
722
|
-
...options.latestStarted ? { latestStarted: "1" } : {}
|
|
723
|
-
}
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
/** Newest turn that durably emitted `turn.started`, or null before any admission. */
|
|
727
|
-
async getLatestStartedTurn(workspaceId, sessionId) {
|
|
728
|
-
const turns = await this.listTurns(workspaceId, sessionId, { latestStarted: true });
|
|
729
|
-
return turns[0] ?? null;
|
|
730
|
-
}
|
|
731
|
-
// --- Bring-your-own-compute: Machines dashboard + metrics (M10) ------------
|
|
732
|
-
/**
|
|
733
|
-
* List the workspace's machines (the Machines dashboard). Each enrolled
|
|
734
|
-
* selfhosted machine carries its derived state + latest metrics +
|
|
735
|
-
* sharedSessionCount. Pass `sessionId` for an in-session view, which adds the
|
|
736
|
-
* session's synthetic Modal group box + the active-sandbox pointer.
|
|
737
|
-
*/
|
|
738
|
-
async listMachines(workspaceId, options = {}) {
|
|
739
|
-
return await this.requestJson(
|
|
740
|
-
"GET",
|
|
741
|
-
`/v1/workspaces/${workspaceId}/machines`,
|
|
742
|
-
void 0,
|
|
743
|
-
{
|
|
744
|
-
...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {}
|
|
745
|
-
},
|
|
746
|
-
{ signal: options.signal }
|
|
747
|
-
);
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* Read the downsampled (~1/min) metrics series for ONE machine over a time
|
|
751
|
-
* window (default 1h). The samples are oldest-first (a left-to-right chart).
|
|
752
|
-
*/
|
|
753
|
-
async machineMetricsSeries(workspaceId, enrollmentId, options = {}) {
|
|
754
|
-
const response = await this.requestJson(
|
|
755
|
-
"GET",
|
|
756
|
-
`/v1/workspaces/${workspaceId}/machines/${enrollmentId}/metrics/series`,
|
|
757
|
-
void 0,
|
|
758
|
-
{ ...options.window !== void 0 ? { window: options.window } : {} }
|
|
759
|
-
);
|
|
760
|
-
return response.samples;
|
|
761
|
-
}
|
|
762
|
-
// --- Self-hosted enrollment UX (design 11) --------------------------------
|
|
763
|
-
/**
|
|
764
|
-
* Resolve a pending device-enrollment flow by its user_code for the click-Grant
|
|
765
|
-
* approve page (EnrollmentConsent). NO workspace in the path — the server
|
|
766
|
-
* resolves the workspace from the (globally-unique-among-pending) code, then
|
|
767
|
-
* authorizes the caller against it (enrollments:read). Rejects (404) when the
|
|
768
|
-
* code is unknown/expired OR the caller lacks the grant — the two are
|
|
769
|
-
* indistinguishable by design (no cross-workspace disclosure). Does not consume
|
|
770
|
-
* the request.
|
|
771
|
-
*/
|
|
772
|
-
async lookupDeviceEnrollment(userCode) {
|
|
773
|
-
return await this.requestJson(
|
|
774
|
-
"POST",
|
|
775
|
-
"/v1/enrollments/device/lookup",
|
|
776
|
-
{ userCode }
|
|
777
|
-
);
|
|
778
|
-
}
|
|
779
|
-
/**
|
|
780
|
-
* Approve a pending device-enrollment flow (the LOUD consent step). `allowScreenControl`
|
|
781
|
-
* is the authoritative screen-control consent (whole-machine is mandatory/implicit).
|
|
782
|
-
* Lands an enrollment + a selfhosted sandbox and unblocks the agent's poll.
|
|
783
|
-
*/
|
|
784
|
-
async approveDeviceEnrollment(workspaceId, request) {
|
|
785
|
-
return await this.requestJson(
|
|
786
|
-
"POST",
|
|
787
|
-
`/v1/workspaces/${workspaceId}/enrollments/device/approve`,
|
|
788
|
-
{ userCode: request.userCode, allowScreenControl: request.allowScreenControl ?? false }
|
|
789
|
-
);
|
|
790
|
-
}
|
|
791
|
-
/** Deny a pending device-enrollment flow (the explicit "no" at the approve page). */
|
|
792
|
-
async denyDeviceEnrollment(workspaceId, request) {
|
|
793
|
-
return await this.requestJson(
|
|
794
|
-
"POST",
|
|
795
|
-
`/v1/workspaces/${workspaceId}/enrollments/device/deny`,
|
|
796
|
-
{ userCode: request.userCode }
|
|
797
|
-
);
|
|
798
|
-
}
|
|
799
|
-
/**
|
|
800
|
-
* Mint a short-TTL headless enroll token (the `oget_` token) for the fleet /
|
|
801
|
-
* non-interactive enroll path. The returned `token` is SECRET — surface it once
|
|
802
|
-
* with a copy-now warning; it cannot be re-read. `allowScreenControl` bakes the
|
|
803
|
-
* screen-control consent into the token.
|
|
804
|
-
*/
|
|
805
|
-
async mintEnrollToken(workspaceId, request = {}) {
|
|
806
|
-
return await this.requestJson(
|
|
807
|
-
"POST",
|
|
808
|
-
`/v1/workspaces/${workspaceId}/enrollments/token`,
|
|
809
|
-
{ allowScreenControl: request.allowScreenControl ?? false }
|
|
810
|
-
);
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Swap a session's active sandbox (the user-authenticated equivalent of the
|
|
814
|
-
* M7 `sandbox_swap` MCP tool). `target` is a `MachineView.sandboxId` from
|
|
815
|
-
* `listMachines`, or "session"/"default" to swap back to the session's own
|
|
816
|
-
* group box. Validation (ownership/liveness/epoch fence) is server-side; the
|
|
817
|
-
* result echoes the resulting pointer (`swapped: false` + `reason` on a
|
|
818
|
-
* rejected target or a lost epoch fence).
|
|
819
|
-
*/
|
|
820
|
-
async swapActiveSandbox(workspaceId, sessionId, request) {
|
|
821
|
-
return await this.requestJson(
|
|
822
|
-
"POST",
|
|
823
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/active-sandbox`,
|
|
824
|
-
request
|
|
825
|
-
);
|
|
826
|
-
}
|
|
827
|
-
// --- Scheduled tasks -------------------------------------------------------
|
|
828
|
-
async listScheduledTasks(workspaceId, options = {}) {
|
|
829
|
-
return await this.requestJson(
|
|
830
|
-
"GET",
|
|
831
|
-
`/v1/workspaces/${workspaceId}/scheduled-tasks`,
|
|
832
|
-
void 0,
|
|
833
|
-
{
|
|
834
|
-
...options.limit !== void 0 ? { limit: String(options.limit) } : {}
|
|
835
|
-
}
|
|
836
|
-
);
|
|
837
|
-
}
|
|
838
|
-
async getScheduledTask(workspaceId, taskId) {
|
|
839
|
-
return await this.requestJson(
|
|
840
|
-
"GET",
|
|
841
|
-
`/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`
|
|
842
|
-
);
|
|
843
|
-
}
|
|
844
|
-
// --- Events: replay, send, stream ----------------------------------------
|
|
845
|
-
/**
|
|
846
|
-
* Return the events from one bounded page. With no cursor, this uses the safe
|
|
847
|
-
* semantic monitoring tail; pass explicit forensic options and a cursor for
|
|
848
|
-
* retained audit replay. Use `listEventPage` when projection, coverage, or
|
|
849
|
-
* resume-cursor facts are required.
|
|
850
|
-
*/
|
|
851
|
-
async listEvents(workspaceId, sessionId, options = {}) {
|
|
852
|
-
return (await this.listEventPage(workspaceId, sessionId, options)).events;
|
|
853
|
-
}
|
|
854
|
-
async listEventPage(workspaceId, sessionId, options = {}) {
|
|
855
|
-
if (options.latest && ["includeTypes", "excludeTypes", "includeClasses", "excludeClasses"].some(
|
|
856
|
-
(name) => Object.prototype.hasOwnProperty.call(options, name)
|
|
857
|
-
)) {
|
|
858
|
-
throw new TypeError("latest cannot be combined with event filters");
|
|
859
|
-
}
|
|
860
|
-
if (options.resultMode === "compact" && !options.latest) {
|
|
861
|
-
throw new TypeError("resultMode=compact requires latest");
|
|
862
|
-
}
|
|
863
|
-
const listOptions = options.resultMode === "compact" ? null : options;
|
|
864
|
-
const correlationId = crypto.randomUUID();
|
|
865
|
-
const response = await this.fetchImpl(
|
|
866
|
-
this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, {
|
|
867
|
-
...listOptions?.after !== void 0 ? { after: String(listOptions.after) } : {},
|
|
868
|
-
...listOptions?.before !== void 0 ? { before: String(listOptions.before) } : {},
|
|
869
|
-
...listOptions?.limit !== void 0 ? { limit: String(listOptions.limit) } : {},
|
|
870
|
-
...listOptions?.compact ? { compact: "1" } : {},
|
|
871
|
-
...options.mode ? { mode: options.mode } : {},
|
|
872
|
-
...listOptions?.direction ? { direction: listOptions.direction } : {},
|
|
873
|
-
...options.payloadMode ? { payloadMode: options.payloadMode } : {},
|
|
874
|
-
...options.resultMode ? { resultMode: options.resultMode } : {},
|
|
875
|
-
...listOptions?.includeTypes?.length ? { includeTypes: listOptions.includeTypes.join(",") } : {},
|
|
876
|
-
...listOptions?.excludeTypes?.length ? { excludeTypes: listOptions.excludeTypes.join(",") } : {},
|
|
877
|
-
...listOptions?.includeClasses?.length ? { includeClasses: listOptions.includeClasses.join(",") } : {},
|
|
878
|
-
...listOptions?.excludeClasses?.length ? { excludeClasses: listOptions.excludeClasses.join(",") } : {},
|
|
879
|
-
...options.latest ? { latest: options.latest } : {}
|
|
880
|
-
}),
|
|
881
|
-
{
|
|
882
|
-
method: "GET",
|
|
883
|
-
headers: { ...this.headers(correlationId), Accept: "application/json" }
|
|
884
|
-
}
|
|
885
|
-
);
|
|
886
|
-
assertApiContractResponse(response);
|
|
887
|
-
if (!response.ok) {
|
|
888
|
-
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
889
|
-
}
|
|
890
|
-
await assertJsonResponse(response, { method: "GET", correlationId });
|
|
891
|
-
const body = await response.json();
|
|
892
|
-
if (options.resultMode === "compact") {
|
|
893
|
-
return body;
|
|
894
|
-
}
|
|
895
|
-
const events = body;
|
|
896
|
-
const integerHeader = (name) => {
|
|
897
|
-
const raw = response.headers.get(name);
|
|
898
|
-
if (raw === null) return null;
|
|
899
|
-
const value = Number(raw);
|
|
900
|
-
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
901
|
-
};
|
|
902
|
-
const mode = response.headers.get("X-OpenGeni-Event-Mode") === "forensic" ? "forensic" : "monitoring";
|
|
903
|
-
const direction = response.headers.get("X-OpenGeni-Event-Direction") === "after" ? "after" : "before";
|
|
904
|
-
const payloadHeader = response.headers.get("X-OpenGeni-Payload-Mode");
|
|
905
|
-
const payloadMode = payloadHeader === "none" || payloadHeader === "full" ? payloadHeader : "summary";
|
|
906
|
-
const first = integerHeader("X-OpenGeni-Covered-First");
|
|
907
|
-
const last = integerHeader("X-OpenGeni-Covered-Last");
|
|
908
|
-
const bytes = integerHeader("X-OpenGeni-Page-Bytes") ?? new TextEncoder().encode(JSON.stringify(events)).byteLength;
|
|
909
|
-
const maxBytes = integerHeader("X-OpenGeni-Page-Max-Bytes") ?? 1024 * 1024;
|
|
910
|
-
const truncatedByHeader = response.headers.get("X-OpenGeni-Truncated-By");
|
|
911
|
-
const truncatedBy = truncatedByHeader === "count" || truncatedByHeader === "bytes" || truncatedByHeader === "http_bytes" ? truncatedByHeader : null;
|
|
912
|
-
return {
|
|
913
|
-
events,
|
|
914
|
-
mode,
|
|
915
|
-
payloadMode,
|
|
916
|
-
direction,
|
|
917
|
-
bytes,
|
|
918
|
-
maxBytes,
|
|
919
|
-
truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
|
|
920
|
-
hasMore: response.headers.get("X-OpenGeni-Has-More") === "true",
|
|
921
|
-
truncatedBy,
|
|
922
|
-
coveredSequence: first === null || last === null ? null : { first, last },
|
|
923
|
-
nextAfter: integerHeader("X-OpenGeni-Next-After"),
|
|
924
|
-
nextBefore: integerHeader("X-OpenGeni-Next-Before"),
|
|
925
|
-
forensicExact: response.headers.get("X-OpenGeni-Forensic-Exact") === "true"
|
|
926
|
-
};
|
|
927
|
-
}
|
|
928
|
-
/**
|
|
929
|
-
* Fetch the authoritative newest-sequence semantic result directly. This is
|
|
930
|
-
* the callback-loss recovery path: it reads one compact durable result and
|
|
931
|
-
* never creates a model turn. `latest: "receipt"` aliases `tool_receipt`;
|
|
932
|
-
* turn generation remains scoped retry metadata.
|
|
933
|
-
*/
|
|
934
|
-
async getLatestEventResult(workspaceId, sessionId, options = { latest: "terminal" }) {
|
|
935
|
-
return await this.listEventPage(workspaceId, sessionId, {
|
|
936
|
-
...options,
|
|
937
|
-
resultMode: "compact"
|
|
938
|
-
});
|
|
939
|
-
}
|
|
940
|
-
/** POST a user/control event to the session. Returns the accepted event. */
|
|
941
|
-
async sendEvent(workspaceId, sessionId, event) {
|
|
942
|
-
return await this.requestJson(
|
|
943
|
-
"POST",
|
|
944
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`,
|
|
945
|
-
event
|
|
946
|
-
);
|
|
947
|
-
}
|
|
948
|
-
async sendMessage(workspaceId, sessionId, message) {
|
|
949
|
-
const input = typeof message === "string" ? { text: message } : message;
|
|
950
|
-
const { clientEventId, ...payload } = input;
|
|
951
|
-
return await this.sendEvent(workspaceId, sessionId, {
|
|
952
|
-
type: "user.message",
|
|
953
|
-
...clientEventId !== void 0 ? { clientEventId } : {},
|
|
954
|
-
payload
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
async pauseSession(workspaceId, sessionId, options = {}) {
|
|
958
|
-
return await this.controlSession(workspaceId, sessionId, {
|
|
959
|
-
action: "pause",
|
|
960
|
-
clientEventId: options.clientEventId ?? crypto.randomUUID(),
|
|
961
|
-
...options.reason ? { reason: options.reason } : {},
|
|
962
|
-
...options.expectedControlEtag ? { expectedControlEtag: options.expectedControlEtag } : {}
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
|
-
async sendApprovalDecision(workspaceId, sessionId, decision) {
|
|
966
|
-
const { clientEventId, ...payload } = decision;
|
|
967
|
-
return await this.sendEvent(workspaceId, sessionId, {
|
|
968
|
-
type: "user.approvalDecision",
|
|
969
|
-
...clientEventId !== void 0 ? { clientEventId } : {},
|
|
970
|
-
payload
|
|
971
|
-
});
|
|
972
|
-
}
|
|
973
|
-
async listHumanInputRequests(workspaceId, sessionId, options = {}) {
|
|
974
|
-
const result = await this.requestJson(
|
|
975
|
-
"GET",
|
|
976
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests`,
|
|
977
|
-
void 0,
|
|
978
|
-
options.status ? { status: options.status } : void 0
|
|
979
|
-
);
|
|
980
|
-
return result.requests;
|
|
981
|
-
}
|
|
982
|
-
async getHumanInputRequest(workspaceId, sessionId, requestId) {
|
|
983
|
-
return await this.requestJson(
|
|
984
|
-
"GET",
|
|
985
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests/${requestId}`
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
async submitHumanInputResponse(workspaceId, sessionId, requestId, response, options = {}) {
|
|
989
|
-
return await this.sendEvent(workspaceId, sessionId, {
|
|
990
|
-
type: "user.humanInputResponse",
|
|
991
|
-
...options.clientEventId ? { clientEventId: options.clientEventId } : {},
|
|
992
|
-
payload: { requestId, response }
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* Live-stream a session's events with automatic reconnect, resume from the
|
|
997
|
-
* last seen sequence, gap backfill, and duplicate suppression. See
|
|
998
|
-
* {@link streamSessionEvents} for the delivery guarantees.
|
|
999
|
-
*/
|
|
1000
|
-
streamEvents(workspaceId, sessionId, options = {}) {
|
|
1001
|
-
return streamSessionEvents(this.eventStreamTransport(workspaceId, sessionId), options);
|
|
1002
|
-
}
|
|
1003
|
-
/** The transport `streamEvents` runs on; useful for custom streaming layers. */
|
|
1004
|
-
eventStreamTransport(workspaceId, sessionId) {
|
|
1005
|
-
return {
|
|
1006
|
-
openStream: async (after, signal) => await this.openEventStream(workspaceId, sessionId, {
|
|
1007
|
-
after,
|
|
1008
|
-
...signal ? { signal } : {}
|
|
1009
|
-
}),
|
|
1010
|
-
listEvents: async (after, limit) => await this.listEvents(workspaceId, sessionId, { after, limit })
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
/** Open one raw SSE connection (no reconnect). Most callers want `streamEvents`. */
|
|
1014
|
-
async openEventStream(workspaceId, sessionId, options = {}) {
|
|
1015
|
-
const url = this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events/stream`, {
|
|
1016
|
-
after: String(options.after ?? 0)
|
|
1017
|
-
});
|
|
1018
|
-
const correlationId = crypto.randomUUID();
|
|
1019
|
-
const response = await this.fetchImpl(url, {
|
|
1020
|
-
method: "GET",
|
|
1021
|
-
headers: { ...this.headers(correlationId), Accept: "text/event-stream" },
|
|
1022
|
-
...options.signal ? { signal: options.signal } : {}
|
|
1023
|
-
});
|
|
1024
|
-
assertApiContractResponse(response);
|
|
1025
|
-
if (!response.ok) {
|
|
1026
|
-
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
1027
|
-
}
|
|
1028
|
-
if (!response.body) {
|
|
1029
|
-
throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
|
|
1030
|
-
}
|
|
1031
|
-
return response.body;
|
|
1032
|
-
}
|
|
1033
|
-
// --- Turn queue ------------------------------------------------------------
|
|
1034
|
-
async getQueue(workspaceId, sessionId) {
|
|
1035
|
-
return await this.requestJson(
|
|
1036
|
-
"GET",
|
|
1037
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue`
|
|
1038
|
-
);
|
|
1039
|
-
}
|
|
1040
|
-
async moveQueueItem(workspaceId, sessionId, turnId, request) {
|
|
1041
|
-
return await this.requestJson(
|
|
1042
|
-
"POST",
|
|
1043
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/move`,
|
|
1044
|
-
request
|
|
1045
|
-
);
|
|
1046
|
-
}
|
|
1047
|
-
async editQueueItem(workspaceId, sessionId, turnId, request) {
|
|
1048
|
-
return await this.requestJson(
|
|
1049
|
-
"POST",
|
|
1050
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/edit`,
|
|
1051
|
-
request
|
|
1052
|
-
);
|
|
1053
|
-
}
|
|
1054
|
-
async steerQueueItem(workspaceId, sessionId, turnId, request) {
|
|
1055
|
-
return await this.requestJson(
|
|
1056
|
-
"POST",
|
|
1057
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/steer`,
|
|
1058
|
-
request
|
|
1059
|
-
);
|
|
1060
|
-
}
|
|
1061
|
-
async deleteQueueItem(workspaceId, sessionId, turnId, request) {
|
|
1062
|
-
return await this.requestJson(
|
|
1063
|
-
"POST",
|
|
1064
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/delete`,
|
|
1065
|
-
request
|
|
1066
|
-
);
|
|
1067
|
-
}
|
|
1068
|
-
async getComposerDraft(workspaceId, sessionId) {
|
|
1069
|
-
return await this.requestJson(
|
|
1070
|
-
"GET",
|
|
1071
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`
|
|
1072
|
-
);
|
|
1073
|
-
}
|
|
1074
|
-
async saveComposerDraft(workspaceId, sessionId, request) {
|
|
1075
|
-
return await this.requestJson(
|
|
1076
|
-
"PUT",
|
|
1077
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`,
|
|
1078
|
-
request
|
|
1079
|
-
);
|
|
1080
|
-
}
|
|
1081
|
-
async controlSession(workspaceId, sessionId, request) {
|
|
1082
|
-
return await this.requestJson(
|
|
1083
|
-
"POST",
|
|
1084
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/control`,
|
|
1085
|
-
request
|
|
1086
|
-
);
|
|
1087
|
-
}
|
|
1088
|
-
async resumeSession(workspaceId, sessionId, options = {}) {
|
|
1089
|
-
return await this.controlSession(workspaceId, sessionId, {
|
|
1090
|
-
action: "resume",
|
|
1091
|
-
clientEventId: options.clientEventId ?? crypto.randomUUID(),
|
|
1092
|
-
...options.reason ? { reason: options.reason } : {},
|
|
1093
|
-
...options.expectedControlEtag ? { expectedControlEtag: options.expectedControlEtag } : {}
|
|
1094
|
-
});
|
|
1095
|
-
}
|
|
1096
|
-
async setWorkspaceInferenceState(workspaceId, request) {
|
|
1097
|
-
return await this.requestJson(
|
|
1098
|
-
"POST",
|
|
1099
|
-
`/v1/workspaces/${workspaceId}/inference-control`,
|
|
1100
|
-
request
|
|
1101
|
-
);
|
|
1102
|
-
}
|
|
1103
|
-
async listWorkspaceControlEvents(workspaceId, options = {}) {
|
|
1104
|
-
return (await this.listWorkspaceControlEventPage(workspaceId, options)).events;
|
|
1105
|
-
}
|
|
1106
|
-
/** Count/byte-bounded page plus an explicit continuation cursor. */
|
|
1107
|
-
async listWorkspaceControlEventPage(workspaceId, options = {}) {
|
|
1108
|
-
const correlationId = crypto.randomUUID();
|
|
1109
|
-
const response = await this.fetchImpl(
|
|
1110
|
-
this.url(`/v1/workspaces/${workspaceId}/control-events`, {
|
|
1111
|
-
...options.after !== void 0 ? { after: String(options.after) } : {},
|
|
1112
|
-
...options.limit !== void 0 ? { limit: String(options.limit) } : {}
|
|
1113
|
-
}),
|
|
1114
|
-
{
|
|
1115
|
-
method: "GET",
|
|
1116
|
-
headers: { ...this.headers(correlationId), Accept: "application/json" }
|
|
1117
|
-
}
|
|
1118
|
-
);
|
|
1119
|
-
assertApiContractResponse(response);
|
|
1120
|
-
if (!response.ok) {
|
|
1121
|
-
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
1122
|
-
}
|
|
1123
|
-
await assertJsonResponse(response, { method: "GET", correlationId });
|
|
1124
|
-
const events = await response.json();
|
|
1125
|
-
const bytesHeader = response.headers.get("X-OpenGeni-Page-Bytes");
|
|
1126
|
-
const nextHeader = response.headers.get("X-OpenGeni-Next-After");
|
|
1127
|
-
const parsedBytes = bytesHeader === null ? Number.NaN : Number(bytesHeader);
|
|
1128
|
-
const parsedNext = nextHeader === null ? null : Number(nextHeader);
|
|
1129
|
-
return {
|
|
1130
|
-
events,
|
|
1131
|
-
bytes: Number.isSafeInteger(parsedBytes) && parsedBytes >= 0 ? parsedBytes : new TextEncoder().encode(JSON.stringify(events)).byteLength,
|
|
1132
|
-
truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
|
|
1133
|
-
nextAfter: parsedNext !== null && Number.isSafeInteger(parsedNext) && parsedNext >= 0 ? parsedNext : null
|
|
1134
|
-
};
|
|
1135
|
-
}
|
|
1136
|
-
streamWorkspaceControlEvents(workspaceId, options = {}) {
|
|
1137
|
-
return streamWorkspaceControlEvents(this.workspaceControlStreamTransport(workspaceId), options);
|
|
1138
|
-
}
|
|
1139
|
-
workspaceControlStreamTransport(workspaceId) {
|
|
1140
|
-
return {
|
|
1141
|
-
openStream: async (after, signal) => await this.openWorkspaceControlEventStream(workspaceId, {
|
|
1142
|
-
after,
|
|
1143
|
-
...signal ? { signal } : {}
|
|
1144
|
-
})
|
|
1145
|
-
};
|
|
1146
|
-
}
|
|
1147
|
-
async openWorkspaceControlEventStream(workspaceId, options = {}) {
|
|
1148
|
-
const correlationId = crypto.randomUUID();
|
|
1149
|
-
const response = await this.fetchImpl(
|
|
1150
|
-
this.url(`/v1/workspaces/${workspaceId}/control-events/stream`, {
|
|
1151
|
-
after: String(options.after ?? 0)
|
|
1152
|
-
}),
|
|
1153
|
-
{
|
|
1154
|
-
method: "GET",
|
|
1155
|
-
headers: { ...this.headers(correlationId), Accept: "text/event-stream" },
|
|
1156
|
-
...options.signal ? { signal: options.signal } : {}
|
|
1157
|
-
}
|
|
1158
|
-
);
|
|
1159
|
-
assertApiContractResponse(response);
|
|
1160
|
-
if (!response.ok) {
|
|
1161
|
-
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
1162
|
-
}
|
|
1163
|
-
if (!response.body) {
|
|
1164
|
-
throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
|
|
1165
|
-
}
|
|
1166
|
-
return response.body;
|
|
1167
|
-
}
|
|
1168
|
-
/**
|
|
1169
|
-
* Steer: atomically put this prompt at the head and supersede the current
|
|
1170
|
-
* inference. The client performs one request and renders server order.
|
|
1171
|
-
*/
|
|
1172
|
-
async steerMessage(workspaceId, sessionId, message) {
|
|
1173
|
-
const input = typeof message === "string" ? { text: message } : message;
|
|
1174
|
-
return await this.requestJson(
|
|
1175
|
-
"POST",
|
|
1176
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/steer`,
|
|
1177
|
-
input
|
|
1178
|
-
);
|
|
1179
|
-
}
|
|
1180
|
-
// --- Goals -------------------------------------------------------------------
|
|
1181
|
-
/** The session's goal. 404s when the session never had one. */
|
|
1182
|
-
async getGoal(workspaceId, sessionId) {
|
|
1183
|
-
return await this.requestJson(
|
|
1184
|
-
"GET",
|
|
1185
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`
|
|
1186
|
-
);
|
|
1187
|
-
}
|
|
1188
|
-
async updateGoal(workspaceId, sessionId, request) {
|
|
1189
|
-
return await this.requestJson(
|
|
1190
|
-
"PATCH",
|
|
1191
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`,
|
|
1192
|
-
request
|
|
1193
|
-
);
|
|
1194
|
-
}
|
|
1195
|
-
async deleteGoal(workspaceId, sessionId) {
|
|
1196
|
-
await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`);
|
|
1197
|
-
}
|
|
1198
|
-
/** Pause the goal loop: the session stops self-continuing until resumed. */
|
|
1199
|
-
async pauseGoal(workspaceId, sessionId, options = {}) {
|
|
1200
|
-
return await this.updateGoal(workspaceId, sessionId, {
|
|
1201
|
-
status: "paused",
|
|
1202
|
-
...options.rationale !== void 0 ? { rationale: options.rationale } : {}
|
|
1203
|
-
});
|
|
1204
|
-
}
|
|
1205
|
-
/** Resume a paused goal: resets counters and re-arms the continuation loop. */
|
|
1206
|
-
async resumeGoal(workspaceId, sessionId) {
|
|
1207
|
-
return await this.updateGoal(workspaceId, sessionId, { status: "active" });
|
|
1208
|
-
}
|
|
1209
|
-
// --- Operator context controls (/clear, /compact) ---------------------------
|
|
1210
|
-
/**
|
|
1211
|
-
* Clear the session's conversation context. Destructive and audit-preserving:
|
|
1212
|
-
* the server supersedes (never deletes) the live history and emits a
|
|
1213
|
-
* `session.context.cleared` event. Refused (409) while a turn is in flight or
|
|
1214
|
-
* awaiting action. `confirm:true` is sent so an accidental call cannot wipe
|
|
1215
|
-
* context — the destructive intent is explicit on the wire.
|
|
1216
|
-
*/
|
|
1217
|
-
async clearSessionContext(workspaceId, sessionId) {
|
|
1218
|
-
await this.requestVoid(
|
|
1219
|
-
"POST",
|
|
1220
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/context/clear`,
|
|
1221
|
-
{ confirm: true }
|
|
1222
|
-
);
|
|
1223
|
-
}
|
|
1224
|
-
/** Request one durable portable compaction at the next safe model boundary. */
|
|
1225
|
-
async compactSessionContext(workspaceId, sessionId) {
|
|
1226
|
-
return await this.requestJson(
|
|
1227
|
-
"POST",
|
|
1228
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/context/compact`,
|
|
1229
|
-
{}
|
|
1230
|
-
);
|
|
1231
|
-
}
|
|
1232
|
-
// --- Channel-A structured services (P4.4) ------------------------------------
|
|
1233
|
-
// FileSystem (Pierre tree), Git (Pierre diff), Terminal (exec + PTY). Each is a
|
|
1234
|
-
// synchronous API-direct point query; the fs.changed/git.changed/terminal.pty.*
|
|
1235
|
-
// notifications + the PTY output stream arrive on the existing event SSE.
|
|
1236
|
-
/** FileSystem: list a directory tree (feeds the Pierre file tree). */
|
|
1237
|
-
async fsList(workspaceId, sessionId, request = {}, options = {}) {
|
|
1238
|
-
return await this.requestJson(
|
|
1239
|
-
"POST",
|
|
1240
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list`,
|
|
1241
|
-
request,
|
|
1242
|
-
{},
|
|
1243
|
-
options
|
|
1244
|
-
);
|
|
1245
|
-
}
|
|
1246
|
-
/** FileSystem: read a file (text or base64; binary-safe, size-capped). */
|
|
1247
|
-
async fsRead(workspaceId, sessionId, request, options = {}) {
|
|
1248
|
-
return await this.requestJson(
|
|
1249
|
-
"POST",
|
|
1250
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/read`,
|
|
1251
|
-
request,
|
|
1252
|
-
{},
|
|
1253
|
-
options
|
|
1254
|
-
);
|
|
1255
|
-
}
|
|
1256
|
-
/** FileSystem: write a file (last-writer-wins; emits fs.changed). */
|
|
1257
|
-
async fsWrite(workspaceId, sessionId, request) {
|
|
1258
|
-
return await this.requestJson(
|
|
1259
|
-
"POST",
|
|
1260
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/write`,
|
|
1261
|
-
request
|
|
1262
|
-
);
|
|
1263
|
-
}
|
|
1264
|
-
/** FileSystem: delete a path (emits fs.changed). */
|
|
1265
|
-
async fsDelete(workspaceId, sessionId, request) {
|
|
1266
|
-
return await this.requestJson(
|
|
1267
|
-
"POST",
|
|
1268
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/delete`,
|
|
1269
|
-
request
|
|
1270
|
-
);
|
|
1271
|
-
}
|
|
1272
|
-
/** FileSystem: move/rename a path (emits fs.changed; 409 if destination exists and overwrite is false). */
|
|
1273
|
-
async fsMove(workspaceId, sessionId, request) {
|
|
1274
|
-
return await this.requestJson(
|
|
1275
|
-
"POST",
|
|
1276
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/move`,
|
|
1277
|
-
request
|
|
1278
|
-
);
|
|
1279
|
-
}
|
|
1280
|
-
/** FileSystem: create a directory (emits fs.changed; recursive defaults to true). */
|
|
1281
|
-
async fsMkdir(workspaceId, sessionId, request) {
|
|
1282
|
-
return await this.requestJson(
|
|
1283
|
-
"POST",
|
|
1284
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/mkdir`,
|
|
1285
|
-
request
|
|
1286
|
-
);
|
|
1287
|
-
}
|
|
1288
|
-
/** Git: working-tree/index status (the Pierre file-status feed). */
|
|
1289
|
-
async gitStatus(workspaceId, sessionId, request = {}, options = {}) {
|
|
1290
|
-
return await this.requestJson(
|
|
1291
|
-
"POST",
|
|
1292
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/status`,
|
|
1293
|
-
request,
|
|
1294
|
-
{},
|
|
1295
|
-
options
|
|
1296
|
-
);
|
|
1297
|
-
}
|
|
1298
|
-
/** Git: structured diff hunks (the Pierre diff feed). */
|
|
1299
|
-
async gitDiff(workspaceId, sessionId, request = {}, options = {}) {
|
|
1300
|
-
return await this.requestJson(
|
|
1301
|
-
"POST",
|
|
1302
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/diff`,
|
|
1303
|
-
request,
|
|
1304
|
-
{},
|
|
1305
|
-
options
|
|
1306
|
-
);
|
|
1307
|
-
}
|
|
1308
|
-
/** Git: commit log. */
|
|
1309
|
-
async gitLog(workspaceId, sessionId, request = {}) {
|
|
1310
|
-
return await this.requestJson(
|
|
1311
|
-
"POST",
|
|
1312
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/log`,
|
|
1313
|
-
request
|
|
1314
|
-
);
|
|
1315
|
-
}
|
|
1316
|
-
/** Git: show a commit (diff vs first parent) or fetch a raw blob at a ref. */
|
|
1317
|
-
async gitShow(workspaceId, sessionId, request) {
|
|
1318
|
-
return await this.requestJson(
|
|
1319
|
-
"POST",
|
|
1320
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/show`,
|
|
1321
|
-
request
|
|
1322
|
-
);
|
|
1323
|
-
}
|
|
1324
|
-
/** Workspace capture: the latest turn-end snapshot of the session's workspace
|
|
1325
|
-
* (tree + per-repo diff + file after-image refs), served from durable storage
|
|
1326
|
-
* WITHOUT warming a machine — the workbench cold-paint source. Returns
|
|
1327
|
-
* `{available:false}` when no capture exists yet (fall back to the live path). */
|
|
1328
|
-
async getWorkspaceCapture(workspaceId, sessionId, options = {}) {
|
|
1329
|
-
return await this.requestJson(
|
|
1330
|
-
"GET",
|
|
1331
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture`,
|
|
1332
|
-
void 0,
|
|
1333
|
-
{},
|
|
1334
|
-
options
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1337
|
-
/** Workspace capture: a single file's after-image from the capture (revision
|
|
1338
|
-
* pins a specific one; omitted → latest). Content is inline for small files,
|
|
1339
|
-
* else a short-TTL signed URL; a tooLarge file returns metadata only. */
|
|
1340
|
-
async getWorkspaceCaptureFile(workspaceId, sessionId, path, revision, options = {}) {
|
|
1341
|
-
const query = { path };
|
|
1342
|
-
if (revision !== void 0) query.revision = String(revision);
|
|
1343
|
-
return await this.requestJson(
|
|
1344
|
-
"GET",
|
|
1345
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture/file`,
|
|
1346
|
-
void 0,
|
|
1347
|
-
query,
|
|
1348
|
-
options
|
|
1349
|
-
);
|
|
1350
|
-
}
|
|
1351
|
-
/** Terminal: run a bounded command, returning buffered stdout/stderr inline. */
|
|
1352
|
-
async terminalExec(workspaceId, sessionId, request) {
|
|
1353
|
-
return await this.requestJson(
|
|
1354
|
-
"POST",
|
|
1355
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/exec`,
|
|
1356
|
-
request
|
|
1357
|
-
);
|
|
1358
|
-
}
|
|
1359
|
-
/** Terminal: open an interactive PTY. Output streams on the event SSE as
|
|
1360
|
-
* terminal.pty.output.delta; drive it with terminalPtyWrite. */
|
|
1361
|
-
async terminalPtyOpen(workspaceId, sessionId, request = {}) {
|
|
1362
|
-
return await this.requestJson(
|
|
1363
|
-
"POST",
|
|
1364
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty`,
|
|
1365
|
-
request
|
|
1366
|
-
);
|
|
1367
|
-
}
|
|
1368
|
-
/** Terminal: send stdin to an open PTY (output rides A1). */
|
|
1369
|
-
async terminalPtyWrite(workspaceId, sessionId, request) {
|
|
1370
|
-
await this.requestVoid(
|
|
1371
|
-
"POST",
|
|
1372
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/write`,
|
|
1373
|
-
request
|
|
1374
|
-
);
|
|
1375
|
-
}
|
|
1376
|
-
/** Terminal: resize an open PTY. */
|
|
1377
|
-
async terminalPtyResize(workspaceId, sessionId, request) {
|
|
1378
|
-
await this.requestVoid(
|
|
1379
|
-
"POST",
|
|
1380
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/resize`,
|
|
1381
|
-
request
|
|
1382
|
-
);
|
|
1383
|
-
}
|
|
1384
|
-
/** Terminal: close an open PTY (idempotent). */
|
|
1385
|
-
async terminalPtyClose(workspaceId, sessionId, request) {
|
|
1386
|
-
await this.requestVoid(
|
|
1387
|
-
"POST",
|
|
1388
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/terminal/pty/close`,
|
|
1389
|
-
request
|
|
1390
|
-
);
|
|
1391
|
-
}
|
|
1392
|
-
// --- Stream surfacing: capability negotiation + viewer lifecycle (Phase 5) ---
|
|
1393
|
-
// The capability doc is the single source of UI truth (degradation is always a
|
|
1394
|
-
// value, never a crash). The desktop pixel plane (Channel B) is gated behind an
|
|
1395
|
-
// un-redacted-acknowledgment + a viewer holder; the structured terminal/files/
|
|
1396
|
-
// git surfaces (Channel A) ride the methods above and the event SSE.
|
|
1397
|
-
/** Read the negotiated capability doc for a session WITHOUT acquiring a viewer
|
|
1398
|
-
* holder (no warm, no spawn). Drives capability-gated rendering: which
|
|
1399
|
-
* surfaces mount, the per-surface unavailability reasons, and the lease
|
|
1400
|
-
* liveness the client polls on while `cold`/`warming`. The desktop URL/token
|
|
1401
|
-
* are minted in-process only when the box is warm AND the principal has
|
|
1402
|
-
* acknowledged the un-redacted plane. */
|
|
1403
|
-
async getStreamCapabilities(workspaceId, sessionId, options = {}) {
|
|
1404
|
-
return await this.requestJson(
|
|
1405
|
-
"GET",
|
|
1406
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`,
|
|
1407
|
-
void 0,
|
|
1408
|
-
{},
|
|
1409
|
-
options
|
|
1410
|
-
);
|
|
1411
|
-
}
|
|
1412
|
-
/** Record the calling principal's acknowledgment of the un-redacted desktop
|
|
1413
|
-
* pixel plane (and, when the box is shared, the shared-exposure disclosure).
|
|
1414
|
-
* The desktop viewer-attach path returns 409 until this is recorded. */
|
|
1415
|
-
async acknowledgeStream(workspaceId, sessionId, request = {}) {
|
|
1416
|
-
return await this.requestJson(
|
|
1417
|
-
"POST",
|
|
1418
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities/acknowledge`,
|
|
1419
|
-
request
|
|
1420
|
-
);
|
|
1421
|
-
}
|
|
1422
|
-
/** Attach a viewer holder (refcounted liveness — keeps the box warm while
|
|
1423
|
-
* watched/used), spinning the box up in-process when cold, and mint the scoped
|
|
1424
|
-
* direct-to-provider URLs for the requested plane(s). `request.desktop:true`
|
|
1425
|
-
* opts into the un-redacted pixel plane and mints the noVNC URL — that plane
|
|
1426
|
-
* alone throws `OpenGeniApiError(409)` when the un-redacted/shared
|
|
1427
|
-
* acknowledgment is missing (the consent gate). A terminal-only attach
|
|
1428
|
-
* (`desktop` omitted/false) warms the box + mints the pty-ws terminal cell with
|
|
1429
|
-
* NO consent gate. An omitted `viewerId` mints a fresh one. */
|
|
1430
|
-
async attachViewer(workspaceId, sessionId, request = {}) {
|
|
1431
|
-
return await this.requestJson(
|
|
1432
|
-
"POST",
|
|
1433
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers`,
|
|
1434
|
-
request
|
|
1435
|
-
);
|
|
1436
|
-
}
|
|
1437
|
-
/** Heartbeat a viewer holder (Channel-A app-level liveness). A closed laptop
|
|
1438
|
-
* stops sending these → the reaper drops the holder within ~90s. Echoes
|
|
1439
|
-
* `leaseEpoch` so a superseded epoch is rejected (`alive:false` → re-attach). */
|
|
1440
|
-
async heartbeatViewer(workspaceId, sessionId, viewerId, request) {
|
|
1441
|
-
return await this.requestJson(
|
|
1442
|
-
"POST",
|
|
1443
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers/${viewerId}/heartbeat`,
|
|
1444
|
-
request
|
|
1445
|
-
);
|
|
1446
|
-
}
|
|
1447
|
-
/** Detach a viewer (delete this holder; idempotent delete-my-row). */
|
|
1448
|
-
async detachViewer(workspaceId, sessionId, viewerId) {
|
|
1449
|
-
await this.requestVoid(
|
|
1450
|
-
"DELETE",
|
|
1451
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/viewers/${viewerId}`
|
|
1452
|
-
);
|
|
1453
|
-
}
|
|
1454
|
-
// --- Access + workspaces -----------------------------------------------------
|
|
1455
|
-
/**
|
|
1456
|
-
* The deployment's public client bootstrap config: the host-exposed models
|
|
1457
|
-
* (provider-grouped in `models`, flat in `allowedModels` for back-compat),
|
|
1458
|
-
* reasoning efforts, MCP servers, file-upload limits, and how the client is
|
|
1459
|
-
* expected to authenticate. Drives a composer's model picker without prior
|
|
1460
|
-
* knowledge of the host setup; safe to call before any auth is established.
|
|
1461
|
-
*/
|
|
1462
|
-
async getClientConfig() {
|
|
1463
|
-
const config = await this.requestJson("GET", "/v1/config/client");
|
|
1464
|
-
if (config.apiContractRevision !== OPENGENI_API_CONTRACT_REVISION) {
|
|
1465
|
-
throw new OpenGeniApiContractMismatchError(
|
|
1466
|
-
OPENGENI_API_CONTRACT_REVISION,
|
|
1467
|
-
String(config.apiContractRevision || "(missing)")
|
|
1468
|
-
);
|
|
1469
|
-
}
|
|
1470
|
-
return config;
|
|
1471
|
-
}
|
|
1472
|
-
/** Authenticated model definitions plus workspace-specific selectability. */
|
|
1473
|
-
async getWorkspaceModelCatalog(workspaceId) {
|
|
1474
|
-
return await this.requestJson(
|
|
1475
|
-
"GET",
|
|
1476
|
-
`/v1/workspaces/${workspaceId}/model-catalog`
|
|
1477
|
-
);
|
|
1478
|
-
}
|
|
1479
|
-
/** The caller's access context: subject, account + workspace grants, defaults. */
|
|
1480
|
-
async getAccessContext() {
|
|
1481
|
-
return await this.requestJson("GET", "/v1/access/me");
|
|
1482
|
-
}
|
|
1483
|
-
async listWorkspaces() {
|
|
1484
|
-
return await this.requestJson("GET", "/v1/workspaces");
|
|
1485
|
-
}
|
|
1486
|
-
async createWorkspace(request) {
|
|
1487
|
-
return await this.requestJson("POST", "/v1/workspaces", request);
|
|
1488
|
-
}
|
|
1489
|
-
async getWorkspace(workspaceId) {
|
|
1490
|
-
return await this.requestJson("GET", `/v1/workspaces/${workspaceId}`);
|
|
1491
|
-
}
|
|
1492
|
-
/** Read-time, secret-safe inventory of policy heads and visible workspace knowledge. */
|
|
1493
|
-
async getWorkspaceState(workspaceId) {
|
|
1494
|
-
return await this.requestJson(
|
|
1495
|
-
"GET",
|
|
1496
|
-
`/v1/workspaces/${workspaceId}/workspace-state`
|
|
1497
|
-
);
|
|
1498
|
-
}
|
|
1499
|
-
async updateWorkspace(workspaceId, request) {
|
|
1500
|
-
return await this.requestJson("PATCH", `/v1/workspaces/${workspaceId}`, request);
|
|
1501
|
-
}
|
|
1502
|
-
/** Inspect immutable instruction-policy history, active heads, and activation audit evidence. */
|
|
1503
|
-
async listWorkspaceInstructionPolicies(workspaceId, options = {}) {
|
|
1504
|
-
const params = new URLSearchParams();
|
|
1505
|
-
if (options.kind !== void 0) params.set("kind", options.kind);
|
|
1506
|
-
if (options.scope !== void 0) params.set("scope", options.scope);
|
|
1507
|
-
if (options.roleKey !== void 0) params.set("roleKey", options.roleKey);
|
|
1508
|
-
if (options.afterRevision !== void 0) {
|
|
1509
|
-
params.set("afterRevision", String(options.afterRevision));
|
|
1510
|
-
}
|
|
1511
|
-
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
1512
|
-
const query = params.toString();
|
|
1513
|
-
return await this.requestJson(
|
|
1514
|
-
"GET",
|
|
1515
|
-
`/v1/workspaces/${workspaceId}/instruction-policies${query ? `?${query}` : ""}`
|
|
1516
|
-
);
|
|
1517
|
-
}
|
|
1518
|
-
async getWorkspaceInstructionPolicyRevision(workspaceId, revisionId) {
|
|
1519
|
-
return await this.requestJson(
|
|
1520
|
-
"GET",
|
|
1521
|
-
`/v1/workspaces/${workspaceId}/instruction-policies/${encodeURIComponent(revisionId)}`
|
|
1522
|
-
);
|
|
1523
|
-
}
|
|
1524
|
-
async createWorkspaceInstructionPolicyDraft(workspaceId, request) {
|
|
1525
|
-
return await this.requestJson(
|
|
1526
|
-
"POST",
|
|
1527
|
-
`/v1/workspaces/${workspaceId}/instruction-policies/drafts`,
|
|
1528
|
-
request
|
|
1529
|
-
);
|
|
1530
|
-
}
|
|
1531
|
-
/** Import the stored legacy workspace override as an inactive charter draft. */
|
|
1532
|
-
async importLegacyWorkspaceInstructionPolicyDraft(workspaceId, request = {}) {
|
|
1533
|
-
return await this.requestJson(
|
|
1534
|
-
"POST",
|
|
1535
|
-
`/v1/workspaces/${workspaceId}/instruction-policies/import-legacy`,
|
|
1536
|
-
request
|
|
1537
|
-
);
|
|
1538
|
-
}
|
|
1539
|
-
async diffWorkspaceInstructionPolicyRevisions(workspaceId, request) {
|
|
1540
|
-
const params = new URLSearchParams({
|
|
1541
|
-
fromRevisionId: request.fromRevisionId,
|
|
1542
|
-
toRevisionId: request.toRevisionId
|
|
1543
|
-
});
|
|
1544
|
-
return await this.requestJson(
|
|
1545
|
-
"GET",
|
|
1546
|
-
`/v1/workspaces/${workspaceId}/instruction-policies/diff?${params}`
|
|
1547
|
-
);
|
|
1548
|
-
}
|
|
1549
|
-
async activateWorkspaceInstructionPolicyRevision(workspaceId, revisionId, request) {
|
|
1550
|
-
return await this.requestJson(
|
|
1551
|
-
"POST",
|
|
1552
|
-
`/v1/workspaces/${workspaceId}/instruction-policies/${encodeURIComponent(revisionId)}/activate`,
|
|
1553
|
-
request
|
|
1554
|
-
);
|
|
1555
|
-
}
|
|
1556
|
-
async rollbackWorkspaceInstructionPolicyRevision(workspaceId, request) {
|
|
1557
|
-
return await this.requestJson(
|
|
1558
|
-
"POST",
|
|
1559
|
-
`/v1/workspaces/${workspaceId}/instruction-policies/rollback`,
|
|
1560
|
-
request
|
|
1561
|
-
);
|
|
1562
|
-
}
|
|
1563
|
-
async listPreferenceRegistry(workspaceId, options = {}) {
|
|
1564
|
-
const params = new URLSearchParams();
|
|
1565
|
-
if (options.scope) params.set("scope", options.scope);
|
|
1566
|
-
if (options.status) params.set("status", options.status);
|
|
1567
|
-
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
1568
|
-
const query = params.toString();
|
|
1569
|
-
return await this.requestJson(
|
|
1570
|
-
"GET",
|
|
1571
|
-
`/v1/workspaces/${workspaceId}/preferences${query ? `?${query}` : ""}`
|
|
1572
|
-
);
|
|
1573
|
-
}
|
|
1574
|
-
async getPreferenceRegistry(workspaceId, preferenceId) {
|
|
1575
|
-
return await this.requestJson(
|
|
1576
|
-
"GET",
|
|
1577
|
-
`/v1/workspaces/${workspaceId}/preferences/${encodeURIComponent(preferenceId)}`
|
|
1578
|
-
);
|
|
1579
|
-
}
|
|
1580
|
-
async createPreferenceRegistryProposal(workspaceId, request) {
|
|
1581
|
-
return await this.requestJson(
|
|
1582
|
-
"POST",
|
|
1583
|
-
`/v1/workspaces/${workspaceId}/preferences/proposals`,
|
|
1584
|
-
request
|
|
1585
|
-
);
|
|
1586
|
-
}
|
|
1587
|
-
async activatePreferenceRegistryRevision(workspaceId, preferenceId, request) {
|
|
1588
|
-
return await this.requestJson(
|
|
1589
|
-
"POST",
|
|
1590
|
-
`/v1/workspaces/${workspaceId}/preferences/${encodeURIComponent(preferenceId)}/activate`,
|
|
1591
|
-
request
|
|
1592
|
-
);
|
|
1593
|
-
}
|
|
1594
|
-
async correctPreferenceRegistry(workspaceId, preferenceId, request) {
|
|
1595
|
-
return await this.requestJson(
|
|
1596
|
-
"POST",
|
|
1597
|
-
`/v1/workspaces/${workspaceId}/preferences/${encodeURIComponent(preferenceId)}/correct`,
|
|
1598
|
-
request
|
|
1599
|
-
);
|
|
1600
|
-
}
|
|
1601
|
-
async changePreferenceRegistryScope(workspaceId, preferenceId, request) {
|
|
1602
|
-
return await this.requestJson(
|
|
1603
|
-
"POST",
|
|
1604
|
-
`/v1/workspaces/${workspaceId}/preferences/${encodeURIComponent(preferenceId)}/scope`,
|
|
1605
|
-
request
|
|
1606
|
-
);
|
|
1607
|
-
}
|
|
1608
|
-
async deactivatePreferenceRegistry(workspaceId, preferenceId, request) {
|
|
1609
|
-
return await this.requestJson(
|
|
1610
|
-
"POST",
|
|
1611
|
-
`/v1/workspaces/${workspaceId}/preferences/${encodeURIComponent(preferenceId)}/deactivate`,
|
|
1612
|
-
request
|
|
1613
|
-
);
|
|
1614
|
-
}
|
|
1615
|
-
async supersedePreferenceRegistry(workspaceId, preferenceId, request) {
|
|
1616
|
-
return await this.requestJson(
|
|
1617
|
-
"POST",
|
|
1618
|
-
`/v1/workspaces/${workspaceId}/preferences/${encodeURIComponent(preferenceId)}/supersede`,
|
|
1619
|
-
request
|
|
1620
|
-
);
|
|
1621
|
-
}
|
|
1622
|
-
async rejectPreferenceRegistryProposal(workspaceId, preferenceId, request) {
|
|
1623
|
-
return await this.requestJson(
|
|
1624
|
-
"POST",
|
|
1625
|
-
`/v1/workspaces/${workspaceId}/preferences/${encodeURIComponent(preferenceId)}/reject`,
|
|
1626
|
-
request
|
|
1627
|
-
);
|
|
1628
|
-
}
|
|
1629
|
-
async getPreferenceRegistrySummary(workspaceId) {
|
|
1630
|
-
return await this.requestJson(
|
|
1631
|
-
"GET",
|
|
1632
|
-
`/v1/workspaces/${workspaceId}/preferences/summary`
|
|
1633
|
-
);
|
|
1634
|
-
}
|
|
1635
|
-
async getPreferenceRegistryFullContent(workspaceId, retrievalHandle) {
|
|
1636
|
-
return await this.requestJson(
|
|
1637
|
-
"POST",
|
|
1638
|
-
`/v1/workspaces/${workspaceId}/preferences/full-content`,
|
|
1639
|
-
{ retrievalHandle }
|
|
1640
|
-
);
|
|
1641
|
-
}
|
|
1642
|
-
/**
|
|
1643
|
-
* Delete a workspace and everything in it. Refused (409) for the account's
|
|
1644
|
-
* only workspace and while it still has a running session. Irreversible.
|
|
1645
|
-
*/
|
|
1646
|
-
async deleteWorkspace(workspaceId) {
|
|
1647
|
-
await this.requestVoid("DELETE", `/v1/workspaces/${workspaceId}`);
|
|
1648
|
-
}
|
|
1649
|
-
// --- Members ("People with access") -------------------------------------------
|
|
1650
|
-
/** The workspace's members (user + api_key subjects). */
|
|
1651
|
-
async listWorkspaceMembers(workspaceId) {
|
|
1652
|
-
const response = await this.requestJson(
|
|
1653
|
-
"GET",
|
|
1654
|
-
`/v1/workspaces/${workspaceId}/members`
|
|
1655
|
-
);
|
|
1656
|
-
return response.members;
|
|
1657
|
-
}
|
|
1658
|
-
/**
|
|
1659
|
-
* Add an already-registered user by email. 404s when no user with that email
|
|
1660
|
-
* exists (email invites for unknown users are deferred).
|
|
1661
|
-
*/
|
|
1662
|
-
async addWorkspaceMember(workspaceId, request) {
|
|
1663
|
-
return await this.requestJson(
|
|
1664
|
-
"POST",
|
|
1665
|
-
`/v1/workspaces/${workspaceId}/members`,
|
|
1666
|
-
request
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
async updateWorkspaceMember(workspaceId, subjectId, request) {
|
|
1670
|
-
return await this.requestJson(
|
|
1671
|
-
"PATCH",
|
|
1672
|
-
`/v1/workspaces/${workspaceId}/members/${encodeURIComponent(subjectId)}`,
|
|
1673
|
-
request
|
|
1674
|
-
);
|
|
1675
|
-
}
|
|
1676
|
-
/**
|
|
1677
|
-
* Remove a member. Refused (409) for your own membership and for the last
|
|
1678
|
-
* member who can still manage the workspace.
|
|
1679
|
-
*/
|
|
1680
|
-
async removeWorkspaceMember(workspaceId, subjectId) {
|
|
1681
|
-
await this.requestVoid(
|
|
1682
|
-
"DELETE",
|
|
1683
|
-
`/v1/workspaces/${workspaceId}/members/${encodeURIComponent(subjectId)}`
|
|
1684
|
-
);
|
|
1685
|
-
}
|
|
1686
|
-
// --- Scheduled tasks (write + runs) -------------------------------------------
|
|
1687
|
-
async createScheduledTask(workspaceId, request) {
|
|
1688
|
-
return await this.requestJson(
|
|
1689
|
-
"POST",
|
|
1690
|
-
`/v1/workspaces/${workspaceId}/scheduled-tasks`,
|
|
1691
|
-
request
|
|
1692
|
-
);
|
|
1693
|
-
}
|
|
1694
|
-
async updateScheduledTask(workspaceId, taskId, request) {
|
|
1695
|
-
return await this.requestJson(
|
|
1696
|
-
"PATCH",
|
|
1697
|
-
`/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`,
|
|
1698
|
-
request
|
|
1699
|
-
);
|
|
1700
|
-
}
|
|
1701
|
-
async pauseScheduledTask(workspaceId, taskId) {
|
|
1702
|
-
return await this.requestJson(
|
|
1703
|
-
"POST",
|
|
1704
|
-
`/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/pause`
|
|
1705
|
-
);
|
|
1706
|
-
}
|
|
1707
|
-
async resumeScheduledTask(workspaceId, taskId) {
|
|
1708
|
-
return await this.requestJson(
|
|
1709
|
-
"POST",
|
|
1710
|
-
`/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/resume`
|
|
1711
|
-
);
|
|
1712
|
-
}
|
|
1713
|
-
/**
|
|
1714
|
-
* Fire the task immediately (manual trigger), independent of its schedule.
|
|
1715
|
-
* Pass a stable `triggerId` to make a retried trigger idempotent — the same
|
|
1716
|
-
* token charges once and starts one run. Omit it and each call is distinct.
|
|
1717
|
-
*/
|
|
1718
|
-
async triggerScheduledTask(workspaceId, taskId, options = {}) {
|
|
1719
|
-
return await this.requestJson(
|
|
1720
|
-
"POST",
|
|
1721
|
-
`/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/trigger`,
|
|
1722
|
-
options.triggerId ? { triggerId: options.triggerId } : void 0
|
|
1723
|
-
);
|
|
1724
|
-
}
|
|
1725
|
-
async deleteScheduledTask(workspaceId, taskId) {
|
|
1726
|
-
await this.requestJson(
|
|
1727
|
-
"DELETE",
|
|
1728
|
-
`/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`
|
|
1729
|
-
);
|
|
1730
|
-
}
|
|
1731
|
-
async listScheduledTaskRuns(workspaceId, taskId, options = {}) {
|
|
1732
|
-
return await this.requestJson(
|
|
1733
|
-
"GET",
|
|
1734
|
-
`/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}/runs`,
|
|
1735
|
-
void 0,
|
|
1736
|
-
{ ...options.limit !== void 0 ? { limit: String(options.limit) } : {} }
|
|
1737
|
-
);
|
|
1738
|
-
}
|
|
1739
|
-
// --- VariableSets --------------------------------------------------------------
|
|
1740
|
-
// Variable values are write-only: reads return name/version metadata only.
|
|
1741
|
-
async listVariableSets(workspaceId) {
|
|
1742
|
-
return await this.requestJson(
|
|
1743
|
-
"GET",
|
|
1744
|
-
`/v1/workspaces/${workspaceId}/variable-sets`
|
|
1745
|
-
);
|
|
1746
|
-
}
|
|
1747
|
-
async createVariableSet(workspaceId, request) {
|
|
1748
|
-
return await this.requestJson(
|
|
1749
|
-
"POST",
|
|
1750
|
-
`/v1/workspaces/${workspaceId}/variable-sets`,
|
|
1751
|
-
request
|
|
1752
|
-
);
|
|
1753
|
-
}
|
|
1754
|
-
async getVariableSet(workspaceId, variableSetId) {
|
|
1755
|
-
return await this.requestJson(
|
|
1756
|
-
"GET",
|
|
1757
|
-
`/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}`
|
|
1758
|
-
);
|
|
1759
|
-
}
|
|
1760
|
-
async updateVariableSet(workspaceId, variableSetId, request) {
|
|
1761
|
-
return await this.requestJson(
|
|
1762
|
-
"PATCH",
|
|
1763
|
-
`/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}`,
|
|
1764
|
-
request
|
|
1765
|
-
);
|
|
1766
|
-
}
|
|
1767
|
-
async deleteVariableSet(workspaceId, variableSetId) {
|
|
1768
|
-
await this.requestJson(
|
|
1769
|
-
"DELETE",
|
|
1770
|
-
`/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}`
|
|
1771
|
-
);
|
|
1772
|
-
}
|
|
1773
|
-
/** Create or rotate a variable. The value never comes back on any read. */
|
|
1774
|
-
async setVariableSetVariable(workspaceId, variableSetId, name, value) {
|
|
1775
|
-
return await this.requestJson(
|
|
1776
|
-
"PUT",
|
|
1777
|
-
`/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}/variables/${encodeURIComponent(name)}`,
|
|
1778
|
-
{ value }
|
|
1779
|
-
);
|
|
1780
|
-
}
|
|
1781
|
-
async deleteVariableSetVariable(workspaceId, variableSetId, name) {
|
|
1782
|
-
await this.requestJson(
|
|
1783
|
-
"DELETE",
|
|
1784
|
-
`/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}/variables/${encodeURIComponent(name)}`
|
|
1785
|
-
);
|
|
1786
|
-
}
|
|
1787
|
-
// --- Rigs ------------------------------------------------------------------
|
|
1788
|
-
// Workspace-scoped, versioned sandbox machine definitions. rigs:use gates read
|
|
1789
|
-
// + proposeRigChange; rigs:manage gates create / update / delete / activate.
|
|
1790
|
-
async listRigs(workspaceId) {
|
|
1791
|
-
return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/rigs`);
|
|
1792
|
-
}
|
|
1793
|
-
async createRig(workspaceId, request) {
|
|
1794
|
-
return await this.requestJson("POST", `/v1/workspaces/${workspaceId}/rigs`, request);
|
|
1795
|
-
}
|
|
1796
|
-
async getRig(workspaceId, rigId) {
|
|
1797
|
-
return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/rigs/${rigId}`);
|
|
1798
|
-
}
|
|
1799
|
-
async updateRig(workspaceId, rigId, request) {
|
|
1800
|
-
return await this.requestJson(
|
|
1801
|
-
"PATCH",
|
|
1802
|
-
`/v1/workspaces/${workspaceId}/rigs/${rigId}`,
|
|
1803
|
-
request
|
|
1804
|
-
);
|
|
1805
|
-
}
|
|
1806
|
-
async deleteRig(workspaceId, rigId) {
|
|
1807
|
-
await this.requestJson("DELETE", `/v1/workspaces/${workspaceId}/rigs/${rigId}`);
|
|
1808
|
-
}
|
|
1809
|
-
async listRigVersions(workspaceId, rigId) {
|
|
1810
|
-
return await this.requestJson(
|
|
1811
|
-
"GET",
|
|
1812
|
-
`/v1/workspaces/${workspaceId}/rigs/${rigId}/versions`
|
|
1813
|
-
);
|
|
1814
|
-
}
|
|
1815
|
-
/** Roll the active version to an existing one (rollback / promote-activate). */
|
|
1816
|
-
async activateRigVersion(workspaceId, rigId, versionId) {
|
|
1817
|
-
return await this.requestJson(
|
|
1818
|
-
"POST",
|
|
1819
|
-
`/v1/workspaces/${workspaceId}/rigs/${rigId}/versions/${versionId}/activate`
|
|
1820
|
-
);
|
|
1821
|
-
}
|
|
1822
|
-
async listRigChanges(workspaceId, rigId) {
|
|
1823
|
-
return await this.requestJson(
|
|
1824
|
-
"GET",
|
|
1825
|
-
`/v1/workspaces/${workspaceId}/rigs/${rigId}/changes`
|
|
1826
|
-
);
|
|
1827
|
-
}
|
|
1828
|
-
/** Propose a change against the rig's active version (rigs:use). */
|
|
1829
|
-
async proposeRigChange(workspaceId, rigId, request) {
|
|
1830
|
-
return await this.requestJson(
|
|
1831
|
-
"POST",
|
|
1832
|
-
`/v1/workspaces/${workspaceId}/rigs/${rigId}/changes`,
|
|
1833
|
-
request
|
|
1834
|
-
);
|
|
1835
|
-
}
|
|
1836
|
-
async getRigChange(workspaceId, rigId, changeId) {
|
|
1837
|
-
return await this.requestJson(
|
|
1838
|
-
"GET",
|
|
1839
|
-
`/v1/workspaces/${workspaceId}/rigs/${rigId}/changes/${changeId}`
|
|
1840
|
-
);
|
|
1841
|
-
}
|
|
1842
|
-
/**
|
|
1843
|
-
* Re-run verification for a change (rigs:use). Verification is asynchronous:
|
|
1844
|
-
* this returns the change immediately with status `verifying`; poll
|
|
1845
|
-
* `getRigChange`/`listRigChanges` for the terminal outcome + logs.
|
|
1846
|
-
*/
|
|
1847
|
-
async verifyRigChange(workspaceId, rigId, changeId) {
|
|
1848
|
-
return await this.requestJson(
|
|
1849
|
-
"POST",
|
|
1850
|
-
`/v1/workspaces/${workspaceId}/rigs/${rigId}/changes/${changeId}/verify`
|
|
1851
|
-
);
|
|
1852
|
-
}
|
|
1853
|
-
/**
|
|
1854
|
-
* Promote a verified `definition_edit` change into a new active rig version
|
|
1855
|
-
* (rigs:manage). Only valid once the change's verification passed; returns the
|
|
1856
|
-
* newly minted version.
|
|
1857
|
-
*/
|
|
1858
|
-
async promoteRigChange(workspaceId, rigId, changeId) {
|
|
1859
|
-
return await this.requestJson(
|
|
1860
|
-
"POST",
|
|
1861
|
-
`/v1/workspaces/${workspaceId}/rigs/${rigId}/changes/${changeId}/promote`
|
|
1862
|
-
);
|
|
1863
|
-
}
|
|
1864
|
-
/**
|
|
1865
|
-
* Re-run the active version's checks in a clean throwaway sandbox (rigs:use).
|
|
1866
|
-
* Asynchronous — returns the version id being verified; the outcome lands on
|
|
1867
|
-
* the version's audit trail.
|
|
1868
|
-
*/
|
|
1869
|
-
async verifyRig(workspaceId, rigId) {
|
|
1870
|
-
return await this.requestJson(
|
|
1871
|
-
"POST",
|
|
1872
|
-
`/v1/workspaces/${workspaceId}/rigs/${rigId}/verify`
|
|
1873
|
-
);
|
|
1874
|
-
}
|
|
1875
|
-
/** @deprecated use listVariableSets */
|
|
1876
|
-
async listEnvironments(workspaceId) {
|
|
1877
|
-
return await this.listVariableSets(workspaceId);
|
|
1878
|
-
}
|
|
1879
|
-
/** @deprecated use createVariableSet */
|
|
1880
|
-
async createEnvironment(workspaceId, request) {
|
|
1881
|
-
return await this.createVariableSet(workspaceId, request);
|
|
1882
|
-
}
|
|
1883
|
-
/** @deprecated use getVariableSet */
|
|
1884
|
-
async getEnvironment(workspaceId, environmentId) {
|
|
1885
|
-
return await this.getVariableSet(workspaceId, environmentId);
|
|
1886
|
-
}
|
|
1887
|
-
/** @deprecated use updateVariableSet */
|
|
1888
|
-
async updateEnvironment(workspaceId, environmentId, request) {
|
|
1889
|
-
return await this.updateVariableSet(workspaceId, environmentId, request);
|
|
1890
|
-
}
|
|
1891
|
-
/** @deprecated use deleteVariableSet */
|
|
1892
|
-
async deleteEnvironment(workspaceId, environmentId) {
|
|
1893
|
-
await this.deleteVariableSet(workspaceId, environmentId);
|
|
1894
|
-
}
|
|
1895
|
-
/** @deprecated use setVariableSetVariable */
|
|
1896
|
-
async setEnvironmentVariable(workspaceId, environmentId, name, value) {
|
|
1897
|
-
return await this.setVariableSetVariable(workspaceId, environmentId, name, value);
|
|
1898
|
-
}
|
|
1899
|
-
/** @deprecated use deleteVariableSetVariable */
|
|
1900
|
-
async deleteEnvironmentVariable(workspaceId, environmentId, name) {
|
|
1901
|
-
await this.deleteVariableSetVariable(workspaceId, environmentId, name);
|
|
1902
|
-
}
|
|
1903
|
-
// --- Files -----------------------------------------------------------------------
|
|
1904
|
-
/** Step 1 of the upload flow: returns the pre-signed PUT target. */
|
|
1905
|
-
async beginFileUpload(workspaceId, request) {
|
|
1906
|
-
return await this.requestJson(
|
|
1907
|
-
"POST",
|
|
1908
|
-
`/v1/workspaces/${workspaceId}/files/uploads`,
|
|
1909
|
-
request
|
|
1910
|
-
);
|
|
1911
|
-
}
|
|
1912
|
-
/** Step 3 of the upload flow: server verifies the object and marks it ready. */
|
|
1913
|
-
async completeFileUpload(workspaceId, uploadId) {
|
|
1914
|
-
const response = await this.requestJson(
|
|
1915
|
-
"POST",
|
|
1916
|
-
`/v1/workspaces/${workspaceId}/files/uploads/${uploadId}/complete`
|
|
1917
|
-
);
|
|
1918
|
-
return response.file;
|
|
1919
|
-
}
|
|
1920
|
-
/**
|
|
1921
|
-
* The whole upload flow as one call: begin -> PUT the bytes to the signed
|
|
1922
|
-
* URL (with its required headers; no API auth is sent to object storage)
|
|
1923
|
-
* -> complete. Returns the ready `FileAsset`.
|
|
1924
|
-
*/
|
|
1925
|
-
async uploadFile(workspaceId, input) {
|
|
1926
|
-
const body = input.data instanceof Uint8Array ? new Blob([input.data.slice()]) : input.data instanceof ArrayBuffer ? input.data.slice(0) : input.data;
|
|
1927
|
-
const sizeBytes = typeof body === "string" ? new TextEncoder().encode(body).byteLength : body instanceof Blob ? body.size : body.byteLength;
|
|
1928
|
-
const sha256 = input.sha256 ?? await sha256ForUpload(body);
|
|
1929
|
-
const upload = await this.beginFileUpload(workspaceId, {
|
|
1930
|
-
filename: input.filename,
|
|
1931
|
-
contentType: input.contentType,
|
|
1932
|
-
sizeBytes,
|
|
1933
|
-
sha256
|
|
1934
|
-
});
|
|
1935
|
-
const putResponse = await this.fetchImpl(upload.putUrl, {
|
|
1936
|
-
method: "PUT",
|
|
1937
|
-
// The backend's requiredHeaders already carry the canonical lowercase
|
|
1938
|
-
// `content-type` for every storage backend (Azure/S3/GCS). Do NOT also set
|
|
1939
|
-
// a `Content-Type` key here: WHATWG Headers treats the two casings as the
|
|
1940
|
-
// same header and comma-joins their values (e.g. "text/plain, text/plain"),
|
|
1941
|
-
// which the object store persists verbatim and COMPLETE then rejects (422),
|
|
1942
|
-
// and which breaks S3's presigned-URL signature.
|
|
1943
|
-
headers: { ...upload.requiredHeaders },
|
|
1944
|
-
body
|
|
1945
|
-
});
|
|
1946
|
-
if (!putResponse.ok) {
|
|
1947
|
-
throw await apiErrorFromResponse(putResponse, { method: "PUT" });
|
|
1948
|
-
}
|
|
1949
|
-
return await this.completeFileUpload(workspaceId, upload.uploadId);
|
|
1950
|
-
}
|
|
1951
|
-
async getFile(workspaceId, fileId) {
|
|
1952
|
-
return await this.requestJson(
|
|
1953
|
-
"GET",
|
|
1954
|
-
`/v1/workspaces/${workspaceId}/files/${fileId}`
|
|
1955
|
-
);
|
|
1956
|
-
}
|
|
1957
|
-
/** Read provider-neutral retained evidence metadata; never returns a storage location. */
|
|
1958
|
-
async getRetainedArtifact(workspaceId, artifactId) {
|
|
1959
|
-
return await this.requestJson(
|
|
1960
|
-
"GET",
|
|
1961
|
-
`/v1/workspaces/${workspaceId}/artifacts/${artifactId}`
|
|
1962
|
-
);
|
|
1963
|
-
}
|
|
1964
|
-
/**
|
|
1965
|
-
* Read at most one authenticated retained-evidence range from the API. This
|
|
1966
|
-
* deliberately does not use the ordinary signed file-download URL.
|
|
1967
|
-
*/
|
|
1968
|
-
async getRetainedArtifactContent(workspaceId, artifactId, options = {}) {
|
|
1969
|
-
if (options.range && (options.range.length > 128 || /[^\x20-\x7e]/.test(options.range))) {
|
|
1970
|
-
throw new RangeError("retained artifact range must be at most 128 printable ASCII bytes");
|
|
1971
|
-
}
|
|
1972
|
-
const correlationId = crypto.randomUUID();
|
|
1973
|
-
const response = await this.fetchImpl(
|
|
1974
|
-
this.url(`/v1/workspaces/${workspaceId}/artifacts/${artifactId}/content`),
|
|
1975
|
-
{
|
|
1976
|
-
method: "GET",
|
|
1977
|
-
headers: {
|
|
1978
|
-
...this.headers(correlationId),
|
|
1979
|
-
Accept: "application/octet-stream",
|
|
1980
|
-
...options.range ? { Range: options.range } : {}
|
|
1981
|
-
},
|
|
1982
|
-
...options.signal ? { signal: options.signal } : {}
|
|
1983
|
-
}
|
|
1984
|
-
);
|
|
1985
|
-
try {
|
|
1986
|
-
assertApiContractResponse(response);
|
|
1987
|
-
} catch (error) {
|
|
1988
|
-
await cancelResponseBody(response, "retained artifact API contract mismatch");
|
|
1989
|
-
throw error;
|
|
1990
|
-
}
|
|
1991
|
-
if (!response.ok) {
|
|
1992
|
-
throw await apiErrorFromResponse(response, { method: "GET", correlationId });
|
|
1993
|
-
}
|
|
1994
|
-
if (response.status !== 200 && response.status !== 206) {
|
|
1995
|
-
await cancelResponseBody(response, "unexpected retained artifact response status");
|
|
1996
|
-
throw new OpenGeniApiError(response.status, "unexpected retained artifact response status");
|
|
1997
|
-
}
|
|
1998
|
-
if (response.headers.get("accept-ranges") !== "bytes") {
|
|
1999
|
-
await cancelResponseBody(response, "retained artifact response omitted byte-range support");
|
|
2000
|
-
throw new OpenGeniApiError(502, "retained artifact response omitted byte-range support");
|
|
2001
|
-
}
|
|
2002
|
-
let declaredLength;
|
|
2003
|
-
try {
|
|
2004
|
-
declaredLength = parseBoundedContentLength(response.headers.get("content-length"));
|
|
2005
|
-
} catch (error) {
|
|
2006
|
-
await cancelResponseBody(response, "invalid retained artifact content-length");
|
|
2007
|
-
throw error;
|
|
2008
|
-
}
|
|
2009
|
-
const bytes = await readBoundedResponseBytes(
|
|
2010
|
-
response,
|
|
2011
|
-
RETAINED_OUTPUT_MAX_PAGE_BYTES,
|
|
2012
|
-
declaredLength
|
|
2013
|
-
);
|
|
2014
|
-
return {
|
|
2015
|
-
bytes,
|
|
2016
|
-
status: response.status,
|
|
2017
|
-
contentType: response.headers.get("content-type") ?? "application/octet-stream",
|
|
2018
|
-
contentLength: bytes.byteLength,
|
|
2019
|
-
contentRange: response.headers.get("content-range"),
|
|
2020
|
-
acceptRanges: "bytes"
|
|
2021
|
-
};
|
|
2022
|
-
}
|
|
2023
|
-
/** Mint a short-lived signed download URL for a ready file. */
|
|
2024
|
-
async createFileDownloadUrl(workspaceId, fileId) {
|
|
2025
|
-
return await this.requestJson(
|
|
2026
|
-
"POST",
|
|
2027
|
-
`/v1/workspaces/${workspaceId}/files/${fileId}/download-url`
|
|
2028
|
-
);
|
|
2029
|
-
}
|
|
2030
|
-
// --- Documents ----------------------------------------------------------------------
|
|
2031
|
-
async createDocumentBase(workspaceId, request) {
|
|
2032
|
-
return await this.requestJson(
|
|
2033
|
-
"POST",
|
|
2034
|
-
`/v1/workspaces/${workspaceId}/document-bases`,
|
|
2035
|
-
request
|
|
2036
|
-
);
|
|
2037
|
-
}
|
|
2038
|
-
async listDocumentBases(workspaceId) {
|
|
2039
|
-
return await this.requestJson(
|
|
2040
|
-
"GET",
|
|
2041
|
-
`/v1/workspaces/${workspaceId}/document-bases`
|
|
2042
|
-
);
|
|
2043
|
-
}
|
|
2044
|
-
async getDocumentBase(workspaceId, baseId) {
|
|
2045
|
-
return await this.requestJson(
|
|
2046
|
-
"GET",
|
|
2047
|
-
`/v1/workspaces/${workspaceId}/document-bases/${baseId}`
|
|
2048
|
-
);
|
|
2049
|
-
}
|
|
2050
|
-
/** Index an uploaded file into the base. The file must be `ready`. */
|
|
2051
|
-
async addDocument(workspaceId, baseId, request) {
|
|
2052
|
-
return await this.requestJson(
|
|
2053
|
-
"POST",
|
|
2054
|
-
`/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`,
|
|
2055
|
-
request
|
|
2056
|
-
);
|
|
2057
|
-
}
|
|
2058
|
-
async listDocuments(workspaceId, baseId) {
|
|
2059
|
-
return await this.requestJson(
|
|
2060
|
-
"GET",
|
|
2061
|
-
`/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents`
|
|
2062
|
-
);
|
|
2063
|
-
}
|
|
2064
|
-
/**
|
|
2065
|
-
* Drop raw text or an already-uploaded file into the workspace's Default
|
|
2066
|
-
* base. When curation is enabled, it may name, summarize, categorize, and
|
|
2067
|
-
* (confidence permitting) file the document into the best-matching base;
|
|
2068
|
-
* provider=none leaves caller metadata and Default placement unchanged.
|
|
2069
|
-
*/
|
|
2070
|
-
async createKnowledgeDrop(workspaceId, request) {
|
|
2071
|
-
return await this.requestJson(
|
|
2072
|
-
"POST",
|
|
2073
|
-
`/v1/workspaces/${workspaceId}/knowledge/drops`,
|
|
2074
|
-
request
|
|
2075
|
-
);
|
|
2076
|
-
}
|
|
2077
|
-
/**
|
|
2078
|
-
* Move a document (and its indexed chunks) to another base. With no
|
|
2079
|
-
* targetBaseId, applies the document's stored curation suggestion.
|
|
2080
|
-
*/
|
|
2081
|
-
async moveDocument(workspaceId, documentId, request = {}) {
|
|
2082
|
-
return await this.requestJson(
|
|
2083
|
-
"POST",
|
|
2084
|
-
`/v1/workspaces/${workspaceId}/documents/${documentId}/move`,
|
|
2085
|
-
request
|
|
2086
|
-
);
|
|
2087
|
-
}
|
|
2088
|
-
/** Retry indexing for a failed document. */
|
|
2089
|
-
async reindexDocument(workspaceId, baseId, documentId) {
|
|
2090
|
-
return await this.requestJson(
|
|
2091
|
-
"POST",
|
|
2092
|
-
`/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents/${documentId}/reindex`
|
|
2093
|
-
);
|
|
2094
|
-
}
|
|
2095
|
-
/**
|
|
2096
|
-
* Delete a document from a base. Removes the document row and its indexed
|
|
2097
|
-
* chunks while leaving the uploaded file asset available for other uses.
|
|
2098
|
-
*/
|
|
2099
|
-
async deleteDocument(workspaceId, baseId, documentId) {
|
|
2100
|
-
await this.requestVoid(
|
|
2101
|
-
"DELETE",
|
|
2102
|
-
`/v1/workspaces/${workspaceId}/document-bases/${baseId}/documents/${documentId}`
|
|
2103
|
-
);
|
|
2104
|
-
}
|
|
2105
|
-
async searchDocuments(workspaceId, baseId, request) {
|
|
2106
|
-
return await this.requestJson(
|
|
2107
|
-
"POST",
|
|
2108
|
-
`/v1/workspaces/${workspaceId}/document-bases/${baseId}/search`,
|
|
2109
|
-
request
|
|
2110
|
-
);
|
|
2111
|
-
}
|
|
2112
|
-
async searchKnowledge(workspaceId, request) {
|
|
2113
|
-
return await this.requestJson(
|
|
2114
|
-
"POST",
|
|
2115
|
-
`/v1/workspaces/${workspaceId}/knowledge/search`,
|
|
2116
|
-
request
|
|
2117
|
-
);
|
|
2118
|
-
}
|
|
2119
|
-
async listKnowledgeMemories(workspaceId, request = {}) {
|
|
2120
|
-
const params = new URLSearchParams();
|
|
2121
|
-
if (request.query) params.set("query", request.query);
|
|
2122
|
-
if (request.status) params.set("status", request.status);
|
|
2123
|
-
if (request.kind) params.set("kind", request.kind);
|
|
2124
|
-
if (request.scope) params.set("scope", request.scope);
|
|
2125
|
-
if (request.limit) params.set("limit", String(request.limit));
|
|
2126
|
-
const query = params.toString();
|
|
2127
|
-
return await this.requestJson(
|
|
2128
|
-
"GET",
|
|
2129
|
-
`/v1/workspaces/${workspaceId}/knowledge/memories${query ? `?${query}` : ""}`
|
|
2130
|
-
);
|
|
2131
|
-
}
|
|
2132
|
-
async getKnowledgeMemory(workspaceId, memoryId) {
|
|
2133
|
-
return await this.requestJson(
|
|
2134
|
-
"GET",
|
|
2135
|
-
`/v1/workspaces/${workspaceId}/knowledge/memories/${memoryId}`
|
|
2136
|
-
);
|
|
2137
|
-
}
|
|
2138
|
-
async createKnowledgeMemory(workspaceId, request) {
|
|
2139
|
-
return await this.requestJson(
|
|
2140
|
-
"POST",
|
|
2141
|
-
`/v1/workspaces/${workspaceId}/knowledge/memories`,
|
|
2142
|
-
request
|
|
2143
|
-
);
|
|
2144
|
-
}
|
|
2145
|
-
async updateKnowledgeMemory(workspaceId, memoryId, request) {
|
|
2146
|
-
return await this.requestJson(
|
|
2147
|
-
"PATCH",
|
|
2148
|
-
`/v1/workspaces/${workspaceId}/knowledge/memories/${memoryId}`,
|
|
2149
|
-
request
|
|
2150
|
-
);
|
|
2151
|
-
}
|
|
2152
|
-
/** Hybrid (semantic + keyword) search over the workspace's agent-visible memory. */
|
|
2153
|
-
async searchWorkspaceMemories(workspaceId, request) {
|
|
2154
|
-
return await this.requestJson(
|
|
2155
|
-
"POST",
|
|
2156
|
-
`/v1/workspaces/${workspaceId}/knowledge/memories/search`,
|
|
2157
|
-
request
|
|
2158
|
-
);
|
|
2159
|
-
}
|
|
2160
|
-
/** Deep-merge a settings patch into the workspace (preserves unknown keys). */
|
|
2161
|
-
async updateWorkspaceSettings(workspaceId, request) {
|
|
2162
|
-
return await this.requestJson(
|
|
2163
|
-
"PATCH",
|
|
2164
|
-
`/v1/workspaces/${workspaceId}/settings`,
|
|
2165
|
-
request
|
|
2166
|
-
);
|
|
2167
|
-
}
|
|
2168
|
-
async setWorkspaceDefaultRig(workspaceId, request) {
|
|
2169
|
-
return await this.requestJson(
|
|
2170
|
-
"PUT",
|
|
2171
|
-
`/v1/workspaces/${workspaceId}/default-rig`,
|
|
2172
|
-
request
|
|
2173
|
-
);
|
|
2174
|
-
}
|
|
2175
|
-
// --- Capability packs ------------------------------------------------------------------
|
|
2176
|
-
/** Built-in + registered packs, with the workspace's installations. */
|
|
2177
|
-
async listPacks(workspaceId) {
|
|
2178
|
-
return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/packs`);
|
|
2179
|
-
}
|
|
2180
|
-
/** Register (or replace) a workspace-scoped pack from a manifest. */
|
|
2181
|
-
async registerPack(workspaceId, manifest) {
|
|
2182
|
-
return await this.requestJson(
|
|
2183
|
-
"POST",
|
|
2184
|
-
`/v1/workspaces/${workspaceId}/packs`,
|
|
2185
|
-
manifest
|
|
2186
|
-
);
|
|
2187
|
-
}
|
|
2188
|
-
async getPack(workspaceId, packId) {
|
|
2189
|
-
return await this.requestJson(
|
|
2190
|
-
"GET",
|
|
2191
|
-
`/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`
|
|
2192
|
-
);
|
|
2193
|
-
}
|
|
2194
|
-
async enablePack(workspaceId, packId, request = {}) {
|
|
2195
|
-
return await this.requestJson(
|
|
2196
|
-
"POST",
|
|
2197
|
-
`/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}/enable`,
|
|
2198
|
-
request
|
|
2199
|
-
);
|
|
2200
|
-
}
|
|
2201
|
-
/** Unregister a workspace-scoped pack (built-in packs cannot be deleted). */
|
|
2202
|
-
async deletePack(workspaceId, packId) {
|
|
2203
|
-
await this.requestVoid(
|
|
2204
|
-
"DELETE",
|
|
2205
|
-
`/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}`
|
|
2206
|
-
);
|
|
2207
|
-
}
|
|
2208
|
-
async listPackInstallations(workspaceId) {
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
|
|
3
|
+
KNOWN_PERMISSIONS,
|
|
4
|
+
KNOWN_USAGE_EVENT_TYPES,
|
|
5
|
+
OPENGENI_API_CONTRACT_HEADER,
|
|
6
|
+
OPENGENI_API_CONTRACT_REVISION,
|
|
7
|
+
OPENGENI_CORRELATION_HEADER,
|
|
8
|
+
OpenGeniApiContractMismatchError,
|
|
9
|
+
OpenGeniApiError,
|
|
10
|
+
OpenGeniClient,
|
|
11
|
+
OpenGeniSessionListCursorError,
|
|
12
|
+
OpenGeniStreamError,
|
|
13
|
+
RETAINED_OUTPUT_DEFAULT_PAGE_BYTES,
|
|
14
|
+
RETAINED_OUTPUT_MAX_PAGE_BYTES,
|
|
15
|
+
SESSION_EVENT_TYPES,
|
|
16
|
+
authorizeTranscriptionAdapter,
|
|
17
|
+
createTranscriptionSessionRequest,
|
|
18
|
+
isRetryableStreamError,
|
|
19
|
+
parseSseStream,
|
|
20
|
+
resolveWorkspaceTranscriptionPolicy,
|
|
21
|
+
resolveWorkspaceVoiceInputEnabled,
|
|
22
|
+
streamSessionEvents,
|
|
23
|
+
streamWorkspaceControlEvents
|
|
24
|
+
} from "./chunk-YROWFD7R.js";
|
|
25
|
+
|
|
26
|
+
// src/artifact-client.ts
|
|
27
|
+
var OpenGeniClient2 = class extends OpenGeniClient {
|
|
28
|
+
async listWorkspaceArtifacts(workspaceId, options = {}) {
|
|
29
|
+
const query = new URLSearchParams();
|
|
30
|
+
if (options.limit !== void 0) query.set("limit", String(options.limit));
|
|
31
|
+
if (options.cursor) query.set("cursor", options.cursor);
|
|
32
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : "";
|
|
2209
33
|
return await this.requestJson(
|
|
2210
34
|
"GET",
|
|
2211
|
-
`/v1/workspaces/${workspaceId}/
|
|
35
|
+
`/v1/workspaces/${workspaceId}/published-artifacts${suffix}`
|
|
2212
36
|
);
|
|
2213
37
|
}
|
|
2214
|
-
|
|
2215
|
-
async listCapabilities(workspaceId) {
|
|
38
|
+
async getWorkspaceArtifact(workspaceId, artifactId) {
|
|
2216
39
|
return await this.requestJson(
|
|
2217
40
|
"GET",
|
|
2218
|
-
`/v1/workspaces/${workspaceId}/
|
|
2219
|
-
);
|
|
2220
|
-
}
|
|
2221
|
-
/** Add a manual capability catalog item (e.g. a remote MCP server). */
|
|
2222
|
-
async createCapability(workspaceId, request) {
|
|
2223
|
-
return await this.requestJson(
|
|
2224
|
-
"POST",
|
|
2225
|
-
`/v1/workspaces/${workspaceId}/capabilities`,
|
|
2226
|
-
request
|
|
2227
|
-
);
|
|
2228
|
-
}
|
|
2229
|
-
async enableCapability(workspaceId, capabilityId, request = {}) {
|
|
2230
|
-
return await this.requestJson(
|
|
2231
|
-
"POST",
|
|
2232
|
-
`/v1/workspaces/${workspaceId}/capabilities/${encodeURIComponent(capabilityId)}/enable`,
|
|
2233
|
-
request
|
|
2234
|
-
);
|
|
2235
|
-
}
|
|
2236
|
-
async disableCapability(workspaceId, capabilityId) {
|
|
2237
|
-
return await this.requestJson(
|
|
2238
|
-
"POST",
|
|
2239
|
-
`/v1/workspaces/${workspaceId}/capabilities/${encodeURIComponent(capabilityId)}/disable`
|
|
41
|
+
`/v1/workspaces/${workspaceId}/published-artifacts/${encodeURIComponent(artifactId)}`
|
|
2240
42
|
);
|
|
2241
43
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
44
|
+
async getWorkspaceArtifactContent(workspaceId, artifactId, versionId) {
|
|
45
|
+
const query = versionId ? `?versionId=${encodeURIComponent(versionId)}` : "";
|
|
2244
46
|
return await this.requestJson(
|
|
2245
47
|
"GET",
|
|
2246
|
-
`/v1/workspaces/${workspaceId}/
|
|
2247
|
-
void 0,
|
|
2248
|
-
{
|
|
2249
|
-
...options.query !== void 0 ? { query: options.query } : {},
|
|
2250
|
-
...options.limit !== void 0 ? { limit: String(options.limit) } : {}
|
|
2251
|
-
}
|
|
2252
|
-
);
|
|
2253
|
-
}
|
|
2254
|
-
// --- Connections -------------------------------------------------------------------------------
|
|
2255
|
-
async listConnections(workspaceId) {
|
|
2256
|
-
const response = await this.requestJson(
|
|
2257
|
-
"GET",
|
|
2258
|
-
`/v1/workspaces/${workspaceId}/connections`
|
|
2259
|
-
);
|
|
2260
|
-
return response.connections;
|
|
2261
|
-
}
|
|
2262
|
-
async createConnection(workspaceId, request) {
|
|
2263
|
-
const response = await this.requestJson(
|
|
2264
|
-
"POST",
|
|
2265
|
-
`/v1/workspaces/${workspaceId}/connections`,
|
|
2266
|
-
request
|
|
48
|
+
`/v1/workspaces/${workspaceId}/published-artifacts/${encodeURIComponent(artifactId)}/content${query}`
|
|
2267
49
|
);
|
|
2268
|
-
return response.connection;
|
|
2269
50
|
}
|
|
2270
|
-
|
|
2271
|
-
async startOpenGeniSlackBotInstall(workspaceId, request = {}) {
|
|
51
|
+
async createWorkspaceArtifact(workspaceId, request) {
|
|
2272
52
|
return await this.requestJson(
|
|
2273
53
|
"POST",
|
|
2274
|
-
`/v1/workspaces/${workspaceId}/
|
|
2275
|
-
request
|
|
2276
|
-
);
|
|
2277
|
-
}
|
|
2278
|
-
async updateConnection(workspaceId, connectionId, request) {
|
|
2279
|
-
const response = await this.requestJson(
|
|
2280
|
-
"PATCH",
|
|
2281
|
-
`/v1/workspaces/${workspaceId}/connections/${connectionId}`,
|
|
54
|
+
`/v1/workspaces/${workspaceId}/published-artifacts`,
|
|
2282
55
|
request
|
|
2283
56
|
);
|
|
2284
|
-
return response.connection;
|
|
2285
|
-
}
|
|
2286
|
-
async deleteConnection(workspaceId, connectionId) {
|
|
2287
|
-
const response = await this.requestJson(
|
|
2288
|
-
"DELETE",
|
|
2289
|
-
`/v1/workspaces/${workspaceId}/connections/${connectionId}`
|
|
2290
|
-
);
|
|
2291
|
-
return response.connection;
|
|
2292
|
-
}
|
|
2293
|
-
/** Start an OAuth connection flow; redirect the user to the returned `authorizationUrl`. */
|
|
2294
|
-
async startConnectionOAuth(workspaceId, request, options = {}) {
|
|
2295
|
-
return await this.requestJson(
|
|
2296
|
-
"POST",
|
|
2297
|
-
`/v1/workspaces/${workspaceId}/connections/oauth/start`,
|
|
2298
|
-
request,
|
|
2299
|
-
{},
|
|
2300
|
-
options
|
|
2301
|
-
);
|
|
2302
|
-
}
|
|
2303
|
-
/** Public, immutably-cached URL for a catalog item's logo, or null when the item has none. */
|
|
2304
|
-
catalogAssetUrl(logoAssetPath) {
|
|
2305
|
-
return logoAssetPath ? `${this.baseUrl}/v1/${logoAssetPath}` : null;
|
|
2306
|
-
}
|
|
2307
|
-
// --- GitHub ----------------------------------------------------------------------------------
|
|
2308
|
-
/** GitHub App server configuration plus truthful workspace binding status. */
|
|
2309
|
-
async getGitHubApp(workspaceId) {
|
|
2310
|
-
return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/github/app`);
|
|
2311
|
-
}
|
|
2312
|
-
/** Build the GitHub owner-consent entry URL for fresh workspace-bound state. */
|
|
2313
|
-
githubConnectUrl(workspaceId, state) {
|
|
2314
|
-
return this.url(`/v1/workspaces/${workspaceId}/github/connect`, { state });
|
|
2315
|
-
}
|
|
2316
|
-
async listGitHubRepositories(workspaceId) {
|
|
2317
|
-
return await this.requestJson(
|
|
2318
|
-
"GET",
|
|
2319
|
-
`/v1/workspaces/${workspaceId}/github/repositories`
|
|
2320
|
-
);
|
|
2321
|
-
}
|
|
2322
|
-
/** Re-sync the installation's repository list from GitHub. */
|
|
2323
|
-
async syncGitHubRepositories(workspaceId) {
|
|
2324
|
-
return await this.requestJson(
|
|
2325
|
-
"POST",
|
|
2326
|
-
`/v1/workspaces/${workspaceId}/github/repositories/sync`
|
|
2327
|
-
);
|
|
2328
|
-
}
|
|
2329
|
-
/** Remove one workspace binding without uninstalling the GitHub App itself. */
|
|
2330
|
-
async unlinkGitHubInstallation(workspaceId, installationId) {
|
|
2331
|
-
await this.requestVoid(
|
|
2332
|
-
"DELETE",
|
|
2333
|
-
`/v1/workspaces/${workspaceId}/github/installations/${installationId}`
|
|
2334
|
-
);
|
|
2335
57
|
}
|
|
2336
|
-
|
|
2337
|
-
async createGitHubAppManifest(workspaceId, request = {}) {
|
|
58
|
+
async publishWorkspaceArtifactVersion(workspaceId, artifactId, request) {
|
|
2338
59
|
return await this.requestJson(
|
|
2339
60
|
"POST",
|
|
2340
|
-
`/v1/workspaces/${workspaceId}/
|
|
61
|
+
`/v1/workspaces/${workspaceId}/published-artifacts/${encodeURIComponent(artifactId)}/versions`,
|
|
2341
62
|
request
|
|
2342
63
|
);
|
|
2343
64
|
}
|
|
2344
|
-
|
|
2345
|
-
async listApiKeys(workspaceId) {
|
|
2346
|
-
const response = await this.requestJson(
|
|
2347
|
-
"GET",
|
|
2348
|
-
`/v1/workspaces/${workspaceId}/api-keys`
|
|
2349
|
-
);
|
|
2350
|
-
return response.apiKeys;
|
|
2351
|
-
}
|
|
2352
|
-
/** The returned `token` is shown once; only its prefix is stored. */
|
|
2353
|
-
async createApiKey(workspaceId, request) {
|
|
65
|
+
async rollbackWorkspaceArtifact(workspaceId, artifactId, request) {
|
|
2354
66
|
return await this.requestJson(
|
|
2355
67
|
"POST",
|
|
2356
|
-
`/v1/workspaces/${workspaceId}/
|
|
68
|
+
`/v1/workspaces/${workspaceId}/published-artifacts/${encodeURIComponent(artifactId)}/rollback`,
|
|
2357
69
|
request
|
|
2358
70
|
);
|
|
2359
71
|
}
|
|
2360
|
-
/** Revoke an API key. Returns the revoked key. */
|
|
2361
|
-
async deleteApiKey(workspaceId, apiKeyId) {
|
|
2362
|
-
return await this.requestJson(
|
|
2363
|
-
"DELETE",
|
|
2364
|
-
`/v1/workspaces/${workspaceId}/api-keys/${apiKeyId}`
|
|
2365
|
-
);
|
|
2366
|
-
}
|
|
2367
|
-
// --- Billing (account-scoped) --------------------------------------------------------------------
|
|
2368
|
-
async getBilling(options = {}) {
|
|
2369
|
-
return await this.requestJson("GET", "/v1/billing", void 0, {
|
|
2370
|
-
...options.accountId !== void 0 ? { accountId: options.accountId } : {}
|
|
2371
|
-
});
|
|
2372
|
-
}
|
|
2373
|
-
async getBillingUsage(options = {}) {
|
|
2374
|
-
return await this.requestJson("GET", "/v1/billing/usage", void 0, {
|
|
2375
|
-
...options.accountId !== void 0 ? { accountId: options.accountId } : {},
|
|
2376
|
-
...options.workspaceId !== void 0 ? { workspaceId: options.workspaceId } : {}
|
|
2377
|
-
});
|
|
2378
|
-
}
|
|
2379
|
-
async getWorkspaceInsights(workspaceId, options = {}) {
|
|
2380
|
-
return await this.requestJson(
|
|
2381
|
-
"GET",
|
|
2382
|
-
`/v1/workspaces/${workspaceId}/insights`,
|
|
2383
|
-
void 0,
|
|
2384
|
-
{
|
|
2385
|
-
range: options.range ?? "week",
|
|
2386
|
-
...options.provider !== void 0 ? { provider: options.provider } : {},
|
|
2387
|
-
...options.model !== void 0 ? { model: options.model } : {}
|
|
2388
|
-
}
|
|
2389
|
-
);
|
|
2390
|
-
}
|
|
2391
|
-
async getBillingEntitlements(options = {}) {
|
|
2392
|
-
return await this.requestJson(
|
|
2393
|
-
"GET",
|
|
2394
|
-
"/v1/billing/entitlements",
|
|
2395
|
-
void 0,
|
|
2396
|
-
{
|
|
2397
|
-
...options.accountId !== void 0 ? { accountId: options.accountId } : {}
|
|
2398
|
-
}
|
|
2399
|
-
);
|
|
2400
|
-
}
|
|
2401
|
-
/** Start a Stripe checkout for prepaid credits. */
|
|
2402
|
-
async createBillingCheckout(request) {
|
|
2403
|
-
return await this.requestJson("POST", "/v1/billing/checkout", request);
|
|
2404
|
-
}
|
|
2405
|
-
// --- Internals -------------------------------------------------------------
|
|
2406
|
-
headers(correlationId) {
|
|
2407
|
-
const extra = typeof this.options.headers === "function" ? this.options.headers() : this.options.headers;
|
|
2408
|
-
return {
|
|
2409
|
-
...this.options.apiKey ? { Authorization: `Bearer ${this.options.apiKey}` } : {},
|
|
2410
|
-
...extra,
|
|
2411
|
-
[OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION,
|
|
2412
|
-
...correlationId ? { [OPENGENI_CORRELATION_HEADER]: correlationId } : {}
|
|
2413
|
-
};
|
|
2414
|
-
}
|
|
2415
|
-
url(path, query = {}) {
|
|
2416
|
-
const params = new URLSearchParams(query).toString();
|
|
2417
|
-
return `${this.baseUrl}${path}${params ? `?${params}` : ""}`;
|
|
2418
|
-
}
|
|
2419
|
-
// --- Codex (ChatGPT) subscription (workspace-scoped) --------------------------------------------
|
|
2420
|
-
/** Connection state + the codex models the workspace may select (empty until connected). */
|
|
2421
|
-
async codexStatus(workspaceId) {
|
|
2422
|
-
return await this.requestJson(
|
|
2423
|
-
"GET",
|
|
2424
|
-
`/v1/workspaces/${workspaceId}/codex/status`
|
|
2425
|
-
);
|
|
2426
|
-
}
|
|
2427
|
-
/** Begin device-code login: show `userCode` at `verificationUri`, then poll with `state`. */
|
|
2428
|
-
async codexConnectStart(workspaceId) {
|
|
2429
|
-
return await this.requestJson(
|
|
2430
|
-
"POST",
|
|
2431
|
-
`/v1/workspaces/${workspaceId}/codex/connect/start`
|
|
2432
|
-
);
|
|
2433
|
-
}
|
|
2434
|
-
/** Poll device-code authorization with the `state` from {@link codexConnectStart}. */
|
|
2435
|
-
async codexConnectPoll(workspaceId, state) {
|
|
2436
|
-
return await this.requestJson(
|
|
2437
|
-
"POST",
|
|
2438
|
-
`/v1/workspaces/${workspaceId}/codex/connect/poll`,
|
|
2439
|
-
{ state }
|
|
2440
|
-
);
|
|
2441
|
-
}
|
|
2442
|
-
/** Remaining usage / limits for the connected (ACTIVE) subscription. Back-compat. */
|
|
2443
|
-
async codexUsage(workspaceId) {
|
|
2444
|
-
return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/codex/usage`);
|
|
2445
|
-
}
|
|
2446
|
-
/** Live per-account usage read (refreshes THIS account's bearer; writes the cache). */
|
|
2447
|
-
async codexAccountUsage(workspaceId, accountId) {
|
|
2448
|
-
return await this.requestJson(
|
|
2449
|
-
"GET",
|
|
2450
|
-
`/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/usage`
|
|
2451
|
-
);
|
|
2452
|
-
}
|
|
2453
|
-
/** Batched live refresh across every connected account, keyed by credential id. */
|
|
2454
|
-
async refreshCodexUsage(workspaceId) {
|
|
2455
|
-
return await this.requestJson(
|
|
2456
|
-
"POST",
|
|
2457
|
-
`/v1/workspaces/${workspaceId}/codex/usage/refresh`
|
|
2458
|
-
);
|
|
2459
|
-
}
|
|
2460
|
-
/** Live independently-settled quota + reset-credit overview for every account. */
|
|
2461
|
-
async codexOverview(workspaceId) {
|
|
2462
|
-
return await this.requestJson(
|
|
2463
|
-
"GET",
|
|
2464
|
-
`/v1/workspaces/${workspaceId}/codex/overview`
|
|
2465
|
-
);
|
|
2466
|
-
}
|
|
2467
|
-
/** Disconnect ALL accounts (legacy workspace-wide). Prefer `disconnectCodexAccount`. */
|
|
2468
|
-
async codexDisconnect(workspaceId) {
|
|
2469
|
-
return await this.requestJson(
|
|
2470
|
-
"DELETE",
|
|
2471
|
-
`/v1/workspaces/${workspaceId}/codex`
|
|
2472
|
-
);
|
|
2473
|
-
}
|
|
2474
|
-
/** List every connected Codex account + the workspace active pointer + settings. */
|
|
2475
|
-
async listCodexAccounts(workspaceId) {
|
|
2476
|
-
return await this.requestJson(
|
|
2477
|
-
"GET",
|
|
2478
|
-
`/v1/workspaces/${workspaceId}/codex/accounts`
|
|
2479
|
-
);
|
|
2480
|
-
}
|
|
2481
|
-
/** Switch the workspace ACTIVE Codex account (the one unpinned sessions use). */
|
|
2482
|
-
async activateCodexAccount(workspaceId, accountId) {
|
|
2483
|
-
return await this.requestJson(
|
|
2484
|
-
"POST",
|
|
2485
|
-
`/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/activate`
|
|
2486
|
-
);
|
|
2487
|
-
}
|
|
2488
|
-
/** P3: enable/disable Codex auto-rotation and/or pick the strategy. Returns the effective settings. */
|
|
2489
|
-
async setCodexRotationSettings(workspaceId, patch) {
|
|
2490
|
-
return await this.requestJson(
|
|
2491
|
-
"PATCH",
|
|
2492
|
-
`/v1/workspaces/${workspaceId}/codex/settings`,
|
|
2493
|
-
patch
|
|
2494
|
-
);
|
|
2495
|
-
}
|
|
2496
|
-
/** Toggle only NEW automatic allocations under independent allocator OCC. */
|
|
2497
|
-
async setCodexAccountAllocator(workspaceId, accountId, input) {
|
|
2498
|
-
return await this.requestJson(
|
|
2499
|
-
"PATCH",
|
|
2500
|
-
`/v1/workspaces/${workspaceId}/codex/accounts/${accountId}/allocator`,
|
|
2501
|
-
input
|
|
2502
|
-
);
|
|
2503
|
-
}
|
|
2504
|
-
/** Disconnect ONE Codex account by id (re-picks active when the removed one was active). */
|
|
2505
|
-
async disconnectCodexAccount(workspaceId, accountId) {
|
|
2506
|
-
return await this.requestJson(
|
|
2507
|
-
"DELETE",
|
|
2508
|
-
`/v1/workspaces/${workspaceId}/codex/accounts/${accountId}`
|
|
2509
|
-
);
|
|
2510
|
-
}
|
|
2511
|
-
/** Rename a Codex account (label only in P1). */
|
|
2512
|
-
async renameCodexAccount(workspaceId, accountId, label) {
|
|
2513
|
-
return await this.requestJson(
|
|
2514
|
-
"PATCH",
|
|
2515
|
-
`/v1/workspaces/${workspaceId}/codex/accounts/${accountId}`,
|
|
2516
|
-
{ label }
|
|
2517
|
-
);
|
|
2518
|
-
}
|
|
2519
|
-
/** Pin (or unpin via "auto") a session's Codex account. Applies on the next turn. */
|
|
2520
|
-
async pinSessionCodexAccount(workspaceId, sessionId, target) {
|
|
2521
|
-
return await this.requestJson(
|
|
2522
|
-
"POST",
|
|
2523
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/codex-account`,
|
|
2524
|
-
{ target }
|
|
2525
|
-
);
|
|
2526
|
-
}
|
|
2527
|
-
async requestJson(method, path, body, query = {}, options = {}) {
|
|
2528
|
-
const correlationId = crypto.randomUUID();
|
|
2529
|
-
let response;
|
|
2530
|
-
try {
|
|
2531
|
-
response = await this.fetchImpl(this.url(path, query), {
|
|
2532
|
-
method,
|
|
2533
|
-
headers: {
|
|
2534
|
-
...this.headers(correlationId),
|
|
2535
|
-
Accept: "application/json",
|
|
2536
|
-
...body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
2537
|
-
},
|
|
2538
|
-
...body !== void 0 ? { body: JSON.stringify(body) } : {},
|
|
2539
|
-
...options.signal ? { signal: options.signal } : {}
|
|
2540
|
-
});
|
|
2541
|
-
} catch (error) {
|
|
2542
|
-
if (isMutationMethod(method)) {
|
|
2543
|
-
throw mutationTransportError(correlationId);
|
|
2544
|
-
}
|
|
2545
|
-
throw error;
|
|
2546
|
-
}
|
|
2547
|
-
assertApiContractResponse(response);
|
|
2548
|
-
if (!response.ok) {
|
|
2549
|
-
throw await apiErrorFromResponse(response, { method, correlationId });
|
|
2550
|
-
}
|
|
2551
|
-
await assertJsonResponse(response, { method, correlationId });
|
|
2552
|
-
try {
|
|
2553
|
-
return await response.json();
|
|
2554
|
-
} catch (error) {
|
|
2555
|
-
if (isMutationMethod(method)) {
|
|
2556
|
-
throw mutationTransportError(correlationId);
|
|
2557
|
-
}
|
|
2558
|
-
throw error;
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
/** Like `requestJson` for endpoints that respond with no body (204). */
|
|
2562
|
-
async requestVoid(method, path, body) {
|
|
2563
|
-
const correlationId = crypto.randomUUID();
|
|
2564
|
-
let response;
|
|
2565
|
-
try {
|
|
2566
|
-
response = await this.fetchImpl(this.url(path), {
|
|
2567
|
-
method,
|
|
2568
|
-
headers: {
|
|
2569
|
-
...this.headers(correlationId),
|
|
2570
|
-
Accept: "application/json",
|
|
2571
|
-
...body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
2572
|
-
},
|
|
2573
|
-
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
2574
|
-
});
|
|
2575
|
-
} catch (error) {
|
|
2576
|
-
if (isMutationMethod(method)) {
|
|
2577
|
-
throw mutationTransportError(correlationId);
|
|
2578
|
-
}
|
|
2579
|
-
throw error;
|
|
2580
|
-
}
|
|
2581
|
-
assertApiContractResponse(response);
|
|
2582
|
-
if (!response.ok) {
|
|
2583
|
-
throw await apiErrorFromResponse(response, { method, correlationId });
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
72
|
};
|
|
2587
|
-
function assertApiContractResponse(response) {
|
|
2588
|
-
const actual = response.headers.get(OPENGENI_API_CONTRACT_HEADER);
|
|
2589
|
-
if (actual && actual !== OPENGENI_API_CONTRACT_REVISION) {
|
|
2590
|
-
throw new OpenGeniApiContractMismatchError(OPENGENI_API_CONTRACT_REVISION, actual);
|
|
2591
|
-
}
|
|
2592
|
-
}
|
|
2593
|
-
function isTranscribeAudioResponse(value) {
|
|
2594
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2595
|
-
const record = value;
|
|
2596
|
-
return typeof record.text === "string" && Array.isArray(record.languages) && record.languages.every((language) => typeof language === "string");
|
|
2597
|
-
}
|
|
2598
|
-
function filenameForAudioMimeType(mimeType) {
|
|
2599
|
-
const bare = mimeType.trim().toLowerCase().split(";")[0] ?? "audio/webm";
|
|
2600
|
-
switch (bare) {
|
|
2601
|
-
case "audio/mp4":
|
|
2602
|
-
case "audio/m4a":
|
|
2603
|
-
return "audio.mp4";
|
|
2604
|
-
case "audio/ogg":
|
|
2605
|
-
return "audio.ogg";
|
|
2606
|
-
case "audio/mpeg":
|
|
2607
|
-
case "audio/mp3":
|
|
2608
|
-
return "audio.mp3";
|
|
2609
|
-
case "audio/wav":
|
|
2610
|
-
case "audio/x-wav":
|
|
2611
|
-
return "audio.wav";
|
|
2612
|
-
case "audio/webm":
|
|
2613
|
-
default:
|
|
2614
|
-
return "audio.webm";
|
|
2615
|
-
}
|
|
2616
|
-
}
|
|
2617
|
-
var API_ERROR_MAX_BYTES = 16 * 1024;
|
|
2618
|
-
async function apiErrorFromResponse(response, context) {
|
|
2619
|
-
return new OpenGeniApiError(response.status, await readBoundedJsonErrorBody(response), {
|
|
2620
|
-
correlationId: response.headers.get(OPENGENI_CORRELATION_HEADER) ?? context.correlationId,
|
|
2621
|
-
mutation: isMutationMethod(context.method)
|
|
2622
|
-
});
|
|
2623
|
-
}
|
|
2624
|
-
async function assertJsonResponse(response, context) {
|
|
2625
|
-
if (isJsonContentType(response.headers.get("content-type"))) return;
|
|
2626
|
-
await cancelResponseBody(response, "unexpected non-JSON API response");
|
|
2627
|
-
throw new OpenGeniApiError(502, "", {
|
|
2628
|
-
code: "upstream_unavailable",
|
|
2629
|
-
retryable: true,
|
|
2630
|
-
correlationId: response.headers.get(OPENGENI_CORRELATION_HEADER) ?? context.correlationId,
|
|
2631
|
-
outcomeUnknown: isMutationMethod(context.method),
|
|
2632
|
-
displayMessage: "OpenGeni is temporarily unavailable \u2014 retry."
|
|
2633
|
-
});
|
|
2634
|
-
}
|
|
2635
|
-
async function readBoundedJsonErrorBody(response) {
|
|
2636
|
-
if (!isJsonContentType(response.headers.get("content-type"))) {
|
|
2637
|
-
await cancelResponseBody(response, "discarding API error body");
|
|
2638
|
-
return "";
|
|
2639
|
-
}
|
|
2640
|
-
if (Number(response.headers.get("content-length")) > API_ERROR_MAX_BYTES) {
|
|
2641
|
-
await cancelResponseBody(response, "discarding API error body");
|
|
2642
|
-
return "";
|
|
2643
|
-
}
|
|
2644
|
-
try {
|
|
2645
|
-
return new TextDecoder().decode(
|
|
2646
|
-
await readBoundedResponseBytes(response, API_ERROR_MAX_BYTES, null)
|
|
2647
|
-
);
|
|
2648
|
-
} catch {
|
|
2649
|
-
return "";
|
|
2650
|
-
}
|
|
2651
|
-
}
|
|
2652
|
-
function isJsonContentType(value) {
|
|
2653
|
-
return /^(application\/json|[^;]+\+json)\s*(;|$)/i.test(value ?? "");
|
|
2654
|
-
}
|
|
2655
|
-
function isMutationMethod(method) {
|
|
2656
|
-
return method !== "GET" && method !== "HEAD" && method !== "OPTIONS";
|
|
2657
|
-
}
|
|
2658
|
-
function mutationTransportError(correlationId) {
|
|
2659
|
-
return new OpenGeniApiError(0, "", {
|
|
2660
|
-
code: "network_error",
|
|
2661
|
-
retryable: true,
|
|
2662
|
-
correlationId,
|
|
2663
|
-
outcomeUnknown: true,
|
|
2664
|
-
mutation: true,
|
|
2665
|
-
displayMessage: "OpenGeni could not confirm the request \u2014 reconcile before retrying."
|
|
2666
|
-
});
|
|
2667
|
-
}
|
|
2668
|
-
async function sha256ForUpload(body) {
|
|
2669
|
-
const bytes = typeof body === "string" ? new TextEncoder().encode(body) : body instanceof Blob ? new Uint8Array(await body.arrayBuffer()) : new Uint8Array(body);
|
|
2670
|
-
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
2671
|
-
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2672
|
-
}
|
|
2673
|
-
async function cancelResponseBody(response, reason) {
|
|
2674
|
-
await response.body?.cancel(reason).catch(() => void 0);
|
|
2675
|
-
}
|
|
2676
|
-
function parseBoundedContentLength(value) {
|
|
2677
|
-
if (value === null) return null;
|
|
2678
|
-
if (!/^\d+$/.test(value)) {
|
|
2679
|
-
throw new OpenGeniApiError(502, "invalid retained artifact content-length");
|
|
2680
|
-
}
|
|
2681
|
-
const length = Number(value);
|
|
2682
|
-
if (!Number.isSafeInteger(length) || length > RETAINED_OUTPUT_MAX_PAGE_BYTES) {
|
|
2683
|
-
throw new OpenGeniApiError(502, "retained artifact response exceeds the SDK byte limit");
|
|
2684
|
-
}
|
|
2685
|
-
return length;
|
|
2686
|
-
}
|
|
2687
|
-
async function readBoundedResponseBytes(response, maxBytes, expectedBytes) {
|
|
2688
|
-
if (!response.body) {
|
|
2689
|
-
if (expectedBytes !== null && expectedBytes !== 0) {
|
|
2690
|
-
throw new OpenGeniApiError(502, "retained artifact response length mismatch");
|
|
2691
|
-
}
|
|
2692
|
-
return new Uint8Array();
|
|
2693
|
-
}
|
|
2694
|
-
const reader = response.body.getReader();
|
|
2695
|
-
const chunks = [];
|
|
2696
|
-
let totalBytes = 0;
|
|
2697
|
-
try {
|
|
2698
|
-
while (true) {
|
|
2699
|
-
const { done, value } = await reader.read();
|
|
2700
|
-
if (done) break;
|
|
2701
|
-
totalBytes += value.byteLength;
|
|
2702
|
-
if (totalBytes > maxBytes) {
|
|
2703
|
-
await reader.cancel("retained artifact response exceeded the SDK byte limit").catch(() => void 0);
|
|
2704
|
-
throw new OpenGeniApiError(502, "retained artifact response exceeds the SDK byte limit");
|
|
2705
|
-
}
|
|
2706
|
-
chunks.push(value);
|
|
2707
|
-
}
|
|
2708
|
-
} finally {
|
|
2709
|
-
reader.releaseLock();
|
|
2710
|
-
}
|
|
2711
|
-
if (expectedBytes !== null && totalBytes !== expectedBytes) {
|
|
2712
|
-
throw new OpenGeniApiError(502, "retained artifact response length mismatch");
|
|
2713
|
-
}
|
|
2714
|
-
const bytes = new Uint8Array(totalBytes);
|
|
2715
|
-
let offset = 0;
|
|
2716
|
-
for (const chunk of chunks) {
|
|
2717
|
-
bytes.set(chunk, offset);
|
|
2718
|
-
offset += chunk.byteLength;
|
|
2719
|
-
}
|
|
2720
|
-
return bytes;
|
|
2721
|
-
}
|
|
2722
73
|
|
|
2723
74
|
// src/proxy.ts
|
|
2724
75
|
function formatSseEvent(event) {
|
|
@@ -3033,228 +384,6 @@ function normalizeWorkspaceInstructionPolicyRoleKey(value) {
|
|
|
3033
384
|
function normalizePreferenceRegistryStableKey(value) {
|
|
3034
385
|
return value.normalize("NFKC").trim().toLowerCase().replace(/\s+/gu, "-").replace(/-+/g, "-");
|
|
3035
386
|
}
|
|
3036
|
-
|
|
3037
|
-
// src/transcription.ts
|
|
3038
|
-
var DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY = {
|
|
3039
|
-
enabled: false,
|
|
3040
|
-
acceptanceId: null,
|
|
3041
|
-
primary: null,
|
|
3042
|
-
language: null,
|
|
3043
|
-
autoDetectLanguage: false,
|
|
3044
|
-
diarization: { enabled: false, maxSpeakers: null },
|
|
3045
|
-
retention: { mode: "none", maxDays: null },
|
|
3046
|
-
privacy: { allowProviderLogging: false, allowProviderTraining: false },
|
|
3047
|
-
fallback: { mode: "disabled", targets: [] },
|
|
3048
|
-
cost: { currency: "USD", maxPerHour: null, maxPerMonth: null }
|
|
3049
|
-
};
|
|
3050
|
-
function resolveWorkspaceVoiceInputEnabled(settings) {
|
|
3051
|
-
if (!isRecord(settings)) return null;
|
|
3052
|
-
const voiceInput = settings.voiceInput;
|
|
3053
|
-
if (isRecord(voiceInput) && typeof voiceInput.enabled === "boolean") {
|
|
3054
|
-
return voiceInput.enabled;
|
|
3055
|
-
}
|
|
3056
|
-
const legacy = settings.transcription;
|
|
3057
|
-
return isRecord(legacy) && typeof legacy.enabled === "boolean" ? legacy.enabled : null;
|
|
3058
|
-
}
|
|
3059
|
-
function resolveWorkspaceTranscriptionPolicy(settings) {
|
|
3060
|
-
if (!isRecord(settings)) return cloneDefaultPolicy();
|
|
3061
|
-
const candidate = settings.transcription;
|
|
3062
|
-
if (!isWorkspaceTranscriptionPolicy(candidate)) return cloneDefaultPolicy();
|
|
3063
|
-
return {
|
|
3064
|
-
...candidate,
|
|
3065
|
-
primary: candidate.primary ? normalizeTarget(candidate.primary) : null,
|
|
3066
|
-
language: candidate.language?.trim() ?? null,
|
|
3067
|
-
diarization: { ...candidate.diarization },
|
|
3068
|
-
retention: { ...candidate.retention },
|
|
3069
|
-
privacy: { ...candidate.privacy },
|
|
3070
|
-
fallback: {
|
|
3071
|
-
mode: candidate.fallback.mode,
|
|
3072
|
-
targets: candidate.fallback.targets.map(normalizeTarget)
|
|
3073
|
-
},
|
|
3074
|
-
cost: { ...candidate.cost }
|
|
3075
|
-
};
|
|
3076
|
-
}
|
|
3077
|
-
function authorizeTranscriptionAdapter(policy, descriptor, selection = { kind: "primary" }) {
|
|
3078
|
-
if (!isWorkspaceTranscriptionPolicy(policy)) {
|
|
3079
|
-
return { authorized: false, reason: "unaccepted" };
|
|
3080
|
-
}
|
|
3081
|
-
if (!policy.enabled) return { authorized: false, reason: "disabled" };
|
|
3082
|
-
if (!policy.acceptanceId) return { authorized: false, reason: "unaccepted" };
|
|
3083
|
-
let target;
|
|
3084
|
-
if (selection.kind === "primary") {
|
|
3085
|
-
target = policy.primary;
|
|
3086
|
-
} else {
|
|
3087
|
-
if (policy.fallback.mode !== "explicit") {
|
|
3088
|
-
return { authorized: false, reason: "fallback_disabled" };
|
|
3089
|
-
}
|
|
3090
|
-
target = policy.fallback.targets[selection.index];
|
|
3091
|
-
if (!target) return { authorized: false, reason: "fallback_unaccepted" };
|
|
3092
|
-
}
|
|
3093
|
-
if (!target) return { authorized: false, reason: "target_missing" };
|
|
3094
|
-
const acceptedTarget = normalizeTarget(target);
|
|
3095
|
-
if (acceptedTarget.provider !== descriptor.provider) {
|
|
3096
|
-
return { authorized: false, reason: "provider_mismatch" };
|
|
3097
|
-
}
|
|
3098
|
-
if (acceptedTarget.model !== descriptor.model) {
|
|
3099
|
-
return { authorized: false, reason: "model_mismatch" };
|
|
3100
|
-
}
|
|
3101
|
-
if (acceptedTarget.credentialMode !== descriptor.credentialMode) {
|
|
3102
|
-
return { authorized: false, reason: "credential_mode_mismatch" };
|
|
3103
|
-
}
|
|
3104
|
-
if (acceptedTarget.region !== descriptor.region) {
|
|
3105
|
-
return { authorized: false, reason: "region_mismatch" };
|
|
3106
|
-
}
|
|
3107
|
-
return {
|
|
3108
|
-
authorized: true,
|
|
3109
|
-
acceptanceId: policy.acceptanceId,
|
|
3110
|
-
target: acceptedTarget,
|
|
3111
|
-
selection
|
|
3112
|
-
};
|
|
3113
|
-
}
|
|
3114
|
-
function createTranscriptionSessionRequest(input) {
|
|
3115
|
-
const sequenceFloor = input.sequenceFloor ?? 0;
|
|
3116
|
-
if (!Number.isSafeInteger(sequenceFloor) || sequenceFloor < 0) return null;
|
|
3117
|
-
const authorization = authorizeTranscriptionAdapter(
|
|
3118
|
-
input.policy,
|
|
3119
|
-
input.adapter.descriptor,
|
|
3120
|
-
input.selection
|
|
3121
|
-
);
|
|
3122
|
-
if (!authorization.authorized) return null;
|
|
3123
|
-
return {
|
|
3124
|
-
localSessionId: input.localSessionId,
|
|
3125
|
-
policyAcceptanceId: authorization.acceptanceId,
|
|
3126
|
-
selection: authorization.selection,
|
|
3127
|
-
target: { ...authorization.target },
|
|
3128
|
-
language: input.policy.language?.trim() ?? null,
|
|
3129
|
-
autoDetectLanguage: input.policy.autoDetectLanguage,
|
|
3130
|
-
diarization: { ...input.policy.diarization },
|
|
3131
|
-
retention: { ...input.policy.retention },
|
|
3132
|
-
privacy: { ...input.policy.privacy },
|
|
3133
|
-
cost: { ...input.policy.cost },
|
|
3134
|
-
sequenceFloor
|
|
3135
|
-
};
|
|
3136
|
-
}
|
|
3137
|
-
function cloneDefaultPolicy() {
|
|
3138
|
-
return {
|
|
3139
|
-
...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
|
|
3140
|
-
diarization: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.diarization },
|
|
3141
|
-
retention: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.retention },
|
|
3142
|
-
privacy: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.privacy },
|
|
3143
|
-
fallback: { mode: "disabled", targets: [] },
|
|
3144
|
-
cost: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.cost }
|
|
3145
|
-
};
|
|
3146
|
-
}
|
|
3147
|
-
function isWorkspaceTranscriptionPolicy(value) {
|
|
3148
|
-
if (!isRecord(value) || typeof value.enabled !== "boolean") return false;
|
|
3149
|
-
if (!hasOnlyKeys(value, [
|
|
3150
|
-
"enabled",
|
|
3151
|
-
"acceptanceId",
|
|
3152
|
-
"primary",
|
|
3153
|
-
"language",
|
|
3154
|
-
"autoDetectLanguage",
|
|
3155
|
-
"diarization",
|
|
3156
|
-
"retention",
|
|
3157
|
-
"privacy",
|
|
3158
|
-
"fallback",
|
|
3159
|
-
"cost"
|
|
3160
|
-
])) {
|
|
3161
|
-
return false;
|
|
3162
|
-
}
|
|
3163
|
-
if (!(value.acceptanceId === null || isUuid(value.acceptanceId))) return false;
|
|
3164
|
-
if (!(value.primary === null || isTarget(value.primary))) return false;
|
|
3165
|
-
if (!(value.language === null || isBoundedString(value.language, 64))) return false;
|
|
3166
|
-
if (typeof value.autoDetectLanguage !== "boolean") return false;
|
|
3167
|
-
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)) {
|
|
3168
|
-
return false;
|
|
3169
|
-
}
|
|
3170
|
-
if (!value.diarization.enabled && value.diarization.maxSpeakers !== null) return false;
|
|
3171
|
-
if (!isRecord(value.retention) || !hasOnlyKeys(value.retention, ["mode", "maxDays"])) {
|
|
3172
|
-
return false;
|
|
3173
|
-
}
|
|
3174
|
-
if (value.retention.mode !== "none" && value.retention.mode !== "provider-policy") return false;
|
|
3175
|
-
if (!(value.retention.maxDays === null || isBoundedInteger(value.retention.maxDays, 3650))) {
|
|
3176
|
-
return false;
|
|
3177
|
-
}
|
|
3178
|
-
if (!isRecord(value.privacy) || !hasOnlyKeys(value.privacy, ["allowProviderLogging", "allowProviderTraining"]) || typeof value.privacy.allowProviderLogging !== "boolean" || typeof value.privacy.allowProviderTraining !== "boolean") {
|
|
3179
|
-
return false;
|
|
3180
|
-
}
|
|
3181
|
-
if (!isRecord(value.fallback) || !hasOnlyKeys(value.fallback, ["mode", "targets"])) {
|
|
3182
|
-
return false;
|
|
3183
|
-
}
|
|
3184
|
-
if (value.fallback.mode !== "disabled" && value.fallback.mode !== "explicit") return false;
|
|
3185
|
-
if (!Array.isArray(value.fallback.targets) || value.fallback.targets.length > 8 || !value.fallback.targets.every(isTarget)) {
|
|
3186
|
-
return false;
|
|
3187
|
-
}
|
|
3188
|
-
if (value.fallback.mode === "disabled" && value.fallback.targets.length !== 0) return false;
|
|
3189
|
-
if (value.fallback.mode === "explicit" && value.fallback.targets.length === 0) return false;
|
|
3190
|
-
if (!isRecord(value.cost) || !hasOnlyKeys(value.cost, ["currency", "maxPerHour", "maxPerMonth"]) || value.cost.currency !== "USD") {
|
|
3191
|
-
return false;
|
|
3192
|
-
}
|
|
3193
|
-
if (!isNullableBoundedNumber(value.cost.maxPerHour, 1e4)) return false;
|
|
3194
|
-
if (!isNullableBoundedNumber(value.cost.maxPerMonth, 1e6)) return false;
|
|
3195
|
-
if (value.enabled && (!value.acceptanceId || !value.primary)) return false;
|
|
3196
|
-
if (value.enabled && !value.autoDetectLanguage && value.language === null) return false;
|
|
3197
|
-
if (value.autoDetectLanguage && value.language !== null) return false;
|
|
3198
|
-
const targets = [value.primary, ...value.fallback.targets].filter(
|
|
3199
|
-
(target) => target !== null
|
|
3200
|
-
);
|
|
3201
|
-
if (new Set(targets.map(targetKey)).size !== targets.length) return false;
|
|
3202
|
-
return true;
|
|
3203
|
-
}
|
|
3204
|
-
function targetKey(target) {
|
|
3205
|
-
return [
|
|
3206
|
-
target.provider.trim(),
|
|
3207
|
-
target.model?.trim() ?? "",
|
|
3208
|
-
target.credentialMode,
|
|
3209
|
-
target.credentialConnectionId ?? "",
|
|
3210
|
-
target.region?.trim() ?? ""
|
|
3211
|
-
].join("\0");
|
|
3212
|
-
}
|
|
3213
|
-
function isTarget(value) {
|
|
3214
|
-
if (!isRecord(value)) return false;
|
|
3215
|
-
if (!hasOnlyKeys(value, ["provider", "model", "credentialMode", "credentialConnectionId", "region"])) {
|
|
3216
|
-
return false;
|
|
3217
|
-
}
|
|
3218
|
-
if (!isBoundedString(value.provider, 128)) return false;
|
|
3219
|
-
if (!(value.model === null || isBoundedString(value.model, 256))) return false;
|
|
3220
|
-
if (value.credentialMode !== "managed" && value.credentialMode !== "byok") return false;
|
|
3221
|
-
if (value.provider.trim() === "azure-speech" && value.credentialMode !== "byok") return false;
|
|
3222
|
-
if (!(value.credentialConnectionId === null || isUuid(value.credentialConnectionId))) {
|
|
3223
|
-
return false;
|
|
3224
|
-
}
|
|
3225
|
-
if (!(value.region === null || isBoundedString(value.region, 128))) return false;
|
|
3226
|
-
if (value.credentialMode === "byok" && value.credentialConnectionId === null) return false;
|
|
3227
|
-
if (value.credentialMode === "managed" && value.credentialConnectionId !== null) return false;
|
|
3228
|
-
return true;
|
|
3229
|
-
}
|
|
3230
|
-
function normalizeTarget(target) {
|
|
3231
|
-
return {
|
|
3232
|
-
provider: target.provider.trim(),
|
|
3233
|
-
model: target.model?.trim() ?? null,
|
|
3234
|
-
credentialMode: target.credentialMode,
|
|
3235
|
-
credentialConnectionId: target.credentialConnectionId,
|
|
3236
|
-
region: target.region?.trim() ?? null
|
|
3237
|
-
};
|
|
3238
|
-
}
|
|
3239
|
-
function isRecord(value) {
|
|
3240
|
-
return typeof value === "object" && value !== null;
|
|
3241
|
-
}
|
|
3242
|
-
function hasOnlyKeys(value, keys) {
|
|
3243
|
-
const accepted = new Set(keys);
|
|
3244
|
-
return Object.keys(value).every((key) => accepted.has(key));
|
|
3245
|
-
}
|
|
3246
|
-
function isBoundedString(value, maximum) {
|
|
3247
|
-
return typeof value === "string" && value.trim().length > 0 && value.length <= maximum;
|
|
3248
|
-
}
|
|
3249
|
-
function isUuid(value) {
|
|
3250
|
-
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);
|
|
3251
|
-
}
|
|
3252
|
-
function isBoundedInteger(value, maximum) {
|
|
3253
|
-
return Number.isInteger(value) && value >= 0 && value <= maximum;
|
|
3254
|
-
}
|
|
3255
|
-
function isNullableBoundedNumber(value, maximum) {
|
|
3256
|
-
return value === null || typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= maximum;
|
|
3257
|
-
}
|
|
3258
387
|
export {
|
|
3259
388
|
DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
|
|
3260
389
|
KNOWN_PERMISSIONS,
|
|
@@ -3264,7 +393,7 @@ export {
|
|
|
3264
393
|
OPENGENI_CORRELATION_HEADER,
|
|
3265
394
|
OpenGeniApiContractMismatchError,
|
|
3266
395
|
OpenGeniApiError,
|
|
3267
|
-
OpenGeniClient,
|
|
396
|
+
OpenGeniClient2 as OpenGeniClient,
|
|
3268
397
|
OpenGeniSessionListCursorError,
|
|
3269
398
|
OpenGeniStreamError,
|
|
3270
399
|
RETAINED_OUTPUT_DEFAULT_PAGE_BYTES,
|