@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.
@@ -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
@@ -6,6 +6,7 @@ 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, HttpError, type CohubClientOptions, type Fetch } from "./transport.js";
10
11
  export declare class CohubHttpClient {
11
12
  readonly spaces: SpacesApi;
@@ -16,6 +17,7 @@ export declare class CohubHttpClient {
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
  constructor(options?: CohubClientOptions);
21
23
  space(spaceId: string): SpaceClient;
package/dist/http.js CHANGED
@@ -6,7 +6,9 @@ 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, HttpError } from "./transport.js";
11
+ import { resolveApiBaseUrl } from "./environment.js";
10
12
  export class CohubHttpClient {
11
13
  spaces;
12
14
  channels;
@@ -16,17 +18,20 @@ export class CohubHttpClient {
16
18
  sessionAccess;
17
19
  tasks;
18
20
  cronJobs;
21
+ invite;
19
22
  transport;
20
23
  constructor(options = {}) {
24
+ const apiBaseUrl = resolveApiBaseUrl(options);
21
25
  this.transport = new HttpTransport(options);
22
26
  this.spaces = new SpacesApi(this.transport);
23
27
  this.channels = new ChannelsApi(this.transport);
24
- this.user = new UserApi(this.transport, options.baseUrl ?? "", options.setStoredAuthToken, options.clearStoredAuthToken);
25
- this.models = new ModelsApi(options.fetch ?? fetch, options.baseUrl ?? "");
26
- this.prompts = new PromptsApi(options.fetch ?? fetch, options.baseUrl ?? "");
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);
27
31
  this.sessionAccess = new SessionAccessApi(this.transport);
28
32
  this.tasks = new TasksApi(this.transport);
29
33
  this.cronJobs = new CronJobsApi(this.transport);
34
+ this.invite = new PublicInviteApi(this.transport);
30
35
  }
31
36
  space(spaceId) {
32
37
  return new SpaceClient(spaceId, this.transport, null);
package/dist/index.d.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  export { CohubHttpClient, createHttpClient } from "./http.js";
2
2
  export { CohubClient, createCohubClient } from "./client.js";
3
3
  export { WebsocketClient, createWebsocketClient } from "./websocket.js";
4
+ export { SessionPatchReducer, createSessionPatchReducer } from "./session-patch-reducer.js";
4
5
  export { HttpError } from "./transport.js";
6
+ export { COHUB_ENVIRONMENTS, normalizeBaseUrl, normalizeWebsocketUrl, resolveApiBaseUrl, resolveCohubEnvironment, resolveWebsocketUrl, } from "./environment.js";
5
7
  export type { CohubClientOptions, Fetch } from "./transport.js";
8
+ export type { CohubEnvironment } from "./environment.js";
9
+ export type { SessionPatchApplyInput, SessionPatchApplyResult, SessionPatchState, SessionPatchStatus, } from "./session-patch-reducer.js";
6
10
  export * from "./types.js";
7
- export type { SessionEventName, SessionSubscriptionHandlers, SpaceEventName, WebSocketConnectionState } from "./apis/spaces.js";
11
+ export type { SessionEventName, SessionSubscriptionHandlers, SpaceChannelBindingRecord, SpaceEventName, WebSocketConnectionState } from "./apis/spaces.js";
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  export { CohubHttpClient, createHttpClient } from "./http.js";
2
2
  export { CohubClient, createCohubClient } from "./client.js";
3
3
  export { WebsocketClient, createWebsocketClient } from "./websocket.js";
4
+ export { SessionPatchReducer, createSessionPatchReducer } from "./session-patch-reducer.js";
4
5
  export { HttpError } from "./transport.js";
6
+ export { COHUB_ENVIRONMENTS, normalizeBaseUrl, normalizeWebsocketUrl, resolveApiBaseUrl, resolveCohubEnvironment, resolveWebsocketUrl, } from "./environment.js";
5
7
  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
  });
@@ -0,0 +1,49 @@
1
+ import type { ContentBlock } from "@neta-art/cohub-protocol/core";
2
+ import type { RealtimePatchOperation, SessionTurnPatchEvent } from "@neta-art/cohub-protocol/realtime";
3
+ export type SessionPatchStatus = "idle" | "pending" | "streaming" | "completed" | "failed";
4
+ export type SessionPatchState = {
5
+ spaceId: string | null;
6
+ sessionId: string;
7
+ status: SessionPatchStatus;
8
+ contentBlocks: ContentBlock[];
9
+ anchorUserMessageId: string | null;
10
+ patchSeq: number;
11
+ turnId: string | null;
12
+ appendPath: string | null;
13
+ };
14
+ export type SessionPatchApplyInput = {
15
+ spaceId?: string | null;
16
+ sessionId: string;
17
+ turnId?: string | null;
18
+ seq: number;
19
+ baseSeq: number;
20
+ ops: RealtimePatchOperation[];
21
+ anchorUserMessageId?: string | null;
22
+ };
23
+ export type SessionPatchApplyResult = {
24
+ applied: true;
25
+ state: SessionPatchState;
26
+ } | {
27
+ applied: false;
28
+ reason: "duplicate" | "version_mismatch" | "invalid";
29
+ state: SessionPatchState;
30
+ };
31
+ type SessionPatchKeyInput = {
32
+ spaceId?: string | null;
33
+ sessionId: string;
34
+ turnId?: string | null;
35
+ };
36
+ export declare class SessionPatchReducer {
37
+ private readonly states;
38
+ private key;
39
+ get(input: SessionPatchKeyInput): SessionPatchState;
40
+ start(input: SessionPatchKeyInput): SessionPatchState;
41
+ complete(input: SessionPatchKeyInput): SessionPatchState;
42
+ fail(input: SessionPatchKeyInput): SessionPatchState;
43
+ reset(input: SessionPatchKeyInput): void;
44
+ resetAll(): void;
45
+ applyEvent(event: SessionTurnPatchEvent): SessionPatchApplyResult;
46
+ applyPatch(input: SessionPatchApplyInput): SessionPatchApplyResult;
47
+ }
48
+ export declare const createSessionPatchReducer: () => SessionPatchReducer;
49
+ export {};
@@ -0,0 +1,273 @@
1
+ const blockTextPathPattern = /^\/message\/content\/blocks\/(\d+)\/(text|thinking)$/;
2
+ const blockPathPattern = /^\/message\/content\/blocks\/(\d+)$/;
3
+ const blockMetaPathPattern = /^\/message\/content\/blocks\/(\d+)\/_meta$/;
4
+ const blockSignaturePathPattern = /^\/message\/content\/blocks\/(\d+)\/signature$/;
5
+ const createIdleState = (input) => ({
6
+ spaceId: input.spaceId ?? null,
7
+ sessionId: input.sessionId,
8
+ status: "idle",
9
+ contentBlocks: [],
10
+ anchorUserMessageId: null,
11
+ patchSeq: 0,
12
+ turnId: null,
13
+ appendPath: null,
14
+ });
15
+ function cloneBlock(block) {
16
+ return structuredClone(block);
17
+ }
18
+ function getStreamIndex(block) {
19
+ const value = block._meta?.streamIndex;
20
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
21
+ }
22
+ function findBlockByStreamIndex(blocks, streamIndex) {
23
+ return blocks.findIndex((block) => getStreamIndex(block) === streamIndex);
24
+ }
25
+ function sortBlocksByStreamIndex(blocks) {
26
+ return [...blocks].sort((a, b) => {
27
+ const aIndex = getStreamIndex(a);
28
+ const bIndex = getStreamIndex(b);
29
+ if (aIndex == null && bIndex == null)
30
+ return 0;
31
+ if (aIndex == null)
32
+ return 1;
33
+ if (bIndex == null)
34
+ return -1;
35
+ return aIndex - bIndex;
36
+ });
37
+ }
38
+ function isContentBlock(value) {
39
+ return Boolean(value && typeof value === "object" && "type" in value);
40
+ }
41
+ function ensureTextLikeBlock(blocks, streamIndex, field) {
42
+ const existingIndex = findBlockByStreamIndex(blocks, streamIndex);
43
+ const existing = existingIndex >= 0 ? blocks[existingIndex] : undefined;
44
+ if (field === "text") {
45
+ if (existing?.type === "text")
46
+ return existing;
47
+ const block = {
48
+ type: "text",
49
+ text: "",
50
+ _meta: { streamIndex },
51
+ };
52
+ blocks.push(block);
53
+ return block;
54
+ }
55
+ if (existing?.type === "thinking")
56
+ return existing;
57
+ const block = {
58
+ type: "thinking",
59
+ thinking: "",
60
+ _meta: { streamIndex },
61
+ };
62
+ blocks.push(block);
63
+ return block;
64
+ }
65
+ function appendTextLikeValue(blocks, path, value) {
66
+ const match = path.match(blockTextPathPattern);
67
+ if (!match || typeof value !== "string")
68
+ return false;
69
+ const streamIndex = Number(match[1]);
70
+ const field = match[2];
71
+ const block = ensureTextLikeBlock(blocks, streamIndex, field);
72
+ if (field === "text" && block.type === "text") {
73
+ block.text += value;
74
+ }
75
+ if (field === "thinking" && block.type === "thinking") {
76
+ block.thinking += value;
77
+ }
78
+ return true;
79
+ }
80
+ function applyPatchOpsToBlocks(current, ops, initialAppendPath) {
81
+ const next = current.map(cloneBlock);
82
+ let anchorUserMessageId;
83
+ let appendPath = initialAppendPath;
84
+ let failed = false;
85
+ for (const op of ops) {
86
+ if (!op.o && !op.p) {
87
+ if (!appendPath || !appendTextLikeValue(next, appendPath, op.v)) {
88
+ failed = true;
89
+ break;
90
+ }
91
+ continue;
92
+ }
93
+ if (op.o === "merge" && op.p === "/message/metadata") {
94
+ const anchor = op.v.anchorUserMessageId;
95
+ if (typeof anchor === "string" && anchor.trim()) {
96
+ anchorUserMessageId = anchor;
97
+ }
98
+ continue;
99
+ }
100
+ if (op.o === "append") {
101
+ if (!appendTextLikeValue(next, op.p, op.v)) {
102
+ failed = true;
103
+ break;
104
+ }
105
+ appendPath = op.p;
106
+ continue;
107
+ }
108
+ if (op.o === "merge") {
109
+ const match = op.p.match(blockMetaPathPattern);
110
+ if (!match)
111
+ continue;
112
+ const streamIndex = Number(match[1]);
113
+ const blockIndex = findBlockByStreamIndex(next, streamIndex);
114
+ const block = blockIndex >= 0 ? next[blockIndex] : undefined;
115
+ if (!block)
116
+ continue;
117
+ block._meta = { ...(block._meta ?? {}), ...op.v };
118
+ continue;
119
+ }
120
+ if (op.o === "replace") {
121
+ const match = op.p.match(blockSignaturePathPattern);
122
+ if (match) {
123
+ if (typeof op.v !== "string")
124
+ continue;
125
+ const blockIndex = findBlockByStreamIndex(next, Number(match[1]));
126
+ const block = blockIndex >= 0 ? next[blockIndex] : undefined;
127
+ if (block?.type === "thinking")
128
+ block.signature = op.v;
129
+ continue;
130
+ }
131
+ }
132
+ if (op.o === "replace" || op.o === "add") {
133
+ const match = op.p.match(blockPathPattern);
134
+ if (!match || !isContentBlock(op.v))
135
+ continue;
136
+ const streamIndex = Number(match[1]);
137
+ const block = cloneBlock(op.v);
138
+ block._meta = { ...(block._meta ?? {}), streamIndex };
139
+ const blockIndex = findBlockByStreamIndex(next, streamIndex);
140
+ if (blockIndex >= 0) {
141
+ next[blockIndex] = block;
142
+ }
143
+ else {
144
+ next.push(block);
145
+ }
146
+ continue;
147
+ }
148
+ if (op.o === "remove") {
149
+ const match = op.p.match(blockPathPattern);
150
+ if (!match)
151
+ continue;
152
+ const blockIndex = findBlockByStreamIndex(next, Number(match[1]));
153
+ if (blockIndex >= 0)
154
+ next.splice(blockIndex, 1);
155
+ }
156
+ }
157
+ return {
158
+ failed,
159
+ contentBlocks: sortBlocksByStreamIndex(next),
160
+ anchorUserMessageId,
161
+ appendPath,
162
+ };
163
+ }
164
+ export class SessionPatchReducer {
165
+ states = new Map();
166
+ key(input) {
167
+ return `${input.spaceId ?? ""}:${input.sessionId}`;
168
+ }
169
+ get(input) {
170
+ const key = this.key(input);
171
+ return this.states.get(key) ?? createIdleState(input);
172
+ }
173
+ start(input) {
174
+ const state = {
175
+ ...this.get(input),
176
+ status: "pending",
177
+ contentBlocks: [],
178
+ anchorUserMessageId: null,
179
+ patchSeq: 0,
180
+ turnId: null,
181
+ appendPath: null,
182
+ };
183
+ this.states.set(this.key(input), state);
184
+ return state;
185
+ }
186
+ complete(input) {
187
+ const current = this.get(input);
188
+ const state = {
189
+ ...current,
190
+ turnId: input.turnId ?? current.turnId,
191
+ status: "completed",
192
+ contentBlocks: [],
193
+ anchorUserMessageId: null,
194
+ };
195
+ this.states.set(this.key(input), state);
196
+ return state;
197
+ }
198
+ fail(input) {
199
+ const current = this.get(input);
200
+ const state = {
201
+ ...current,
202
+ turnId: input.turnId ?? current.turnId,
203
+ status: "failed",
204
+ contentBlocks: [],
205
+ anchorUserMessageId: null,
206
+ };
207
+ this.states.set(this.key(input), state);
208
+ return state;
209
+ }
210
+ reset(input) {
211
+ this.states.delete(this.key(input));
212
+ }
213
+ resetAll() {
214
+ this.states.clear();
215
+ }
216
+ applyEvent(event) {
217
+ return this.applyPatch({
218
+ spaceId: event.spaceId,
219
+ sessionId: event.sessionId,
220
+ turnId: event.payload.turnId,
221
+ seq: event.payload.seq,
222
+ baseSeq: event.payload.baseSeq,
223
+ ops: event.payload.ops,
224
+ anchorUserMessageId: event.payload.anchorUserMessageId,
225
+ });
226
+ }
227
+ applyPatch(input) {
228
+ const current = this.get(input);
229
+ const currentTurnId = current.turnId;
230
+ const inputTurnId = input.turnId ?? null;
231
+ const isDifferentKnownTurn = Boolean(currentTurnId && inputTurnId && currentTurnId !== inputTurnId);
232
+ const isFreshKnownTurn = isDifferentKnownTurn && input.baseSeq === 0;
233
+ const currentSeq = isFreshKnownTurn ? 0 : current.patchSeq;
234
+ const isTerminalSameTurn = (current.status === "completed" || current.status === "failed") &&
235
+ Boolean(currentTurnId) &&
236
+ currentTurnId === inputTurnId;
237
+ if (isTerminalSameTurn) {
238
+ return { applied: false, reason: "duplicate", state: current };
239
+ }
240
+ if (isDifferentKnownTurn && !isFreshKnownTurn) {
241
+ return { applied: false, reason: "version_mismatch", state: current };
242
+ }
243
+ if (input.seq <= currentSeq) {
244
+ return { applied: false, reason: "duplicate", state: current };
245
+ }
246
+ if (input.baseSeq !== currentSeq) {
247
+ return { applied: false, reason: "version_mismatch", state: current };
248
+ }
249
+ const startingFresh = input.baseSeq === 0 || isFreshKnownTurn;
250
+ const baseBlocks = startingFresh ? [] : current.contentBlocks;
251
+ const patched = applyPatchOpsToBlocks(baseBlocks, input.ops, startingFresh ? null : current.appendPath);
252
+ if (patched.failed) {
253
+ return { applied: false, reason: "version_mismatch", state: current };
254
+ }
255
+ const next = {
256
+ ...current,
257
+ spaceId: input.spaceId ?? current.spaceId ?? null,
258
+ sessionId: input.sessionId,
259
+ status: "streaming",
260
+ contentBlocks: patched.contentBlocks,
261
+ anchorUserMessageId: patched.anchorUserMessageId ??
262
+ input.anchorUserMessageId ??
263
+ current.anchorUserMessageId ??
264
+ null,
265
+ patchSeq: input.seq,
266
+ turnId: input.turnId ?? current.turnId ?? null,
267
+ appendPath: patched.appendPath,
268
+ };
269
+ this.states.set(this.key(next), next);
270
+ return { applied: true, state: next };
271
+ }
272
+ }
273
+ export const createSessionPatchReducer = () => new SessionPatchReducer();
@@ -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.baseUrl ?? "";
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";
@@ -89,6 +110,10 @@ export type SessionMessagesResponse = {
89
110
  session: SessionRecord;
90
111
  messages: MessageRecord[];
91
112
  };
113
+ export type SessionMessageResponse = {
114
+ session: SessionRecord;
115
+ message: MessageRecord;
116
+ };
92
117
  export type SessionMessagesPaginatedResponse = {
93
118
  session: SessionRecord;
94
119
  messages: MessageRecord[];
@@ -134,6 +159,10 @@ export type SpaceChannelBindingInput = {
134
159
  };
135
160
  export type SpaceSessionsResponse = {
136
161
  sessions: SessionRecord[];
162
+ pageInfo?: {
163
+ hasMore: boolean;
164
+ nextCursor: string | null;
165
+ };
137
166
  };
138
167
  export type UserSshKey = {
139
168
  id: string;
@@ -223,6 +252,10 @@ export type SpaceUsageHourlyStat = {
223
252
  outputTokens: number;
224
253
  cacheReadTokens: number;
225
254
  cacheWriteTokens: number;
255
+ costInput: number;
256
+ costOutput: number;
257
+ costCacheRead: number;
258
+ costCacheWrite: number;
226
259
  costTotal: number;
227
260
  requestCount: number;
228
261
  successCount: number;
@@ -235,6 +268,10 @@ export type SpaceUsageSummary = {
235
268
  outputTokens: number;
236
269
  cacheReadTokens: number;
237
270
  cacheWriteTokens: number;
271
+ costInput: number;
272
+ costOutput: number;
273
+ costCacheRead: number;
274
+ costCacheWrite: number;
238
275
  costTotal: number;
239
276
  requestCount: number;
240
277
  successCount: number;
@@ -245,3 +282,36 @@ export type SpaceUsageResponse = {
245
282
  summary: SpaceUsageSummary;
246
283
  days: number;
247
284
  };
285
+ export type SpaceInvitation = {
286
+ token: string;
287
+ role: SpaceRole;
288
+ status: "active" | "revoked" | "exhausted";
289
+ useCount: number;
290
+ maxUses: number | null;
291
+ createdAt: string | null;
292
+ expiresInSeconds: number | null;
293
+ };
294
+ export type CreateInvitationInput = {
295
+ role?: SpaceRole;
296
+ ttlSeconds?: number;
297
+ maxUses?: number;
298
+ };
299
+ export type CreateInvitationResponse = {
300
+ token: string;
301
+ role: SpaceRole;
302
+ expiresAt: string;
303
+ maxUses: number | null;
304
+ };
305
+ export type InvitationDetail = {
306
+ token: string;
307
+ spaceId: string;
308
+ spaceName: string;
309
+ role: SpaceRole;
310
+ expiresInSeconds: number | null;
311
+ };
312
+ export type AcceptInvitationResponse = {
313
+ ok: true;
314
+ spaceId: string;
315
+ spaceName: string;
316
+ role: SpaceRole;
317
+ };
@@ -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;
@@ -82,6 +96,7 @@ export declare class WebsocketClient {
82
96
  private awaitingPong;
83
97
  private lastPingRequestId;
84
98
  private pongDeadlineAt;
99
+ private readonly compactStreamContexts;
85
100
  state: WebsocketClientState;
86
101
  connectionId: string | null;
87
102
  private readonly listeners;
@@ -110,6 +125,8 @@ export declare class WebsocketClient {
110
125
  private resolveAuthWaiter;
111
126
  private rejectAuthWaiter;
112
127
  private handleMessage;
128
+ private rememberCompactStreamContext;
129
+ private handleCompactFrame;
113
130
  private startPingLoop;
114
131
  private stopPingLoop;
115
132
  private clearReconnectTimer;