@neta-art/cohub 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -14
- 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/prompts.d.ts +10 -0
- package/dist/apis/prompts.js +22 -0
- package/dist/apis/session-access.js +1 -1
- package/dist/apis/spaces.d.ts +74 -2
- package/dist/apis/spaces.js +113 -4
- package/dist/client.d.ts +6 -1
- package/dist/client.js +58 -2
- package/dist/environment.d.ts +22 -0
- package/dist/environment.js +37 -0
- package/dist/http.d.ts +4 -0
- package/dist/http.js +10 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/realtime.js +3 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +2 -1
- package/dist/types.d.ts +102 -4
- package/dist/websocket.d.ts +15 -1
- package/dist/websocket.js +60 -41
- package/package.json +4 -4
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"),
|
|
@@ -87,7 +120,6 @@ If you only want HTTP transport, use the dedicated entry:
|
|
|
87
120
|
import { createHttpClient } from "@neta-art/cohub/http";
|
|
88
121
|
|
|
89
122
|
const http = createHttpClient({
|
|
90
|
-
baseUrl: "https://api.example.com",
|
|
91
123
|
getAccessToken: async () => localStorage.getItem("token"),
|
|
92
124
|
});
|
|
93
125
|
|
|
@@ -104,7 +136,6 @@ If you need direct realtime transport access, use the websocket entry:
|
|
|
104
136
|
import { createWebsocketClient } from "@neta-art/cohub/websocket";
|
|
105
137
|
|
|
106
138
|
const ws = createWebsocketClient({
|
|
107
|
-
url: "https://gateway.example.com",
|
|
108
139
|
getAccessToken: async () => localStorage.getItem("token"),
|
|
109
140
|
});
|
|
110
141
|
|
|
@@ -118,15 +149,4 @@ This SDK is intentionally built around Cohub's co-creation model:
|
|
|
118
149
|
- work with `space(...)` and `session(...)` as the primary creative surface
|
|
119
150
|
- send messages through `session.messages.send(...)`
|
|
120
151
|
- 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
|
|
152
|
+
- 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
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Fetch } from "../transport.js";
|
|
2
|
+
import type { PromptTemplateCatalogResponse } from "../types.js";
|
|
3
|
+
export declare class PromptsApi {
|
|
4
|
+
private readonly fetcher;
|
|
5
|
+
private readonly baseUrl;
|
|
6
|
+
constructor(fetcher: Fetch, baseUrl: string);
|
|
7
|
+
list(options?: {
|
|
8
|
+
spaceId?: string;
|
|
9
|
+
}, customFetch?: Fetch): Promise<PromptTemplateCatalogResponse>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class PromptsApi {
|
|
2
|
+
fetcher;
|
|
3
|
+
baseUrl;
|
|
4
|
+
constructor(fetcher, baseUrl) {
|
|
5
|
+
this.fetcher = fetcher;
|
|
6
|
+
this.baseUrl = baseUrl;
|
|
7
|
+
}
|
|
8
|
+
async list(options, customFetch) {
|
|
9
|
+
const fetchImpl = customFetch ?? this.fetcher;
|
|
10
|
+
const params = new URLSearchParams();
|
|
11
|
+
if (options?.spaceId)
|
|
12
|
+
params.set("spaceId", options.spaceId);
|
|
13
|
+
const query = params.toString();
|
|
14
|
+
const base = this.baseUrl ? `${this.baseUrl}/api/prompts` : "/api/prompts";
|
|
15
|
+
const url = query ? `${base}?${query}` : base;
|
|
16
|
+
const response = await fetchImpl(url);
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
throw new Error(`Failed to fetch prompt templates: ${response.status} ${response.statusText}`);
|
|
19
|
+
}
|
|
20
|
+
return response.json();
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/apis/spaces.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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, SpaceFsWriteFileInput, SpaceMember, SpaceRecord, SpaceRole, SpaceSessionsResponse } from "../types.js";
|
|
3
|
+
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";
|
|
4
|
+
import { SpaceInvitationsApi } from "./invitations.js";
|
|
4
5
|
export type SessionSubscriptionHandlers = {
|
|
5
6
|
progress?: (event: WebsocketEventPayload) => void;
|
|
6
7
|
final?: (event: WebsocketEventPayload) => void;
|
|
@@ -58,6 +59,7 @@ export declare class SpaceFilesApi {
|
|
|
58
59
|
fromPath: string;
|
|
59
60
|
toPath: string;
|
|
60
61
|
}>;
|
|
62
|
+
upload(files: File[], dir?: string): Promise<SpaceFsUploadResponse>;
|
|
61
63
|
}
|
|
62
64
|
declare class SessionMessagesClient {
|
|
63
65
|
private readonly transport;
|
|
@@ -67,6 +69,9 @@ declare class SessionMessagesClient {
|
|
|
67
69
|
private lastSentAt;
|
|
68
70
|
constructor(transport: HttpTransport, sessionId: string);
|
|
69
71
|
list(customFetch?: Fetch): Promise<SessionMessagesResponse>;
|
|
72
|
+
get(messageId: string, optionsOrFetch?: {
|
|
73
|
+
detail?: "summary" | "full";
|
|
74
|
+
} | Fetch, customFetch?: Fetch): Promise<SessionMessageResponse>;
|
|
70
75
|
listPaginated(options?: {
|
|
71
76
|
cursor?: number;
|
|
72
77
|
limit?: number;
|
|
@@ -96,6 +101,9 @@ export declare class SessionClient {
|
|
|
96
101
|
space: SpaceRecord;
|
|
97
102
|
session: SessionRecord;
|
|
98
103
|
}>;
|
|
104
|
+
rename(title: string | null, customFetch?: Fetch): Promise<{
|
|
105
|
+
session: SessionRecord;
|
|
106
|
+
}>;
|
|
99
107
|
subscribe(handlers: SessionSubscriptionHandlers): () => void;
|
|
100
108
|
on(type: SessionEventName, handler: (event: WebsocketEventPayload) => void): () => void;
|
|
101
109
|
}
|
|
@@ -111,9 +119,20 @@ export declare class SpaceSessionsApi {
|
|
|
111
119
|
ok: true;
|
|
112
120
|
session: SessionRecord;
|
|
113
121
|
}>;
|
|
114
|
-
list(
|
|
122
|
+
list(optionsOrFetch?: {
|
|
123
|
+
limit?: number;
|
|
124
|
+
cursor?: string | null;
|
|
125
|
+
} | Fetch, customFetch?: Fetch): Promise<SpaceSessionsResponse>;
|
|
115
126
|
byId(sessionId: string): SessionClient;
|
|
116
127
|
}
|
|
128
|
+
export type WebSocketConnectionState = {
|
|
129
|
+
state: "connecting" | "reconnecting" | "open" | "closed" | "error";
|
|
130
|
+
willReconnect: boolean;
|
|
131
|
+
connectionId?: string | null;
|
|
132
|
+
attempt?: number;
|
|
133
|
+
delayMs?: number;
|
|
134
|
+
recoverable?: boolean;
|
|
135
|
+
};
|
|
117
136
|
export declare class SpaceEventsApi {
|
|
118
137
|
private readonly websocketClient;
|
|
119
138
|
private readonly spaceId;
|
|
@@ -143,6 +162,55 @@ export declare class SpaceAccessApi {
|
|
|
143
162
|
anonymous_user?: SpaceRole | null;
|
|
144
163
|
}): Promise<SpaceAccessPolicy>;
|
|
145
164
|
}
|
|
165
|
+
export declare class SpaceUsageApi {
|
|
166
|
+
private readonly transport;
|
|
167
|
+
private readonly spaceId;
|
|
168
|
+
constructor(transport: HttpTransport, spaceId: string);
|
|
169
|
+
get(days?: number, customFetch?: Fetch): Promise<SpaceUsageResponse>;
|
|
170
|
+
}
|
|
171
|
+
export type SpaceChannelBindingRecord = {
|
|
172
|
+
id: string;
|
|
173
|
+
spaceId: string;
|
|
174
|
+
channelId: string;
|
|
175
|
+
config: Record<string, unknown> | null;
|
|
176
|
+
createdAt: string;
|
|
177
|
+
channel: {
|
|
178
|
+
id: string;
|
|
179
|
+
userUuid: string;
|
|
180
|
+
provider: string;
|
|
181
|
+
name: string;
|
|
182
|
+
status: string;
|
|
183
|
+
createdAt: string;
|
|
184
|
+
updatedAt: string;
|
|
185
|
+
} | null;
|
|
186
|
+
};
|
|
187
|
+
export declare class SpaceChannelsApi {
|
|
188
|
+
private readonly transport;
|
|
189
|
+
private readonly spaceId;
|
|
190
|
+
constructor(transport: HttpTransport, spaceId: string);
|
|
191
|
+
list(): Promise<SpaceChannelBindingRecord[]>;
|
|
192
|
+
bind(channelId: string, config?: Record<string, unknown> | null): Promise<SpaceChannelBindingRecord>;
|
|
193
|
+
unbind(channelId: string): Promise<{
|
|
194
|
+
ok: true;
|
|
195
|
+
}>;
|
|
196
|
+
}
|
|
197
|
+
export declare class SpaceEnvApi {
|
|
198
|
+
private readonly transport;
|
|
199
|
+
private readonly spaceId;
|
|
200
|
+
constructor(transport: HttpTransport, spaceId: string);
|
|
201
|
+
list(): Promise<{
|
|
202
|
+
env: SpaceEnvInput[];
|
|
203
|
+
}>;
|
|
204
|
+
create(input: SpaceEnvInput): Promise<{
|
|
205
|
+
env: SpaceEnvInput[];
|
|
206
|
+
}>;
|
|
207
|
+
update(name: string, value: string): Promise<{
|
|
208
|
+
env: SpaceEnvInput[];
|
|
209
|
+
}>;
|
|
210
|
+
remove(name: string): Promise<{
|
|
211
|
+
env: SpaceEnvInput[];
|
|
212
|
+
}>;
|
|
213
|
+
}
|
|
146
214
|
export declare class SpaceCheckpointsApi {
|
|
147
215
|
private readonly transport;
|
|
148
216
|
private readonly spaceId;
|
|
@@ -165,6 +233,10 @@ export declare class SpaceClient {
|
|
|
165
233
|
readonly members: SpaceMembersApi;
|
|
166
234
|
readonly access: SpaceAccessApi;
|
|
167
235
|
readonly checkpoints: SpaceCheckpointsApi;
|
|
236
|
+
readonly usage: SpaceUsageApi;
|
|
237
|
+
readonly channels: SpaceChannelsApi;
|
|
238
|
+
readonly env: SpaceEnvApi;
|
|
239
|
+
readonly invitations: SpaceInvitationsApi;
|
|
168
240
|
constructor(id: string, transport: HttpTransport, websocketClient: WebsocketClient | null);
|
|
169
241
|
get(customFetch?: Fetch): Promise<SpaceRecord>;
|
|
170
242
|
rename(name: string): Promise<{
|
package/dist/apis/spaces.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ensureRealtimeConnected } from "../realtime.js";
|
|
2
|
+
import { SpaceInvitationsApi } from "./invitations.js";
|
|
2
3
|
const DEFAULT_DEDUP_WINDOW_MS = 2000;
|
|
3
4
|
const toSessionEventName = (type) => {
|
|
4
5
|
switch (type) {
|
|
@@ -90,6 +91,19 @@ export class SpaceFilesApi {
|
|
|
90
91
|
body: JSON.stringify(input),
|
|
91
92
|
});
|
|
92
93
|
}
|
|
94
|
+
upload(files, dir = "") {
|
|
95
|
+
const params = new URLSearchParams();
|
|
96
|
+
if (dir)
|
|
97
|
+
params.set("dir", dir);
|
|
98
|
+
const query = params.toString();
|
|
99
|
+
const formData = new FormData();
|
|
100
|
+
for (const file of files)
|
|
101
|
+
formData.append("files", file);
|
|
102
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/fs/upload${query ? `?${query}` : ""}`, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
body: formData,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
93
107
|
}
|
|
94
108
|
class SessionMessagesClient {
|
|
95
109
|
transport;
|
|
@@ -106,6 +120,17 @@ class SessionMessagesClient {
|
|
|
106
120
|
fetch: customFetch,
|
|
107
121
|
});
|
|
108
122
|
}
|
|
123
|
+
get(messageId, optionsOrFetch, customFetch) {
|
|
124
|
+
const options = typeof optionsOrFetch === "function" ? undefined : optionsOrFetch;
|
|
125
|
+
const fetch = typeof optionsOrFetch === "function" ? optionsOrFetch : customFetch;
|
|
126
|
+
const params = new URLSearchParams();
|
|
127
|
+
if (options?.detail)
|
|
128
|
+
params.set("detail", options.detail);
|
|
129
|
+
const query = params.toString();
|
|
130
|
+
return this.transport.request(`/api/sessions/${this.sessionId}/messages/${messageId}${query ? `?${query}` : ""}`, {
|
|
131
|
+
fetch,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
109
134
|
listPaginated(options, customFetch) {
|
|
110
135
|
const params = new URLSearchParams();
|
|
111
136
|
if (options?.cursor !== undefined)
|
|
@@ -201,6 +226,13 @@ export class SessionClient {
|
|
|
201
226
|
fetch: customFetch,
|
|
202
227
|
});
|
|
203
228
|
}
|
|
229
|
+
rename(title, customFetch) {
|
|
230
|
+
return this.transport.request(`/api/sessions/${this.id}`, {
|
|
231
|
+
method: "PATCH",
|
|
232
|
+
body: JSON.stringify({ title }),
|
|
233
|
+
fetch: customFetch,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
204
236
|
subscribe(handlers) {
|
|
205
237
|
return this.realtime.subscribe(handlers);
|
|
206
238
|
}
|
|
@@ -226,9 +258,17 @@ export class SpaceSessionsApi {
|
|
|
226
258
|
body: JSON.stringify(input ?? {}),
|
|
227
259
|
});
|
|
228
260
|
}
|
|
229
|
-
list(customFetch) {
|
|
230
|
-
|
|
231
|
-
|
|
261
|
+
list(optionsOrFetch, customFetch) {
|
|
262
|
+
const options = typeof optionsOrFetch === "function" ? undefined : optionsOrFetch;
|
|
263
|
+
const fetch = typeof optionsOrFetch === "function" ? optionsOrFetch : customFetch;
|
|
264
|
+
const params = new URLSearchParams();
|
|
265
|
+
if (options?.limit !== undefined)
|
|
266
|
+
params.set("limit", String(options.limit));
|
|
267
|
+
if (options?.cursor)
|
|
268
|
+
params.set("cursor", options.cursor);
|
|
269
|
+
const query = params.toString();
|
|
270
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/sessions${query ? `?${query}` : ""}`, {
|
|
271
|
+
fetch,
|
|
232
272
|
});
|
|
233
273
|
}
|
|
234
274
|
byId(sessionId) {
|
|
@@ -301,12 +341,73 @@ export class SpaceAccessApi {
|
|
|
301
341
|
}
|
|
302
342
|
set(body) {
|
|
303
343
|
return this.transport.request(`/api/spaces/${this.spaceId}/access`, {
|
|
304
|
-
method: "
|
|
344
|
+
method: "PATCH",
|
|
305
345
|
headers: { "Content-Type": "application/json" },
|
|
306
346
|
body: JSON.stringify(body),
|
|
307
347
|
});
|
|
308
348
|
}
|
|
309
349
|
}
|
|
350
|
+
export class SpaceUsageApi {
|
|
351
|
+
transport;
|
|
352
|
+
spaceId;
|
|
353
|
+
constructor(transport, spaceId) {
|
|
354
|
+
this.transport = transport;
|
|
355
|
+
this.spaceId = spaceId;
|
|
356
|
+
}
|
|
357
|
+
get(days = 30, customFetch) {
|
|
358
|
+
const params = new URLSearchParams({ days: String(days) });
|
|
359
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/usage?${params.toString()}`, { fetch: customFetch });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
export class SpaceChannelsApi {
|
|
363
|
+
transport;
|
|
364
|
+
spaceId;
|
|
365
|
+
constructor(transport, spaceId) {
|
|
366
|
+
this.transport = transport;
|
|
367
|
+
this.spaceId = spaceId;
|
|
368
|
+
}
|
|
369
|
+
list() {
|
|
370
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/channels`);
|
|
371
|
+
}
|
|
372
|
+
bind(channelId, config) {
|
|
373
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/channels/${channelId}`, {
|
|
374
|
+
method: "POST",
|
|
375
|
+
headers: { "Content-Type": "application/json" },
|
|
376
|
+
body: JSON.stringify({ config: config ?? null }),
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
unbind(channelId) {
|
|
380
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/channels/${channelId}`, { method: "DELETE" });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
export class SpaceEnvApi {
|
|
384
|
+
transport;
|
|
385
|
+
spaceId;
|
|
386
|
+
constructor(transport, spaceId) {
|
|
387
|
+
this.transport = transport;
|
|
388
|
+
this.spaceId = spaceId;
|
|
389
|
+
}
|
|
390
|
+
list() {
|
|
391
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/env`);
|
|
392
|
+
}
|
|
393
|
+
create(input) {
|
|
394
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/env`, {
|
|
395
|
+
method: "POST",
|
|
396
|
+
headers: { "Content-Type": "application/json" },
|
|
397
|
+
body: JSON.stringify(input),
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
update(name, value) {
|
|
401
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/env/${encodeURIComponent(name)}`, {
|
|
402
|
+
method: "PUT",
|
|
403
|
+
headers: { "Content-Type": "application/json" },
|
|
404
|
+
body: JSON.stringify({ value }),
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
remove(name) {
|
|
408
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/env/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
409
|
+
}
|
|
410
|
+
}
|
|
310
411
|
export class SpaceCheckpointsApi {
|
|
311
412
|
transport;
|
|
312
413
|
spaceId;
|
|
@@ -337,6 +438,10 @@ export class SpaceClient {
|
|
|
337
438
|
members;
|
|
338
439
|
access;
|
|
339
440
|
checkpoints;
|
|
441
|
+
usage;
|
|
442
|
+
channels;
|
|
443
|
+
env;
|
|
444
|
+
invitations;
|
|
340
445
|
constructor(id, transport, websocketClient) {
|
|
341
446
|
this.id = id;
|
|
342
447
|
this.transport = transport;
|
|
@@ -346,6 +451,10 @@ export class SpaceClient {
|
|
|
346
451
|
this.members = new SpaceMembersApi(transport, id);
|
|
347
452
|
this.access = new SpaceAccessApi(transport, id);
|
|
348
453
|
this.checkpoints = new SpaceCheckpointsApi(transport, id);
|
|
454
|
+
this.usage = new SpaceUsageApi(transport, id);
|
|
455
|
+
this.channels = new SpaceChannelsApi(transport, id);
|
|
456
|
+
this.env = new SpaceEnvApi(transport, id);
|
|
457
|
+
this.invitations = new SpaceInvitationsApi(transport, id);
|
|
349
458
|
}
|
|
350
459
|
get(customFetch) {
|
|
351
460
|
return this.transport.request(`/api/spaces/${this.id}`, {
|
package/dist/client.d.ts
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import { ChannelsApi } from "./apis/channels.js";
|
|
2
2
|
import { CronJobsApi } from "./apis/cron-jobs.js";
|
|
3
3
|
import { ModelsApi } from "./apis/models.js";
|
|
4
|
+
import { PromptsApi } from "./apis/prompts.js";
|
|
4
5
|
import { SessionAccessApi } from "./apis/session-access.js";
|
|
5
|
-
import { SpaceClient, SpacesApi } from "./apis/spaces.js";
|
|
6
|
+
import { SpaceClient, SpacesApi, type WebSocketConnectionState } from "./apis/spaces.js";
|
|
6
7
|
import { TasksApi } from "./apis/tasks.js";
|
|
7
8
|
import { UserApi } from "./apis/user.js";
|
|
9
|
+
import { PublicInviteApi } from "./apis/invitations.js";
|
|
8
10
|
import { type CohubClientOptions } from "./transport.js";
|
|
9
11
|
export declare class CohubClient {
|
|
10
12
|
readonly spaces: SpacesApi;
|
|
11
13
|
readonly channels: ChannelsApi;
|
|
12
14
|
readonly user: UserApi;
|
|
13
15
|
readonly models: ModelsApi;
|
|
16
|
+
readonly prompts: PromptsApi;
|
|
14
17
|
readonly sessionAccess: SessionAccessApi;
|
|
15
18
|
readonly tasks: TasksApi;
|
|
16
19
|
readonly cronJobs: CronJobsApi;
|
|
20
|
+
readonly invite: PublicInviteApi;
|
|
17
21
|
private readonly transport;
|
|
18
22
|
private readonly websocketClient;
|
|
19
23
|
constructor(options?: CohubClientOptions);
|
|
20
24
|
space(spaceId: string): SpaceClient;
|
|
25
|
+
onConnection(handler: (state: WebSocketConnectionState) => void): () => void;
|
|
21
26
|
}
|
|
22
27
|
export declare const createCohubClient: (options?: CohubClientOptions) => CohubClient;
|
package/dist/client.js
CHANGED
|
@@ -1,38 +1,94 @@
|
|
|
1
1
|
import { ChannelsApi } from "./apis/channels.js";
|
|
2
2
|
import { CronJobsApi } from "./apis/cron-jobs.js";
|
|
3
3
|
import { ModelsApi } from "./apis/models.js";
|
|
4
|
+
import { PromptsApi } from "./apis/prompts.js";
|
|
4
5
|
import { SessionAccessApi } from "./apis/session-access.js";
|
|
5
6
|
import { SpaceClient, SpacesApi } from "./apis/spaces.js";
|
|
6
7
|
import { TasksApi } from "./apis/tasks.js";
|
|
7
8
|
import { UserApi } from "./apis/user.js";
|
|
9
|
+
import { PublicInviteApi } from "./apis/invitations.js";
|
|
8
10
|
import { HttpTransport } from "./transport.js";
|
|
9
11
|
import { createWebsocketClient } from "./websocket.js";
|
|
12
|
+
import { resolveApiBaseUrl, resolveWebsocketUrl } from "./environment.js";
|
|
10
13
|
export class CohubClient {
|
|
11
14
|
spaces;
|
|
12
15
|
channels;
|
|
13
16
|
user;
|
|
14
17
|
models;
|
|
18
|
+
prompts;
|
|
15
19
|
sessionAccess;
|
|
16
20
|
tasks;
|
|
17
21
|
cronJobs;
|
|
22
|
+
invite;
|
|
18
23
|
transport;
|
|
19
24
|
websocketClient;
|
|
20
25
|
constructor(options = {}) {
|
|
26
|
+
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
21
27
|
this.transport = new HttpTransport(options);
|
|
22
28
|
this.websocketClient = createWebsocketClient({
|
|
29
|
+
url: resolveWebsocketUrl({
|
|
30
|
+
env: options.websocket?.env ?? options.env,
|
|
31
|
+
url: options.websocket?.url,
|
|
32
|
+
}),
|
|
23
33
|
...options.websocket,
|
|
24
34
|
getAccessToken: options.getAccessToken,
|
|
25
35
|
});
|
|
26
36
|
this.spaces = new SpacesApi(this.transport);
|
|
27
37
|
this.channels = new ChannelsApi(this.transport);
|
|
28
|
-
this.user = new UserApi(this.transport,
|
|
29
|
-
this.models = new ModelsApi(
|
|
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);
|
|
30
41
|
this.sessionAccess = new SessionAccessApi(this.transport);
|
|
31
42
|
this.tasks = new TasksApi(this.transport);
|
|
32
43
|
this.cronJobs = new CronJobsApi(this.transport);
|
|
44
|
+
this.invite = new PublicInviteApi(this.transport);
|
|
33
45
|
}
|
|
34
46
|
space(spaceId) {
|
|
35
47
|
return new SpaceClient(spaceId, this.transport, this.websocketClient);
|
|
36
48
|
}
|
|
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
|
+
});
|
|
65
|
+
const openCleanup = this.websocketClient.on("open", (payload) => {
|
|
66
|
+
handler({
|
|
67
|
+
state: "open",
|
|
68
|
+
willReconnect: false,
|
|
69
|
+
connectionId: payload.connectionId,
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
const closeCleanup = this.websocketClient.on("close", (payload) => {
|
|
73
|
+
handler({
|
|
74
|
+
state: "closed",
|
|
75
|
+
willReconnect: payload.willReconnect,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
const errorCleanup = this.websocketClient.on("error", (payload) => {
|
|
79
|
+
handler({
|
|
80
|
+
state: "error",
|
|
81
|
+
willReconnect: payload.recoverable,
|
|
82
|
+
recoverable: payload.recoverable,
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
return () => {
|
|
86
|
+
connectingCleanup();
|
|
87
|
+
reconnectingCleanup();
|
|
88
|
+
openCleanup();
|
|
89
|
+
closeCleanup();
|
|
90
|
+
errorCleanup();
|
|
91
|
+
};
|
|
92
|
+
}
|
|
37
93
|
}
|
|
38
94
|
export const createCohubClient = (options) => new CohubClient(options);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type CohubEnvironment = "prod" | "dev";
|
|
2
|
+
export declare const COHUB_ENVIRONMENTS: {
|
|
3
|
+
readonly prod: {
|
|
4
|
+
readonly apiBaseUrl: "https://api.cohub.run";
|
|
5
|
+
readonly websocketUrl: "wss://gateway.cohub.run/ws";
|
|
6
|
+
};
|
|
7
|
+
readonly dev: {
|
|
8
|
+
readonly apiBaseUrl: "https://api-dev.cohub.run";
|
|
9
|
+
readonly websocketUrl: "wss://gateway-dev.cohub.run/ws";
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare const resolveCohubEnvironment: (env?: CohubEnvironment) => CohubEnvironment;
|
|
13
|
+
export declare const normalizeBaseUrl: (url: string) => string;
|
|
14
|
+
export declare const normalizeWebsocketUrl: (input: string) => string;
|
|
15
|
+
export declare const resolveApiBaseUrl: (options?: {
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
env?: CohubEnvironment;
|
|
18
|
+
}) => string;
|
|
19
|
+
export declare const resolveWebsocketUrl: (options?: {
|
|
20
|
+
url?: string;
|
|
21
|
+
env?: CohubEnvironment;
|
|
22
|
+
}) => string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const COHUB_ENVIRONMENTS = {
|
|
2
|
+
prod: {
|
|
3
|
+
apiBaseUrl: "https://api.cohub.run",
|
|
4
|
+
websocketUrl: "wss://gateway.cohub.run/ws",
|
|
5
|
+
},
|
|
6
|
+
dev: {
|
|
7
|
+
apiBaseUrl: "https://api-dev.cohub.run",
|
|
8
|
+
websocketUrl: "wss://gateway-dev.cohub.run/ws",
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
const readRuntimeEnv = () => {
|
|
12
|
+
const runtime = globalThis;
|
|
13
|
+
return runtime.process?.env?.ENV;
|
|
14
|
+
};
|
|
15
|
+
export const resolveCohubEnvironment = (env) => {
|
|
16
|
+
if (env)
|
|
17
|
+
return env;
|
|
18
|
+
return readRuntimeEnv() === "dev" ? "dev" : "prod";
|
|
19
|
+
};
|
|
20
|
+
export const normalizeBaseUrl = (url) => url.trim().replace(/\/+$/, "");
|
|
21
|
+
export const normalizeWebsocketUrl = (input) => {
|
|
22
|
+
const trimmed = normalizeBaseUrl(input);
|
|
23
|
+
const withProtocol = trimmed
|
|
24
|
+
.replace(/^http:/, "ws:")
|
|
25
|
+
.replace(/^https:/, "wss:");
|
|
26
|
+
return withProtocol.endsWith("/ws") ? withProtocol : `${withProtocol}/ws`;
|
|
27
|
+
};
|
|
28
|
+
export const resolveApiBaseUrl = (options = {}) => {
|
|
29
|
+
if (options.baseUrl)
|
|
30
|
+
return normalizeBaseUrl(options.baseUrl);
|
|
31
|
+
return COHUB_ENVIRONMENTS[resolveCohubEnvironment(options.env)].apiBaseUrl;
|
|
32
|
+
};
|
|
33
|
+
export const resolveWebsocketUrl = (options = {}) => {
|
|
34
|
+
if (options.url)
|
|
35
|
+
return normalizeWebsocketUrl(options.url);
|
|
36
|
+
return COHUB_ENVIRONMENTS[resolveCohubEnvironment(options.env)].websocketUrl;
|
|
37
|
+
};
|
package/dist/http.d.ts
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { ChannelsApi } from "./apis/channels.js";
|
|
2
2
|
import { CronJobsApi } from "./apis/cron-jobs.js";
|
|
3
3
|
import { ModelsApi } from "./apis/models.js";
|
|
4
|
+
import { PromptsApi } from "./apis/prompts.js";
|
|
4
5
|
import { SessionAccessApi } from "./apis/session-access.js";
|
|
5
6
|
import { SpaceClient, SpacesApi } from "./apis/spaces.js";
|
|
6
7
|
import { TasksApi } from "./apis/tasks.js";
|
|
7
8
|
import { UserApi } from "./apis/user.js";
|
|
9
|
+
import { PublicInviteApi } from "./apis/invitations.js";
|
|
8
10
|
import { HttpTransport, HttpError, type CohubClientOptions, type Fetch } from "./transport.js";
|
|
9
11
|
export declare class CohubHttpClient {
|
|
10
12
|
readonly spaces: SpacesApi;
|
|
11
13
|
readonly channels: ChannelsApi;
|
|
12
14
|
readonly user: UserApi;
|
|
13
15
|
readonly models: ModelsApi;
|
|
16
|
+
readonly prompts: PromptsApi;
|
|
14
17
|
readonly sessionAccess: SessionAccessApi;
|
|
15
18
|
readonly tasks: TasksApi;
|
|
16
19
|
readonly cronJobs: CronJobsApi;
|
|
20
|
+
readonly invite: PublicInviteApi;
|
|
17
21
|
private readonly transport;
|
|
18
22
|
constructor(options?: CohubClientOptions);
|
|
19
23
|
space(spaceId: string): SpaceClient;
|
package/dist/http.js
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
import { ChannelsApi } from "./apis/channels.js";
|
|
2
2
|
import { CronJobsApi } from "./apis/cron-jobs.js";
|
|
3
3
|
import { ModelsApi } from "./apis/models.js";
|
|
4
|
+
import { PromptsApi } from "./apis/prompts.js";
|
|
4
5
|
import { SessionAccessApi } from "./apis/session-access.js";
|
|
5
6
|
import { SpaceClient, SpacesApi } from "./apis/spaces.js";
|
|
6
7
|
import { TasksApi } from "./apis/tasks.js";
|
|
7
8
|
import { UserApi } from "./apis/user.js";
|
|
9
|
+
import { PublicInviteApi } from "./apis/invitations.js";
|
|
8
10
|
import { HttpTransport, HttpError } from "./transport.js";
|
|
11
|
+
import { resolveApiBaseUrl } from "./environment.js";
|
|
9
12
|
export class CohubHttpClient {
|
|
10
13
|
spaces;
|
|
11
14
|
channels;
|
|
12
15
|
user;
|
|
13
16
|
models;
|
|
17
|
+
prompts;
|
|
14
18
|
sessionAccess;
|
|
15
19
|
tasks;
|
|
16
20
|
cronJobs;
|
|
21
|
+
invite;
|
|
17
22
|
transport;
|
|
18
23
|
constructor(options = {}) {
|
|
24
|
+
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
19
25
|
this.transport = new HttpTransport(options);
|
|
20
26
|
this.spaces = new SpacesApi(this.transport);
|
|
21
27
|
this.channels = new ChannelsApi(this.transport);
|
|
22
|
-
this.user = new UserApi(this.transport,
|
|
23
|
-
this.models = new ModelsApi(
|
|
28
|
+
this.user = new UserApi(this.transport, apiBaseUrl, options.setStoredAuthToken, options.clearStoredAuthToken);
|
|
29
|
+
this.models = new ModelsApi(this.transport);
|
|
30
|
+
this.prompts = new PromptsApi(options.fetch ?? fetch, apiBaseUrl);
|
|
24
31
|
this.sessionAccess = new SessionAccessApi(this.transport);
|
|
25
32
|
this.tasks = new TasksApi(this.transport);
|
|
26
33
|
this.cronJobs = new CronJobsApi(this.transport);
|
|
34
|
+
this.invite = new PublicInviteApi(this.transport);
|
|
27
35
|
}
|
|
28
36
|
space(spaceId) {
|
|
29
37
|
return new SpaceClient(spaceId, this.transport, null);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { CohubHttpClient, createHttpClient } from "./http.js";
|
|
|
2
2
|
export { CohubClient, createCohubClient } from "./client.js";
|
|
3
3
|
export { WebsocketClient, createWebsocketClient } from "./websocket.js";
|
|
4
4
|
export { HttpError } from "./transport.js";
|
|
5
|
+
export { COHUB_ENVIRONMENTS, normalizeBaseUrl, normalizeWebsocketUrl, resolveApiBaseUrl, resolveCohubEnvironment, resolveWebsocketUrl, } from "./environment.js";
|
|
5
6
|
export type { CohubClientOptions, Fetch } from "./transport.js";
|
|
7
|
+
export type { CohubEnvironment } from "./environment.js";
|
|
6
8
|
export * from "./types.js";
|
|
7
|
-
export type { SessionEventName, SessionSubscriptionHandlers, SpaceEventName } from "./apis/spaces.js";
|
|
9
|
+
export type { SessionEventName, SessionSubscriptionHandlers, SpaceChannelBindingRecord, SpaceEventName, WebSocketConnectionState } from "./apis/spaces.js";
|
package/dist/index.js
CHANGED
|
@@ -2,4 +2,5 @@ export { CohubHttpClient, createHttpClient } from "./http.js";
|
|
|
2
2
|
export { CohubClient, createCohubClient } from "./client.js";
|
|
3
3
|
export { WebsocketClient, createWebsocketClient } from "./websocket.js";
|
|
4
4
|
export { HttpError } from "./transport.js";
|
|
5
|
+
export { COHUB_ENVIRONMENTS, normalizeBaseUrl, normalizeWebsocketUrl, resolveApiBaseUrl, resolveCohubEnvironment, resolveWebsocketUrl, } from "./environment.js";
|
|
5
6
|
export * from "./types.js";
|
package/dist/realtime.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export function ensureRealtimeConnected(websocketClient) {
|
|
2
|
+
if (websocketClient.state === "open" || websocketClient.state === "connecting" || websocketClient.state === "reconnecting") {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
2
5
|
void websocketClient.connect().catch((error) => {
|
|
3
6
|
console.error("[CohubClient] Failed to connect realtime websocket:", error);
|
|
4
7
|
});
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { CohubEnvironment } from "./environment.js";
|
|
1
2
|
import type { WebsocketClientOptions } from "./websocket.js";
|
|
2
3
|
export type Fetch = typeof globalThis.fetch;
|
|
3
4
|
export type CohubClientOptions = {
|
|
5
|
+
env?: CohubEnvironment;
|
|
4
6
|
baseUrl?: string;
|
|
5
7
|
getAccessToken?: () => Promise<string | null> | string | null;
|
|
6
8
|
onUnauthorized?: () => Promise<void> | void;
|
package/dist/transport.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveApiBaseUrl } from "./environment.js";
|
|
1
2
|
export class HttpError extends Error {
|
|
2
3
|
status;
|
|
3
4
|
body;
|
|
@@ -14,7 +15,7 @@ export class HttpTransport {
|
|
|
14
15
|
getAccessToken;
|
|
15
16
|
onUnauthorized;
|
|
16
17
|
constructor(options = {}) {
|
|
17
|
-
this.baseUrl = options
|
|
18
|
+
this.baseUrl = resolveApiBaseUrl(options);
|
|
18
19
|
this.fetcher = options.fetch ?? fetch;
|
|
19
20
|
this.getAccessToken = options.getAccessToken;
|
|
20
21
|
this.onUnauthorized = options.onUnauthorized;
|
package/dist/types.d.ts
CHANGED
|
@@ -40,6 +40,22 @@ export type SpaceFsMoveInput = {
|
|
|
40
40
|
fromPath: string;
|
|
41
41
|
toPath: string;
|
|
42
42
|
};
|
|
43
|
+
export type SpaceFsUploadEntry = {
|
|
44
|
+
path: string;
|
|
45
|
+
name: string;
|
|
46
|
+
size: number;
|
|
47
|
+
mimeType: string | null;
|
|
48
|
+
mtimeMs: number;
|
|
49
|
+
};
|
|
50
|
+
export type SpaceFsUploadError = {
|
|
51
|
+
name: string;
|
|
52
|
+
code: "file_too_large" | "name_invalid" | "write_failed";
|
|
53
|
+
message: string;
|
|
54
|
+
};
|
|
55
|
+
export type SpaceFsUploadResponse = {
|
|
56
|
+
uploaded: SpaceFsUploadEntry[];
|
|
57
|
+
errors: SpaceFsUploadError[];
|
|
58
|
+
};
|
|
43
59
|
export type SessionBindingRecord = ProtocolSessionBindingRecord;
|
|
44
60
|
export type SessionRecord = ProtocolSessionRecord & {
|
|
45
61
|
bindings?: SessionBindingRecord[];
|
|
@@ -49,6 +65,10 @@ export type SessionRecord = ProtocolSessionRecord & {
|
|
|
49
65
|
totalOutputTokens?: number;
|
|
50
66
|
totalCost?: string | number | null;
|
|
51
67
|
};
|
|
68
|
+
export type SpaceGitInfo = {
|
|
69
|
+
giteaHost: string;
|
|
70
|
+
giteaUsername: string;
|
|
71
|
+
};
|
|
52
72
|
export type SpaceRecord = {
|
|
53
73
|
id: string;
|
|
54
74
|
userUuid: string;
|
|
@@ -69,6 +89,7 @@ export type SpaceRecord = {
|
|
|
69
89
|
status: string;
|
|
70
90
|
}[];
|
|
71
91
|
accessLevel?: "minimal";
|
|
92
|
+
gitInfo?: SpaceGitInfo | null;
|
|
72
93
|
};
|
|
73
94
|
export type SpaceBootstrapSource = {
|
|
74
95
|
type: "blank";
|
|
@@ -86,12 +107,14 @@ export type SpaceCreateResponse = {
|
|
|
86
107
|
};
|
|
87
108
|
export type SpaceListItem = SpaceRecord;
|
|
88
109
|
export type SessionMessagesResponse = {
|
|
89
|
-
space: SpaceRecord;
|
|
90
110
|
session: SessionRecord;
|
|
91
111
|
messages: MessageRecord[];
|
|
92
112
|
};
|
|
113
|
+
export type SessionMessageResponse = {
|
|
114
|
+
session: SessionRecord;
|
|
115
|
+
message: MessageRecord;
|
|
116
|
+
};
|
|
93
117
|
export type SessionMessagesPaginatedResponse = {
|
|
94
|
-
space: SpaceRecord;
|
|
95
118
|
session: SessionRecord;
|
|
96
119
|
messages: MessageRecord[];
|
|
97
120
|
hasMore: boolean;
|
|
@@ -102,6 +125,16 @@ export type ModelCatalogEntry = {
|
|
|
102
125
|
id: string;
|
|
103
126
|
model: Record<string, unknown>;
|
|
104
127
|
};
|
|
128
|
+
export type PromptTemplateCatalogEntry = {
|
|
129
|
+
name: string;
|
|
130
|
+
description: string;
|
|
131
|
+
argumentHint?: string;
|
|
132
|
+
category?: string;
|
|
133
|
+
scope: "platform";
|
|
134
|
+
};
|
|
135
|
+
export type PromptTemplateCatalogResponse = {
|
|
136
|
+
prompts: PromptTemplateCatalogEntry[];
|
|
137
|
+
};
|
|
105
138
|
export type Channel = {
|
|
106
139
|
id: string;
|
|
107
140
|
userUuid: string;
|
|
@@ -125,8 +158,11 @@ export type SpaceChannelBindingInput = {
|
|
|
125
158
|
config?: ChannelConfig | null;
|
|
126
159
|
};
|
|
127
160
|
export type SpaceSessionsResponse = {
|
|
128
|
-
space: SpaceRecord;
|
|
129
161
|
sessions: SessionRecord[];
|
|
162
|
+
pageInfo?: {
|
|
163
|
+
hasMore: boolean;
|
|
164
|
+
nextCursor: string | null;
|
|
165
|
+
};
|
|
130
166
|
};
|
|
131
167
|
export type UserSshKey = {
|
|
132
168
|
id: string;
|
|
@@ -198,7 +234,7 @@ export type CreateScheduledTaskInput = {
|
|
|
198
234
|
spaceId?: string;
|
|
199
235
|
sessionId?: string;
|
|
200
236
|
};
|
|
201
|
-
export type SpaceRole = "host" | "
|
|
237
|
+
export type SpaceRole = "host" | "builder" | "guest";
|
|
202
238
|
export type SpaceMember = {
|
|
203
239
|
userId: string;
|
|
204
240
|
role: SpaceRole;
|
|
@@ -209,3 +245,65 @@ export type SpaceAccessPolicy = {
|
|
|
209
245
|
signed_in_user: SpaceRole | null;
|
|
210
246
|
anonymous_user: SpaceRole | null;
|
|
211
247
|
};
|
|
248
|
+
export type SpaceUsageHourlyStat = {
|
|
249
|
+
bucketStartAt: string;
|
|
250
|
+
totalTokens: number;
|
|
251
|
+
inputTokens: number;
|
|
252
|
+
outputTokens: number;
|
|
253
|
+
cacheReadTokens: number;
|
|
254
|
+
cacheWriteTokens: number;
|
|
255
|
+
costTotal: number;
|
|
256
|
+
requestCount: number;
|
|
257
|
+
successCount: number;
|
|
258
|
+
errorCount: number;
|
|
259
|
+
models: string[];
|
|
260
|
+
};
|
|
261
|
+
export type SpaceUsageSummary = {
|
|
262
|
+
totalTokens: number;
|
|
263
|
+
inputTokens: number;
|
|
264
|
+
outputTokens: number;
|
|
265
|
+
cacheReadTokens: number;
|
|
266
|
+
cacheWriteTokens: number;
|
|
267
|
+
costTotal: number;
|
|
268
|
+
requestCount: number;
|
|
269
|
+
successCount: number;
|
|
270
|
+
errorCount: number;
|
|
271
|
+
};
|
|
272
|
+
export type SpaceUsageResponse = {
|
|
273
|
+
hourly: SpaceUsageHourlyStat[];
|
|
274
|
+
summary: SpaceUsageSummary;
|
|
275
|
+
days: number;
|
|
276
|
+
};
|
|
277
|
+
export type SpaceInvitation = {
|
|
278
|
+
token: string;
|
|
279
|
+
role: SpaceRole;
|
|
280
|
+
status: "active" | "revoked" | "exhausted";
|
|
281
|
+
useCount: number;
|
|
282
|
+
maxUses: number | null;
|
|
283
|
+
createdAt: string | null;
|
|
284
|
+
expiresInSeconds: number | null;
|
|
285
|
+
};
|
|
286
|
+
export type CreateInvitationInput = {
|
|
287
|
+
role?: SpaceRole;
|
|
288
|
+
ttlSeconds?: number;
|
|
289
|
+
maxUses?: number;
|
|
290
|
+
};
|
|
291
|
+
export type CreateInvitationResponse = {
|
|
292
|
+
token: string;
|
|
293
|
+
role: SpaceRole;
|
|
294
|
+
expiresAt: string;
|
|
295
|
+
maxUses: number | null;
|
|
296
|
+
};
|
|
297
|
+
export type InvitationDetail = {
|
|
298
|
+
token: string;
|
|
299
|
+
spaceId: string;
|
|
300
|
+
spaceName: string;
|
|
301
|
+
role: SpaceRole;
|
|
302
|
+
expiresInSeconds: number | null;
|
|
303
|
+
};
|
|
304
|
+
export type AcceptInvitationResponse = {
|
|
305
|
+
ok: true;
|
|
306
|
+
spaceId: string;
|
|
307
|
+
spaceName: string;
|
|
308
|
+
role: SpaceRole;
|
|
309
|
+
};
|
package/dist/websocket.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ChannelEnvelope } from "@neta-art/cohub-protocol/realtime";
|
|
2
2
|
import type { ContentBlock } from "@neta-art/cohub-protocol/core";
|
|
3
|
+
import type { CohubEnvironment } from "./environment.js";
|
|
3
4
|
export type WebsocketEventPayload = ChannelEnvelope;
|
|
4
5
|
export type WebSocketLike = {
|
|
5
6
|
readonly readyState: number;
|
|
@@ -12,6 +13,7 @@ export type WebSocketLike = {
|
|
|
12
13
|
};
|
|
13
14
|
export type WebSocketConstructor = new (url: string) => WebSocketLike;
|
|
14
15
|
export type WebsocketClientOptions = {
|
|
16
|
+
env?: CohubEnvironment;
|
|
15
17
|
url?: string;
|
|
16
18
|
autoReconnect?: boolean;
|
|
17
19
|
reconnectBaseDelayMs?: number;
|
|
@@ -22,8 +24,18 @@ export type WebsocketClientOptions = {
|
|
|
22
24
|
getAccessToken?: () => Promise<string | null> | string | null;
|
|
23
25
|
WebSocketImpl?: WebSocketConstructor;
|
|
24
26
|
};
|
|
25
|
-
export type WebsocketClientState = "idle" | "connecting" | "open" | "closed";
|
|
27
|
+
export type WebsocketClientState = "idle" | "connecting" | "reconnecting" | "open" | "closed";
|
|
26
28
|
export type WebsocketClientEvents = {
|
|
29
|
+
connecting: {
|
|
30
|
+
isReconnect: boolean;
|
|
31
|
+
attempt: number;
|
|
32
|
+
};
|
|
33
|
+
reconnecting: {
|
|
34
|
+
attempt: number;
|
|
35
|
+
delayMs: number;
|
|
36
|
+
reason?: string;
|
|
37
|
+
code?: number;
|
|
38
|
+
};
|
|
27
39
|
open: {
|
|
28
40
|
connectionId?: string | null;
|
|
29
41
|
};
|
|
@@ -34,6 +46,7 @@ export type WebsocketClientEvents = {
|
|
|
34
46
|
};
|
|
35
47
|
error: {
|
|
36
48
|
error: unknown;
|
|
49
|
+
recoverable: boolean;
|
|
37
50
|
};
|
|
38
51
|
event: WebsocketEventPayload;
|
|
39
52
|
ready: {
|
|
@@ -75,6 +88,7 @@ export declare class WebsocketClient {
|
|
|
75
88
|
private ws;
|
|
76
89
|
private pingTimer;
|
|
77
90
|
private reconnectTimer;
|
|
91
|
+
private reconnectTimerResolver;
|
|
78
92
|
private reconnectAttempt;
|
|
79
93
|
private manuallyClosed;
|
|
80
94
|
private connectPromise;
|
package/dist/websocket.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { realtimeEnvelopeSchema, } from "@neta-art/cohub-protocol/realtime";
|
|
2
|
+
import { resolveWebsocketUrl } from "./environment.js";
|
|
2
3
|
const createEventMap = () => ({
|
|
4
|
+
connecting: new Set(),
|
|
5
|
+
reconnecting: new Set(),
|
|
3
6
|
open: new Set(),
|
|
4
7
|
close: new Set(),
|
|
5
8
|
error: new Set(),
|
|
@@ -10,24 +13,9 @@ const createEventMap = () => ({
|
|
|
10
13
|
serverError: new Set(),
|
|
11
14
|
pong: new Set(),
|
|
12
15
|
});
|
|
13
|
-
const toWebSocketUrl = (input) => {
|
|
14
|
-
const base = (input?.trim() || "").replace(/\/$/, "");
|
|
15
|
-
if (base) {
|
|
16
|
-
if (base.startsWith("ws://") || base.startsWith("wss://"))
|
|
17
|
-
return `${base}/ws`;
|
|
18
|
-
if (base.startsWith("http://"))
|
|
19
|
-
return `${base.replace(/^http:/, "ws:")}/ws`;
|
|
20
|
-
if (base.startsWith("https://"))
|
|
21
|
-
return `${base.replace(/^https:/, "wss:")}/ws`;
|
|
22
|
-
}
|
|
23
|
-
if (typeof window !== "undefined") {
|
|
24
|
-
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
25
|
-
return `${protocol}//${window.location.host}/ws`;
|
|
26
|
-
}
|
|
27
|
-
return "ws://localhost:8788/ws";
|
|
28
|
-
};
|
|
16
|
+
const toWebSocketUrl = (input, env) => resolveWebsocketUrl({ url: input, env });
|
|
29
17
|
const normalizeOptions = (options = {}) => ({
|
|
30
|
-
url: toWebSocketUrl(options.url),
|
|
18
|
+
url: toWebSocketUrl(options.url, options.env),
|
|
31
19
|
autoReconnect: options.autoReconnect !== false,
|
|
32
20
|
reconnectBaseDelayMs: options.reconnectBaseDelayMs ?? 1000,
|
|
33
21
|
reconnectMaxDelayMs: options.reconnectMaxDelayMs ?? 15000,
|
|
@@ -35,6 +23,15 @@ const normalizeOptions = (options = {}) => ({
|
|
|
35
23
|
pongTimeoutMs: options.pongTimeoutMs ?? 15000,
|
|
36
24
|
debug: options.debug === true,
|
|
37
25
|
});
|
|
26
|
+
const formatCloseMessage = (code, reason) => `WebSocket closed: ${code ?? 0} ${reason || ""}`.trim();
|
|
27
|
+
const isRetryableCloseCode = (code) => {
|
|
28
|
+
if (code === 1000)
|
|
29
|
+
return false;
|
|
30
|
+
if (code === 4003)
|
|
31
|
+
return false;
|
|
32
|
+
return true;
|
|
33
|
+
};
|
|
34
|
+
const AUTH_CLOSE_REASON = "authentication failed";
|
|
38
35
|
class WebsocketAuthError extends Error {
|
|
39
36
|
constructor(message) {
|
|
40
37
|
super(message);
|
|
@@ -54,6 +51,7 @@ export class WebsocketClient {
|
|
|
54
51
|
ws = null;
|
|
55
52
|
pingTimer = null;
|
|
56
53
|
reconnectTimer = null;
|
|
54
|
+
reconnectTimerResolver = null;
|
|
57
55
|
reconnectAttempt = 0;
|
|
58
56
|
manuallyClosed = false;
|
|
59
57
|
connectPromise = null;
|
|
@@ -97,8 +95,11 @@ export class WebsocketClient {
|
|
|
97
95
|
return this.connectPromise;
|
|
98
96
|
if (this.state === "open" && this.ws?.readyState === WebSocket.OPEN)
|
|
99
97
|
return;
|
|
98
|
+
const isReconnect = this.reconnectAttempt > 0 || this.state === "reconnecting";
|
|
100
99
|
this.manuallyClosed = false;
|
|
101
|
-
this.
|
|
100
|
+
this.clearReconnectTimer();
|
|
101
|
+
this.state = isReconnect ? "reconnecting" : "connecting";
|
|
102
|
+
this.emit("connecting", { isReconnect, attempt: this.reconnectAttempt });
|
|
102
103
|
this.connectPromise = new Promise((resolve, reject) => {
|
|
103
104
|
const ws = new this.WebSocketImpl(this.url);
|
|
104
105
|
this.ws = ws;
|
|
@@ -119,7 +120,7 @@ export class WebsocketClient {
|
|
|
119
120
|
};
|
|
120
121
|
ws.onopen = async () => {
|
|
121
122
|
try {
|
|
122
|
-
this.log("connected", this.url);
|
|
123
|
+
this.log("connected", { url: this.url, isReconnect, attempt: this.reconnectAttempt });
|
|
123
124
|
this.startPingLoop();
|
|
124
125
|
await this.authenticate();
|
|
125
126
|
this.state = "open";
|
|
@@ -129,37 +130,36 @@ export class WebsocketClient {
|
|
|
129
130
|
}
|
|
130
131
|
catch (error) {
|
|
131
132
|
const authError = error instanceof Error ? error : new Error("authentication failed");
|
|
133
|
+
this.emit("error", { error: authError, recoverable: false });
|
|
132
134
|
rejectOnce(authError);
|
|
133
|
-
ws.close(4003,
|
|
135
|
+
ws.close(4003, AUTH_CLOSE_REASON);
|
|
134
136
|
}
|
|
135
137
|
};
|
|
136
138
|
ws.onmessage = (event) => {
|
|
137
139
|
this.handleMessage(event.data);
|
|
138
140
|
};
|
|
139
141
|
ws.onerror = (error) => {
|
|
140
|
-
this.emit("error", { error });
|
|
142
|
+
this.emit("error", { error, recoverable: !this.manuallyClosed });
|
|
141
143
|
};
|
|
142
144
|
ws.onclose = (event) => {
|
|
143
145
|
this.stopPingLoop();
|
|
144
|
-
const wasConnecting = this.state === "connecting";
|
|
146
|
+
const wasConnecting = this.state === "connecting" || this.state === "reconnecting";
|
|
145
147
|
this.state = "closed";
|
|
146
148
|
this.ws = null;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
const closeError = new Error(formatCloseMessage(event.code, event.reason));
|
|
150
|
+
this.rejectAuthWaiter(closeError);
|
|
151
|
+
const willReconnect = !this.manuallyClosed && this.autoReconnect && isRetryableCloseCode(event.code);
|
|
152
|
+
this.log("closed", { code: event.code, reason: event.reason, willReconnect, wasConnecting });
|
|
149
153
|
this.emit("close", {
|
|
150
154
|
code: event.code,
|
|
151
155
|
reason: event.reason,
|
|
152
156
|
willReconnect,
|
|
153
157
|
});
|
|
154
158
|
if (wasConnecting) {
|
|
155
|
-
rejectOnce(
|
|
156
|
-
if (event.code === 4001 && willReconnect) {
|
|
157
|
-
void this.scheduleReconnect();
|
|
158
|
-
}
|
|
159
|
-
return;
|
|
159
|
+
rejectOnce(closeError);
|
|
160
160
|
}
|
|
161
161
|
if (willReconnect) {
|
|
162
|
-
void this.scheduleReconnect();
|
|
162
|
+
void this.scheduleReconnect(event.code, event.reason);
|
|
163
163
|
}
|
|
164
164
|
};
|
|
165
165
|
});
|
|
@@ -253,12 +253,12 @@ export class WebsocketClient {
|
|
|
253
253
|
parsed = typeof raw === "string" ? JSON.parse(raw) : JSON.parse(String(raw));
|
|
254
254
|
}
|
|
255
255
|
catch {
|
|
256
|
-
this.emit("error", { error: new Error("invalid websocket payload") });
|
|
256
|
+
this.emit("error", { error: new Error("invalid websocket payload"), recoverable: true });
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
259
259
|
const result = realtimeEnvelopeSchema.safeParse(parsed);
|
|
260
260
|
if (!result.success) {
|
|
261
|
-
this.emit("error", { error: new Error("invalid realtime envelope") });
|
|
261
|
+
this.emit("error", { error: new Error("invalid realtime envelope"), recoverable: true });
|
|
262
262
|
return;
|
|
263
263
|
}
|
|
264
264
|
const envelope = result.data;
|
|
@@ -359,7 +359,7 @@ export class WebsocketClient {
|
|
|
359
359
|
if (this.awaitingPong &&
|
|
360
360
|
this.pongDeadlineAt > 0 &&
|
|
361
361
|
Date.now() > this.pongDeadlineAt) {
|
|
362
|
-
this.emit("error", { error: new Error("websocket pong timeout") });
|
|
362
|
+
this.emit("error", { error: new Error("websocket pong timeout"), recoverable: true });
|
|
363
363
|
this.ws.close(4002, "pong timeout");
|
|
364
364
|
return;
|
|
365
365
|
}
|
|
@@ -376,22 +376,41 @@ export class WebsocketClient {
|
|
|
376
376
|
this.pongDeadlineAt = 0;
|
|
377
377
|
}
|
|
378
378
|
clearReconnectTimer() {
|
|
379
|
-
if (
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
379
|
+
if (this.reconnectTimer) {
|
|
380
|
+
clearTimeout(this.reconnectTimer);
|
|
381
|
+
this.reconnectTimer = null;
|
|
382
|
+
}
|
|
383
|
+
if (this.reconnectTimerResolver) {
|
|
384
|
+
const resolve = this.reconnectTimerResolver;
|
|
385
|
+
this.reconnectTimerResolver = null;
|
|
386
|
+
resolve();
|
|
387
|
+
}
|
|
383
388
|
}
|
|
384
|
-
async scheduleReconnect() {
|
|
389
|
+
async scheduleReconnect(code, reason) {
|
|
385
390
|
this.clearReconnectTimer();
|
|
391
|
+
const attempt = this.reconnectAttempt + 1;
|
|
386
392
|
const delay = Math.min(this.reconnectBaseDelayMs * 2 ** this.reconnectAttempt, this.reconnectMaxDelayMs);
|
|
387
|
-
this.reconnectAttempt
|
|
393
|
+
this.reconnectAttempt = attempt;
|
|
394
|
+
this.state = "reconnecting";
|
|
395
|
+
this.log("schedule reconnect", { attempt, delay, code, reason });
|
|
396
|
+
this.emit("reconnecting", {
|
|
397
|
+
attempt,
|
|
398
|
+
delayMs: delay,
|
|
399
|
+
code,
|
|
400
|
+
reason,
|
|
401
|
+
});
|
|
388
402
|
await new Promise((resolve) => {
|
|
389
|
-
this.
|
|
403
|
+
this.reconnectTimerResolver = resolve;
|
|
404
|
+
this.reconnectTimer = setTimeout(() => {
|
|
405
|
+
this.reconnectTimer = null;
|
|
406
|
+
this.reconnectTimerResolver = null;
|
|
407
|
+
resolve();
|
|
408
|
+
}, delay);
|
|
390
409
|
});
|
|
391
410
|
if (this.manuallyClosed)
|
|
392
411
|
return;
|
|
393
412
|
await this.connect().catch((error) => {
|
|
394
|
-
this.emit("error", { error });
|
|
413
|
+
this.emit("error", { error, recoverable: true });
|
|
395
414
|
});
|
|
396
415
|
}
|
|
397
416
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Cohub SDK for spaces, sessions, checkpoints, and realtime agent collaboration.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
"sideEffects": false,
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/
|
|
11
|
+
"url": "https://github.com/talesofai/cohub.git",
|
|
12
12
|
"directory": "packages/sdk"
|
|
13
13
|
},
|
|
14
|
-
"homepage": "https://github.com/
|
|
14
|
+
"homepage": "https://github.com/talesofai/cohub",
|
|
15
15
|
"keywords": [
|
|
16
16
|
"cohub",
|
|
17
17
|
"sdk",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"README.md"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@neta-art/cohub-protocol": "
|
|
47
|
+
"@neta-art/cohub-protocol": "1.2.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"typescript": "^6.0.3"
|