@opengeni/sdk 0.13.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -17
- package/dist/index.d.ts +610 -85
- package/dist/index.js +730 -221
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +384 -71
- package/src/errors.ts +13 -0
- package/src/index.ts +77 -2
- package/src/stream.ts +3 -0
- package/src/transcription.ts +496 -0
- package/src/types.ts +465 -60
- package/src/workspace-control-stream.ts +101 -0
package/src/client.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { OpenGeniApiError } from "./errors";
|
|
1
|
+
import { OpenGeniApiContractMismatchError, OpenGeniApiError } from "./errors";
|
|
2
2
|
import {
|
|
3
3
|
streamSessionEvents,
|
|
4
4
|
type SessionEventStreamTransport,
|
|
5
5
|
type StreamSessionEventsOptions,
|
|
6
6
|
} from "./stream";
|
|
7
|
+
import {
|
|
8
|
+
streamWorkspaceControlEvents,
|
|
9
|
+
type WorkspaceControlStreamTransport,
|
|
10
|
+
} from "./workspace-control-stream";
|
|
7
11
|
import type {
|
|
8
12
|
AccessContext,
|
|
9
13
|
AddWorkspaceMemberRequest,
|
|
@@ -42,6 +46,7 @@ import type {
|
|
|
42
46
|
CreateKnowledgeMemoryRequest,
|
|
43
47
|
CreateScheduledTaskRequest,
|
|
44
48
|
CreateSessionRequest,
|
|
49
|
+
CreateSessionResponse,
|
|
45
50
|
CreateVariableSetRequest,
|
|
46
51
|
CreateRigRequest,
|
|
47
52
|
CreateWorkspaceRequest,
|
|
@@ -85,14 +90,25 @@ import type {
|
|
|
85
90
|
SessionListResponse,
|
|
86
91
|
UpdateSessionPinRequest,
|
|
87
92
|
SessionEvent,
|
|
93
|
+
SessionEventListOptions,
|
|
94
|
+
SessionEventPage,
|
|
88
95
|
SessionGoal,
|
|
96
|
+
SessionHumanInputRequest,
|
|
89
97
|
SessionLineageResponse,
|
|
90
98
|
SessionMcpCredentialUpdateInput,
|
|
91
99
|
SessionQueueSnapshot,
|
|
92
100
|
SessionQueueMutationResponse,
|
|
101
|
+
ComposerDraft,
|
|
102
|
+
DeleteSessionQueueItemRequest,
|
|
103
|
+
EditSessionQueueItemRequest,
|
|
104
|
+
MoveSessionQueueItemRequest,
|
|
105
|
+
SaveComposerDraftRequest,
|
|
106
|
+
SteerSessionQueueItemRequest,
|
|
93
107
|
SessionControlResponse,
|
|
94
108
|
WorkspaceInferenceControlResponse,
|
|
109
|
+
WorkspaceControlEvent,
|
|
95
110
|
SessionTurn,
|
|
111
|
+
SubmitHumanInputResponseRequest,
|
|
96
112
|
// Stream surfacing (Phase 5): capability negotiation + viewer lifecycle + config.
|
|
97
113
|
SessionCapabilities,
|
|
98
114
|
AttachViewerRequest,
|
|
@@ -122,7 +138,7 @@ import type {
|
|
|
122
138
|
GitLogResponse,
|
|
123
139
|
GitShowRequest,
|
|
124
140
|
GitShowResponse,
|
|
125
|
-
// Workbench v2 turn-end capture reads (M2
|
|
141
|
+
// Workbench v2 turn-end capture reads (M2).
|
|
126
142
|
GetWorkspaceCaptureResponse,
|
|
127
143
|
GetWorkspaceCaptureFileResponse,
|
|
128
144
|
TerminalExecRequest,
|
|
@@ -161,9 +177,17 @@ import type {
|
|
|
161
177
|
OAuthStartRequest,
|
|
162
178
|
OAuthStartResponse,
|
|
163
179
|
} from "./types";
|
|
180
|
+
import { OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION } from "./types";
|
|
164
181
|
|
|
165
182
|
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
166
183
|
|
|
184
|
+
export type WorkspaceControlEventPage = {
|
|
185
|
+
events: WorkspaceControlEvent[];
|
|
186
|
+
bytes: number;
|
|
187
|
+
truncated: boolean;
|
|
188
|
+
nextAfter: number | null;
|
|
189
|
+
};
|
|
190
|
+
|
|
167
191
|
export type OpenGeniClientOptions = {
|
|
168
192
|
/** Base URL of the OpenGeni API, e.g. `https://api.example.com`. */
|
|
169
193
|
baseUrl: string;
|
|
@@ -175,15 +199,22 @@ export type OpenGeniClientOptions = {
|
|
|
175
199
|
fetch?: FetchLike;
|
|
176
200
|
};
|
|
177
201
|
|
|
202
|
+
/** Per-request cancellation for identity-scoped, side-effect-free reads. */
|
|
203
|
+
export type OpenGeniRequestOptions = {
|
|
204
|
+
signal?: AbortSignal | undefined;
|
|
205
|
+
};
|
|
206
|
+
|
|
178
207
|
export type SendMessageInput = {
|
|
179
208
|
text: string;
|
|
209
|
+
/** System instructions scoped to this exact turn; never visible timeline text. */
|
|
210
|
+
turnInstructions?: string;
|
|
180
211
|
resources?: ResourceRef[];
|
|
181
212
|
tools?: ToolRef[];
|
|
182
213
|
model?: string;
|
|
183
214
|
reasoningEffort?: ReasoningEffort;
|
|
184
215
|
clientEventId?: string;
|
|
185
|
-
|
|
186
|
-
|
|
216
|
+
controlEtag?: string;
|
|
217
|
+
expectedDraftRevision?: number;
|
|
187
218
|
mcpCredentialUpdates?: SessionMcpCredentialUpdateInput[];
|
|
188
219
|
};
|
|
189
220
|
|
|
@@ -213,8 +244,11 @@ export class OpenGeniClient {
|
|
|
213
244
|
|
|
214
245
|
// --- Session lifecycle ---------------------------------------------------
|
|
215
246
|
|
|
216
|
-
async createSession(
|
|
217
|
-
|
|
247
|
+
async createSession(
|
|
248
|
+
workspaceId: string,
|
|
249
|
+
request: CreateSessionRequest,
|
|
250
|
+
): Promise<CreateSessionResponse> {
|
|
251
|
+
return await this.requestJson<CreateSessionResponse>(
|
|
218
252
|
"POST",
|
|
219
253
|
`/v1/workspaces/${workspaceId}/sessions`,
|
|
220
254
|
request,
|
|
@@ -276,7 +310,7 @@ export class OpenGeniClient {
|
|
|
276
310
|
search?: string;
|
|
277
311
|
} = {},
|
|
278
312
|
): Promise<SessionListResponse> {
|
|
279
|
-
|
|
313
|
+
return await this.requestJson<SessionListResponse>(
|
|
280
314
|
"GET",
|
|
281
315
|
`/v1/workspaces/${workspaceId}/sessions`,
|
|
282
316
|
undefined,
|
|
@@ -294,23 +328,6 @@ export class OpenGeniClient {
|
|
|
294
328
|
: {}),
|
|
295
329
|
},
|
|
296
330
|
);
|
|
297
|
-
if (Array.isArray(response)) {
|
|
298
|
-
// Rolling/same-major compatibility: an older API ignores `view=page` and
|
|
299
|
-
// returns the historical array. That is an honest one-page projection;
|
|
300
|
-
// never pretend it honored a cursor supplied directly by a caller.
|
|
301
|
-
if (options.cursor) {
|
|
302
|
-
throw new Error("The connected OpenGeni API does not support stable session-page cursors");
|
|
303
|
-
}
|
|
304
|
-
// Older APIs ignore unknown query parameters. Treating their unfiltered
|
|
305
|
-
// array as a successful search would be worse than an explicit rolling-
|
|
306
|
-
// upgrade error (and client-side filtering cannot recover matches beyond
|
|
307
|
-
// the old endpoint's bounded first page).
|
|
308
|
-
if (options.search?.trim()) {
|
|
309
|
-
throw new Error("The connected OpenGeni API does not support session search");
|
|
310
|
-
}
|
|
311
|
-
return { pinned: [], sessions: response, nextCursor: null };
|
|
312
|
-
}
|
|
313
|
-
return response;
|
|
314
331
|
}
|
|
315
332
|
|
|
316
333
|
/** Set this authenticated member's personal workspace pin for a session. */
|
|
@@ -358,7 +375,7 @@ export class OpenGeniClient {
|
|
|
358
375
|
*/
|
|
359
376
|
async listMachines(
|
|
360
377
|
workspaceId: string,
|
|
361
|
-
options: { sessionId?: string } = {},
|
|
378
|
+
options: { sessionId?: string; signal?: AbortSignal } = {},
|
|
362
379
|
): Promise<MachinesResponse> {
|
|
363
380
|
return await this.requestJson<MachinesResponse>(
|
|
364
381
|
"GET",
|
|
@@ -367,6 +384,7 @@ export class OpenGeniClient {
|
|
|
367
384
|
{
|
|
368
385
|
...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
|
|
369
386
|
},
|
|
387
|
+
{ signal: options.signal },
|
|
370
388
|
);
|
|
371
389
|
}
|
|
372
390
|
|
|
@@ -498,27 +516,101 @@ export class OpenGeniClient {
|
|
|
498
516
|
// --- Events: replay, send, stream ----------------------------------------
|
|
499
517
|
|
|
500
518
|
/**
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
519
|
+
* Return the events from one bounded page. With no cursor, this uses the safe
|
|
520
|
+
* semantic monitoring tail; pass explicit forensic options and a cursor for
|
|
521
|
+
* retained audit replay. Use `listEventPage` when projection, coverage, or
|
|
522
|
+
* resume-cursor facts are required.
|
|
505
523
|
*/
|
|
506
524
|
async listEvents(
|
|
507
525
|
workspaceId: string,
|
|
508
526
|
sessionId: string,
|
|
509
|
-
options:
|
|
527
|
+
options: SessionEventListOptions = {},
|
|
510
528
|
): Promise<SessionEvent[]> {
|
|
511
|
-
return await this.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
529
|
+
return (await this.listEventPage(workspaceId, sessionId, options)).events;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Bounded durable/monitoring page plus exact projection and cursor facts. */
|
|
533
|
+
async listEventPage(
|
|
534
|
+
workspaceId: string,
|
|
535
|
+
sessionId: string,
|
|
536
|
+
options: SessionEventListOptions = {},
|
|
537
|
+
): Promise<SessionEventPage> {
|
|
538
|
+
if (
|
|
539
|
+
options.latest &&
|
|
540
|
+
["includeTypes", "excludeTypes", "includeClasses", "excludeClasses"].some((name) =>
|
|
541
|
+
Object.prototype.hasOwnProperty.call(options, name),
|
|
542
|
+
)
|
|
543
|
+
) {
|
|
544
|
+
throw new TypeError("latest cannot be combined with event filters");
|
|
545
|
+
}
|
|
546
|
+
const response = await this.fetchImpl(
|
|
547
|
+
this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, {
|
|
516
548
|
...(options.after !== undefined ? { after: String(options.after) } : {}),
|
|
517
549
|
...(options.before !== undefined ? { before: String(options.before) } : {}),
|
|
518
550
|
...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
|
|
519
551
|
...(options.compact ? { compact: "1" } : {}),
|
|
552
|
+
...(options.mode ? { mode: options.mode } : {}),
|
|
553
|
+
...(options.direction ? { direction: options.direction } : {}),
|
|
554
|
+
...(options.payloadMode ? { payloadMode: options.payloadMode } : {}),
|
|
555
|
+
...(options.includeTypes?.length ? { includeTypes: options.includeTypes.join(",") } : {}),
|
|
556
|
+
...(options.excludeTypes?.length ? { excludeTypes: options.excludeTypes.join(",") } : {}),
|
|
557
|
+
...(options.includeClasses?.length
|
|
558
|
+
? { includeClasses: options.includeClasses.join(",") }
|
|
559
|
+
: {}),
|
|
560
|
+
...(options.excludeClasses?.length
|
|
561
|
+
? { excludeClasses: options.excludeClasses.join(",") }
|
|
562
|
+
: {}),
|
|
563
|
+
...(options.latest ? { latest: options.latest } : {}),
|
|
564
|
+
}),
|
|
565
|
+
{
|
|
566
|
+
method: "GET",
|
|
567
|
+
headers: { ...this.headers(), Accept: "application/json" },
|
|
520
568
|
},
|
|
521
569
|
);
|
|
570
|
+
assertApiContractResponse(response);
|
|
571
|
+
if (!response.ok) throw new OpenGeniApiError(response.status, await safeText(response));
|
|
572
|
+
const events = (await response.json()) as SessionEvent[];
|
|
573
|
+
const integerHeader = (name: string): number | null => {
|
|
574
|
+
const raw = response.headers.get(name);
|
|
575
|
+
if (raw === null) return null;
|
|
576
|
+
const value = Number(raw);
|
|
577
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
578
|
+
};
|
|
579
|
+
const mode =
|
|
580
|
+
response.headers.get("X-OpenGeni-Event-Mode") === "forensic" ? "forensic" : "monitoring";
|
|
581
|
+
const direction =
|
|
582
|
+
response.headers.get("X-OpenGeni-Event-Direction") === "after" ? "after" : "before";
|
|
583
|
+
const payloadHeader = response.headers.get("X-OpenGeni-Payload-Mode");
|
|
584
|
+
const payloadMode =
|
|
585
|
+
payloadHeader === "none" || payloadHeader === "full" ? payloadHeader : "summary";
|
|
586
|
+
const first = integerHeader("X-OpenGeni-Covered-First");
|
|
587
|
+
const last = integerHeader("X-OpenGeni-Covered-Last");
|
|
588
|
+
const bytes =
|
|
589
|
+
integerHeader("X-OpenGeni-Page-Bytes") ??
|
|
590
|
+
new TextEncoder().encode(JSON.stringify(events)).byteLength;
|
|
591
|
+
const maxBytes = integerHeader("X-OpenGeni-Page-Max-Bytes") ?? 1024 * 1024;
|
|
592
|
+
const truncatedByHeader = response.headers.get("X-OpenGeni-Truncated-By");
|
|
593
|
+
const truncatedBy =
|
|
594
|
+
truncatedByHeader === "count" ||
|
|
595
|
+
truncatedByHeader === "bytes" ||
|
|
596
|
+
truncatedByHeader === "http_bytes"
|
|
597
|
+
? truncatedByHeader
|
|
598
|
+
: null;
|
|
599
|
+
return {
|
|
600
|
+
events,
|
|
601
|
+
mode,
|
|
602
|
+
payloadMode,
|
|
603
|
+
direction,
|
|
604
|
+
bytes,
|
|
605
|
+
maxBytes,
|
|
606
|
+
truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
|
|
607
|
+
hasMore: response.headers.get("X-OpenGeni-Has-More") === "true",
|
|
608
|
+
truncatedBy,
|
|
609
|
+
coveredSequence: first === null || last === null ? null : { first, last },
|
|
610
|
+
nextAfter: integerHeader("X-OpenGeni-Next-After"),
|
|
611
|
+
nextBefore: integerHeader("X-OpenGeni-Next-Before"),
|
|
612
|
+
forensicExact: response.headers.get("X-OpenGeni-Forensic-Exact") === "true",
|
|
613
|
+
};
|
|
522
614
|
}
|
|
523
615
|
|
|
524
616
|
/** POST a user/control event to the session. Returns the accepted event. */
|
|
@@ -551,14 +643,14 @@ export class OpenGeniClient {
|
|
|
551
643
|
async pauseSession(
|
|
552
644
|
workspaceId: string,
|
|
553
645
|
sessionId: string,
|
|
554
|
-
options: { reason?: string; clientEventId?: string } = {},
|
|
555
|
-
): Promise<
|
|
556
|
-
return (
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
})
|
|
561
|
-
)
|
|
646
|
+
options: { reason?: string; clientEventId?: string; expectedControlEtag?: string } = {},
|
|
647
|
+
): Promise<SessionControlResponse> {
|
|
648
|
+
return await this.controlSession(workspaceId, sessionId, {
|
|
649
|
+
action: "pause",
|
|
650
|
+
clientEventId: options.clientEventId ?? crypto.randomUUID(),
|
|
651
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
652
|
+
...(options.expectedControlEtag ? { expectedControlEtag: options.expectedControlEtag } : {}),
|
|
653
|
+
});
|
|
562
654
|
}
|
|
563
655
|
|
|
564
656
|
async sendApprovalDecision(
|
|
@@ -579,6 +671,47 @@ export class OpenGeniClient {
|
|
|
579
671
|
});
|
|
580
672
|
}
|
|
581
673
|
|
|
674
|
+
async listHumanInputRequests(
|
|
675
|
+
workspaceId: string,
|
|
676
|
+
sessionId: string,
|
|
677
|
+
options: {
|
|
678
|
+
status?: SessionHumanInputRequest["status"];
|
|
679
|
+
} = {},
|
|
680
|
+
): Promise<SessionHumanInputRequest[]> {
|
|
681
|
+
const result = await this.requestJson<{ requests: SessionHumanInputRequest[] }>(
|
|
682
|
+
"GET",
|
|
683
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests`,
|
|
684
|
+
undefined,
|
|
685
|
+
options.status ? { status: options.status } : undefined,
|
|
686
|
+
);
|
|
687
|
+
return result.requests;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
async getHumanInputRequest(
|
|
691
|
+
workspaceId: string,
|
|
692
|
+
sessionId: string,
|
|
693
|
+
requestId: string,
|
|
694
|
+
): Promise<SessionHumanInputRequest> {
|
|
695
|
+
return await this.requestJson<SessionHumanInputRequest>(
|
|
696
|
+
"GET",
|
|
697
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests/${requestId}`,
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async submitHumanInputResponse(
|
|
702
|
+
workspaceId: string,
|
|
703
|
+
sessionId: string,
|
|
704
|
+
requestId: string,
|
|
705
|
+
response: SubmitHumanInputResponseRequest,
|
|
706
|
+
options: { clientEventId?: string } = {},
|
|
707
|
+
): Promise<SessionEvent> {
|
|
708
|
+
return await this.sendEvent(workspaceId, sessionId, {
|
|
709
|
+
type: "user.humanInputResponse",
|
|
710
|
+
...(options.clientEventId ? { clientEventId: options.clientEventId } : {}),
|
|
711
|
+
payload: { requestId, response },
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
582
715
|
/**
|
|
583
716
|
* Live-stream a session's events with automatic reconnect, resume from the
|
|
584
717
|
* last seen sequence, gap backfill, and duplicate suppression. See
|
|
@@ -619,6 +752,7 @@ export class OpenGeniClient {
|
|
|
619
752
|
headers: { ...this.headers(), Accept: "text/event-stream" },
|
|
620
753
|
...(options.signal ? { signal: options.signal } : {}),
|
|
621
754
|
});
|
|
755
|
+
assertApiContractResponse(response);
|
|
622
756
|
if (!response.ok) {
|
|
623
757
|
throw new OpenGeniApiError(response.status, await safeText(response));
|
|
624
758
|
}
|
|
@@ -637,15 +771,73 @@ export class OpenGeniClient {
|
|
|
637
771
|
);
|
|
638
772
|
}
|
|
639
773
|
|
|
640
|
-
async
|
|
774
|
+
async moveQueueItem(
|
|
775
|
+
workspaceId: string,
|
|
776
|
+
sessionId: string,
|
|
777
|
+
turnId: string,
|
|
778
|
+
request: MoveSessionQueueItemRequest,
|
|
779
|
+
): Promise<SessionQueueMutationResponse> {
|
|
780
|
+
return await this.requestJson<SessionQueueMutationResponse>(
|
|
781
|
+
"POST",
|
|
782
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/move`,
|
|
783
|
+
request,
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
async editQueueItem(
|
|
788
|
+
workspaceId: string,
|
|
789
|
+
sessionId: string,
|
|
790
|
+
turnId: string,
|
|
791
|
+
request: EditSessionQueueItemRequest,
|
|
792
|
+
): Promise<SessionQueueMutationResponse> {
|
|
793
|
+
return await this.requestJson<SessionQueueMutationResponse>(
|
|
794
|
+
"POST",
|
|
795
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/edit`,
|
|
796
|
+
request,
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
async steerQueueItem(
|
|
801
|
+
workspaceId: string,
|
|
802
|
+
sessionId: string,
|
|
803
|
+
turnId: string,
|
|
804
|
+
request: SteerSessionQueueItemRequest,
|
|
805
|
+
): Promise<SessionQueueMutationResponse> {
|
|
806
|
+
return await this.requestJson<SessionQueueMutationResponse>(
|
|
807
|
+
"POST",
|
|
808
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/steer`,
|
|
809
|
+
request,
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async deleteQueueItem(
|
|
641
814
|
workspaceId: string,
|
|
642
815
|
sessionId: string,
|
|
643
816
|
turnId: string,
|
|
644
|
-
request:
|
|
817
|
+
request: DeleteSessionQueueItemRequest,
|
|
645
818
|
): Promise<SessionQueueMutationResponse> {
|
|
646
819
|
return await this.requestJson<SessionQueueMutationResponse>(
|
|
647
820
|
"POST",
|
|
648
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/
|
|
821
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue/${turnId}/delete`,
|
|
822
|
+
request,
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
async getComposerDraft(workspaceId: string, sessionId: string): Promise<ComposerDraft> {
|
|
827
|
+
return await this.requestJson<ComposerDraft>(
|
|
828
|
+
"GET",
|
|
829
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`,
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
async saveComposerDraft(
|
|
834
|
+
workspaceId: string,
|
|
835
|
+
sessionId: string,
|
|
836
|
+
request: SaveComposerDraftRequest,
|
|
837
|
+
): Promise<ComposerDraft> {
|
|
838
|
+
return await this.requestJson<ComposerDraft>(
|
|
839
|
+
"PUT",
|
|
840
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`,
|
|
649
841
|
request,
|
|
650
842
|
);
|
|
651
843
|
}
|
|
@@ -654,12 +846,10 @@ export class OpenGeniClient {
|
|
|
654
846
|
workspaceId: string,
|
|
655
847
|
sessionId: string,
|
|
656
848
|
request: {
|
|
657
|
-
|
|
849
|
+
action: "pause" | "resume";
|
|
658
850
|
reason?: string;
|
|
659
|
-
clientEventId
|
|
660
|
-
|
|
661
|
-
expectedControlGeneration?: number;
|
|
662
|
-
expectedWorkspaceInferenceGeneration?: number;
|
|
851
|
+
clientEventId: string;
|
|
852
|
+
expectedControlEtag?: string;
|
|
663
853
|
},
|
|
664
854
|
): Promise<SessionControlResponse> {
|
|
665
855
|
return await this.requestJson<SessionControlResponse>(
|
|
@@ -672,20 +862,23 @@ export class OpenGeniClient {
|
|
|
672
862
|
async resumeSession(
|
|
673
863
|
workspaceId: string,
|
|
674
864
|
sessionId: string,
|
|
675
|
-
options: { reason?: string; clientEventId?: string } = {},
|
|
865
|
+
options: { reason?: string; clientEventId?: string; expectedControlEtag?: string } = {},
|
|
676
866
|
): Promise<SessionControlResponse> {
|
|
677
|
-
return await this.controlSession(workspaceId, sessionId, {
|
|
867
|
+
return await this.controlSession(workspaceId, sessionId, {
|
|
868
|
+
action: "resume",
|
|
869
|
+
clientEventId: options.clientEventId ?? crypto.randomUUID(),
|
|
870
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
871
|
+
...(options.expectedControlEtag ? { expectedControlEtag: options.expectedControlEtag } : {}),
|
|
872
|
+
});
|
|
678
873
|
}
|
|
679
874
|
|
|
680
875
|
async setWorkspaceInferenceState(
|
|
681
876
|
workspaceId: string,
|
|
682
877
|
request: {
|
|
683
|
-
|
|
684
|
-
reason
|
|
878
|
+
action: "pause" | "resume";
|
|
879
|
+
reason?: string;
|
|
685
880
|
clientEventId: string;
|
|
686
|
-
|
|
687
|
-
expectedGeneration: number;
|
|
688
|
-
exceptSessionIds?: string[];
|
|
881
|
+
expectedRevision?: number;
|
|
689
882
|
},
|
|
690
883
|
): Promise<WorkspaceInferenceControlResponse> {
|
|
691
884
|
return await this.requestJson<WorkspaceInferenceControlResponse>(
|
|
@@ -695,16 +888,88 @@ export class OpenGeniClient {
|
|
|
695
888
|
);
|
|
696
889
|
}
|
|
697
890
|
|
|
698
|
-
|
|
699
|
-
async deleteQueuedTurn(
|
|
891
|
+
async listWorkspaceControlEvents(
|
|
700
892
|
workspaceId: string,
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
893
|
+
options: { after?: number; limit?: number } = {},
|
|
894
|
+
): Promise<WorkspaceControlEvent[]> {
|
|
895
|
+
return (await this.listWorkspaceControlEventPage(workspaceId, options)).events;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/** Count/byte-bounded page plus an explicit continuation cursor. */
|
|
899
|
+
async listWorkspaceControlEventPage(
|
|
900
|
+
workspaceId: string,
|
|
901
|
+
options: { after?: number; limit?: number } = {},
|
|
902
|
+
): Promise<WorkspaceControlEventPage> {
|
|
903
|
+
const response = await this.fetchImpl(
|
|
904
|
+
this.url(`/v1/workspaces/${workspaceId}/control-events`, {
|
|
905
|
+
...(options.after !== undefined ? { after: String(options.after) } : {}),
|
|
906
|
+
...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
|
|
907
|
+
}),
|
|
908
|
+
{
|
|
909
|
+
method: "GET",
|
|
910
|
+
headers: { ...this.headers(), Accept: "application/json" },
|
|
911
|
+
},
|
|
707
912
|
);
|
|
913
|
+
assertApiContractResponse(response);
|
|
914
|
+
if (!response.ok) {
|
|
915
|
+
throw new OpenGeniApiError(response.status, await safeText(response));
|
|
916
|
+
}
|
|
917
|
+
const events = (await response.json()) as WorkspaceControlEvent[];
|
|
918
|
+
const bytesHeader = response.headers.get("X-OpenGeni-Page-Bytes");
|
|
919
|
+
const nextHeader = response.headers.get("X-OpenGeni-Next-After");
|
|
920
|
+
const parsedBytes = bytesHeader === null ? Number.NaN : Number(bytesHeader);
|
|
921
|
+
const parsedNext = nextHeader === null ? null : Number(nextHeader);
|
|
922
|
+
return {
|
|
923
|
+
events,
|
|
924
|
+
bytes:
|
|
925
|
+
Number.isSafeInteger(parsedBytes) && parsedBytes >= 0
|
|
926
|
+
? parsedBytes
|
|
927
|
+
: new TextEncoder().encode(JSON.stringify(events)).byteLength,
|
|
928
|
+
truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
|
|
929
|
+
nextAfter:
|
|
930
|
+
parsedNext !== null && Number.isSafeInteger(parsedNext) && parsedNext >= 0
|
|
931
|
+
? parsedNext
|
|
932
|
+
: null,
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
streamWorkspaceControlEvents(
|
|
937
|
+
workspaceId: string,
|
|
938
|
+
options: StreamSessionEventsOptions = {},
|
|
939
|
+
): AsyncGenerator<WorkspaceControlEvent, void, void> {
|
|
940
|
+
return streamWorkspaceControlEvents(this.workspaceControlStreamTransport(workspaceId), options);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
workspaceControlStreamTransport(workspaceId: string): WorkspaceControlStreamTransport {
|
|
944
|
+
return {
|
|
945
|
+
openStream: async (after, signal) =>
|
|
946
|
+
await this.openWorkspaceControlEventStream(workspaceId, {
|
|
947
|
+
after,
|
|
948
|
+
...(signal ? { signal } : {}),
|
|
949
|
+
}),
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
async openWorkspaceControlEventStream(
|
|
954
|
+
workspaceId: string,
|
|
955
|
+
options: { after?: number; signal?: AbortSignal } = {},
|
|
956
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
957
|
+
const response = await this.fetchImpl(
|
|
958
|
+
this.url(`/v1/workspaces/${workspaceId}/control-events/stream`, {
|
|
959
|
+
after: String(options.after ?? 0),
|
|
960
|
+
}),
|
|
961
|
+
{
|
|
962
|
+
method: "GET",
|
|
963
|
+
headers: { ...this.headers(), Accept: "text/event-stream" },
|
|
964
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
965
|
+
},
|
|
966
|
+
);
|
|
967
|
+
assertApiContractResponse(response);
|
|
968
|
+
if (!response.ok) throw new OpenGeniApiError(response.status, await safeText(response));
|
|
969
|
+
if (!response.body) {
|
|
970
|
+
throw new OpenGeniApiError(response.status, "SSE response did not include a readable body");
|
|
971
|
+
}
|
|
972
|
+
return response.body;
|
|
708
973
|
}
|
|
709
974
|
|
|
710
975
|
/**
|
|
@@ -806,11 +1071,14 @@ export class OpenGeniClient {
|
|
|
806
1071
|
workspaceId: string,
|
|
807
1072
|
sessionId: string,
|
|
808
1073
|
request: FsListRequest = {},
|
|
1074
|
+
options: OpenGeniRequestOptions = {},
|
|
809
1075
|
): Promise<FsListResponse> {
|
|
810
1076
|
return await this.requestJson<FsListResponse>(
|
|
811
1077
|
"POST",
|
|
812
1078
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list`,
|
|
813
1079
|
request,
|
|
1080
|
+
{},
|
|
1081
|
+
options,
|
|
814
1082
|
);
|
|
815
1083
|
}
|
|
816
1084
|
|
|
@@ -819,11 +1087,14 @@ export class OpenGeniClient {
|
|
|
819
1087
|
workspaceId: string,
|
|
820
1088
|
sessionId: string,
|
|
821
1089
|
request: FsReadRequest,
|
|
1090
|
+
options: OpenGeniRequestOptions = {},
|
|
822
1091
|
): Promise<FsReadResponse> {
|
|
823
1092
|
return await this.requestJson<FsReadResponse>(
|
|
824
1093
|
"POST",
|
|
825
1094
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/read`,
|
|
826
1095
|
request,
|
|
1096
|
+
{},
|
|
1097
|
+
options,
|
|
827
1098
|
);
|
|
828
1099
|
}
|
|
829
1100
|
|
|
@@ -884,11 +1155,14 @@ export class OpenGeniClient {
|
|
|
884
1155
|
workspaceId: string,
|
|
885
1156
|
sessionId: string,
|
|
886
1157
|
request: GitStatusRequest = {},
|
|
1158
|
+
options: OpenGeniRequestOptions = {},
|
|
887
1159
|
): Promise<GitStatusResponse> {
|
|
888
1160
|
return await this.requestJson<GitStatusResponse>(
|
|
889
1161
|
"POST",
|
|
890
1162
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/status`,
|
|
891
1163
|
request,
|
|
1164
|
+
{},
|
|
1165
|
+
options,
|
|
892
1166
|
);
|
|
893
1167
|
}
|
|
894
1168
|
|
|
@@ -897,11 +1171,14 @@ export class OpenGeniClient {
|
|
|
897
1171
|
workspaceId: string,
|
|
898
1172
|
sessionId: string,
|
|
899
1173
|
request: GitDiffRequest = {},
|
|
1174
|
+
options: OpenGeniRequestOptions = {},
|
|
900
1175
|
): Promise<GitDiffResponse> {
|
|
901
1176
|
return await this.requestJson<GitDiffResponse>(
|
|
902
1177
|
"POST",
|
|
903
1178
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/diff`,
|
|
904
1179
|
request,
|
|
1180
|
+
{},
|
|
1181
|
+
options,
|
|
905
1182
|
);
|
|
906
1183
|
}
|
|
907
1184
|
|
|
@@ -938,10 +1215,14 @@ export class OpenGeniClient {
|
|
|
938
1215
|
async getWorkspaceCapture(
|
|
939
1216
|
workspaceId: string,
|
|
940
1217
|
sessionId: string,
|
|
1218
|
+
options: OpenGeniRequestOptions = {},
|
|
941
1219
|
): Promise<GetWorkspaceCaptureResponse> {
|
|
942
1220
|
return await this.requestJson<GetWorkspaceCaptureResponse>(
|
|
943
1221
|
"GET",
|
|
944
1222
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture`,
|
|
1223
|
+
undefined,
|
|
1224
|
+
{},
|
|
1225
|
+
options,
|
|
945
1226
|
);
|
|
946
1227
|
}
|
|
947
1228
|
|
|
@@ -953,6 +1234,7 @@ export class OpenGeniClient {
|
|
|
953
1234
|
sessionId: string,
|
|
954
1235
|
path: string,
|
|
955
1236
|
revision?: number,
|
|
1237
|
+
options: OpenGeniRequestOptions = {},
|
|
956
1238
|
): Promise<GetWorkspaceCaptureFileResponse> {
|
|
957
1239
|
const query: Record<string, string> = { path };
|
|
958
1240
|
if (revision !== undefined) query.revision = String(revision);
|
|
@@ -961,6 +1243,7 @@ export class OpenGeniClient {
|
|
|
961
1243
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture/file`,
|
|
962
1244
|
undefined,
|
|
963
1245
|
query,
|
|
1246
|
+
options,
|
|
964
1247
|
);
|
|
965
1248
|
}
|
|
966
1249
|
|
|
@@ -1045,10 +1328,14 @@ export class OpenGeniClient {
|
|
|
1045
1328
|
async getStreamCapabilities(
|
|
1046
1329
|
workspaceId: string,
|
|
1047
1330
|
sessionId: string,
|
|
1331
|
+
options: OpenGeniRequestOptions = {},
|
|
1048
1332
|
): Promise<SessionCapabilities> {
|
|
1049
1333
|
return await this.requestJson<SessionCapabilities>(
|
|
1050
1334
|
"GET",
|
|
1051
1335
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`,
|
|
1336
|
+
undefined,
|
|
1337
|
+
{},
|
|
1338
|
+
options,
|
|
1052
1339
|
);
|
|
1053
1340
|
}
|
|
1054
1341
|
|
|
@@ -1121,7 +1408,14 @@ export class OpenGeniClient {
|
|
|
1121
1408
|
* knowledge of the host setup; safe to call before any auth is established.
|
|
1122
1409
|
*/
|
|
1123
1410
|
async getClientConfig(): Promise<ClientConfig> {
|
|
1124
|
-
|
|
1411
|
+
const config = await this.requestJson<ClientConfig>("GET", "/v1/config/client");
|
|
1412
|
+
if (config.apiContractRevision !== OPENGENI_API_CONTRACT_REVISION) {
|
|
1413
|
+
throw new OpenGeniApiContractMismatchError(
|
|
1414
|
+
OPENGENI_API_CONTRACT_REVISION,
|
|
1415
|
+
String(config.apiContractRevision || "(missing)"),
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
return config;
|
|
1125
1419
|
}
|
|
1126
1420
|
|
|
1127
1421
|
/** The caller's access context: subject, account + workspace grants, defaults. */
|
|
@@ -1944,15 +2238,14 @@ export class OpenGeniClient {
|
|
|
1944
2238
|
|
|
1945
2239
|
// --- GitHub ----------------------------------------------------------------------------------
|
|
1946
2240
|
|
|
1947
|
-
/** GitHub App configuration status
|
|
2241
|
+
/** GitHub App configuration status; install/link URLs are null while new binding is disabled. */
|
|
1948
2242
|
async getGitHubApp(workspaceId: string): Promise<GitHubAppInfo> {
|
|
1949
2243
|
return await this.requestJson<GitHubAppInfo>("GET", `/v1/workspaces/${workspaceId}/github/app`);
|
|
1950
2244
|
}
|
|
1951
2245
|
|
|
1952
2246
|
/**
|
|
1953
|
-
*
|
|
1954
|
-
*
|
|
1955
|
-
* `getGitHubApp().installUrl` or a github_connect_link tool.
|
|
2247
|
+
* Compatibility URL for previously issued state. New installation binding is
|
|
2248
|
+
* disabled, so the endpoint validates state and terminates with HTTP 410.
|
|
1956
2249
|
*/
|
|
1957
2250
|
githubConnectUrl(workspaceId: string, state: string): string {
|
|
1958
2251
|
return this.url(`/v1/workspaces/${workspaceId}/github/connect`, { state });
|
|
@@ -1973,6 +2266,14 @@ export class OpenGeniClient {
|
|
|
1973
2266
|
);
|
|
1974
2267
|
}
|
|
1975
2268
|
|
|
2269
|
+
/** Remove one workspace binding without uninstalling the GitHub App itself. */
|
|
2270
|
+
async unlinkGitHubInstallation(workspaceId: string, installationId: number): Promise<void> {
|
|
2271
|
+
await this.requestVoid(
|
|
2272
|
+
"DELETE",
|
|
2273
|
+
`/v1/workspaces/${workspaceId}/github/installations/${installationId}`,
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
|
|
1976
2277
|
/** Build a GitHub App manifest + the GitHub URL to submit it to. */
|
|
1977
2278
|
async createGitHubAppManifest(
|
|
1978
2279
|
workspaceId: string,
|
|
@@ -2058,6 +2359,7 @@ export class OpenGeniClient {
|
|
|
2058
2359
|
return {
|
|
2059
2360
|
...(this.options.apiKey ? { Authorization: `Bearer ${this.options.apiKey}` } : {}),
|
|
2060
2361
|
...extra,
|
|
2362
|
+
[OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION,
|
|
2061
2363
|
};
|
|
2062
2364
|
}
|
|
2063
2365
|
|
|
@@ -2198,6 +2500,7 @@ export class OpenGeniClient {
|
|
|
2198
2500
|
path: string,
|
|
2199
2501
|
body?: unknown,
|
|
2200
2502
|
query: Record<string, string> = {},
|
|
2503
|
+
options: OpenGeniRequestOptions = {},
|
|
2201
2504
|
): Promise<T> {
|
|
2202
2505
|
const response = await this.fetchImpl(this.url(path, query), {
|
|
2203
2506
|
method,
|
|
@@ -2207,7 +2510,9 @@ export class OpenGeniClient {
|
|
|
2207
2510
|
...(body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
2208
2511
|
},
|
|
2209
2512
|
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
2513
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
2210
2514
|
});
|
|
2515
|
+
assertApiContractResponse(response);
|
|
2211
2516
|
if (!response.ok) {
|
|
2212
2517
|
throw new OpenGeniApiError(response.status, await safeText(response));
|
|
2213
2518
|
}
|
|
@@ -2225,12 +2530,20 @@ export class OpenGeniClient {
|
|
|
2225
2530
|
},
|
|
2226
2531
|
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
2227
2532
|
});
|
|
2533
|
+
assertApiContractResponse(response);
|
|
2228
2534
|
if (!response.ok) {
|
|
2229
2535
|
throw new OpenGeniApiError(response.status, await safeText(response));
|
|
2230
2536
|
}
|
|
2231
2537
|
}
|
|
2232
2538
|
}
|
|
2233
2539
|
|
|
2540
|
+
function assertApiContractResponse(response: Response): void {
|
|
2541
|
+
const actual = response.headers.get(OPENGENI_API_CONTRACT_HEADER);
|
|
2542
|
+
if (actual && actual !== OPENGENI_API_CONTRACT_REVISION) {
|
|
2543
|
+
throw new OpenGeniApiContractMismatchError(OPENGENI_API_CONTRACT_REVISION, actual);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2234
2547
|
async function safeText(response: Response): Promise<string> {
|
|
2235
2548
|
try {
|
|
2236
2549
|
return await response.text();
|