@neta-art/cohub 1.3.1 → 1.5.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.
@@ -1,12 +1,11 @@
1
1
  import type { HttpTransport } from "../transport.js";
2
- import type { CreateCronJobInput, CronJobRecord, TaskRunRecord } from "../types.js";
2
+ import type { CronJobRecord, TaskRunRecord } from "../types.js";
3
3
  export declare class CronJobsApi {
4
4
  private readonly transport;
5
5
  constructor(transport: HttpTransport);
6
6
  list(spaceId?: string): Promise<{
7
7
  jobs: CronJobRecord[];
8
8
  }>;
9
- create(data: CreateCronJobInput): Promise<CronJobRecord>;
10
9
  delete(id: string): Promise<{
11
10
  ok: true;
12
11
  }>;
@@ -7,13 +7,6 @@ export class CronJobsApi {
7
7
  const query = spaceId ? `?spaceId=${encodeURIComponent(spaceId)}` : "";
8
8
  return this.transport.request(`/api/cron-jobs${query}`);
9
9
  }
10
- create(data) {
11
- return this.transport.request("/api/cron-jobs", {
12
- method: "POST",
13
- headers: { "Content-Type": "application/json" },
14
- body: JSON.stringify(data),
15
- });
16
- }
17
10
  delete(id) {
18
11
  return this.transport.request(`/api/cron-jobs/${id}`, {
19
12
  method: "DELETE",
@@ -2,7 +2,7 @@ import type { SpacePublicEndpoints } from "@neta-art/cohub-protocol/ports";
2
2
  import type { WebsocketClient, WebsocketEventPayload } from "../websocket.js";
3
3
  import type { HttpTransport, Fetch } from "../transport.js";
4
4
  import { type SessionPatchApplyResult } from "../session-patch-reducer.js";
5
- import type { CheckpointRecord, ContentBlock, SessionMessageResponse, SessionMessagesPaginatedResponse, SessionMessagesResponse, SessionTurnResponse, SessionTurnIndexResponse, SessionTurnWindowResponse, SessionTurnsPaginatedResponse, SessionTurnSignedUrlsResponse, SessionRecord, SpaceAccessPolicy, SpaceBootstrapSource, SpaceChannelBindingInput, SpaceCheckpointDetailResponse, SpaceCreateResponse, SpaceEnvInput, SpaceFsFileResponse, SpaceFsMoveInput, SpaceFsTreeResponse, SpaceFsUploadResponse, SpaceUsageResponse, SpaceFsWriteFileInput, SpaceMarkKind, SpaceMarkListItem, SpaceMarkResourceType, SpaceMember, SpaceRecord, SpaceRole, SpaceSessionsResponse } from "../types.js";
5
+ import type { CheckpointRecord, ContentBlock, SessionMessageResponse, SessionMessagesPaginatedResponse, SessionMessagesResponse, SessionTurnResponse, SessionTurnStreamSnapshotResponse, SessionTurnIndexResponse, SessionTurnWindowResponse, SessionTurnsPaginatedResponse, SessionTurnSignedUrlsResponse, SessionRecord, SpaceAccessPolicy, SpaceBootstrapSource, SpaceChannelBindingInput, SpaceCheckpointDetailResponse, SpaceCreateResponse, CreateSpacePromptInput, CreateSpacePromptResponse, SpaceEnvInput, SpaceFsCompleteUploadInput, SpaceFsCompleteUploadResponse, SpaceFsCreateUploadInput, SpaceFsCreateUploadResponse, SpaceFsFileResponse, SpaceFsMoveInput, SpaceFsTreeResponse, SpaceFsUploadResponse, SpaceUsageResponse, SpaceFsWriteFileInput, SpaceMarkKind, SpaceMarkListItem, SpaceMarkResourceType, SpaceMember, SpaceRecord, SpaceRole, SpaceSessionsResponse } from "../types.js";
6
6
  import { SpaceInvitationsApi } from "./invitations.js";
7
7
  export type SessionSubscriptionHandlers = {
8
8
  patch?: (event: WebsocketEventPayload) => void;
@@ -22,6 +22,7 @@ type SessionSendMessageInput = {
22
22
  content: ContentBlock[];
23
23
  model?: string;
24
24
  provider?: string;
25
+ clientMessageId?: string;
25
26
  };
26
27
  export declare class SpacesApi {
27
28
  private readonly transport;
@@ -75,6 +76,8 @@ export declare class SpaceFilesApi {
75
76
  toPath: string;
76
77
  }>;
77
78
  upload(files: File[], dir?: string): Promise<SpaceFsUploadResponse>;
79
+ createUpload(input: SpaceFsCreateUploadInput): Promise<SpaceFsCreateUploadResponse>;
80
+ completeUpload(uploadId: string, input: SpaceFsCompleteUploadInput): Promise<SpaceFsCompleteUploadResponse>;
78
81
  }
79
82
  declare class SessionMessagesClient {
80
83
  private readonly transport;
@@ -117,6 +120,7 @@ declare class SessionTurnsClient {
117
120
  before?: number;
118
121
  after?: number;
119
122
  }, customFetch?: Fetch): Promise<SessionTurnWindowResponse>;
123
+ streamSnapshot(customFetch?: Fetch): Promise<SessionTurnStreamSnapshotResponse>;
120
124
  get(turnId: string, customFetch?: Fetch): Promise<SessionTurnResponse>;
121
125
  signedUrls(turnId: string, objectKeys: string[]): Promise<SessionTurnSignedUrlsResponse>;
122
126
  }
@@ -327,6 +331,7 @@ export declare class SpaceClient {
327
331
  readonly marks: SpaceMarksApi;
328
332
  constructor(id: string, transport: HttpTransport, websocketClient: WebsocketClient | null);
329
333
  get(customFetch?: Fetch): Promise<SpaceRecord>;
334
+ prompt(input: CreateSpacePromptInput): Promise<CreateSpacePromptResponse>;
330
335
  rename(name: string): Promise<{
331
336
  space: SpaceRecord;
332
337
  }>;
@@ -169,6 +169,20 @@ export class SpaceFilesApi {
169
169
  body: formData,
170
170
  });
171
171
  }
172
+ createUpload(input) {
173
+ return this.transport.request(`/api/spaces/${this.spaceId}/fs/uploads`, {
174
+ method: "POST",
175
+ headers: { "Content-Type": "application/json" },
176
+ body: JSON.stringify(input),
177
+ });
178
+ }
179
+ completeUpload(uploadId, input) {
180
+ return this.transport.request(`/api/spaces/${this.spaceId}/fs/uploads/${uploadId}/complete`, {
181
+ method: "POST",
182
+ headers: { "Content-Type": "application/json" },
183
+ body: JSON.stringify(input),
184
+ });
185
+ }
172
186
  }
173
187
  class SessionMessagesClient {
174
188
  transport;
@@ -229,6 +243,7 @@ class SessionMessagesClient {
229
243
  content: input.content,
230
244
  model: input.model,
231
245
  provider: input.provider,
246
+ clientMessageId: input.clientMessageId,
232
247
  }),
233
248
  });
234
249
  }
@@ -273,6 +288,9 @@ class SessionTurnsClient {
273
288
  const query = params.toString();
274
289
  return this.transport.request(`/api/sessions/${this.sessionId}/turns/window${query ? `?${query}` : ""}`, { fetch: customFetch });
275
290
  }
291
+ streamSnapshot(customFetch) {
292
+ return this.transport.request(`/api/sessions/${this.sessionId}/turns/stream-snapshot`, { fetch: customFetch });
293
+ }
276
294
  get(turnId, customFetch) {
277
295
  return this.transport.request(`/api/sessions/${this.sessionId}/turns/${turnId}`, { fetch: customFetch });
278
296
  }
@@ -688,6 +706,13 @@ export class SpaceClient {
688
706
  fetch: customFetch,
689
707
  });
690
708
  }
709
+ prompt(input) {
710
+ return this.transport.request(`/api/spaces/${this.id}/prompt`, {
711
+ method: "POST",
712
+ headers: { "Content-Type": "application/json" },
713
+ body: JSON.stringify(input),
714
+ });
715
+ }
691
716
  rename(name) {
692
717
  return this.transport.request(`/api/spaces/${this.id}`, {
693
718
  method: "PATCH",
@@ -1,20 +1,13 @@
1
1
  import type { HttpTransport } from "../transport.js";
2
- import type { CreateScheduledTaskInput, TaskRunRecord } from "../types.js";
2
+ import type { TaskRunDetailResponse, TaskRunRecord } from "../types.js";
3
3
  export declare class TasksApi {
4
4
  private readonly transport;
5
5
  constructor(transport: HttpTransport);
6
- get(taskRunId: string): Promise<{
7
- run: TaskRunRecord;
8
- }>;
6
+ get(taskRunId: string): Promise<TaskRunDetailResponse>;
9
7
  list(filters?: {
10
8
  cronJobId?: string;
11
9
  spaceId?: string;
12
10
  }): Promise<{
13
11
  runs: TaskRunRecord[];
14
12
  }>;
15
- createScheduled(data: CreateScheduledTaskInput): Promise<{
16
- ok: true;
17
- taskRunId: string;
18
- scheduledAt: string;
19
- }>;
20
13
  }
@@ -15,11 +15,4 @@ export class TasksApi {
15
15
  const query = params.toString();
16
16
  return this.transport.request(`/api/tasks${query ? `?${query}` : ""}`);
17
17
  }
18
- createScheduled(data) {
19
- return this.transport.request("/api/tasks", {
20
- method: "POST",
21
- headers: { "Content-Type": "application/json" },
22
- body: JSON.stringify(data),
23
- });
24
- }
25
18
  }
@@ -1,12 +1,18 @@
1
1
  import { type HttpTransport, type Fetch } from "../transport.js";
2
- import type { UserRulesResponse, UserSshKey } from "../types.js";
2
+ import type { MeResponse, UserProfile, UserRulesResponse, UserSshKey } from "../types.js";
3
3
  export declare class UserApi {
4
4
  private readonly transport;
5
5
  private readonly transportBaseUrl;
6
6
  private readonly setStoredAuthToken?;
7
7
  private readonly clearStoredAuthToken?;
8
8
  constructor(transport: HttpTransport, transportBaseUrl: string, setStoredAuthToken?: ((token: string) => void) | undefined, clearStoredAuthToken?: (() => void) | undefined);
9
- getMe(customFetch?: Fetch): Promise<unknown>;
9
+ getMe(customFetch?: Fetch): Promise<MeResponse>;
10
+ updateProfile(input: {
11
+ displayName?: string;
12
+ avatarUrl?: string | null;
13
+ }): Promise<{
14
+ profile: UserProfile;
15
+ }>;
10
16
  getRules(customFetch?: Fetch): Promise<UserRulesResponse>;
11
17
  setAuthToken(token: string): Promise<any>;
12
18
  clearAuthToken(): Promise<null>;
package/dist/apis/user.js CHANGED
@@ -13,6 +13,13 @@ export class UserApi {
13
13
  getMe(customFetch) {
14
14
  return this.transport.request("/api/me", { fetch: customFetch });
15
15
  }
16
+ updateProfile(input) {
17
+ return this.transport.request("/api/me/profile", {
18
+ method: "PATCH",
19
+ headers: { "Content-Type": "application/json" },
20
+ body: JSON.stringify(input),
21
+ });
22
+ }
16
23
  getRules(customFetch) {
17
24
  return this.transport.request("/api/me/rules", {
18
25
  method: "GET",
@@ -28,6 +28,13 @@ export type SessionPatchApplyResult = {
28
28
  reason: "duplicate" | "version_mismatch" | "invalid";
29
29
  state: SessionPatchState;
30
30
  };
31
+ export type SessionPatchSnapshotInput = SessionPatchKeyInput & {
32
+ turnId?: string | null;
33
+ seq: number;
34
+ contentBlocks: ContentBlock[];
35
+ anchorUserMessageId?: string | null;
36
+ appendPath?: string | null;
37
+ };
31
38
  type SessionPatchKeyInput = {
32
39
  spaceId?: string | null;
33
40
  sessionId: string;
@@ -44,6 +51,7 @@ export declare class SessionPatchReducer {
44
51
  complete(input: SessionPatchKeyInput): SessionPatchState;
45
52
  fail(input: SessionPatchKeyInput): SessionPatchState;
46
53
  reset(input: SessionPatchKeyInput): void;
54
+ applySnapshot(input: SessionPatchSnapshotInput): SessionPatchApplyResult;
47
55
  resetAll(): void;
48
56
  applyEvent(event: SessionTurnPatchEvent): SessionPatchApplyResult;
49
57
  applyPatch(input: SessionPatchApplyInput): SessionPatchApplyResult;
@@ -1,7 +1,6 @@
1
- const blockTextPathPattern = /^\/message\/content\/blocks\/(\d+)\/(text|thinking)$/;
1
+ const blockSubPathPattern = /^\/message\/content\/blocks\/(\d+)\/(.+)$/;
2
2
  const blockPathPattern = /^\/message\/content\/blocks\/(\d+)$/;
3
3
  const blockMetaPathPattern = /^\/message\/content\/blocks\/(\d+)\/_meta$/;
4
- const blockSignaturePathPattern = /^\/message\/content\/blocks\/(\d+)\/signature$/;
5
4
  const createIdleState = (input) => ({
6
5
  spaceId: input.spaceId ?? null,
7
6
  sessionId: input.sessionId,
@@ -38,6 +37,14 @@ function sortBlocksByStreamIndex(blocks) {
38
37
  function isContentBlock(value) {
39
38
  return Boolean(value && typeof value === "object" && "type" in value);
40
39
  }
40
+ function isPlainObject(value) {
41
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
42
+ }
43
+ function decodePointerSegments(encoded) {
44
+ if (!encoded)
45
+ return [];
46
+ return encoded.split("/").map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
47
+ }
41
48
  function ensureTextLikeBlock(blocks, streamIndex, field) {
42
49
  const existingIndex = findBlockByStreamIndex(blocks, streamIndex);
43
50
  const existing = existingIndex >= 0 ? blocks[existingIndex] : undefined;
@@ -62,21 +69,103 @@ function ensureTextLikeBlock(blocks, streamIndex, field) {
62
69
  blocks.push(block);
63
70
  return block;
64
71
  }
65
- function appendTextLikeValue(blocks, path, value) {
66
- const match = path.match(blockTextPathPattern);
67
- if (!match || typeof value !== "string")
72
+ function getOrCreateBlockForSubpath(blocks, streamIndex, firstSegment) {
73
+ const idx = findBlockByStreamIndex(blocks, streamIndex);
74
+ if (idx >= 0)
75
+ return blocks[idx] ?? null;
76
+ if (firstSegment === "text") {
77
+ return ensureTextLikeBlock(blocks, streamIndex, "text");
78
+ }
79
+ if (firstSegment === "thinking") {
80
+ return ensureTextLikeBlock(blocks, streamIndex, "thinking");
81
+ }
82
+ return null;
83
+ }
84
+ function setDeepOnContentBlock(root, segments, value) {
85
+ if (segments.length === 0)
68
86
  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;
87
+ let cur = root;
88
+ for (let i = 0; i < segments.length - 1; i++) {
89
+ const k = segments[i];
90
+ if (k === undefined)
91
+ return false;
92
+ if (!isPlainObject(cur))
93
+ return false;
94
+ const next = cur[k];
95
+ if (next === undefined)
96
+ return false;
97
+ cur = next;
74
98
  }
75
- if (field === "thinking" && block.type === "thinking") {
76
- block.thinking += value;
99
+ const last = segments[segments.length - 1];
100
+ if (last === undefined)
101
+ return false;
102
+ if (!isPlainObject(cur))
103
+ return false;
104
+ const toAssign = value !== null && typeof value === "object"
105
+ ? structuredClone(value)
106
+ : value;
107
+ cur[last] = toAssign;
108
+ return true;
109
+ }
110
+ function appendDeepOnContentBlock(root, segments, suffix) {
111
+ if (segments.length === 0)
112
+ return false;
113
+ let cur = root;
114
+ for (let i = 0; i < segments.length - 1; i++) {
115
+ const k = segments[i];
116
+ if (k === undefined)
117
+ return false;
118
+ if (!isPlainObject(cur))
119
+ return false;
120
+ const next = cur[k];
121
+ if (next === undefined)
122
+ return false;
123
+ cur = next;
77
124
  }
125
+ const last = segments[segments.length - 1];
126
+ if (last === undefined)
127
+ return false;
128
+ if (!isPlainObject(cur))
129
+ return false;
130
+ const parent = cur;
131
+ const leaf = parent[last];
132
+ if (typeof leaf !== "string")
133
+ return false;
134
+ parent[last] = leaf + suffix;
78
135
  return true;
79
136
  }
137
+ function resolveBlockForSubpath(blocks, streamIndex, firstSegment) {
138
+ const idx = findBlockByStreamIndex(blocks, streamIndex);
139
+ if (idx >= 0)
140
+ return blocks[idx] ?? null;
141
+ return getOrCreateBlockForSubpath(blocks, streamIndex, firstSegment);
142
+ }
143
+ function applyReplaceAtBlockSubpath(blocks, streamIndex, encodedTail, value) {
144
+ const segs = decodePointerSegments(encodedTail);
145
+ if (segs.length === 0)
146
+ return false;
147
+ const block = resolveBlockForSubpath(blocks, streamIndex, segs[0] ?? "");
148
+ if (!block)
149
+ return false;
150
+ return setDeepOnContentBlock(block, segs, value);
151
+ }
152
+ function applyAppendAtBlockSubpath(blocks, streamIndex, encodedTail, suffix) {
153
+ if (typeof suffix !== "string")
154
+ return false;
155
+ const segs = decodePointerSegments(encodedTail);
156
+ if (segs.length === 0)
157
+ return false;
158
+ const block = resolveBlockForSubpath(blocks, streamIndex, segs[0] ?? "");
159
+ if (!block)
160
+ return false;
161
+ return appendDeepOnContentBlock(block, segs, suffix);
162
+ }
163
+ function appendPatchStreamValue(blocks, path, value) {
164
+ const m = path.match(blockSubPathPattern);
165
+ if (!m)
166
+ return false;
167
+ return applyAppendAtBlockSubpath(blocks, Number(m[1]), m[2] ?? "", value);
168
+ }
80
169
  function applyPatchOpsToBlocks(current, ops, initialAppendPath) {
81
170
  const next = current.map(cloneBlock);
82
171
  let anchorUserMessageId;
@@ -84,7 +173,7 @@ function applyPatchOpsToBlocks(current, ops, initialAppendPath) {
84
173
  let failed = false;
85
174
  for (const op of ops) {
86
175
  if (!op.o && !op.p) {
87
- if (!appendPath || !appendTextLikeValue(next, appendPath, op.v)) {
176
+ if (!appendPath || !appendPatchStreamValue(next, appendPath, op.v)) {
88
177
  failed = true;
89
178
  break;
90
179
  }
@@ -98,7 +187,7 @@ function applyPatchOpsToBlocks(current, ops, initialAppendPath) {
98
187
  continue;
99
188
  }
100
189
  if (op.o === "append") {
101
- if (!appendTextLikeValue(next, op.p, op.v)) {
190
+ if (typeof op.p !== "string" || !appendPatchStreamValue(next, op.p, op.v)) {
102
191
  failed = true;
103
192
  break;
104
193
  }
@@ -118,15 +207,15 @@ function applyPatchOpsToBlocks(current, ops, initialAppendPath) {
118
207
  continue;
119
208
  }
120
209
  if (op.o === "replace") {
121
- const match = op.p.match(blockSignaturePathPattern);
122
- if (match) {
123
- if (typeof op.v !== "string")
210
+ const sub = op.p.match(blockSubPathPattern);
211
+ if (sub?.[2] && typeof op.p === "string") {
212
+ const streamIndex = Number(sub[1]);
213
+ const encodedTail = sub[2];
214
+ if (applyReplaceAtBlockSubpath(next, streamIndex, encodedTail, op.v)) {
124
215
  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;
216
+ }
217
+ failed = true;
218
+ break;
130
219
  }
131
220
  }
132
221
  if (op.o === "replace" || op.o === "add") {
@@ -219,6 +308,28 @@ export class SessionPatchReducer {
219
308
  reset(input) {
220
309
  this.states.delete(this.key(input));
221
310
  }
311
+ applySnapshot(input) {
312
+ const current = this.get(input);
313
+ const inputTurnId = input.turnId ?? null;
314
+ const currentTurnId = current.turnId;
315
+ const isDifferentKnownTurn = Boolean(currentTurnId && inputTurnId && currentTurnId !== inputTurnId);
316
+ if (!isDifferentKnownTurn && input.seq < current.patchSeq) {
317
+ return { applied: false, reason: "duplicate", state: current };
318
+ }
319
+ const state = {
320
+ ...current,
321
+ spaceId: input.spaceId ?? current.spaceId ?? null,
322
+ sessionId: input.sessionId,
323
+ status: "streaming",
324
+ contentBlocks: sortBlocksByStreamIndex(input.contentBlocks.map(cloneBlock)),
325
+ anchorUserMessageId: input.anchorUserMessageId ?? current.anchorUserMessageId ?? null,
326
+ patchSeq: input.seq,
327
+ turnId: inputTurnId ?? current.turnId ?? null,
328
+ appendPath: input.appendPath ?? null,
329
+ };
330
+ this.states.set(this.key(input), state);
331
+ return { applied: true, state };
332
+ }
222
333
  resetAll() {
223
334
  this.states.clear();
224
335
  }
package/dist/types.d.ts CHANGED
@@ -6,6 +6,18 @@ export type { ChannelConfig, DiscordChannelConfig, } from "@neta-art/cohub-proto
6
6
  export type ApiError = {
7
7
  message: string;
8
8
  };
9
+ export type UserProfile = {
10
+ userUuid: string;
11
+ logtoUserId?: string;
12
+ displayName: string;
13
+ avatarUrl: string | null;
14
+ syncedAt?: string;
15
+ };
16
+ export type MeResponse = {
17
+ uuid: string;
18
+ profile: UserProfile;
19
+ email: string | null;
20
+ };
9
21
  export type UserRulesResponse = {
10
22
  content: string;
11
23
  updatedAt: string | null;
@@ -55,13 +67,55 @@ export type SpaceFsUploadEntry = {
55
67
  };
56
68
  export type SpaceFsUploadError = {
57
69
  name: string;
58
- code: "file_too_large" | "name_invalid" | "write_failed";
70
+ code: "file_too_large" | "name_invalid" | "path_invalid" | "write_failed" | "object_missing";
59
71
  message: string;
60
72
  };
61
73
  export type SpaceFsUploadResponse = {
62
74
  uploaded: SpaceFsUploadEntry[];
63
75
  errors: SpaceFsUploadError[];
64
76
  };
77
+ export type SpaceFsUploadPlanEntryInput = {
78
+ id: string;
79
+ name: string;
80
+ relativePath: string;
81
+ size: number;
82
+ mimeType?: string | null;
83
+ lastModified?: number;
84
+ };
85
+ export type SpaceFsCreateUploadInput = {
86
+ targetDir?: string;
87
+ entries: SpaceFsUploadPlanEntryInput[];
88
+ };
89
+ export type SpaceFsUploadPlanEntry = {
90
+ id: string;
91
+ objectKey: string;
92
+ uploadUrl: string;
93
+ headers?: Record<string, string>;
94
+ };
95
+ export type SpaceFsCreateUploadResponse = {
96
+ uploadId: string;
97
+ expiresAt: string;
98
+ entries: SpaceFsUploadPlanEntry[];
99
+ };
100
+ export type SpaceFsCompleteUploadInput = {
101
+ entries: Array<{
102
+ id: string;
103
+ etag?: string | null;
104
+ }>;
105
+ };
106
+ export type SpaceFsCompleteUploadResponse = {
107
+ ok: true;
108
+ taskRunId: string;
109
+ };
110
+ export type SpaceFsUploadProgress = {
111
+ phase: "queued" | "importing" | "done" | "failed";
112
+ totalFiles: number;
113
+ importedFiles: number;
114
+ totalBytes: number;
115
+ importedBytes: number;
116
+ currentPath?: string;
117
+ errors: SpaceFsUploadError[];
118
+ };
65
119
  export type SessionBindingRecord = ProtocolSessionBindingRecord;
66
120
  export type SessionRecord = ProtocolSessionRecord & {
67
121
  bindings?: SessionBindingRecord[];
@@ -154,6 +208,28 @@ export type SessionTurnResponse = {
154
208
  export type SessionTurnSignedUrlsResponse = {
155
209
  urls: Record<string, string>;
156
210
  };
211
+ export type SessionTurnStreamSnapshotResponse = {
212
+ snapshot: {
213
+ version: 2;
214
+ spaceId: string;
215
+ sessionId: string;
216
+ turnId: string | null;
217
+ anchorUserMessageId: string | null;
218
+ seq: number;
219
+ current: {
220
+ messageId: string | null;
221
+ messageOrdinal: number | null;
222
+ content: ContentBlock[];
223
+ appendPath: string | null;
224
+ };
225
+ intermediateMessages: Array<{
226
+ messageId: string | null;
227
+ messageOrdinal: number | null;
228
+ content: ContentBlock[];
229
+ }>;
230
+ updatedAt: number;
231
+ } | null;
232
+ };
157
233
  export type ModelCatalogEntry = {
158
234
  provider: string;
159
235
  id: string;
@@ -205,9 +281,51 @@ export type UserSshKey = {
205
281
  giteaKeyId: number;
206
282
  createdAt: string;
207
283
  };
284
+ export type CreateSpacePromptInput = {
285
+ sessionId?: string | null;
286
+ title?: string | null;
287
+ content: ContentBlock[];
288
+ model?: string | null;
289
+ provider?: string | null;
290
+ clientMessageId?: string | null;
291
+ schedule?: {
292
+ mode?: "immediate";
293
+ } | {
294
+ mode: "delay";
295
+ delayMs: number;
296
+ } | {
297
+ mode: "at";
298
+ sendAt: string;
299
+ } | {
300
+ mode: "repeat";
301
+ cronExpression: string;
302
+ timezone: string;
303
+ };
304
+ };
305
+ export type CreateSpacePromptResponse = {
306
+ ok: true;
307
+ mode: "immediate";
308
+ sessionId: string;
309
+ userMessageId: string;
310
+ turnId: string;
311
+ } | {
312
+ ok: true;
313
+ mode: "delay" | "at";
314
+ taskRunId: string;
315
+ scheduledAt: string;
316
+ sessionId: string | null;
317
+ } | {
318
+ ok: true;
319
+ mode: "repeat";
320
+ cronJobId: string;
321
+ nextRunAt: string;
322
+ timezone: string;
323
+ sessionId: string | null;
324
+ };
208
325
  export type CronJobRecord = {
209
326
  id: string;
210
327
  userUuid: string;
328
+ userProfile?: UserProfile;
211
329
  title: string;
212
330
  taskType: string;
213
331
  payload: Record<string, unknown>;
@@ -220,6 +338,10 @@ export type CronJobRecord = {
220
338
  createdAt: string;
221
339
  updatedAt: string;
222
340
  };
341
+ export type TaskRunDetailResponse = {
342
+ run: TaskRunRecord;
343
+ progress: unknown;
344
+ };
223
345
  export type TaskRunRecord = {
224
346
  id: string;
225
347
  jobId: string;
@@ -252,26 +374,11 @@ export type CheckpointRecord = {
252
374
  export type SpaceCheckpointDetailResponse = {
253
375
  checkpoint: CheckpointRecord;
254
376
  };
255
- export type CreateCronJobInput = {
256
- title: string;
257
- taskType: string;
258
- payload: Record<string, unknown>;
259
- cronExpression: string;
260
- timezone?: string;
261
- spaceId?: string;
262
- sessionId?: string;
263
- };
264
- export type CreateScheduledTaskInput = {
265
- taskType: string;
266
- payload: Record<string, unknown>;
267
- scheduleAt: string;
268
- spaceId?: string;
269
- sessionId?: string;
270
- };
271
377
  export type SpaceRole = "host" | "builder" | "guest";
272
378
  export type SpaceMember = {
273
379
  userId: string;
274
380
  role: SpaceRole;
381
+ profile: UserProfile;
275
382
  createdAt: string;
276
383
  updatedAt: string;
277
384
  };
package/dist/websocket.js CHANGED
@@ -1,4 +1,4 @@
1
- import { realtimeCompactFrameSchema, realtimeEnvelopeSchema, WS_COMPACT_STREAM_CAPABILITY, } from "@neta-art/cohub-protocol/realtime";
1
+ import { getSessionTurnPatchStreamKey, realtimeCompactFrameSchema, realtimeEnvelopeSchema, WS_COMPACT_STREAM_CAPABILITY, } from "@neta-art/cohub-protocol/realtime";
2
2
  import { resolveWebsocketUrl } from "./environment.js";
3
3
  const createEventMap = () => ({
4
4
  connecting: new Set(),
@@ -446,13 +446,7 @@ export class WebsocketClient {
446
446
  if (typeof realtimeMeta?.sid === "string" && realtimeMeta.sid.trim()) {
447
447
  return realtimeMeta.sid;
448
448
  }
449
- if (typeof payload.turnId === "string" && payload.turnId.trim())
450
- return payload.turnId;
451
- if (typeof payload.messageId === "string" && payload.messageId.trim())
452
- return payload.messageId;
453
- return typeof envelope.sessionId === "string" && envelope.sessionId.trim()
454
- ? envelope.sessionId
455
- : null;
449
+ return getSessionTurnPatchStreamKey(payload, { includeSessionFallback: true });
456
450
  }
457
451
  handlePatchEnvelope(envelope) {
458
452
  const payload = envelope.payload;
@@ -543,6 +537,7 @@ export class WebsocketClient {
543
537
  turnId: context.turnId,
544
538
  messageId: context.messageId,
545
539
  messageOrdinal: context.messageOrdinal,
540
+ sourceMessageId: context.messageId,
546
541
  anchorUserMessageId: context.anchorUserMessageId,
547
542
  seq: frame.s,
548
543
  baseSeq: frame.b,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "Cohub SDK for spaces, sessions, checkpoints, and realtime agent collaboration.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -44,7 +44,7 @@
44
44
  "README.md"
45
45
  ],
46
46
  "dependencies": {
47
- "@neta-art/cohub-protocol": "1.2.3"
47
+ "@neta-art/cohub-protocol": "1.3.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "typescript": "^6.0.3"