@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 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
- console.log(event.payload);
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.progress`
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 protocol details behind the SDK surface
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
+ }
@@ -1,8 +1,7 @@
1
- import type { Fetch } from "../transport.js";
1
+ import type { HttpTransport } from "../transport.js";
2
2
  import type { ModelCatalogEntry } from "../types.js";
3
3
  export declare class ModelsApi {
4
- private readonly fetcher;
5
- private readonly baseUrl;
6
- constructor(fetcher: Fetch, baseUrl: string);
7
- list(customFetch?: Fetch): Promise<Record<string, ModelCatalogEntry[]>>;
4
+ private readonly transport;
5
+ constructor(transport: HttpTransport);
6
+ list(): Promise<Record<string, ModelCatalogEntry[]>>;
8
7
  }
@@ -1,17 +1,9 @@
1
1
  export class ModelsApi {
2
- fetcher;
3
- baseUrl;
4
- constructor(fetcher, baseUrl) {
5
- this.fetcher = fetcher;
6
- this.baseUrl = baseUrl;
2
+ transport;
3
+ constructor(transport) {
4
+ this.transport = transport;
7
5
  }
8
- async list(customFetch) {
9
- const fetchImpl = customFetch ?? this.fetcher;
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
  }
@@ -1,14 +1,18 @@
1
1
  import type { WebsocketClient, WebsocketEventPayload } from "../websocket.js";
2
2
  import type { HttpTransport, Fetch } from "../transport.js";
3
- import type { CheckpointRecord, ContentBlock, SessionMessagesPaginatedResponse, SessionMessagesResponse, SessionRecord, SpaceAccessPolicy, SpaceBootstrapSource, SpaceChannelBindingInput, SpaceCheckpointDetailResponse, SpaceCreateResponse, SpaceEnvInput, SpaceFsFileResponse, SpaceFsMoveInput, SpaceFsTreeResponse, SpaceUsageResponse, SpaceFsWriteFileInput, SpaceMember, SpaceRecord, SpaceRole, SpaceSessionsResponse } from "../types.js";
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(customFetch?: Fetch): Promise<SpaceSessionsResponse>;
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<{
@@ -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.final")
169
- handlers.final?.(event);
170
- if (eventName === "turn.error")
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
- return this.transport.request(`/api/spaces/${this.spaceId}/sessions`, {
238
- fetch: customFetch,
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, options.baseUrl ?? "", options.setStoredAuthToken, options.clearStoredAuthToken);
31
- this.models = new ModelsApi(options.fetch ?? fetch, options.baseUrl ?? "");
32
- this.prompts = new PromptsApi(options.fetch ?? fetch, options.baseUrl ?? "");
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({ state: "error", willReconnect: true });
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();