@neta-art/cohub 1.1.0 → 1.2.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 +52 -16
- package/dist/apis/invitations.d.ts +20 -0
- package/dist/apis/invitations.js +36 -0
- package/dist/apis/models.d.ts +4 -5
- package/dist/apis/models.js +5 -13
- package/dist/apis/spaces.d.ts +65 -4
- package/dist/apis/spaces.js +156 -8
- package/dist/client.d.ts +2 -0
- package/dist/client.js +35 -5
- package/dist/environment.d.ts +22 -0
- package/dist/environment.js +37 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +8 -3
- package/dist/index.d.ts +5 -1
- package/dist/index.js +2 -0
- package/dist/realtime.js +3 -0
- package/dist/session-patch-reducer.d.ts +49 -0
- package/dist/session-patch-reducer.js +273 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +2 -1
- package/dist/types.d.ts +70 -0
- package/dist/websocket.d.ts +18 -1
- package/dist/websocket.js +167 -43
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -13,6 +13,39 @@ npm install @neta-art/cohub @neta-art/cohub-protocol
|
|
|
13
13
|
```ts
|
|
14
14
|
import { createCohubClient } from "@neta-art/cohub";
|
|
15
15
|
|
|
16
|
+
const client = createCohubClient({
|
|
17
|
+
getAccessToken: async () => localStorage.getItem("token"),
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The SDK connects to production by default:
|
|
22
|
+
|
|
23
|
+
- API: `https://api.cohub.run`
|
|
24
|
+
- WebSocket: `wss://gateway.cohub.run/ws`
|
|
25
|
+
|
|
26
|
+
Use development with `ENV=dev` in Node.js:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
ENV=dev node app.js
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or select it explicitly in code:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
const client = createCohubClient({
|
|
36
|
+
env: "dev",
|
|
37
|
+
getAccessToken: async () => localStorage.getItem("token"),
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Development uses:
|
|
42
|
+
|
|
43
|
+
- API: `https://api-dev.cohub.run`
|
|
44
|
+
- WebSocket: `wss://gateway-dev.cohub.run/ws`
|
|
45
|
+
|
|
46
|
+
Custom endpoints are still supported when needed:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
16
49
|
const client = createCohubClient({
|
|
17
50
|
baseUrl: "https://api.example.com",
|
|
18
51
|
getAccessToken: async () => localStorage.getItem("token"),
|
|
@@ -63,8 +96,23 @@ stop();
|
|
|
63
96
|
You can also listen with business-oriented event names:
|
|
64
97
|
|
|
65
98
|
```ts
|
|
99
|
+
session.on("turn.patch", (event) => {
|
|
100
|
+
// Streaming state-machine patch: { turnId, seq, baseSeq, ops }.
|
|
101
|
+
// Consecutive append ops may be compacted to { v }.
|
|
102
|
+
console.log(event.payload.ops);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
session.subscribe({
|
|
106
|
+
patchState: (result) => {
|
|
107
|
+
if (result.applied) {
|
|
108
|
+
console.log(result.state.contentBlocks);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
66
113
|
session.on("turn.final", (event) => {
|
|
67
|
-
|
|
114
|
+
// Fired when an assistant_final message.persisted event arrives.
|
|
115
|
+
console.log(event.payload.message);
|
|
68
116
|
});
|
|
69
117
|
|
|
70
118
|
space.on("message.persisted", (event) => {
|
|
@@ -74,7 +122,8 @@ space.on("message.persisted", (event) => {
|
|
|
74
122
|
|
|
75
123
|
Supported business event names:
|
|
76
124
|
|
|
77
|
-
- `turn.
|
|
125
|
+
- `turn.patch`
|
|
126
|
+
- `turn.progress` (legacy compatibility)
|
|
78
127
|
- `turn.final`
|
|
79
128
|
- `turn.error`
|
|
80
129
|
- `message.persisted`
|
|
@@ -87,7 +136,6 @@ If you only want HTTP transport, use the dedicated entry:
|
|
|
87
136
|
import { createHttpClient } from "@neta-art/cohub/http";
|
|
88
137
|
|
|
89
138
|
const http = createHttpClient({
|
|
90
|
-
baseUrl: "https://api.example.com",
|
|
91
139
|
getAccessToken: async () => localStorage.getItem("token"),
|
|
92
140
|
});
|
|
93
141
|
|
|
@@ -104,7 +152,6 @@ If you need direct realtime transport access, use the websocket entry:
|
|
|
104
152
|
import { createWebsocketClient } from "@neta-art/cohub/websocket";
|
|
105
153
|
|
|
106
154
|
const ws = createWebsocketClient({
|
|
107
|
-
url: "https://gateway.example.com",
|
|
108
155
|
getAccessToken: async () => localStorage.getItem("token"),
|
|
109
156
|
});
|
|
110
157
|
|
|
@@ -118,15 +165,4 @@ This SDK is intentionally built around Cohub's co-creation model:
|
|
|
118
165
|
- work with `space(...)` and `session(...)` as the primary creative surface
|
|
119
166
|
- send messages through `session.messages.send(...)`
|
|
120
167
|
- subscribe through `space.subscribe(...)` and `session.subscribe(...)`
|
|
121
|
-
- keep
|
|
122
|
-
|
|
123
|
-
## Publish checklist
|
|
124
|
-
|
|
125
|
-
Before publishing:
|
|
126
|
-
|
|
127
|
-
1. build the protocol package: `pnpm --filter @neta-art/cohub-protocol build`
|
|
128
|
-
2. build this package: `pnpm --filter @neta-art/cohub build`
|
|
129
|
-
3. typecheck the protocol package: `pnpm --filter @neta-art/cohub-protocol typecheck`
|
|
130
|
-
4. typecheck this package: `pnpm --filter @neta-art/cohub typecheck`
|
|
131
|
-
5. verify consuming apps still typecheck
|
|
132
|
-
6. verify `dist/` contains `index`, `http`, and `websocket` outputs
|
|
168
|
+
- keep HTTP and realtime transports separate but coordinated
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HttpTransport } from "../transport.js";
|
|
2
|
+
import type { SpaceInvitation, CreateInvitationInput, CreateInvitationResponse, InvitationDetail, AcceptInvitationResponse } from "../types.js";
|
|
3
|
+
export declare class SpaceInvitationsApi {
|
|
4
|
+
private readonly transport;
|
|
5
|
+
private readonly spaceId;
|
|
6
|
+
constructor(transport: HttpTransport, spaceId: string);
|
|
7
|
+
list(): Promise<{
|
|
8
|
+
items: SpaceInvitation[];
|
|
9
|
+
}>;
|
|
10
|
+
create(input?: CreateInvitationInput): Promise<CreateInvitationResponse>;
|
|
11
|
+
revoke(token: string): Promise<{
|
|
12
|
+
ok: true;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export declare class PublicInviteApi {
|
|
16
|
+
private readonly transport;
|
|
17
|
+
constructor(transport: HttpTransport);
|
|
18
|
+
get(token: string): Promise<InvitationDetail>;
|
|
19
|
+
accept(token: string): Promise<AcceptInvitationResponse>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export class SpaceInvitationsApi {
|
|
2
|
+
transport;
|
|
3
|
+
spaceId;
|
|
4
|
+
constructor(transport, spaceId) {
|
|
5
|
+
this.transport = transport;
|
|
6
|
+
this.spaceId = spaceId;
|
|
7
|
+
}
|
|
8
|
+
list() {
|
|
9
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/invitations`);
|
|
10
|
+
}
|
|
11
|
+
create(input) {
|
|
12
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/invitations`, {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: { "Content-Type": "application/json" },
|
|
15
|
+
body: JSON.stringify(input ?? {}),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
revoke(token) {
|
|
19
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/invitations/${token}`, { method: "DELETE" });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Public invite API (no auth required for viewing)
|
|
23
|
+
export class PublicInviteApi {
|
|
24
|
+
transport;
|
|
25
|
+
constructor(transport) {
|
|
26
|
+
this.transport = transport;
|
|
27
|
+
}
|
|
28
|
+
get(token) {
|
|
29
|
+
return this.transport.request(`/api/invite/${token}`);
|
|
30
|
+
}
|
|
31
|
+
accept(token) {
|
|
32
|
+
return this.transport.request(`/api/invite/${token}/accept`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
package/dist/apis/models.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HttpTransport } from "../transport.js";
|
|
2
2
|
import type { ModelCatalogEntry } from "../types.js";
|
|
3
3
|
export declare class ModelsApi {
|
|
4
|
-
private readonly
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
list(customFetch?: Fetch): Promise<Record<string, ModelCatalogEntry[]>>;
|
|
4
|
+
private readonly transport;
|
|
5
|
+
constructor(transport: HttpTransport);
|
|
6
|
+
list(): Promise<Record<string, ModelCatalogEntry[]>>;
|
|
8
7
|
}
|
package/dist/apis/models.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
export class ModelsApi {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
this.fetcher = fetcher;
|
|
6
|
-
this.baseUrl = baseUrl;
|
|
2
|
+
transport;
|
|
3
|
+
constructor(transport) {
|
|
4
|
+
this.transport = transport;
|
|
7
5
|
}
|
|
8
|
-
async list(
|
|
9
|
-
|
|
10
|
-
const url = this.baseUrl ? `${this.baseUrl}/api/models` : "/api/models";
|
|
11
|
-
const response = await fetchImpl(url);
|
|
12
|
-
if (!response.ok) {
|
|
13
|
-
throw new Error(`Failed to fetch models: ${response.status} ${response.statusText}`);
|
|
14
|
-
}
|
|
15
|
-
return response.json();
|
|
6
|
+
async list() {
|
|
7
|
+
return this.transport.request("/api/models");
|
|
16
8
|
}
|
|
17
9
|
}
|
package/dist/apis/spaces.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import type { WebsocketClient, WebsocketEventPayload } from "../websocket.js";
|
|
2
2
|
import type { HttpTransport, Fetch } from "../transport.js";
|
|
3
|
-
import
|
|
3
|
+
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 { SpaceInvitationsApi } from "./invitations.js";
|
|
4
6
|
export type SessionSubscriptionHandlers = {
|
|
7
|
+
patch?: (event: WebsocketEventPayload) => void;
|
|
8
|
+
patchState?: (result: SessionPatchApplyResult) => void;
|
|
5
9
|
progress?: (event: WebsocketEventPayload) => void;
|
|
6
10
|
final?: (event: WebsocketEventPayload) => void;
|
|
7
11
|
error?: (event: WebsocketEventPayload) => void;
|
|
8
12
|
persisted?: (event: WebsocketEventPayload) => void;
|
|
9
13
|
event?: (event: WebsocketEventPayload) => void;
|
|
10
14
|
};
|
|
11
|
-
export type SessionEventName = "turn.progress" | "turn.final" | "turn.error" | "message.persisted";
|
|
15
|
+
export type SessionEventName = "turn.patch" | "turn.progress" | "turn.final" | "turn.error" | "message.persisted";
|
|
12
16
|
export type SpaceEventName = SessionEventName | "event";
|
|
13
17
|
type SessionSendMessageInput = {
|
|
14
18
|
content: ContentBlock[];
|
|
@@ -58,6 +62,7 @@ export declare class SpaceFilesApi {
|
|
|
58
62
|
fromPath: string;
|
|
59
63
|
toPath: string;
|
|
60
64
|
}>;
|
|
65
|
+
upload(files: File[], dir?: string): Promise<SpaceFsUploadResponse>;
|
|
61
66
|
}
|
|
62
67
|
declare class SessionMessagesClient {
|
|
63
68
|
private readonly transport;
|
|
@@ -67,6 +72,9 @@ declare class SessionMessagesClient {
|
|
|
67
72
|
private lastSentAt;
|
|
68
73
|
constructor(transport: HttpTransport, sessionId: string);
|
|
69
74
|
list(customFetch?: Fetch): Promise<SessionMessagesResponse>;
|
|
75
|
+
get(messageId: string, optionsOrFetch?: {
|
|
76
|
+
detail?: "summary" | "full";
|
|
77
|
+
} | Fetch, customFetch?: Fetch): Promise<SessionMessageResponse>;
|
|
70
78
|
listPaginated(options?: {
|
|
71
79
|
cursor?: number;
|
|
72
80
|
limit?: number;
|
|
@@ -81,6 +89,7 @@ declare class SessionRealtimeClient {
|
|
|
81
89
|
private readonly websocketClient;
|
|
82
90
|
private readonly spaceId;
|
|
83
91
|
private readonly sessionId;
|
|
92
|
+
private readonly patchReducer;
|
|
84
93
|
constructor(websocketClient: WebsocketClient | null, spaceId: string, sessionId: string);
|
|
85
94
|
subscribe(handlers: SessionSubscriptionHandlers): () => void;
|
|
86
95
|
on(type: SessionEventName, handler: (event: WebsocketEventPayload) => void): () => void;
|
|
@@ -114,13 +123,19 @@ export declare class SpaceSessionsApi {
|
|
|
114
123
|
ok: true;
|
|
115
124
|
session: SessionRecord;
|
|
116
125
|
}>;
|
|
117
|
-
list(
|
|
126
|
+
list(optionsOrFetch?: {
|
|
127
|
+
limit?: number;
|
|
128
|
+
cursor?: string | null;
|
|
129
|
+
} | Fetch, customFetch?: Fetch): Promise<SpaceSessionsResponse>;
|
|
118
130
|
byId(sessionId: string): SessionClient;
|
|
119
131
|
}
|
|
120
132
|
export type WebSocketConnectionState = {
|
|
121
|
-
state: "open" | "closed" | "error";
|
|
133
|
+
state: "connecting" | "reconnecting" | "open" | "closed" | "error";
|
|
122
134
|
willReconnect: boolean;
|
|
123
135
|
connectionId?: string | null;
|
|
136
|
+
attempt?: number;
|
|
137
|
+
delayMs?: number;
|
|
138
|
+
recoverable?: boolean;
|
|
124
139
|
};
|
|
125
140
|
export declare class SpaceEventsApi {
|
|
126
141
|
private readonly websocketClient;
|
|
@@ -157,6 +172,49 @@ export declare class SpaceUsageApi {
|
|
|
157
172
|
constructor(transport: HttpTransport, spaceId: string);
|
|
158
173
|
get(days?: number, customFetch?: Fetch): Promise<SpaceUsageResponse>;
|
|
159
174
|
}
|
|
175
|
+
export type SpaceChannelBindingRecord = {
|
|
176
|
+
id: string;
|
|
177
|
+
spaceId: string;
|
|
178
|
+
channelId: string;
|
|
179
|
+
config: Record<string, unknown> | null;
|
|
180
|
+
createdAt: string;
|
|
181
|
+
channel: {
|
|
182
|
+
id: string;
|
|
183
|
+
userUuid: string;
|
|
184
|
+
provider: string;
|
|
185
|
+
name: string;
|
|
186
|
+
status: string;
|
|
187
|
+
createdAt: string;
|
|
188
|
+
updatedAt: string;
|
|
189
|
+
} | null;
|
|
190
|
+
};
|
|
191
|
+
export declare class SpaceChannelsApi {
|
|
192
|
+
private readonly transport;
|
|
193
|
+
private readonly spaceId;
|
|
194
|
+
constructor(transport: HttpTransport, spaceId: string);
|
|
195
|
+
list(): Promise<SpaceChannelBindingRecord[]>;
|
|
196
|
+
bind(channelId: string, config?: Record<string, unknown> | null): Promise<SpaceChannelBindingRecord>;
|
|
197
|
+
unbind(channelId: string): Promise<{
|
|
198
|
+
ok: true;
|
|
199
|
+
}>;
|
|
200
|
+
}
|
|
201
|
+
export declare class SpaceEnvApi {
|
|
202
|
+
private readonly transport;
|
|
203
|
+
private readonly spaceId;
|
|
204
|
+
constructor(transport: HttpTransport, spaceId: string);
|
|
205
|
+
list(): Promise<{
|
|
206
|
+
env: SpaceEnvInput[];
|
|
207
|
+
}>;
|
|
208
|
+
create(input: SpaceEnvInput): Promise<{
|
|
209
|
+
env: SpaceEnvInput[];
|
|
210
|
+
}>;
|
|
211
|
+
update(name: string, value: string): Promise<{
|
|
212
|
+
env: SpaceEnvInput[];
|
|
213
|
+
}>;
|
|
214
|
+
remove(name: string): Promise<{
|
|
215
|
+
env: SpaceEnvInput[];
|
|
216
|
+
}>;
|
|
217
|
+
}
|
|
160
218
|
export declare class SpaceCheckpointsApi {
|
|
161
219
|
private readonly transport;
|
|
162
220
|
private readonly spaceId;
|
|
@@ -180,6 +238,9 @@ export declare class SpaceClient {
|
|
|
180
238
|
readonly access: SpaceAccessApi;
|
|
181
239
|
readonly checkpoints: SpaceCheckpointsApi;
|
|
182
240
|
readonly usage: SpaceUsageApi;
|
|
241
|
+
readonly channels: SpaceChannelsApi;
|
|
242
|
+
readonly env: SpaceEnvApi;
|
|
243
|
+
readonly invitations: SpaceInvitationsApi;
|
|
183
244
|
constructor(id: string, transport: HttpTransport, websocketClient: WebsocketClient | null);
|
|
184
245
|
get(customFetch?: Fetch): Promise<SpaceRecord>;
|
|
185
246
|
rename(name: string): Promise<{
|
package/dist/apis/spaces.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ensureRealtimeConnected } from "../realtime.js";
|
|
2
|
+
import { SessionPatchReducer, } from "../session-patch-reducer.js";
|
|
3
|
+
import { SpaceInvitationsApi } from "./invitations.js";
|
|
2
4
|
const DEFAULT_DEDUP_WINDOW_MS = 2000;
|
|
3
5
|
const toSessionEventName = (type) => {
|
|
4
6
|
switch (type) {
|
|
7
|
+
case "session.turn.patch":
|
|
8
|
+
return "turn.patch";
|
|
5
9
|
case "session.turn.progress":
|
|
6
10
|
return "turn.progress";
|
|
7
|
-
case "session.turn.final":
|
|
8
|
-
return "turn.final";
|
|
9
11
|
case "session.turn.error":
|
|
10
12
|
return "turn.error";
|
|
11
13
|
case "session.message.persisted":
|
|
@@ -14,6 +16,24 @@ const toSessionEventName = (type) => {
|
|
|
14
16
|
return null;
|
|
15
17
|
}
|
|
16
18
|
};
|
|
19
|
+
const isAssistantFinalPersistedEvent = (event) => {
|
|
20
|
+
if (event.type !== "session.message.persisted")
|
|
21
|
+
return false;
|
|
22
|
+
const message = event.payload.message;
|
|
23
|
+
if (!message || typeof message !== "object")
|
|
24
|
+
return false;
|
|
25
|
+
const record = message;
|
|
26
|
+
return record.role === "assistant" && record.meta?.messageKind === "assistant_final";
|
|
27
|
+
};
|
|
28
|
+
const getPersistedMessageTurnId = (event) => {
|
|
29
|
+
if (event.type !== "session.message.persisted")
|
|
30
|
+
return null;
|
|
31
|
+
const message = event.payload.message;
|
|
32
|
+
if (!message || typeof message !== "object")
|
|
33
|
+
return null;
|
|
34
|
+
const meta = message.meta;
|
|
35
|
+
return typeof meta?.turnId === "string" ? meta.turnId : null;
|
|
36
|
+
};
|
|
17
37
|
export class SpacesApi {
|
|
18
38
|
transport;
|
|
19
39
|
constructor(transport) {
|
|
@@ -90,6 +110,19 @@ export class SpaceFilesApi {
|
|
|
90
110
|
body: JSON.stringify(input),
|
|
91
111
|
});
|
|
92
112
|
}
|
|
113
|
+
upload(files, dir = "") {
|
|
114
|
+
const params = new URLSearchParams();
|
|
115
|
+
if (dir)
|
|
116
|
+
params.set("dir", dir);
|
|
117
|
+
const query = params.toString();
|
|
118
|
+
const formData = new FormData();
|
|
119
|
+
for (const file of files)
|
|
120
|
+
formData.append("files", file);
|
|
121
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/fs/upload${query ? `?${query}` : ""}`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
body: formData,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
93
126
|
}
|
|
94
127
|
class SessionMessagesClient {
|
|
95
128
|
transport;
|
|
@@ -106,6 +139,17 @@ class SessionMessagesClient {
|
|
|
106
139
|
fetch: customFetch,
|
|
107
140
|
});
|
|
108
141
|
}
|
|
142
|
+
get(messageId, optionsOrFetch, customFetch) {
|
|
143
|
+
const options = typeof optionsOrFetch === "function" ? undefined : optionsOrFetch;
|
|
144
|
+
const fetch = typeof optionsOrFetch === "function" ? optionsOrFetch : customFetch;
|
|
145
|
+
const params = new URLSearchParams();
|
|
146
|
+
if (options?.detail)
|
|
147
|
+
params.set("detail", options.detail);
|
|
148
|
+
const query = params.toString();
|
|
149
|
+
return this.transport.request(`/api/sessions/${this.sessionId}/messages/${messageId}${query ? `?${query}` : ""}`, {
|
|
150
|
+
fetch,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
109
153
|
listPaginated(options, customFetch) {
|
|
110
154
|
const params = new URLSearchParams();
|
|
111
155
|
if (options?.cursor !== undefined)
|
|
@@ -148,6 +192,7 @@ class SessionRealtimeClient {
|
|
|
148
192
|
websocketClient;
|
|
149
193
|
spaceId;
|
|
150
194
|
sessionId;
|
|
195
|
+
patchReducer = new SessionPatchReducer();
|
|
151
196
|
constructor(websocketClient, spaceId, sessionId) {
|
|
152
197
|
this.websocketClient = websocketClient;
|
|
153
198
|
this.spaceId = spaceId;
|
|
@@ -163,20 +208,56 @@ class SessionRealtimeClient {
|
|
|
163
208
|
return;
|
|
164
209
|
handlers.event?.(event);
|
|
165
210
|
const eventName = toSessionEventName(event.type);
|
|
211
|
+
if (eventName === "turn.patch") {
|
|
212
|
+
handlers.patch?.(event);
|
|
213
|
+
if (event.type === "session.turn.patch") {
|
|
214
|
+
const payload = event.payload;
|
|
215
|
+
if (typeof payload.seq === "number" &&
|
|
216
|
+
typeof payload.baseSeq === "number" &&
|
|
217
|
+
Array.isArray(payload.ops)) {
|
|
218
|
+
handlers.patchState?.(this.patchReducer.applyPatch({
|
|
219
|
+
spaceId: this.spaceId,
|
|
220
|
+
sessionId: this.sessionId,
|
|
221
|
+
turnId: typeof payload.turnId === "string" ? payload.turnId : null,
|
|
222
|
+
seq: payload.seq,
|
|
223
|
+
baseSeq: payload.baseSeq,
|
|
224
|
+
ops: payload.ops,
|
|
225
|
+
anchorUserMessageId: typeof payload.anchorUserMessageId === "string"
|
|
226
|
+
? payload.anchorUserMessageId
|
|
227
|
+
: null,
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
166
232
|
if (eventName === "turn.progress")
|
|
167
233
|
handlers.progress?.(event);
|
|
168
|
-
if (eventName === "turn.
|
|
169
|
-
|
|
170
|
-
|
|
234
|
+
if (eventName === "turn.error") {
|
|
235
|
+
this.patchReducer.fail({
|
|
236
|
+
spaceId: this.spaceId,
|
|
237
|
+
sessionId: this.sessionId,
|
|
238
|
+
});
|
|
171
239
|
handlers.error?.(event);
|
|
240
|
+
}
|
|
172
241
|
if (eventName === "message.persisted")
|
|
173
242
|
handlers.persisted?.(event);
|
|
243
|
+
if (isAssistantFinalPersistedEvent(event)) {
|
|
244
|
+
this.patchReducer.complete({
|
|
245
|
+
spaceId: this.spaceId,
|
|
246
|
+
sessionId: this.sessionId,
|
|
247
|
+
turnId: getPersistedMessageTurnId(event),
|
|
248
|
+
});
|
|
249
|
+
handlers.final?.(event);
|
|
250
|
+
}
|
|
174
251
|
});
|
|
175
252
|
return () => unsubscribe();
|
|
176
253
|
}
|
|
177
254
|
on(type, handler) {
|
|
178
255
|
return this.subscribe({
|
|
179
256
|
event: (event) => {
|
|
257
|
+
if (type === "turn.final" && isAssistantFinalPersistedEvent(event)) {
|
|
258
|
+
handler(event);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
180
261
|
if (toSessionEventName(event.type) === type)
|
|
181
262
|
handler(event);
|
|
182
263
|
},
|
|
@@ -233,9 +314,17 @@ export class SpaceSessionsApi {
|
|
|
233
314
|
body: JSON.stringify(input ?? {}),
|
|
234
315
|
});
|
|
235
316
|
}
|
|
236
|
-
list(customFetch) {
|
|
237
|
-
|
|
238
|
-
|
|
317
|
+
list(optionsOrFetch, customFetch) {
|
|
318
|
+
const options = typeof optionsOrFetch === "function" ? undefined : optionsOrFetch;
|
|
319
|
+
const fetch = typeof optionsOrFetch === "function" ? optionsOrFetch : customFetch;
|
|
320
|
+
const params = new URLSearchParams();
|
|
321
|
+
if (options?.limit !== undefined)
|
|
322
|
+
params.set("limit", String(options.limit));
|
|
323
|
+
if (options?.cursor)
|
|
324
|
+
params.set("cursor", options.cursor);
|
|
325
|
+
const query = params.toString();
|
|
326
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/sessions${query ? `?${query}` : ""}`, {
|
|
327
|
+
fetch,
|
|
239
328
|
});
|
|
240
329
|
}
|
|
241
330
|
byId(sessionId) {
|
|
@@ -266,6 +355,10 @@ export class SpaceEventsApi {
|
|
|
266
355
|
handler(event);
|
|
267
356
|
return;
|
|
268
357
|
}
|
|
358
|
+
if (type === "turn.final" && isAssistantFinalPersistedEvent(event)) {
|
|
359
|
+
handler(event);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
269
362
|
if (toSessionEventName(event.type) === type)
|
|
270
363
|
handler(event);
|
|
271
364
|
});
|
|
@@ -326,6 +419,55 @@ export class SpaceUsageApi {
|
|
|
326
419
|
return this.transport.request(`/api/spaces/${this.spaceId}/usage?${params.toString()}`, { fetch: customFetch });
|
|
327
420
|
}
|
|
328
421
|
}
|
|
422
|
+
export class SpaceChannelsApi {
|
|
423
|
+
transport;
|
|
424
|
+
spaceId;
|
|
425
|
+
constructor(transport, spaceId) {
|
|
426
|
+
this.transport = transport;
|
|
427
|
+
this.spaceId = spaceId;
|
|
428
|
+
}
|
|
429
|
+
list() {
|
|
430
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/channels`);
|
|
431
|
+
}
|
|
432
|
+
bind(channelId, config) {
|
|
433
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/channels/${channelId}`, {
|
|
434
|
+
method: "POST",
|
|
435
|
+
headers: { "Content-Type": "application/json" },
|
|
436
|
+
body: JSON.stringify({ config: config ?? null }),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
unbind(channelId) {
|
|
440
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/channels/${channelId}`, { method: "DELETE" });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
export class SpaceEnvApi {
|
|
444
|
+
transport;
|
|
445
|
+
spaceId;
|
|
446
|
+
constructor(transport, spaceId) {
|
|
447
|
+
this.transport = transport;
|
|
448
|
+
this.spaceId = spaceId;
|
|
449
|
+
}
|
|
450
|
+
list() {
|
|
451
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/env`);
|
|
452
|
+
}
|
|
453
|
+
create(input) {
|
|
454
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/env`, {
|
|
455
|
+
method: "POST",
|
|
456
|
+
headers: { "Content-Type": "application/json" },
|
|
457
|
+
body: JSON.stringify(input),
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
update(name, value) {
|
|
461
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/env/${encodeURIComponent(name)}`, {
|
|
462
|
+
method: "PUT",
|
|
463
|
+
headers: { "Content-Type": "application/json" },
|
|
464
|
+
body: JSON.stringify({ value }),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
remove(name) {
|
|
468
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/env/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
329
471
|
export class SpaceCheckpointsApi {
|
|
330
472
|
transport;
|
|
331
473
|
spaceId;
|
|
@@ -357,6 +499,9 @@ export class SpaceClient {
|
|
|
357
499
|
access;
|
|
358
500
|
checkpoints;
|
|
359
501
|
usage;
|
|
502
|
+
channels;
|
|
503
|
+
env;
|
|
504
|
+
invitations;
|
|
360
505
|
constructor(id, transport, websocketClient) {
|
|
361
506
|
this.id = id;
|
|
362
507
|
this.transport = transport;
|
|
@@ -367,6 +512,9 @@ export class SpaceClient {
|
|
|
367
512
|
this.access = new SpaceAccessApi(transport, id);
|
|
368
513
|
this.checkpoints = new SpaceCheckpointsApi(transport, id);
|
|
369
514
|
this.usage = new SpaceUsageApi(transport, id);
|
|
515
|
+
this.channels = new SpaceChannelsApi(transport, id);
|
|
516
|
+
this.env = new SpaceEnvApi(transport, id);
|
|
517
|
+
this.invitations = new SpaceInvitationsApi(transport, id);
|
|
370
518
|
}
|
|
371
519
|
get(customFetch) {
|
|
372
520
|
return this.transport.request(`/api/spaces/${this.id}`, {
|
package/dist/client.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { SessionAccessApi } from "./apis/session-access.js";
|
|
|
6
6
|
import { SpaceClient, SpacesApi, type WebSocketConnectionState } from "./apis/spaces.js";
|
|
7
7
|
import { TasksApi } from "./apis/tasks.js";
|
|
8
8
|
import { UserApi } from "./apis/user.js";
|
|
9
|
+
import { PublicInviteApi } from "./apis/invitations.js";
|
|
9
10
|
import { type CohubClientOptions } from "./transport.js";
|
|
10
11
|
export declare class CohubClient {
|
|
11
12
|
readonly spaces: SpacesApi;
|
|
@@ -16,6 +17,7 @@ export declare class CohubClient {
|
|
|
16
17
|
readonly sessionAccess: SessionAccessApi;
|
|
17
18
|
readonly tasks: TasksApi;
|
|
18
19
|
readonly cronJobs: CronJobsApi;
|
|
20
|
+
readonly invite: PublicInviteApi;
|
|
19
21
|
private readonly transport;
|
|
20
22
|
private readonly websocketClient;
|
|
21
23
|
constructor(options?: CohubClientOptions);
|
package/dist/client.js
CHANGED
|
@@ -6,8 +6,10 @@ import { SessionAccessApi } from "./apis/session-access.js";
|
|
|
6
6
|
import { SpaceClient, SpacesApi } from "./apis/spaces.js";
|
|
7
7
|
import { TasksApi } from "./apis/tasks.js";
|
|
8
8
|
import { UserApi } from "./apis/user.js";
|
|
9
|
+
import { PublicInviteApi } from "./apis/invitations.js";
|
|
9
10
|
import { HttpTransport } from "./transport.js";
|
|
10
11
|
import { createWebsocketClient } from "./websocket.js";
|
|
12
|
+
import { resolveApiBaseUrl, resolveWebsocketUrl } from "./environment.js";
|
|
11
13
|
export class CohubClient {
|
|
12
14
|
spaces;
|
|
13
15
|
channels;
|
|
@@ -17,27 +19,49 @@ export class CohubClient {
|
|
|
17
19
|
sessionAccess;
|
|
18
20
|
tasks;
|
|
19
21
|
cronJobs;
|
|
22
|
+
invite;
|
|
20
23
|
transport;
|
|
21
24
|
websocketClient;
|
|
22
25
|
constructor(options = {}) {
|
|
26
|
+
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
23
27
|
this.transport = new HttpTransport(options);
|
|
24
28
|
this.websocketClient = createWebsocketClient({
|
|
29
|
+
url: resolveWebsocketUrl({
|
|
30
|
+
env: options.websocket?.env ?? options.env,
|
|
31
|
+
url: options.websocket?.url,
|
|
32
|
+
}),
|
|
25
33
|
...options.websocket,
|
|
26
34
|
getAccessToken: options.getAccessToken,
|
|
27
35
|
});
|
|
28
36
|
this.spaces = new SpacesApi(this.transport);
|
|
29
37
|
this.channels = new ChannelsApi(this.transport);
|
|
30
|
-
this.user = new UserApi(this.transport,
|
|
31
|
-
this.models = new ModelsApi(
|
|
32
|
-
this.prompts = new PromptsApi(options.fetch ?? fetch,
|
|
38
|
+
this.user = new UserApi(this.transport, apiBaseUrl, options.setStoredAuthToken, options.clearStoredAuthToken);
|
|
39
|
+
this.models = new ModelsApi(this.transport);
|
|
40
|
+
this.prompts = new PromptsApi(options.fetch ?? fetch, apiBaseUrl);
|
|
33
41
|
this.sessionAccess = new SessionAccessApi(this.transport);
|
|
34
42
|
this.tasks = new TasksApi(this.transport);
|
|
35
43
|
this.cronJobs = new CronJobsApi(this.transport);
|
|
44
|
+
this.invite = new PublicInviteApi(this.transport);
|
|
36
45
|
}
|
|
37
46
|
space(spaceId) {
|
|
38
47
|
return new SpaceClient(spaceId, this.transport, this.websocketClient);
|
|
39
48
|
}
|
|
40
49
|
onConnection(handler) {
|
|
50
|
+
const connectingCleanup = this.websocketClient.on("connecting", (payload) => {
|
|
51
|
+
handler({
|
|
52
|
+
state: payload.isReconnect ? "reconnecting" : "connecting",
|
|
53
|
+
willReconnect: payload.isReconnect,
|
|
54
|
+
attempt: payload.attempt,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
const reconnectingCleanup = this.websocketClient.on("reconnecting", (payload) => {
|
|
58
|
+
handler({
|
|
59
|
+
state: "reconnecting",
|
|
60
|
+
willReconnect: true,
|
|
61
|
+
attempt: payload.attempt,
|
|
62
|
+
delayMs: payload.delayMs,
|
|
63
|
+
});
|
|
64
|
+
});
|
|
41
65
|
const openCleanup = this.websocketClient.on("open", (payload) => {
|
|
42
66
|
handler({
|
|
43
67
|
state: "open",
|
|
@@ -51,10 +75,16 @@ export class CohubClient {
|
|
|
51
75
|
willReconnect: payload.willReconnect,
|
|
52
76
|
});
|
|
53
77
|
});
|
|
54
|
-
const errorCleanup = this.websocketClient.on("error", () => {
|
|
55
|
-
handler({
|
|
78
|
+
const errorCleanup = this.websocketClient.on("error", (payload) => {
|
|
79
|
+
handler({
|
|
80
|
+
state: "error",
|
|
81
|
+
willReconnect: payload.recoverable,
|
|
82
|
+
recoverable: payload.recoverable,
|
|
83
|
+
});
|
|
56
84
|
});
|
|
57
85
|
return () => {
|
|
86
|
+
connectingCleanup();
|
|
87
|
+
reconnectingCleanup();
|
|
58
88
|
openCleanup();
|
|
59
89
|
closeCleanup();
|
|
60
90
|
errorCleanup();
|