@neta-art/cohub 1.2.1 → 1.3.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/dist/apis/explore.d.ts +9 -0
- package/dist/apis/explore.js +9 -0
- package/dist/apis/spaces.d.ts +94 -4
- package/dist/apis/spaces.js +177 -3
- package/dist/apis/user.d.ts +2 -1
- package/dist/apis/user.js +6 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/session-patch-reducer.d.ts +3 -0
- package/dist/session-patch-reducer.js +18 -4
- package/dist/transport.d.ts +14 -3
- package/dist/transport.js +27 -7
- package/dist/types.d.ts +74 -2
- package/dist/websocket.d.ts +5 -0
- package/dist/websocket.js +98 -2
- package/package.json +2 -2
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ExploreSpaceItem } from "../types.js";
|
|
2
|
+
import type { HttpTransport } from "../transport.js";
|
|
3
|
+
export declare class ExploreApi {
|
|
4
|
+
private readonly transport;
|
|
5
|
+
constructor(transport: HttpTransport);
|
|
6
|
+
spaces(): Promise<{
|
|
7
|
+
spaces: ExploreSpaceItem[];
|
|
8
|
+
}>;
|
|
9
|
+
}
|
package/dist/apis/spaces.d.ts
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
+
import type { SpacePublicEndpoints } from "@neta-art/cohub-protocol/ports";
|
|
1
2
|
import type { WebsocketClient, WebsocketEventPayload } from "../websocket.js";
|
|
2
3
|
import type { HttpTransport, Fetch } from "../transport.js";
|
|
3
4
|
import { type SessionPatchApplyResult } from "../session-patch-reducer.js";
|
|
4
|
-
import type { CheckpointRecord, ContentBlock, SessionMessageResponse, SessionMessagesPaginatedResponse, SessionMessagesResponse, SessionRecord, SpaceAccessPolicy, SpaceBootstrapSource, SpaceChannelBindingInput, SpaceCheckpointDetailResponse, SpaceCreateResponse, SpaceEnvInput, SpaceFsFileResponse, SpaceFsMoveInput, SpaceFsTreeResponse, SpaceFsUploadResponse, SpaceUsageResponse, SpaceFsWriteFileInput, SpaceMember, SpaceRecord, SpaceRole, SpaceSessionsResponse } from "../types.js";
|
|
5
|
+
import type { CheckpointRecord, ContentBlock, SessionMessageResponse, SessionMessagesPaginatedResponse, SessionMessagesResponse, SessionTurnResponse, SessionTurnIndexResponse, SessionTurnWindowResponse, SessionTurnsPaginatedResponse, SessionTurnSignedUrlsResponse, SessionRecord, SpaceAccessPolicy, SpaceBootstrapSource, SpaceChannelBindingInput, SpaceCheckpointDetailResponse, SpaceCreateResponse, SpaceEnvInput, SpaceFsFileResponse, SpaceFsMoveInput, SpaceFsTreeResponse, SpaceFsUploadResponse, SpaceUsageResponse, SpaceFsWriteFileInput, SpaceMarkKind, SpaceMarkListItem, SpaceMarkResourceType, SpaceMember, SpaceRecord, SpaceRole, SpaceSessionsResponse } from "../types.js";
|
|
5
6
|
import { SpaceInvitationsApi } from "./invitations.js";
|
|
6
7
|
export type SessionSubscriptionHandlers = {
|
|
7
8
|
patch?: (event: WebsocketEventPayload) => void;
|
|
8
9
|
patchState?: (result: SessionPatchApplyResult) => void;
|
|
10
|
+
snapshot?: (event: WebsocketEventPayload) => void;
|
|
9
11
|
progress?: (event: WebsocketEventPayload) => void;
|
|
10
12
|
final?: (event: WebsocketEventPayload) => void;
|
|
13
|
+
turnUpdated?: (event: WebsocketEventPayload) => void;
|
|
14
|
+
turnFinalized?: (event: WebsocketEventPayload) => void;
|
|
11
15
|
error?: (event: WebsocketEventPayload) => void;
|
|
12
16
|
persisted?: (event: WebsocketEventPayload) => void;
|
|
13
17
|
event?: (event: WebsocketEventPayload) => void;
|
|
14
18
|
};
|
|
15
|
-
export type SessionEventName = "turn.patch" | "turn.progress" | "turn.final" | "turn.error" | "message.persisted";
|
|
16
|
-
export type SpaceEventName = SessionEventName | "event";
|
|
19
|
+
export type SessionEventName = "turn.patch" | "turn.snapshot" | "turn.progress" | "turn.final" | "turn.updated" | "turn.error" | "message.persisted";
|
|
20
|
+
export type SpaceEventName = SessionEventName | "ports.changed" | "event";
|
|
17
21
|
type SessionSendMessageInput = {
|
|
18
22
|
content: ContentBlock[];
|
|
19
23
|
model?: string;
|
|
20
24
|
provider?: string;
|
|
21
|
-
clientMessageId?: string;
|
|
22
25
|
};
|
|
23
26
|
export declare class SpacesApi {
|
|
24
27
|
private readonly transport;
|
|
@@ -40,7 +43,16 @@ export declare class SpaceFilesApi {
|
|
|
40
43
|
constructor(transport: HttpTransport, spaceId: string);
|
|
41
44
|
list(path?: string, customFetch?: Fetch): Promise<SpaceFsTreeResponse>;
|
|
42
45
|
read(path: string, customFetch?: Fetch): Promise<SpaceFsFileResponse>;
|
|
46
|
+
/**
|
|
47
|
+
* Build a direct download URL. For private files, prefer `download()` so the
|
|
48
|
+
* SDK can attach authorization headers.
|
|
49
|
+
*/
|
|
43
50
|
getDownloadUrl(path: string): string;
|
|
51
|
+
download(path: string, customFetch?: Fetch): Promise<{
|
|
52
|
+
blob: Blob;
|
|
53
|
+
filename: string;
|
|
54
|
+
mimeType: string;
|
|
55
|
+
}>;
|
|
44
56
|
write(input: SpaceFsWriteFileInput): Promise<{
|
|
45
57
|
ok: true;
|
|
46
58
|
path: string;
|
|
@@ -83,8 +95,31 @@ declare class SessionMessagesClient {
|
|
|
83
95
|
send(input: SessionSendMessageInput): Promise<{
|
|
84
96
|
ok: true;
|
|
85
97
|
userMessageId: string;
|
|
98
|
+
turnId: string;
|
|
86
99
|
}>;
|
|
87
100
|
}
|
|
101
|
+
declare class SessionTurnsClient {
|
|
102
|
+
private readonly transport;
|
|
103
|
+
private readonly sessionId;
|
|
104
|
+
constructor(transport: HttpTransport, sessionId: string);
|
|
105
|
+
listPaginated(options?: {
|
|
106
|
+
cursor?: number;
|
|
107
|
+
limit?: number;
|
|
108
|
+
direction?: "older" | "newer";
|
|
109
|
+
}, customFetch?: Fetch): Promise<SessionTurnsPaginatedResponse>;
|
|
110
|
+
index(options?: {
|
|
111
|
+
cursor?: number;
|
|
112
|
+
limit?: number;
|
|
113
|
+
}, customFetch?: Fetch): Promise<SessionTurnIndexResponse>;
|
|
114
|
+
window(options: {
|
|
115
|
+
sequence?: number;
|
|
116
|
+
turnId?: string;
|
|
117
|
+
before?: number;
|
|
118
|
+
after?: number;
|
|
119
|
+
}, customFetch?: Fetch): Promise<SessionTurnWindowResponse>;
|
|
120
|
+
get(turnId: string, customFetch?: Fetch): Promise<SessionTurnResponse>;
|
|
121
|
+
signedUrls(turnId: string, objectKeys: string[]): Promise<SessionTurnSignedUrlsResponse>;
|
|
122
|
+
}
|
|
88
123
|
declare class SessionRealtimeClient {
|
|
89
124
|
private readonly websocketClient;
|
|
90
125
|
private readonly spaceId;
|
|
@@ -99,6 +134,7 @@ export declare class SessionClient {
|
|
|
99
134
|
readonly id: string;
|
|
100
135
|
private readonly transport;
|
|
101
136
|
readonly messages: SessionMessagesClient;
|
|
137
|
+
readonly turns: SessionTurnsClient;
|
|
102
138
|
readonly realtime: SessionRealtimeClient;
|
|
103
139
|
constructor(spaceId: string, id: string, transport: HttpTransport, websocketClient: WebsocketClient | null);
|
|
104
140
|
get(customFetch?: Fetch): Promise<{
|
|
@@ -215,6 +251,52 @@ export declare class SpaceEnvApi {
|
|
|
215
251
|
env: SpaceEnvInput[];
|
|
216
252
|
}>;
|
|
217
253
|
}
|
|
254
|
+
export type SpaceSandboxRecord = {
|
|
255
|
+
status: string | null;
|
|
256
|
+
podName?: string | null;
|
|
257
|
+
desiredImage?: string | null;
|
|
258
|
+
reportedImageVersion?: string | null;
|
|
259
|
+
lastHeartbeatAt?: string | null;
|
|
260
|
+
reportedAt?: string | null;
|
|
261
|
+
meta?: Record<string, unknown> | null;
|
|
262
|
+
};
|
|
263
|
+
export declare class SpaceSandboxApi {
|
|
264
|
+
private readonly transport;
|
|
265
|
+
private readonly spaceId;
|
|
266
|
+
constructor(transport: HttpTransport, spaceId: string);
|
|
267
|
+
get(): Promise<{
|
|
268
|
+
sandbox: SpaceSandboxRecord | null;
|
|
269
|
+
}>;
|
|
270
|
+
ports(): Promise<{
|
|
271
|
+
endpoints: SpacePublicEndpoints;
|
|
272
|
+
}>;
|
|
273
|
+
recreate(): Promise<{
|
|
274
|
+
ok: boolean;
|
|
275
|
+
status?: string;
|
|
276
|
+
verified?: boolean;
|
|
277
|
+
checks?: Record<string, boolean> | null;
|
|
278
|
+
message?: string;
|
|
279
|
+
}>;
|
|
280
|
+
}
|
|
281
|
+
export declare class SpaceMarksApi {
|
|
282
|
+
private readonly transport;
|
|
283
|
+
private readonly spaceId;
|
|
284
|
+
constructor(transport: HttpTransport, spaceId: string);
|
|
285
|
+
list(kind?: SpaceMarkKind): Promise<{
|
|
286
|
+
marks: SpaceMarkListItem[];
|
|
287
|
+
}>;
|
|
288
|
+
create(input: {
|
|
289
|
+
kind?: SpaceMarkKind;
|
|
290
|
+
resourceType: SpaceMarkResourceType;
|
|
291
|
+
resourceRef: string;
|
|
292
|
+
label?: string | null;
|
|
293
|
+
}): Promise<{
|
|
294
|
+
mark: SpaceMarkListItem;
|
|
295
|
+
}>;
|
|
296
|
+
delete(markId: string): Promise<{
|
|
297
|
+
ok: true;
|
|
298
|
+
}>;
|
|
299
|
+
}
|
|
218
300
|
export declare class SpaceCheckpointsApi {
|
|
219
301
|
private readonly transport;
|
|
220
302
|
private readonly spaceId;
|
|
@@ -240,12 +322,20 @@ export declare class SpaceClient {
|
|
|
240
322
|
readonly usage: SpaceUsageApi;
|
|
241
323
|
readonly channels: SpaceChannelsApi;
|
|
242
324
|
readonly env: SpaceEnvApi;
|
|
325
|
+
readonly sandbox: SpaceSandboxApi;
|
|
243
326
|
readonly invitations: SpaceInvitationsApi;
|
|
327
|
+
readonly marks: SpaceMarksApi;
|
|
244
328
|
constructor(id: string, transport: HttpTransport, websocketClient: WebsocketClient | null);
|
|
245
329
|
get(customFetch?: Fetch): Promise<SpaceRecord>;
|
|
246
330
|
rename(name: string): Promise<{
|
|
247
331
|
space: SpaceRecord;
|
|
248
332
|
}>;
|
|
333
|
+
profile(body: {
|
|
334
|
+
description?: string | null;
|
|
335
|
+
pictureUrl?: string | null;
|
|
336
|
+
}): Promise<{
|
|
337
|
+
space: SpaceRecord;
|
|
338
|
+
}>;
|
|
249
339
|
session(sessionId: string): SessionClient;
|
|
250
340
|
subscribe(handler: (event: WebsocketEventPayload) => void): () => void;
|
|
251
341
|
on(type: SpaceEventName, handler: (event: WebsocketEventPayload) => void): () => void;
|
package/dist/apis/spaces.js
CHANGED
|
@@ -2,14 +2,35 @@ import { ensureRealtimeConnected } from "../realtime.js";
|
|
|
2
2
|
import { SessionPatchReducer, } from "../session-patch-reducer.js";
|
|
3
3
|
import { SpaceInvitationsApi } from "./invitations.js";
|
|
4
4
|
const DEFAULT_DEDUP_WINDOW_MS = 2000;
|
|
5
|
+
const getFilenameFromContentDisposition = (value) => {
|
|
6
|
+
if (!value)
|
|
7
|
+
return null;
|
|
8
|
+
const encodedMatch = value.match(/filename\*=UTF-8''([^;]+)/i);
|
|
9
|
+
if (encodedMatch?.[1]) {
|
|
10
|
+
try {
|
|
11
|
+
return decodeURIComponent(encodedMatch[1]);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return encodedMatch[1];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const plainMatch = value.match(/filename="?([^";]+)"?/i);
|
|
18
|
+
return plainMatch?.[1] ?? null;
|
|
19
|
+
};
|
|
5
20
|
const toSessionEventName = (type) => {
|
|
6
21
|
switch (type) {
|
|
7
22
|
case "session.turn.patch":
|
|
8
23
|
return "turn.patch";
|
|
24
|
+
case "session.turn.snapshot":
|
|
25
|
+
return "turn.snapshot";
|
|
9
26
|
case "session.turn.progress":
|
|
10
27
|
return "turn.progress";
|
|
11
28
|
case "session.turn.error":
|
|
12
29
|
return "turn.error";
|
|
30
|
+
case "session.turn.updated":
|
|
31
|
+
return "turn.updated";
|
|
32
|
+
case "session.turn.finalized":
|
|
33
|
+
return "turn.final";
|
|
13
34
|
case "session.message.persisted":
|
|
14
35
|
return "message.persisted";
|
|
15
36
|
default:
|
|
@@ -23,7 +44,16 @@ const isAssistantFinalPersistedEvent = (event) => {
|
|
|
23
44
|
if (!message || typeof message !== "object")
|
|
24
45
|
return false;
|
|
25
46
|
const record = message;
|
|
26
|
-
return record.role === "assistant" && record.meta?.messageKind === "assistant_final";
|
|
47
|
+
return record.role === "assistant" && (record.meta?.messageKind === "assistant_final" || record.meta?.messageKind === "assistant_error");
|
|
48
|
+
};
|
|
49
|
+
const isAssistantIntermediatePersistedEvent = (event) => {
|
|
50
|
+
if (event.type !== "session.message.persisted")
|
|
51
|
+
return false;
|
|
52
|
+
const message = event.payload.message;
|
|
53
|
+
if (!message || typeof message !== "object")
|
|
54
|
+
return false;
|
|
55
|
+
const record = message;
|
|
56
|
+
return record.role === "assistant" && record.meta?.messageKind === "assistant_intermediate";
|
|
27
57
|
};
|
|
28
58
|
const getPersistedMessageTurnId = (event) => {
|
|
29
59
|
if (event.type !== "session.message.persisted")
|
|
@@ -79,10 +109,26 @@ export class SpaceFilesApi {
|
|
|
79
109
|
const params = new URLSearchParams({ path });
|
|
80
110
|
return this.transport.request(`/api/spaces/${this.spaceId}/fs/file?${params.toString()}`, { fetch: customFetch });
|
|
81
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Build a direct download URL. For private files, prefer `download()` so the
|
|
114
|
+
* SDK can attach authorization headers.
|
|
115
|
+
*/
|
|
82
116
|
getDownloadUrl(path) {
|
|
83
117
|
const params = new URLSearchParams({ path });
|
|
84
118
|
return `/api/spaces/${this.spaceId}/fs/download?${params.toString()}`;
|
|
85
119
|
}
|
|
120
|
+
async download(path, customFetch) {
|
|
121
|
+
const params = new URLSearchParams({ path });
|
|
122
|
+
const raw = await this.transport.raw(`/api/spaces/${this.spaceId}/fs/download?${params.toString()}`, { fetch: customFetch });
|
|
123
|
+
const blob = await raw.blob();
|
|
124
|
+
const filename = getFilenameFromContentDisposition(raw.response.headers.get("content-disposition")) ??
|
|
125
|
+
path.split("/").pop() ??
|
|
126
|
+
"download";
|
|
127
|
+
const mimeType = raw.response.headers.get("content-type") ??
|
|
128
|
+
blob.type ??
|
|
129
|
+
"application/octet-stream";
|
|
130
|
+
return { blob, filename, mimeType };
|
|
131
|
+
}
|
|
86
132
|
write(input) {
|
|
87
133
|
return this.transport.request(`/api/spaces/${this.spaceId}/fs/file`, {
|
|
88
134
|
method: "PUT",
|
|
@@ -183,11 +229,61 @@ class SessionMessagesClient {
|
|
|
183
229
|
content: input.content,
|
|
184
230
|
model: input.model,
|
|
185
231
|
provider: input.provider,
|
|
186
|
-
clientMessageId: input.clientMessageId,
|
|
187
232
|
}),
|
|
188
233
|
});
|
|
189
234
|
}
|
|
190
235
|
}
|
|
236
|
+
class SessionTurnsClient {
|
|
237
|
+
transport;
|
|
238
|
+
sessionId;
|
|
239
|
+
constructor(transport, sessionId) {
|
|
240
|
+
this.transport = transport;
|
|
241
|
+
this.sessionId = sessionId;
|
|
242
|
+
}
|
|
243
|
+
listPaginated(options, customFetch) {
|
|
244
|
+
const params = new URLSearchParams();
|
|
245
|
+
if (options?.cursor !== undefined)
|
|
246
|
+
params.set("cursor", String(options.cursor));
|
|
247
|
+
if (options?.limit !== undefined)
|
|
248
|
+
params.set("limit", String(options.limit));
|
|
249
|
+
if (options?.direction)
|
|
250
|
+
params.set("direction", options.direction);
|
|
251
|
+
const query = params.toString();
|
|
252
|
+
return this.transport.request(`/api/sessions/${this.sessionId}/turns${query ? `?${query}` : ""}`, { fetch: customFetch });
|
|
253
|
+
}
|
|
254
|
+
index(options, customFetch) {
|
|
255
|
+
const params = new URLSearchParams();
|
|
256
|
+
if (options?.cursor !== undefined)
|
|
257
|
+
params.set("cursor", String(options.cursor));
|
|
258
|
+
if (options?.limit !== undefined)
|
|
259
|
+
params.set("limit", String(options.limit));
|
|
260
|
+
const query = params.toString();
|
|
261
|
+
return this.transport.request(`/api/sessions/${this.sessionId}/turns/index${query ? `?${query}` : ""}`, { fetch: customFetch });
|
|
262
|
+
}
|
|
263
|
+
window(options, customFetch) {
|
|
264
|
+
const params = new URLSearchParams();
|
|
265
|
+
if (options.sequence !== undefined)
|
|
266
|
+
params.set("sequence", String(options.sequence));
|
|
267
|
+
if (options.turnId)
|
|
268
|
+
params.set("turnId", options.turnId);
|
|
269
|
+
if (options.before !== undefined)
|
|
270
|
+
params.set("before", String(options.before));
|
|
271
|
+
if (options.after !== undefined)
|
|
272
|
+
params.set("after", String(options.after));
|
|
273
|
+
const query = params.toString();
|
|
274
|
+
return this.transport.request(`/api/sessions/${this.sessionId}/turns/window${query ? `?${query}` : ""}`, { fetch: customFetch });
|
|
275
|
+
}
|
|
276
|
+
get(turnId, customFetch) {
|
|
277
|
+
return this.transport.request(`/api/sessions/${this.sessionId}/turns/${turnId}`, { fetch: customFetch });
|
|
278
|
+
}
|
|
279
|
+
signedUrls(turnId, objectKeys) {
|
|
280
|
+
return this.transport.request(`/api/sessions/${this.sessionId}/turns/${turnId}/signed-urls`, {
|
|
281
|
+
method: "POST",
|
|
282
|
+
headers: { "Content-Type": "application/json" },
|
|
283
|
+
body: JSON.stringify({ objectKeys }),
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
191
287
|
class SessionRealtimeClient {
|
|
192
288
|
websocketClient;
|
|
193
289
|
spaceId;
|
|
@@ -229,6 +325,8 @@ class SessionRealtimeClient {
|
|
|
229
325
|
}
|
|
230
326
|
}
|
|
231
327
|
}
|
|
328
|
+
if (eventName === "turn.snapshot")
|
|
329
|
+
handlers.snapshot?.(event);
|
|
232
330
|
if (eventName === "turn.progress")
|
|
233
331
|
handlers.progress?.(event);
|
|
234
332
|
if (eventName === "turn.error") {
|
|
@@ -238,8 +336,26 @@ class SessionRealtimeClient {
|
|
|
238
336
|
});
|
|
239
337
|
handlers.error?.(event);
|
|
240
338
|
}
|
|
241
|
-
if (eventName === "message.persisted")
|
|
339
|
+
if (eventName === "message.persisted") {
|
|
242
340
|
handlers.persisted?.(event);
|
|
341
|
+
if (isAssistantIntermediatePersistedEvent(event)) {
|
|
342
|
+
this.patchReducer.reset({
|
|
343
|
+
spaceId: this.spaceId,
|
|
344
|
+
sessionId: this.sessionId,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (eventName === "turn.updated")
|
|
349
|
+
handlers.turnUpdated?.(event);
|
|
350
|
+
if (eventName === "turn.final") {
|
|
351
|
+
this.patchReducer.complete({
|
|
352
|
+
spaceId: this.spaceId,
|
|
353
|
+
sessionId: this.sessionId,
|
|
354
|
+
turnId: typeof event.payload.turn === "object" && event.payload.turn && "id" in event.payload.turn ? String(event.payload.turn.id) : null,
|
|
355
|
+
});
|
|
356
|
+
handlers.turnFinalized?.(event);
|
|
357
|
+
handlers.final?.(event);
|
|
358
|
+
}
|
|
243
359
|
if (isAssistantFinalPersistedEvent(event)) {
|
|
244
360
|
this.patchReducer.complete({
|
|
245
361
|
spaceId: this.spaceId,
|
|
@@ -269,12 +385,14 @@ export class SessionClient {
|
|
|
269
385
|
id;
|
|
270
386
|
transport;
|
|
271
387
|
messages;
|
|
388
|
+
turns;
|
|
272
389
|
realtime;
|
|
273
390
|
constructor(spaceId, id, transport, websocketClient) {
|
|
274
391
|
this.spaceId = spaceId;
|
|
275
392
|
this.id = id;
|
|
276
393
|
this.transport = transport;
|
|
277
394
|
this.messages = new SessionMessagesClient(transport, id);
|
|
395
|
+
this.turns = new SessionTurnsClient(transport, id);
|
|
278
396
|
this.realtime = new SessionRealtimeClient(websocketClient, spaceId, id);
|
|
279
397
|
}
|
|
280
398
|
get(customFetch) {
|
|
@@ -355,6 +473,10 @@ export class SpaceEventsApi {
|
|
|
355
473
|
handler(event);
|
|
356
474
|
return;
|
|
357
475
|
}
|
|
476
|
+
if (type === "ports.changed" && event.type === "space.ports.changed") {
|
|
477
|
+
handler(event);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
358
480
|
if (type === "turn.final" && isAssistantFinalPersistedEvent(event)) {
|
|
359
481
|
handler(event);
|
|
360
482
|
return;
|
|
@@ -468,6 +590,47 @@ export class SpaceEnvApi {
|
|
|
468
590
|
return this.transport.request(`/api/spaces/${this.spaceId}/env/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
469
591
|
}
|
|
470
592
|
}
|
|
593
|
+
export class SpaceSandboxApi {
|
|
594
|
+
transport;
|
|
595
|
+
spaceId;
|
|
596
|
+
constructor(transport, spaceId) {
|
|
597
|
+
this.transport = transport;
|
|
598
|
+
this.spaceId = spaceId;
|
|
599
|
+
}
|
|
600
|
+
get() {
|
|
601
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/sandbox`);
|
|
602
|
+
}
|
|
603
|
+
ports() {
|
|
604
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/sandbox/ports`);
|
|
605
|
+
}
|
|
606
|
+
recreate() {
|
|
607
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/sandbox/recreate`, {
|
|
608
|
+
method: "POST",
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
export class SpaceMarksApi {
|
|
613
|
+
transport;
|
|
614
|
+
spaceId;
|
|
615
|
+
constructor(transport, spaceId) {
|
|
616
|
+
this.transport = transport;
|
|
617
|
+
this.spaceId = spaceId;
|
|
618
|
+
}
|
|
619
|
+
list(kind = "pin") {
|
|
620
|
+
const params = new URLSearchParams({ kind });
|
|
621
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/marks?${params.toString()}`);
|
|
622
|
+
}
|
|
623
|
+
create(input) {
|
|
624
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/marks`, {
|
|
625
|
+
method: "POST",
|
|
626
|
+
headers: { "Content-Type": "application/json" },
|
|
627
|
+
body: JSON.stringify({ kind: "pin", ...input }),
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
delete(markId) {
|
|
631
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/marks/${markId}`, { method: "DELETE" });
|
|
632
|
+
}
|
|
633
|
+
}
|
|
471
634
|
export class SpaceCheckpointsApi {
|
|
472
635
|
transport;
|
|
473
636
|
spaceId;
|
|
@@ -501,7 +664,9 @@ export class SpaceClient {
|
|
|
501
664
|
usage;
|
|
502
665
|
channels;
|
|
503
666
|
env;
|
|
667
|
+
sandbox;
|
|
504
668
|
invitations;
|
|
669
|
+
marks;
|
|
505
670
|
constructor(id, transport, websocketClient) {
|
|
506
671
|
this.id = id;
|
|
507
672
|
this.transport = transport;
|
|
@@ -514,7 +679,9 @@ export class SpaceClient {
|
|
|
514
679
|
this.usage = new SpaceUsageApi(transport, id);
|
|
515
680
|
this.channels = new SpaceChannelsApi(transport, id);
|
|
516
681
|
this.env = new SpaceEnvApi(transport, id);
|
|
682
|
+
this.sandbox = new SpaceSandboxApi(transport, id);
|
|
517
683
|
this.invitations = new SpaceInvitationsApi(transport, id);
|
|
684
|
+
this.marks = new SpaceMarksApi(transport, id);
|
|
518
685
|
}
|
|
519
686
|
get(customFetch) {
|
|
520
687
|
return this.transport.request(`/api/spaces/${this.id}`, {
|
|
@@ -530,6 +697,13 @@ export class SpaceClient {
|
|
|
530
697
|
body: JSON.stringify({ name }),
|
|
531
698
|
});
|
|
532
699
|
}
|
|
700
|
+
profile(body) {
|
|
701
|
+
return this.transport.request(`/api/spaces/${this.id}/profile`, {
|
|
702
|
+
method: "PATCH",
|
|
703
|
+
headers: { "Content-Type": "application/json" },
|
|
704
|
+
body: JSON.stringify(body),
|
|
705
|
+
});
|
|
706
|
+
}
|
|
533
707
|
session(sessionId) {
|
|
534
708
|
return new SessionClient(this.id, sessionId, this.transport, this.websocketClient);
|
|
535
709
|
}
|
package/dist/apis/user.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type HttpTransport, type Fetch } from "../transport.js";
|
|
2
|
-
import type { UserSshKey } from "../types.js";
|
|
2
|
+
import type { UserRulesResponse, UserSshKey } from "../types.js";
|
|
3
3
|
export declare class UserApi {
|
|
4
4
|
private readonly transport;
|
|
5
5
|
private readonly transportBaseUrl;
|
|
@@ -7,6 +7,7 @@ export declare class UserApi {
|
|
|
7
7
|
private readonly clearStoredAuthToken?;
|
|
8
8
|
constructor(transport: HttpTransport, transportBaseUrl: string, setStoredAuthToken?: ((token: string) => void) | undefined, clearStoredAuthToken?: (() => void) | undefined);
|
|
9
9
|
getMe(customFetch?: Fetch): Promise<unknown>;
|
|
10
|
+
getRules(customFetch?: Fetch): Promise<UserRulesResponse>;
|
|
10
11
|
setAuthToken(token: string): Promise<any>;
|
|
11
12
|
clearAuthToken(): Promise<null>;
|
|
12
13
|
getSshKeys(customFetch?: Fetch): Promise<UserSshKey[]>;
|
package/dist/apis/user.js
CHANGED
|
@@ -13,6 +13,12 @@ export class UserApi {
|
|
|
13
13
|
getMe(customFetch) {
|
|
14
14
|
return this.transport.request("/api/me", { fetch: customFetch });
|
|
15
15
|
}
|
|
16
|
+
getRules(customFetch) {
|
|
17
|
+
return this.transport.request("/api/me/rules", {
|
|
18
|
+
method: "GET",
|
|
19
|
+
fetch: customFetch,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
16
22
|
async setAuthToken(token) {
|
|
17
23
|
const trimmedToken = token.trim();
|
|
18
24
|
const response = await fetch(this.transportBaseUrl ? `${this.transportBaseUrl}/api/me` : "/api/me", {
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ChannelsApi } from "./apis/channels.js";
|
|
2
2
|
import { CronJobsApi } from "./apis/cron-jobs.js";
|
|
3
|
+
import { ExploreApi } from "./apis/explore.js";
|
|
3
4
|
import { ModelsApi } from "./apis/models.js";
|
|
4
5
|
import { PromptsApi } from "./apis/prompts.js";
|
|
5
6
|
import { SessionAccessApi } from "./apis/session-access.js";
|
|
@@ -17,6 +18,7 @@ export declare class CohubClient {
|
|
|
17
18
|
readonly sessionAccess: SessionAccessApi;
|
|
18
19
|
readonly tasks: TasksApi;
|
|
19
20
|
readonly cronJobs: CronJobsApi;
|
|
21
|
+
readonly explore: ExploreApi;
|
|
20
22
|
readonly invite: PublicInviteApi;
|
|
21
23
|
private readonly transport;
|
|
22
24
|
private readonly websocketClient;
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ChannelsApi } from "./apis/channels.js";
|
|
2
2
|
import { CronJobsApi } from "./apis/cron-jobs.js";
|
|
3
|
+
import { ExploreApi } from "./apis/explore.js";
|
|
3
4
|
import { ModelsApi } from "./apis/models.js";
|
|
4
5
|
import { PromptsApi } from "./apis/prompts.js";
|
|
5
6
|
import { SessionAccessApi } from "./apis/session-access.js";
|
|
@@ -19,6 +20,7 @@ export class CohubClient {
|
|
|
19
20
|
sessionAccess;
|
|
20
21
|
tasks;
|
|
21
22
|
cronJobs;
|
|
23
|
+
explore;
|
|
22
24
|
invite;
|
|
23
25
|
transport;
|
|
24
26
|
websocketClient;
|
|
@@ -41,6 +43,7 @@ export class CohubClient {
|
|
|
41
43
|
this.sessionAccess = new SessionAccessApi(this.transport);
|
|
42
44
|
this.tasks = new TasksApi(this.transport);
|
|
43
45
|
this.cronJobs = new CronJobsApi(this.transport);
|
|
46
|
+
this.explore = new ExploreApi(this.transport);
|
|
44
47
|
this.invite = new PublicInviteApi(this.transport);
|
|
45
48
|
}
|
|
46
49
|
space(spaceId) {
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { CohubClient, createCohubClient } from "./client.js";
|
|
|
3
3
|
export { WebsocketClient, createWebsocketClient } from "./websocket.js";
|
|
4
4
|
export { SessionPatchReducer, createSessionPatchReducer } from "./session-patch-reducer.js";
|
|
5
5
|
export { HttpError } from "./transport.js";
|
|
6
|
+
export type { RawHttpResponse } from "./transport.js";
|
|
6
7
|
export { COHUB_ENVIRONMENTS, normalizeBaseUrl, normalizeWebsocketUrl, resolveApiBaseUrl, resolveCohubEnvironment, resolveWebsocketUrl, } from "./environment.js";
|
|
7
8
|
export type { CohubClientOptions, Fetch } from "./transport.js";
|
|
8
9
|
export type { CohubEnvironment } from "./environment.js";
|
|
@@ -38,6 +38,9 @@ export declare class SessionPatchReducer {
|
|
|
38
38
|
private key;
|
|
39
39
|
get(input: SessionPatchKeyInput): SessionPatchState;
|
|
40
40
|
start(input: SessionPatchKeyInput): SessionPatchState;
|
|
41
|
+
replaceTurnId(input: SessionPatchKeyInput & {
|
|
42
|
+
nextTurnId: string | null;
|
|
43
|
+
}): SessionPatchState;
|
|
41
44
|
complete(input: SessionPatchKeyInput): SessionPatchState;
|
|
42
45
|
fail(input: SessionPatchKeyInput): SessionPatchState;
|
|
43
46
|
reset(input: SessionPatchKeyInput): void;
|
|
@@ -177,12 +177,21 @@ export class SessionPatchReducer {
|
|
|
177
177
|
contentBlocks: [],
|
|
178
178
|
anchorUserMessageId: null,
|
|
179
179
|
patchSeq: 0,
|
|
180
|
-
turnId: null,
|
|
180
|
+
turnId: input.turnId ?? null,
|
|
181
181
|
appendPath: null,
|
|
182
182
|
};
|
|
183
183
|
this.states.set(this.key(input), state);
|
|
184
184
|
return state;
|
|
185
185
|
}
|
|
186
|
+
replaceTurnId(input) {
|
|
187
|
+
const current = this.get(input);
|
|
188
|
+
const state = {
|
|
189
|
+
...current,
|
|
190
|
+
turnId: input.nextTurnId,
|
|
191
|
+
};
|
|
192
|
+
this.states.set(this.key(input), state);
|
|
193
|
+
return state;
|
|
194
|
+
}
|
|
186
195
|
complete(input) {
|
|
187
196
|
const current = this.get(input);
|
|
188
197
|
const state = {
|
|
@@ -231,6 +240,11 @@ export class SessionPatchReducer {
|
|
|
231
240
|
const isDifferentKnownTurn = Boolean(currentTurnId && inputTurnId && currentTurnId !== inputTurnId);
|
|
232
241
|
const isFreshKnownTurn = isDifferentKnownTurn && input.baseSeq === 0;
|
|
233
242
|
const currentSeq = isFreshKnownTurn ? 0 : current.patchSeq;
|
|
243
|
+
const isSameTurnKeyframe = Boolean(currentTurnId &&
|
|
244
|
+
inputTurnId &&
|
|
245
|
+
currentTurnId === inputTurnId &&
|
|
246
|
+
input.baseSeq === 0 &&
|
|
247
|
+
input.seq >= currentSeq);
|
|
234
248
|
const isTerminalSameTurn = (current.status === "completed" || current.status === "failed") &&
|
|
235
249
|
Boolean(currentTurnId) &&
|
|
236
250
|
currentTurnId === inputTurnId;
|
|
@@ -240,13 +254,13 @@ export class SessionPatchReducer {
|
|
|
240
254
|
if (isDifferentKnownTurn && !isFreshKnownTurn) {
|
|
241
255
|
return { applied: false, reason: "version_mismatch", state: current };
|
|
242
256
|
}
|
|
243
|
-
if (input.seq <= currentSeq) {
|
|
257
|
+
if (!isSameTurnKeyframe && input.seq <= currentSeq) {
|
|
244
258
|
return { applied: false, reason: "duplicate", state: current };
|
|
245
259
|
}
|
|
246
|
-
if (input.baseSeq !== currentSeq) {
|
|
260
|
+
if (!isSameTurnKeyframe && input.baseSeq !== currentSeq) {
|
|
247
261
|
return { applied: false, reason: "version_mismatch", state: current };
|
|
248
262
|
}
|
|
249
|
-
const startingFresh = input.baseSeq === 0 || isFreshKnownTurn;
|
|
263
|
+
const startingFresh = input.baseSeq === 0 || isFreshKnownTurn || isSameTurnKeyframe;
|
|
250
264
|
const baseBlocks = startingFresh ? [] : current.contentBlocks;
|
|
251
265
|
const patched = applyPatchOpsToBlocks(baseBlocks, input.ops, startingFresh ? null : current.appendPath);
|
|
252
266
|
if (patched.failed) {
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { CohubEnvironment } from "./environment.js";
|
|
2
2
|
import type { WebsocketClientOptions } from "./websocket.js";
|
|
3
3
|
export type Fetch = typeof globalThis.fetch;
|
|
4
|
+
type RequestInitWithFetch = RequestInit & {
|
|
5
|
+
fetch?: Fetch;
|
|
6
|
+
};
|
|
7
|
+
export type RawHttpResponse = {
|
|
8
|
+
response: Response;
|
|
9
|
+
blob(): Promise<Blob>;
|
|
10
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
11
|
+
text(): Promise<string>;
|
|
12
|
+
};
|
|
4
13
|
export type CohubClientOptions = {
|
|
5
14
|
env?: CohubEnvironment;
|
|
6
15
|
baseUrl?: string;
|
|
@@ -23,7 +32,9 @@ export declare class HttpTransport {
|
|
|
23
32
|
private readonly onUnauthorized?;
|
|
24
33
|
constructor(options?: CohubClientOptions);
|
|
25
34
|
private withAuthorization;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
private send;
|
|
36
|
+
request<T>(path: string, init?: RequestInitWithFetch): Promise<T>;
|
|
37
|
+
raw(path: string, init?: RequestInitWithFetch): Promise<RawHttpResponse>;
|
|
38
|
+
blob(path: string, init?: RequestInitWithFetch): Promise<Blob>;
|
|
29
39
|
}
|
|
40
|
+
export {};
|
package/dist/transport.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { resolveApiBaseUrl } from "./environment.js";
|
|
2
|
+
const responseBodyForError = async (response) => {
|
|
3
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
4
|
+
return contentType.includes("application/json")
|
|
5
|
+
? await response.json().catch(() => null)
|
|
6
|
+
: await response.text().catch(() => response.statusText);
|
|
7
|
+
};
|
|
8
|
+
const messageFromErrorBody = (body, fallback) => typeof body === "string" ? body : JSON.stringify(body ?? null) || fallback;
|
|
2
9
|
export class HttpError extends Error {
|
|
3
10
|
status;
|
|
4
11
|
body;
|
|
@@ -34,7 +41,7 @@ export class HttpTransport {
|
|
|
34
41
|
headers,
|
|
35
42
|
};
|
|
36
43
|
}
|
|
37
|
-
async
|
|
44
|
+
async send(path, init) {
|
|
38
45
|
const fetcher = init?.fetch ?? this.fetcher;
|
|
39
46
|
const url = this.baseUrl ? `${this.baseUrl}${path}` : path;
|
|
40
47
|
const response = await fetcher(url, await this.withAuthorization(init));
|
|
@@ -43,16 +50,29 @@ export class HttpTransport {
|
|
|
43
50
|
throw new HttpError("unauthorized", 401, null);
|
|
44
51
|
}
|
|
45
52
|
if (!response.ok) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
? await response.json().catch(() => null)
|
|
49
|
-
: await response.text().catch(() => response.statusText);
|
|
50
|
-
const message = typeof body === "string" ? body : JSON.stringify(body ?? null);
|
|
51
|
-
throw new HttpError(message || response.statusText, response.status, body);
|
|
53
|
+
const body = await responseBodyForError(response);
|
|
54
|
+
throw new HttpError(messageFromErrorBody(body, response.statusText), response.status, body);
|
|
52
55
|
}
|
|
56
|
+
return response;
|
|
57
|
+
}
|
|
58
|
+
async request(path, init) {
|
|
59
|
+
const response = await this.send(path, init);
|
|
53
60
|
if (response.status === 204) {
|
|
54
61
|
return null;
|
|
55
62
|
}
|
|
56
63
|
return response.json();
|
|
57
64
|
}
|
|
65
|
+
async raw(path, init) {
|
|
66
|
+
const response = await this.send(path, init);
|
|
67
|
+
return {
|
|
68
|
+
response,
|
|
69
|
+
blob: () => response.blob(),
|
|
70
|
+
arrayBuffer: () => response.arrayBuffer(),
|
|
71
|
+
text: () => response.text(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async blob(path, init) {
|
|
75
|
+
const raw = await this.raw(path, init);
|
|
76
|
+
return raw.blob();
|
|
77
|
+
}
|
|
58
78
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SessionBindingRecord as ProtocolSessionBindingRecord, SessionRecord as ProtocolSessionRecord } from "@neta-art/cohub-protocol/model";
|
|
1
|
+
import type { SessionBindingRecord as ProtocolSessionBindingRecord, SessionRecord as ProtocolSessionRecord, SessionTurnIndexItem, SessionTurnRecord } from "@neta-art/cohub-protocol/model";
|
|
2
2
|
import type { ChannelConfig } from "@neta-art/cohub-protocol/gateway";
|
|
3
3
|
import type { ContentBlock } from "@neta-art/cohub-protocol/core";
|
|
4
4
|
import type { MessageRecord } from "@neta-art/cohub-protocol/model";
|
|
@@ -6,7 +6,13 @@ export type { ChannelConfig, DiscordChannelConfig, } from "@neta-art/cohub-proto
|
|
|
6
6
|
export type ApiError = {
|
|
7
7
|
message: string;
|
|
8
8
|
};
|
|
9
|
-
export type
|
|
9
|
+
export type UserRulesResponse = {
|
|
10
|
+
content: string;
|
|
11
|
+
updatedAt: string | null;
|
|
12
|
+
source: "config-space";
|
|
13
|
+
path: string;
|
|
14
|
+
};
|
|
15
|
+
export type { ContentBlock, MessageRecord, SessionTurnRecord, SessionTurnIndexItem };
|
|
10
16
|
export type SpaceFsEntry = {
|
|
11
17
|
name: string;
|
|
12
18
|
path: string;
|
|
@@ -120,6 +126,34 @@ export type SessionMessagesPaginatedResponse = {
|
|
|
120
126
|
hasMore: boolean;
|
|
121
127
|
nextCursor: number | undefined;
|
|
122
128
|
};
|
|
129
|
+
export type SessionTurnsPaginatedResponse = {
|
|
130
|
+
session: SessionRecord;
|
|
131
|
+
turns: SessionTurnRecord[];
|
|
132
|
+
hasMore: boolean;
|
|
133
|
+
nextCursor: number | undefined;
|
|
134
|
+
};
|
|
135
|
+
export type SessionTurnIndexResponse = {
|
|
136
|
+
session: SessionRecord;
|
|
137
|
+
turns: SessionTurnIndexItem[];
|
|
138
|
+
hasMore: boolean;
|
|
139
|
+
nextCursor: number | undefined;
|
|
140
|
+
};
|
|
141
|
+
export type SessionTurnWindowResponse = {
|
|
142
|
+
session: SessionRecord;
|
|
143
|
+
turns: SessionTurnRecord[];
|
|
144
|
+
hasMoreOlder: boolean;
|
|
145
|
+
hasMoreNewer: boolean;
|
|
146
|
+
oldestCursor: number | undefined;
|
|
147
|
+
newestCursor: number | undefined;
|
|
148
|
+
anchorSequence: number | undefined;
|
|
149
|
+
};
|
|
150
|
+
export type SessionTurnResponse = {
|
|
151
|
+
session: SessionRecord;
|
|
152
|
+
turn: SessionTurnRecord;
|
|
153
|
+
};
|
|
154
|
+
export type SessionTurnSignedUrlsResponse = {
|
|
155
|
+
urls: Record<string, string>;
|
|
156
|
+
};
|
|
123
157
|
export type ModelCatalogEntry = {
|
|
124
158
|
provider: string;
|
|
125
159
|
id: string;
|
|
@@ -241,6 +275,44 @@ export type SpaceMember = {
|
|
|
241
275
|
createdAt: string;
|
|
242
276
|
updatedAt: string;
|
|
243
277
|
};
|
|
278
|
+
export type SpaceMarkKind = "pin";
|
|
279
|
+
export type SpaceMarkResourceType = "session" | "checkpoint" | "file";
|
|
280
|
+
export type SpaceMarkRecord = {
|
|
281
|
+
id: string;
|
|
282
|
+
spaceId: string;
|
|
283
|
+
kind: SpaceMarkKind;
|
|
284
|
+
resourceType: SpaceMarkResourceType;
|
|
285
|
+
resourceRef: string;
|
|
286
|
+
label: string | null;
|
|
287
|
+
rank: number;
|
|
288
|
+
createdBy: string;
|
|
289
|
+
createdAt: string;
|
|
290
|
+
updatedAt: string;
|
|
291
|
+
};
|
|
292
|
+
export type SpaceMarkListItem = SpaceMarkRecord & {
|
|
293
|
+
href: string;
|
|
294
|
+
resource: {
|
|
295
|
+
title: string;
|
|
296
|
+
subtitle: string | null;
|
|
297
|
+
status: string | null;
|
|
298
|
+
} | null;
|
|
299
|
+
};
|
|
300
|
+
export type ExploreSpaceItem = {
|
|
301
|
+
space: SpaceRecord;
|
|
302
|
+
accessAudience: "anonymous" | "signed_in";
|
|
303
|
+
explore: {
|
|
304
|
+
rank: number;
|
|
305
|
+
category: string | null;
|
|
306
|
+
label: string | null;
|
|
307
|
+
};
|
|
308
|
+
latestCheckpoints: CheckpointRecord[];
|
|
309
|
+
stats: {
|
|
310
|
+
pinnedCount: number;
|
|
311
|
+
checkpointCount: number;
|
|
312
|
+
forkCount: number;
|
|
313
|
+
};
|
|
314
|
+
sandboxStatus: string | null;
|
|
315
|
+
};
|
|
244
316
|
export type SpaceAccessPolicy = {
|
|
245
317
|
signed_in_user: SpaceRole | null;
|
|
246
318
|
anonymous_user: SpaceRole | null;
|
package/dist/websocket.d.ts
CHANGED
|
@@ -97,6 +97,7 @@ export declare class WebsocketClient {
|
|
|
97
97
|
private lastPingRequestId;
|
|
98
98
|
private pongDeadlineAt;
|
|
99
99
|
private readonly compactStreamContexts;
|
|
100
|
+
private readonly patchStreamBuffers;
|
|
100
101
|
state: WebsocketClientState;
|
|
101
102
|
connectionId: string | null;
|
|
102
103
|
private readonly listeners;
|
|
@@ -126,6 +127,10 @@ export declare class WebsocketClient {
|
|
|
126
127
|
private rejectAuthWaiter;
|
|
127
128
|
private handleMessage;
|
|
128
129
|
private rememberCompactStreamContext;
|
|
130
|
+
private getPatchStreamBufferKey;
|
|
131
|
+
private handlePatchEnvelope;
|
|
132
|
+
private enforcePatchStreamBufferLimit;
|
|
133
|
+
private flushPatchStreamBuffer;
|
|
129
134
|
private handleCompactFrame;
|
|
130
135
|
private startPingLoop;
|
|
131
136
|
private stopPingLoop;
|
package/dist/websocket.js
CHANGED
|
@@ -32,6 +32,7 @@ const isRetryableCloseCode = (code) => {
|
|
|
32
32
|
return true;
|
|
33
33
|
};
|
|
34
34
|
const AUTH_CLOSE_REASON = "authentication failed";
|
|
35
|
+
const PATCH_STREAM_BUFFER_MAX_PENDING = 128;
|
|
35
36
|
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
36
37
|
const compactFrameToPatchOperation = (frame) => {
|
|
37
38
|
if (frame.t === "d")
|
|
@@ -82,6 +83,7 @@ export class WebsocketClient {
|
|
|
82
83
|
lastPingRequestId = null;
|
|
83
84
|
pongDeadlineAt = 0;
|
|
84
85
|
compactStreamContexts = new Map();
|
|
86
|
+
patchStreamBuffers = new Map();
|
|
85
87
|
state = "idle";
|
|
86
88
|
connectionId = null;
|
|
87
89
|
listeners = createEventMap();
|
|
@@ -169,6 +171,8 @@ export class WebsocketClient {
|
|
|
169
171
|
const wasConnecting = this.state === "connecting" || this.state === "reconnecting";
|
|
170
172
|
this.state = "closed";
|
|
171
173
|
this.ws = null;
|
|
174
|
+
this.compactStreamContexts.clear();
|
|
175
|
+
this.patchStreamBuffers.clear();
|
|
172
176
|
const closeError = new Error(formatCloseMessage(event.code, event.reason));
|
|
173
177
|
this.rejectAuthWaiter(closeError);
|
|
174
178
|
const willReconnect = !this.manuallyClosed && this.autoReconnect && isRetryableCloseCode(event.code);
|
|
@@ -197,6 +201,8 @@ export class WebsocketClient {
|
|
|
197
201
|
this.ws?.close(code, reason);
|
|
198
202
|
this.ws = null;
|
|
199
203
|
this.connectPromise = null;
|
|
204
|
+
this.compactStreamContexts.clear();
|
|
205
|
+
this.patchStreamBuffers.clear();
|
|
200
206
|
}
|
|
201
207
|
async sendMessage(input) {
|
|
202
208
|
await this.ensureOpen();
|
|
@@ -294,6 +300,10 @@ export class WebsocketClient {
|
|
|
294
300
|
}
|
|
295
301
|
const envelope = result.data;
|
|
296
302
|
this.rememberCompactStreamContext(envelope);
|
|
303
|
+
if (envelope.type === "session.turn.patch") {
|
|
304
|
+
this.handlePatchEnvelope(envelope);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
297
307
|
switch (envelope.type) {
|
|
298
308
|
case "system.ready": {
|
|
299
309
|
const connectionId = typeof envelope.payload.connectionId === "string"
|
|
@@ -401,6 +411,9 @@ export class WebsocketClient {
|
|
|
401
411
|
sessionId: envelope.sessionId ?? null,
|
|
402
412
|
turnId,
|
|
403
413
|
messageId,
|
|
414
|
+
messageOrdinal: typeof payload.messageOrdinal === "number"
|
|
415
|
+
? payload.messageOrdinal
|
|
416
|
+
: null,
|
|
404
417
|
anchorUserMessageId: typeof payload.anchorUserMessageId === "string"
|
|
405
418
|
? payload.anchorUserMessageId
|
|
406
419
|
: null,
|
|
@@ -417,8 +430,89 @@ export class WebsocketClient {
|
|
|
417
430
|
if (!turnId)
|
|
418
431
|
return;
|
|
419
432
|
for (const [sid, context] of this.compactStreamContexts.entries()) {
|
|
420
|
-
if (context.turnId === turnId)
|
|
433
|
+
if (context.turnId === turnId) {
|
|
421
434
|
this.compactStreamContexts.delete(sid);
|
|
435
|
+
this.patchStreamBuffers.delete(sid);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
getPatchStreamBufferKey(envelope) {
|
|
440
|
+
if (envelope.type !== "session.turn.patch")
|
|
441
|
+
return null;
|
|
442
|
+
const payload = envelope.payload;
|
|
443
|
+
const realtimeMeta = payload._rt && typeof payload._rt === "object"
|
|
444
|
+
? payload._rt
|
|
445
|
+
: null;
|
|
446
|
+
if (typeof realtimeMeta?.sid === "string" && realtimeMeta.sid.trim()) {
|
|
447
|
+
return realtimeMeta.sid;
|
|
448
|
+
}
|
|
449
|
+
if (typeof payload.turnId === "string" && payload.turnId.trim())
|
|
450
|
+
return payload.turnId;
|
|
451
|
+
if (typeof payload.messageId === "string" && payload.messageId.trim())
|
|
452
|
+
return payload.messageId;
|
|
453
|
+
return typeof envelope.sessionId === "string" && envelope.sessionId.trim()
|
|
454
|
+
? envelope.sessionId
|
|
455
|
+
: null;
|
|
456
|
+
}
|
|
457
|
+
handlePatchEnvelope(envelope) {
|
|
458
|
+
const payload = envelope.payload;
|
|
459
|
+
if (typeof payload.seq !== "number" ||
|
|
460
|
+
typeof payload.baseSeq !== "number" ||
|
|
461
|
+
!Number.isInteger(payload.seq) ||
|
|
462
|
+
!Number.isInteger(payload.baseSeq) ||
|
|
463
|
+
payload.seq < 0 ||
|
|
464
|
+
payload.baseSeq < 0) {
|
|
465
|
+
this.emit("event", envelope);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const key = this.getPatchStreamBufferKey(envelope);
|
|
469
|
+
if (!key) {
|
|
470
|
+
this.emit("event", envelope);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (payload.baseSeq === 0) {
|
|
474
|
+
const buffer = { nextSeq: payload.seq + 1, pending: new Map() };
|
|
475
|
+
this.patchStreamBuffers.set(key, buffer);
|
|
476
|
+
this.emit("event", envelope);
|
|
477
|
+
this.flushPatchStreamBuffer(buffer);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const buffer = this.patchStreamBuffers.get(key);
|
|
481
|
+
if (!buffer) {
|
|
482
|
+
this.patchStreamBuffers.set(key, {
|
|
483
|
+
nextSeq: payload.baseSeq,
|
|
484
|
+
pending: new Map([[payload.seq, envelope]]),
|
|
485
|
+
});
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (payload.seq < buffer.nextSeq)
|
|
489
|
+
return;
|
|
490
|
+
buffer.pending.set(payload.seq, envelope);
|
|
491
|
+
if (!this.enforcePatchStreamBufferLimit(key, buffer))
|
|
492
|
+
return;
|
|
493
|
+
this.flushPatchStreamBuffer(buffer);
|
|
494
|
+
}
|
|
495
|
+
enforcePatchStreamBufferLimit(key, buffer) {
|
|
496
|
+
if (buffer.pending.size <= PATCH_STREAM_BUFFER_MAX_PENDING)
|
|
497
|
+
return true;
|
|
498
|
+
this.patchStreamBuffers.delete(key);
|
|
499
|
+
this.emit("error", {
|
|
500
|
+
error: new Error(`patch stream buffer overflow: ${key}`),
|
|
501
|
+
recoverable: true,
|
|
502
|
+
});
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
flushPatchStreamBuffer(buffer) {
|
|
506
|
+
while (true) {
|
|
507
|
+
const envelope = buffer.pending.get(buffer.nextSeq);
|
|
508
|
+
if (!envelope)
|
|
509
|
+
return;
|
|
510
|
+
const seq = envelope.payload.seq;
|
|
511
|
+
if (typeof seq !== "number" || !Number.isInteger(seq))
|
|
512
|
+
return;
|
|
513
|
+
buffer.pending.delete(buffer.nextSeq);
|
|
514
|
+
buffer.nextSeq = seq + 1;
|
|
515
|
+
this.emit("event", envelope);
|
|
422
516
|
}
|
|
423
517
|
}
|
|
424
518
|
handleCompactFrame(frame) {
|
|
@@ -448,13 +542,15 @@ export class WebsocketClient {
|
|
|
448
542
|
payload: {
|
|
449
543
|
turnId: context.turnId,
|
|
450
544
|
messageId: context.messageId,
|
|
545
|
+
messageOrdinal: context.messageOrdinal,
|
|
451
546
|
anchorUserMessageId: context.anchorUserMessageId,
|
|
452
547
|
seq: frame.s,
|
|
453
548
|
baseSeq: frame.b,
|
|
454
549
|
ops: [op],
|
|
550
|
+
_rt: { sid: frame.sid },
|
|
455
551
|
},
|
|
456
552
|
};
|
|
457
|
-
this.
|
|
553
|
+
this.handlePatchEnvelope(envelope);
|
|
458
554
|
}
|
|
459
555
|
startPingLoop() {
|
|
460
556
|
this.stopPingLoop();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Cohub SDK for spaces, sessions, checkpoints, and realtime agent collaboration.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"README.md"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@neta-art/cohub-protocol": "1.2.
|
|
47
|
+
"@neta-art/cohub-protocol": "1.2.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"typescript": "^6.0.3"
|