@neta-art/cohub 1.6.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/apis/prompts.d.ts +3 -4
- package/dist/apis/prompts.js +7 -13
- package/dist/apis/search.d.ts +10 -0
- package/dist/apis/search.js +14 -0
- package/dist/apis/spaces.d.ts +19 -1
- package/dist/apis/spaces.js +15 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +4 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/session-generation-stream.d.ts +114 -0
- package/dist/session-generation-stream.js +514 -0
- package/dist/session-patch-reducer.js +19 -1
- package/dist/types.d.ts +28 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -120,6 +120,27 @@ space.on("message.persisted", (event) => {
|
|
|
120
120
|
});
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
For product UIs, prefer the normalized generation stream. It folds
|
|
124
|
+
`session.turn.snapshot`, `session.turn.patch`, legacy `session.turn.progress`,
|
|
125
|
+
persisted assistant messages, finalized turns, and turn errors into stable
|
|
126
|
+
semantic events.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const stop = session.subscribeGeneration({
|
|
130
|
+
state(event) {
|
|
131
|
+
console.log(event.source, event.state.contentBlocks);
|
|
132
|
+
},
|
|
133
|
+
commit(event) {
|
|
134
|
+
if (event.commit.kind === "final") {
|
|
135
|
+
console.log("assistant final", event.commit.message);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
outOfSync() {
|
|
139
|
+
// Fetch `session.turns.streamSnapshot()` or reconcile persisted turns.
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
123
144
|
Supported business event names:
|
|
124
145
|
|
|
125
146
|
- `turn.patch`
|
package/dist/apis/prompts.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type { Fetch } from "../transport.js";
|
|
1
|
+
import type { HttpTransport, Fetch } from "../transport.js";
|
|
2
2
|
import type { PromptTemplateCatalogResponse } from "../types.js";
|
|
3
3
|
export declare class PromptsApi {
|
|
4
|
-
private readonly
|
|
5
|
-
|
|
6
|
-
constructor(fetcher: Fetch, baseUrl: string);
|
|
4
|
+
private readonly transport;
|
|
5
|
+
constructor(transport: HttpTransport);
|
|
7
6
|
list(options?: {
|
|
8
7
|
spaceId?: string;
|
|
9
8
|
}, customFetch?: Fetch): Promise<PromptTemplateCatalogResponse>;
|
package/dist/apis/prompts.js
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
export class PromptsApi {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
this.fetcher = fetcher;
|
|
6
|
-
this.baseUrl = baseUrl;
|
|
2
|
+
transport;
|
|
3
|
+
constructor(transport) {
|
|
4
|
+
this.transport = transport;
|
|
7
5
|
}
|
|
8
6
|
async list(options, customFetch) {
|
|
9
|
-
const fetchImpl = customFetch ?? this.fetcher;
|
|
10
7
|
const params = new URLSearchParams();
|
|
11
8
|
if (options?.spaceId)
|
|
12
9
|
params.set("spaceId", options.spaceId);
|
|
13
10
|
const query = params.toString();
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
throw new Error(`Failed to fetch prompt templates: ${response.status} ${response.statusText}`);
|
|
19
|
-
}
|
|
20
|
-
return response.json();
|
|
11
|
+
const path = query ? `/api/prompts?${query}` : "/api/prompts";
|
|
12
|
+
return this.transport.request(path, {
|
|
13
|
+
fetch: customFetch,
|
|
14
|
+
});
|
|
21
15
|
}
|
|
22
16
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Fetch, HttpTransport } from "../transport.js";
|
|
2
|
+
import type { GlobalSearchResponse } from "../types.js";
|
|
3
|
+
export declare class SearchApi {
|
|
4
|
+
private readonly transport;
|
|
5
|
+
constructor(transport: HttpTransport);
|
|
6
|
+
query(input: {
|
|
7
|
+
q: string;
|
|
8
|
+
limit?: number;
|
|
9
|
+
}, customFetch?: Fetch): Promise<GlobalSearchResponse>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class SearchApi {
|
|
2
|
+
transport;
|
|
3
|
+
constructor(transport) {
|
|
4
|
+
this.transport = transport;
|
|
5
|
+
}
|
|
6
|
+
query(input, customFetch) {
|
|
7
|
+
const params = new URLSearchParams({ q: input.q });
|
|
8
|
+
if (input.limit !== undefined)
|
|
9
|
+
params.set("limit", String(input.limit));
|
|
10
|
+
return this.transport.request(`/api/search?${params.toString()}`, {
|
|
11
|
+
fetch: customFetch,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
package/dist/apis/spaces.d.ts
CHANGED
|
@@ -2,13 +2,21 @@ 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
|
|
5
|
+
import { SessionGenerationStreamClient, type GenerationStreamSubscriptionHandlers } from "../session-generation-stream.js";
|
|
6
|
+
import type { CheckpointRecord, ContentBlock, SessionForkRecord, 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
7
|
import { SpaceInvitationsApi } from "./invitations.js";
|
|
7
8
|
export type SessionSubscriptionHandlers = {
|
|
8
9
|
patch?: (event: WebsocketEventPayload) => void;
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated Use `session.subscribeGeneration({ state })` for normalized
|
|
12
|
+
* snapshot/patch/progress generation state.
|
|
13
|
+
*/
|
|
9
14
|
patchState?: (result: SessionPatchApplyResult) => void;
|
|
15
|
+
/** @deprecated Use `session.subscribeGeneration({ state })`. */
|
|
10
16
|
snapshot?: (event: WebsocketEventPayload) => void;
|
|
17
|
+
/** @deprecated Legacy progress events are normalized by `subscribeGeneration`. */
|
|
11
18
|
progress?: (event: WebsocketEventPayload) => void;
|
|
19
|
+
/** @deprecated Use `session.subscribeGeneration({ commit, finalized })`. */
|
|
12
20
|
final?: (event: WebsocketEventPayload) => void;
|
|
13
21
|
turnUpdated?: (event: WebsocketEventPayload) => void;
|
|
14
22
|
turnFinalized?: (event: WebsocketEventPayload) => void;
|
|
@@ -140,6 +148,7 @@ export declare class SessionClient {
|
|
|
140
148
|
readonly messages: SessionMessagesClient;
|
|
141
149
|
readonly turns: SessionTurnsClient;
|
|
142
150
|
readonly realtime: SessionRealtimeClient;
|
|
151
|
+
readonly generation: SessionGenerationStreamClient;
|
|
143
152
|
constructor(spaceId: string, id: string, transport: HttpTransport, websocketClient: WebsocketClient | null);
|
|
144
153
|
get(customFetch?: Fetch): Promise<{
|
|
145
154
|
space: SpaceRecord;
|
|
@@ -153,7 +162,16 @@ export declare class SessionClient {
|
|
|
153
162
|
} | Fetch, customFetch?: Fetch): Promise<{
|
|
154
163
|
ok: true;
|
|
155
164
|
}>;
|
|
165
|
+
turn(turnId: string): {
|
|
166
|
+
fork: (input?: {
|
|
167
|
+
title?: string | null;
|
|
168
|
+
}) => Promise<{
|
|
169
|
+
session: SessionRecord;
|
|
170
|
+
fork: SessionForkRecord;
|
|
171
|
+
}>;
|
|
172
|
+
};
|
|
156
173
|
subscribe(handlers: SessionSubscriptionHandlers): () => void;
|
|
174
|
+
subscribeGeneration(handlers: GenerationStreamSubscriptionHandlers): () => void;
|
|
157
175
|
on(type: SessionEventName, handler: (event: WebsocketEventPayload) => void): () => void;
|
|
158
176
|
}
|
|
159
177
|
export declare class SpaceSessionsApi {
|
package/dist/apis/spaces.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ensureRealtimeConnected } from "../realtime.js";
|
|
2
2
|
import { SessionPatchReducer, } from "../session-patch-reducer.js";
|
|
3
|
+
import { SessionGenerationStreamClient, } from "../session-generation-stream.js";
|
|
3
4
|
import { SpaceInvitationsApi } from "./invitations.js";
|
|
4
5
|
const DEFAULT_DEDUP_WINDOW_MS = 2000;
|
|
5
6
|
const getFilenameFromContentDisposition = (value) => {
|
|
@@ -405,6 +406,7 @@ export class SessionClient {
|
|
|
405
406
|
messages;
|
|
406
407
|
turns;
|
|
407
408
|
realtime;
|
|
409
|
+
generation;
|
|
408
410
|
constructor(spaceId, id, transport, websocketClient) {
|
|
409
411
|
this.spaceId = spaceId;
|
|
410
412
|
this.id = id;
|
|
@@ -412,6 +414,7 @@ export class SessionClient {
|
|
|
412
414
|
this.messages = new SessionMessagesClient(transport, id);
|
|
413
415
|
this.turns = new SessionTurnsClient(transport, id);
|
|
414
416
|
this.realtime = new SessionRealtimeClient(websocketClient, spaceId, id);
|
|
417
|
+
this.generation = new SessionGenerationStreamClient(websocketClient, spaceId, id);
|
|
415
418
|
}
|
|
416
419
|
get(customFetch) {
|
|
417
420
|
return this.transport.request(`/api/sessions/${this.id}`, {
|
|
@@ -435,9 +438,21 @@ export class SessionClient {
|
|
|
435
438
|
fetch,
|
|
436
439
|
});
|
|
437
440
|
}
|
|
441
|
+
turn(turnId) {
|
|
442
|
+
return {
|
|
443
|
+
fork: (input = {}) => this.transport.request(`/api/sessions/${this.id}/turns/${turnId}/fork`, {
|
|
444
|
+
method: "POST",
|
|
445
|
+
headers: { "Content-Type": "application/json" },
|
|
446
|
+
body: JSON.stringify(input),
|
|
447
|
+
}),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
438
450
|
subscribe(handlers) {
|
|
439
451
|
return this.realtime.subscribe(handlers);
|
|
440
452
|
}
|
|
453
|
+
subscribeGeneration(handlers) {
|
|
454
|
+
return this.generation.subscribe(handlers);
|
|
455
|
+
}
|
|
441
456
|
on(type, handler) {
|
|
442
457
|
return this.realtime.on(type, handler);
|
|
443
458
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { ExploreApi } from "./apis/explore.js";
|
|
|
4
4
|
import { ModelsApi } from "./apis/models.js";
|
|
5
5
|
import { PromptsApi } from "./apis/prompts.js";
|
|
6
6
|
import { SessionAccessApi } from "./apis/session-access.js";
|
|
7
|
+
import { SearchApi } from "./apis/search.js";
|
|
7
8
|
import { SpaceClient, SpacesApi, type WebSocketConnectionState } from "./apis/spaces.js";
|
|
8
9
|
import { TasksApi } from "./apis/tasks.js";
|
|
9
10
|
import { UserApi } from "./apis/user.js";
|
|
@@ -16,6 +17,7 @@ export declare class CohubClient {
|
|
|
16
17
|
readonly models: ModelsApi;
|
|
17
18
|
readonly prompts: PromptsApi;
|
|
18
19
|
readonly sessionAccess: SessionAccessApi;
|
|
20
|
+
readonly search: SearchApi;
|
|
19
21
|
readonly tasks: TasksApi;
|
|
20
22
|
readonly cronJobs: CronJobsApi;
|
|
21
23
|
readonly explore: ExploreApi;
|
package/dist/client.js
CHANGED
|
@@ -4,6 +4,7 @@ import { ExploreApi } from "./apis/explore.js";
|
|
|
4
4
|
import { ModelsApi } from "./apis/models.js";
|
|
5
5
|
import { PromptsApi } from "./apis/prompts.js";
|
|
6
6
|
import { SessionAccessApi } from "./apis/session-access.js";
|
|
7
|
+
import { SearchApi } from "./apis/search.js";
|
|
7
8
|
import { SpaceClient, SpacesApi } from "./apis/spaces.js";
|
|
8
9
|
import { TasksApi } from "./apis/tasks.js";
|
|
9
10
|
import { UserApi } from "./apis/user.js";
|
|
@@ -18,6 +19,7 @@ export class CohubClient {
|
|
|
18
19
|
models;
|
|
19
20
|
prompts;
|
|
20
21
|
sessionAccess;
|
|
22
|
+
search;
|
|
21
23
|
tasks;
|
|
22
24
|
cronJobs;
|
|
23
25
|
explore;
|
|
@@ -39,8 +41,9 @@ export class CohubClient {
|
|
|
39
41
|
this.channels = new ChannelsApi(this.transport);
|
|
40
42
|
this.user = new UserApi(this.transport, apiBaseUrl, options.setStoredAuthToken, options.clearStoredAuthToken);
|
|
41
43
|
this.models = new ModelsApi(this.transport);
|
|
42
|
-
this.prompts = new PromptsApi(
|
|
44
|
+
this.prompts = new PromptsApi(this.transport);
|
|
43
45
|
this.sessionAccess = new SessionAccessApi(this.transport);
|
|
46
|
+
this.search = new SearchApi(this.transport);
|
|
44
47
|
this.tasks = new TasksApi(this.transport);
|
|
45
48
|
this.cronJobs = new CronJobsApi(this.transport);
|
|
46
49
|
this.explore = new ExploreApi(this.transport);
|
package/dist/http.js
CHANGED
|
@@ -27,7 +27,7 @@ export class CohubHttpClient {
|
|
|
27
27
|
this.channels = new ChannelsApi(this.transport);
|
|
28
28
|
this.user = new UserApi(this.transport, apiBaseUrl, options.setStoredAuthToken, options.clearStoredAuthToken);
|
|
29
29
|
this.models = new ModelsApi(this.transport);
|
|
30
|
-
this.prompts = new PromptsApi(
|
|
30
|
+
this.prompts = new PromptsApi(this.transport);
|
|
31
31
|
this.sessionAccess = new SessionAccessApi(this.transport);
|
|
32
32
|
this.tasks = new TasksApi(this.transport);
|
|
33
33
|
this.cronJobs = new CronJobsApi(this.transport);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,11 +2,13 @@ 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 { SessionPatchReducer, createSessionPatchReducer } from "./session-patch-reducer.js";
|
|
5
|
+
export { SessionGenerationStreamClient, createSessionGenerationStreamClient, parseAssistantMessageCommit, } from "./session-generation-stream.js";
|
|
5
6
|
export { HttpError } from "./transport.js";
|
|
6
7
|
export type { RawHttpResponse } from "./transport.js";
|
|
7
8
|
export { COHUB_ENVIRONMENTS, normalizeBaseUrl, normalizeWebsocketUrl, resolveApiBaseUrl, resolveCohubEnvironment, resolveWebsocketUrl, } from "./environment.js";
|
|
8
9
|
export type { CohubClientOptions, Fetch } from "./transport.js";
|
|
9
10
|
export type { CohubEnvironment } from "./environment.js";
|
|
10
11
|
export type { SessionPatchApplyInput, SessionPatchApplyResult, SessionPatchState, SessionPatchStatus, } from "./session-patch-reducer.js";
|
|
12
|
+
export type { AssistantMessageCommit, GenerationStreamCommitEvent, GenerationStreamErrorEvent, GenerationStreamEvent, GenerationStreamFinalizedEvent, GenerationStreamIntermediateMessage, GenerationStreamOutOfSyncEvent, GenerationStreamStateEvent, GenerationStreamSubscriptionHandlers, GenerationStreamTurnUpdatedEvent, } from "./session-generation-stream.js";
|
|
11
13
|
export * from "./types.js";
|
|
12
14
|
export type { SessionEventName, SessionSubscriptionHandlers, SpaceChannelBindingRecord, SpaceEventName, WebSocketConnectionState } from "./apis/spaces.js";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ 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 { SessionPatchReducer, createSessionPatchReducer } from "./session-patch-reducer.js";
|
|
5
|
+
export { SessionGenerationStreamClient, createSessionGenerationStreamClient, parseAssistantMessageCommit, } from "./session-generation-stream.js";
|
|
5
6
|
export { HttpError } from "./transport.js";
|
|
6
7
|
export { COHUB_ENVIRONMENTS, normalizeBaseUrl, normalizeWebsocketUrl, resolveApiBaseUrl, resolveCohubEnvironment, resolveWebsocketUrl, } from "./environment.js";
|
|
7
8
|
export * from "./types.js";
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { ContentBlock, Usage } from "@neta-art/cohub-protocol/core";
|
|
2
|
+
import type { MessageRecord, SessionTurnRecord } from "@neta-art/cohub-protocol/model";
|
|
3
|
+
import { type SessionPatchState } from "./session-patch-reducer.js";
|
|
4
|
+
import type { WebsocketClient, WebsocketEventPayload } from "./websocket.js";
|
|
5
|
+
export type AssistantMessageCommit = {
|
|
6
|
+
kind: "intermediate";
|
|
7
|
+
message: MessageRecord;
|
|
8
|
+
isFinal: false;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "final";
|
|
11
|
+
message: MessageRecord;
|
|
12
|
+
isFinal: true;
|
|
13
|
+
} | {
|
|
14
|
+
kind: "error";
|
|
15
|
+
message: MessageRecord;
|
|
16
|
+
isFinal: true;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "ignored";
|
|
19
|
+
message: MessageRecord;
|
|
20
|
+
isFinal: false;
|
|
21
|
+
};
|
|
22
|
+
export type GenerationStreamIntermediateMessage = {
|
|
23
|
+
id?: string;
|
|
24
|
+
sessionId?: string;
|
|
25
|
+
role?: "user" | "assistant" | "system";
|
|
26
|
+
messageId: string | null;
|
|
27
|
+
messageOrdinal: number | null;
|
|
28
|
+
content: ContentBlock[];
|
|
29
|
+
text?: string | null;
|
|
30
|
+
provider?: string | null;
|
|
31
|
+
model?: string | null;
|
|
32
|
+
stopReason?: string | null;
|
|
33
|
+
errorMessage?: string | null;
|
|
34
|
+
usage?: Usage | null;
|
|
35
|
+
toolCallsObjectKey?: string | null;
|
|
36
|
+
meta?: Record<string, unknown> | null;
|
|
37
|
+
createdAt?: string;
|
|
38
|
+
};
|
|
39
|
+
export type GenerationStreamStateEvent = {
|
|
40
|
+
type: "state";
|
|
41
|
+
source: "snapshot" | "patch" | "progress";
|
|
42
|
+
state: SessionPatchState;
|
|
43
|
+
messageId: string | null;
|
|
44
|
+
messageOrdinal: number | null;
|
|
45
|
+
intermediateMessages: GenerationStreamIntermediateMessage[];
|
|
46
|
+
rawEvent: WebsocketEventPayload;
|
|
47
|
+
};
|
|
48
|
+
export type GenerationStreamCommitEvent = {
|
|
49
|
+
type: "commit";
|
|
50
|
+
commit: AssistantMessageCommit;
|
|
51
|
+
rawEvent: WebsocketEventPayload;
|
|
52
|
+
};
|
|
53
|
+
export type GenerationStreamFinalizedEvent = {
|
|
54
|
+
type: "finalized";
|
|
55
|
+
turn: SessionTurnRecord;
|
|
56
|
+
rawEvent: WebsocketEventPayload;
|
|
57
|
+
};
|
|
58
|
+
export type GenerationStreamTurnUpdatedEvent = {
|
|
59
|
+
type: "turn_updated";
|
|
60
|
+
turn: Partial<SessionTurnRecord>;
|
|
61
|
+
rawEvent: WebsocketEventPayload;
|
|
62
|
+
};
|
|
63
|
+
export type GenerationStreamErrorEvent = {
|
|
64
|
+
type: "error";
|
|
65
|
+
message: string;
|
|
66
|
+
rawEvent: WebsocketEventPayload;
|
|
67
|
+
};
|
|
68
|
+
export type GenerationStreamOutOfSyncEvent = {
|
|
69
|
+
type: "out_of_sync";
|
|
70
|
+
source: "snapshot" | "patch";
|
|
71
|
+
reason: "duplicate" | "version_mismatch" | "invalid";
|
|
72
|
+
state: SessionPatchState;
|
|
73
|
+
rawEvent: WebsocketEventPayload;
|
|
74
|
+
};
|
|
75
|
+
export type GenerationStreamEvent = GenerationStreamStateEvent | GenerationStreamCommitEvent | GenerationStreamFinalizedEvent | GenerationStreamTurnUpdatedEvent | GenerationStreamErrorEvent | GenerationStreamOutOfSyncEvent;
|
|
76
|
+
export type GenerationStreamSubscriptionHandlers = {
|
|
77
|
+
event?: (event: GenerationStreamEvent) => void;
|
|
78
|
+
state?: (event: GenerationStreamStateEvent) => void;
|
|
79
|
+
commit?: (event: GenerationStreamCommitEvent) => void;
|
|
80
|
+
finalized?: (event: GenerationStreamFinalizedEvent) => void;
|
|
81
|
+
turnUpdated?: (event: GenerationStreamTurnUpdatedEvent) => void;
|
|
82
|
+
error?: (event: GenerationStreamErrorEvent) => void;
|
|
83
|
+
outOfSync?: (event: GenerationStreamOutOfSyncEvent) => void;
|
|
84
|
+
};
|
|
85
|
+
export declare function parseAssistantMessageCommit(message: MessageRecord): AssistantMessageCommit;
|
|
86
|
+
export declare class SessionGenerationStreamClient {
|
|
87
|
+
private readonly websocketClient;
|
|
88
|
+
private readonly spaceId;
|
|
89
|
+
private readonly sessionId;
|
|
90
|
+
private readonly reducer;
|
|
91
|
+
private messageId;
|
|
92
|
+
private messageOrdinal;
|
|
93
|
+
private intermediateMessages;
|
|
94
|
+
private progressState;
|
|
95
|
+
constructor(websocketClient: WebsocketClient | null, spaceId: string, sessionId: string);
|
|
96
|
+
subscribe(handlers: GenerationStreamSubscriptionHandlers): () => void;
|
|
97
|
+
private emit;
|
|
98
|
+
private resetCurrentMessage;
|
|
99
|
+
private appendCurrentMessage;
|
|
100
|
+
private addIntermediateMessage;
|
|
101
|
+
private handleAppliedState;
|
|
102
|
+
private prepareMessageBoundary;
|
|
103
|
+
private handleSnapshot;
|
|
104
|
+
private handlePatch;
|
|
105
|
+
private handleProgress;
|
|
106
|
+
private handlePersisted;
|
|
107
|
+
private handleFinalized;
|
|
108
|
+
private handleEvent;
|
|
109
|
+
}
|
|
110
|
+
export declare function createSessionGenerationStreamClient(input: {
|
|
111
|
+
websocketClient: WebsocketClient | null;
|
|
112
|
+
spaceId: string;
|
|
113
|
+
sessionId: string;
|
|
114
|
+
}): SessionGenerationStreamClient;
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import { ensureRealtimeConnected } from "./realtime.js";
|
|
2
|
+
import { SessionPatchReducer, } from "./session-patch-reducer.js";
|
|
3
|
+
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
4
|
+
const isContentBlockArray = (value) => Array.isArray(value) &&
|
|
5
|
+
value.every((item) => isRecord(item) && typeof item.type === "string");
|
|
6
|
+
const stringField = (record, key) => {
|
|
7
|
+
const value = record[key];
|
|
8
|
+
return typeof value === "string" ? value : null;
|
|
9
|
+
};
|
|
10
|
+
const numberField = (record, key) => {
|
|
11
|
+
const value = record[key];
|
|
12
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
13
|
+
};
|
|
14
|
+
const getMessageKind = (message) => {
|
|
15
|
+
const kind = message.meta?.messageKind;
|
|
16
|
+
return typeof kind === "string" ? kind : null;
|
|
17
|
+
};
|
|
18
|
+
export function parseAssistantMessageCommit(message) {
|
|
19
|
+
if (message.role !== "assistant") {
|
|
20
|
+
return { kind: "ignored", message, isFinal: false };
|
|
21
|
+
}
|
|
22
|
+
const kind = getMessageKind(message);
|
|
23
|
+
if (kind === "assistant_intermediate") {
|
|
24
|
+
return { kind: "intermediate", message, isFinal: false };
|
|
25
|
+
}
|
|
26
|
+
if (kind === "assistant_final") {
|
|
27
|
+
return { kind: "final", message, isFinal: true };
|
|
28
|
+
}
|
|
29
|
+
if (kind === "assistant_error") {
|
|
30
|
+
return { kind: "error", message, isFinal: true };
|
|
31
|
+
}
|
|
32
|
+
return { kind: "ignored", message, isFinal: false };
|
|
33
|
+
}
|
|
34
|
+
function cloneContentBlock(block) {
|
|
35
|
+
return structuredClone(block);
|
|
36
|
+
}
|
|
37
|
+
function getStreamIndex(block) {
|
|
38
|
+
const value = block._meta?.streamIndex;
|
|
39
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
40
|
+
}
|
|
41
|
+
function findMergeTargetIndex(result, block) {
|
|
42
|
+
const streamIndex = getStreamIndex(block);
|
|
43
|
+
if (streamIndex != null) {
|
|
44
|
+
return result.findIndex((existing) => existing.type === block.type && getStreamIndex(existing) === streamIndex);
|
|
45
|
+
}
|
|
46
|
+
if (block.type === "tool_use") {
|
|
47
|
+
return result.findIndex((existing) => existing.type === "tool_use" && existing.id === block.id);
|
|
48
|
+
}
|
|
49
|
+
if (block.type === "tool_result") {
|
|
50
|
+
return result.findIndex((existing) => existing.type === "tool_result" &&
|
|
51
|
+
existing.tool_use_id === block.tool_use_id);
|
|
52
|
+
}
|
|
53
|
+
return -1;
|
|
54
|
+
}
|
|
55
|
+
function mergeStreamingDeltaBlocks(existing, delta) {
|
|
56
|
+
if (delta.length === 0)
|
|
57
|
+
return existing;
|
|
58
|
+
const result = existing.map(cloneContentBlock);
|
|
59
|
+
for (const block of delta) {
|
|
60
|
+
const targetIndex = findMergeTargetIndex(result, block);
|
|
61
|
+
if (targetIndex === -1) {
|
|
62
|
+
result.push(cloneContentBlock(block));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const target = result[targetIndex];
|
|
66
|
+
if (block.type === "text" && target?.type === "text") {
|
|
67
|
+
target.text += block.text;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (block.type === "thinking" && target?.type === "thinking") {
|
|
71
|
+
target.thinking += block.thinking;
|
|
72
|
+
if (block.signature)
|
|
73
|
+
target.signature = block.signature;
|
|
74
|
+
if (block._meta)
|
|
75
|
+
target._meta = { ...(target._meta ?? {}), ...block._meta };
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
result[targetIndex] = Object.assign(target ?? {}, cloneContentBlock(block));
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
function parseSnapshotMessage(value) {
|
|
83
|
+
if (!isRecord(value) || !isContentBlockArray(value.content))
|
|
84
|
+
return null;
|
|
85
|
+
return {
|
|
86
|
+
messageId: stringField(value, "messageId"),
|
|
87
|
+
messageOrdinal: numberField(value, "messageOrdinal"),
|
|
88
|
+
content: value.content,
|
|
89
|
+
...(typeof value.id === "string" ? { id: value.id } : {}),
|
|
90
|
+
...(typeof value.sessionId === "string" ? { sessionId: value.sessionId } : {}),
|
|
91
|
+
...(value.role === "user" ||
|
|
92
|
+
value.role === "assistant" ||
|
|
93
|
+
value.role === "system"
|
|
94
|
+
? { role: value.role }
|
|
95
|
+
: {}),
|
|
96
|
+
...(typeof value.text === "string" ? { text: value.text } : {}),
|
|
97
|
+
...(typeof value.provider === "string" ? { provider: value.provider } : {}),
|
|
98
|
+
...(typeof value.model === "string" ? { model: value.model } : {}),
|
|
99
|
+
...(typeof value.stopReason === "string"
|
|
100
|
+
? { stopReason: value.stopReason }
|
|
101
|
+
: {}),
|
|
102
|
+
...(typeof value.errorMessage === "string"
|
|
103
|
+
? { errorMessage: value.errorMessage }
|
|
104
|
+
: {}),
|
|
105
|
+
...(isRecord(value.usage) ? { usage: value.usage } : {}),
|
|
106
|
+
...(typeof value.toolCallsObjectKey === "string"
|
|
107
|
+
? { toolCallsObjectKey: value.toolCallsObjectKey }
|
|
108
|
+
: {}),
|
|
109
|
+
...(isRecord(value.meta) ? { meta: value.meta } : {}),
|
|
110
|
+
...(typeof value.createdAt === "string" ? { createdAt: value.createdAt } : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function messageRecordToIntermediate(message) {
|
|
114
|
+
if (!isContentBlockArray(message.content) || message.content.length === 0) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const meta = isRecord(message.meta) ? message.meta : {};
|
|
118
|
+
return {
|
|
119
|
+
id: message.id,
|
|
120
|
+
sessionId: message.sessionId,
|
|
121
|
+
role: message.role,
|
|
122
|
+
messageId: typeof meta.streamMessageId === "string"
|
|
123
|
+
? meta.streamMessageId
|
|
124
|
+
: message.id ?? null,
|
|
125
|
+
messageOrdinal: typeof meta.messageOrdinal === "number" ? meta.messageOrdinal : null,
|
|
126
|
+
content: message.content,
|
|
127
|
+
text: message.text,
|
|
128
|
+
provider: message.provider,
|
|
129
|
+
model: message.model,
|
|
130
|
+
stopReason: message.stopReason,
|
|
131
|
+
errorMessage: message.errorMessage,
|
|
132
|
+
usage: message.usage,
|
|
133
|
+
meta: message.meta,
|
|
134
|
+
createdAt: message.createdAt,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function resolveStreamMessageId(input) {
|
|
138
|
+
if (input.messageId?.trim())
|
|
139
|
+
return input.messageId.trim();
|
|
140
|
+
if (input.messageOrdinal == null)
|
|
141
|
+
return null;
|
|
142
|
+
if (input.turnId?.trim()) {
|
|
143
|
+
return `turn:${input.turnId.trim()}:assistant:${input.messageOrdinal}`;
|
|
144
|
+
}
|
|
145
|
+
return `session:${input.sessionId}:assistant:${input.messageOrdinal}:${input.anchorUserMessageId ?? "unknown"}`;
|
|
146
|
+
}
|
|
147
|
+
function getTurnIdFromMessage(message) {
|
|
148
|
+
const turnId = message.meta?.turnId;
|
|
149
|
+
return typeof turnId === "string" ? turnId : null;
|
|
150
|
+
}
|
|
151
|
+
export class SessionGenerationStreamClient {
|
|
152
|
+
websocketClient;
|
|
153
|
+
spaceId;
|
|
154
|
+
sessionId;
|
|
155
|
+
reducer = new SessionPatchReducer();
|
|
156
|
+
messageId = null;
|
|
157
|
+
messageOrdinal = null;
|
|
158
|
+
intermediateMessages = [];
|
|
159
|
+
progressState = null;
|
|
160
|
+
constructor(websocketClient, spaceId, sessionId) {
|
|
161
|
+
this.websocketClient = websocketClient;
|
|
162
|
+
this.spaceId = spaceId;
|
|
163
|
+
this.sessionId = sessionId;
|
|
164
|
+
}
|
|
165
|
+
subscribe(handlers) {
|
|
166
|
+
if (!this.websocketClient) {
|
|
167
|
+
throw new Error("realtime transport is not configured for this client");
|
|
168
|
+
}
|
|
169
|
+
ensureRealtimeConnected(this.websocketClient);
|
|
170
|
+
const stream = new SessionGenerationStreamClient(this.websocketClient, this.spaceId, this.sessionId);
|
|
171
|
+
const unsubscribe = this.websocketClient.on("event", (event) => {
|
|
172
|
+
if (event.spaceId !== this.spaceId || event.sessionId !== this.sessionId) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
stream.handleEvent(event, handlers);
|
|
176
|
+
});
|
|
177
|
+
return () => unsubscribe();
|
|
178
|
+
}
|
|
179
|
+
emit(handlers, event) {
|
|
180
|
+
handlers.event?.(event);
|
|
181
|
+
if (event.type === "state")
|
|
182
|
+
handlers.state?.(event);
|
|
183
|
+
if (event.type === "commit")
|
|
184
|
+
handlers.commit?.(event);
|
|
185
|
+
if (event.type === "finalized")
|
|
186
|
+
handlers.finalized?.(event);
|
|
187
|
+
if (event.type === "turn_updated")
|
|
188
|
+
handlers.turnUpdated?.(event);
|
|
189
|
+
if (event.type === "error")
|
|
190
|
+
handlers.error?.(event);
|
|
191
|
+
if (event.type === "out_of_sync")
|
|
192
|
+
handlers.outOfSync?.(event);
|
|
193
|
+
}
|
|
194
|
+
resetCurrentMessage() {
|
|
195
|
+
this.messageId = null;
|
|
196
|
+
this.messageOrdinal = null;
|
|
197
|
+
this.progressState = null;
|
|
198
|
+
}
|
|
199
|
+
appendCurrentMessage(state) {
|
|
200
|
+
if (state.contentBlocks.length === 0)
|
|
201
|
+
return;
|
|
202
|
+
this.addIntermediateMessage({
|
|
203
|
+
messageId: this.messageId,
|
|
204
|
+
messageOrdinal: this.messageOrdinal,
|
|
205
|
+
content: state.contentBlocks,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
addIntermediateMessage(message) {
|
|
209
|
+
const index = this.intermediateMessages.findIndex((existing) => {
|
|
210
|
+
if (message.messageId && existing.messageId) {
|
|
211
|
+
return existing.messageId === message.messageId;
|
|
212
|
+
}
|
|
213
|
+
return (message.messageOrdinal !== null &&
|
|
214
|
+
existing.messageOrdinal === message.messageOrdinal);
|
|
215
|
+
});
|
|
216
|
+
if (index < 0) {
|
|
217
|
+
this.intermediateMessages = [...this.intermediateMessages, message];
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.intermediateMessages = this.intermediateMessages.map((existing, i) => i === index ? { ...existing, ...message } : existing);
|
|
221
|
+
}
|
|
222
|
+
handleAppliedState(handlers, source, result, rawEvent, messageId, messageOrdinal) {
|
|
223
|
+
if (!result.applied) {
|
|
224
|
+
this.emit(handlers, {
|
|
225
|
+
type: "out_of_sync",
|
|
226
|
+
source: source === "progress" ? "patch" : source,
|
|
227
|
+
reason: result.reason,
|
|
228
|
+
state: result.state,
|
|
229
|
+
rawEvent,
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
this.progressState = result.state;
|
|
234
|
+
this.messageId = messageId;
|
|
235
|
+
this.messageOrdinal = messageOrdinal;
|
|
236
|
+
this.emit(handlers, {
|
|
237
|
+
type: "state",
|
|
238
|
+
source,
|
|
239
|
+
state: result.state,
|
|
240
|
+
messageId,
|
|
241
|
+
messageOrdinal,
|
|
242
|
+
intermediateMessages: [...this.intermediateMessages],
|
|
243
|
+
rawEvent,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
prepareMessageBoundary(input) {
|
|
247
|
+
const current = this.progressState ??
|
|
248
|
+
this.reducer.get({
|
|
249
|
+
spaceId: this.spaceId,
|
|
250
|
+
sessionId: this.sessionId,
|
|
251
|
+
});
|
|
252
|
+
const nextMessageId = resolveStreamMessageId({
|
|
253
|
+
sessionId: this.sessionId,
|
|
254
|
+
turnId: input.turnId,
|
|
255
|
+
anchorUserMessageId: input.anchorUserMessageId,
|
|
256
|
+
messageId: input.messageId,
|
|
257
|
+
messageOrdinal: input.messageOrdinal,
|
|
258
|
+
});
|
|
259
|
+
const differentTurn = Boolean(current.turnId && input.turnId && current.turnId !== input.turnId);
|
|
260
|
+
const messageChanged = Boolean(nextMessageId &&
|
|
261
|
+
current.contentBlocks.length > 0 &&
|
|
262
|
+
this.messageId &&
|
|
263
|
+
nextMessageId !== this.messageId);
|
|
264
|
+
if (differentTurn) {
|
|
265
|
+
this.intermediateMessages = [];
|
|
266
|
+
this.resetCurrentMessage();
|
|
267
|
+
this.reducer.start({
|
|
268
|
+
spaceId: this.spaceId,
|
|
269
|
+
sessionId: this.sessionId,
|
|
270
|
+
turnId: input.turnId,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
else if (messageChanged) {
|
|
274
|
+
this.appendCurrentMessage(current);
|
|
275
|
+
this.resetCurrentMessage();
|
|
276
|
+
this.reducer.start({
|
|
277
|
+
spaceId: this.spaceId,
|
|
278
|
+
sessionId: this.sessionId,
|
|
279
|
+
turnId: input.turnId ?? current.turnId,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return nextMessageId;
|
|
283
|
+
}
|
|
284
|
+
handleSnapshot(event, handlers) {
|
|
285
|
+
const payload = event.payload;
|
|
286
|
+
const current = isRecord(payload.current) ? payload.current : null;
|
|
287
|
+
const content = current ? current.content : null;
|
|
288
|
+
const seq = typeof payload.seq === "number" ? payload.seq : null;
|
|
289
|
+
if (!current || !isContentBlockArray(content) || seq === null) {
|
|
290
|
+
this.emit(handlers, {
|
|
291
|
+
type: "out_of_sync",
|
|
292
|
+
source: "snapshot",
|
|
293
|
+
reason: "invalid",
|
|
294
|
+
state: this.reducer.get({ spaceId: this.spaceId, sessionId: this.sessionId }),
|
|
295
|
+
rawEvent: event,
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const turnId = typeof payload.turnId === "string" ? payload.turnId : null;
|
|
300
|
+
const anchorUserMessageId = typeof payload.anchorUserMessageId === "string"
|
|
301
|
+
? payload.anchorUserMessageId
|
|
302
|
+
: null;
|
|
303
|
+
const messageOrdinal = numberField(current, "messageOrdinal");
|
|
304
|
+
const messageId = this.prepareMessageBoundary({
|
|
305
|
+
turnId,
|
|
306
|
+
messageId: stringField(current, "messageId"),
|
|
307
|
+
messageOrdinal,
|
|
308
|
+
anchorUserMessageId,
|
|
309
|
+
});
|
|
310
|
+
this.intermediateMessages = Array.isArray(payload.intermediateMessages)
|
|
311
|
+
? payload.intermediateMessages
|
|
312
|
+
.map(parseSnapshotMessage)
|
|
313
|
+
.filter((message) => message !== null)
|
|
314
|
+
: [];
|
|
315
|
+
const result = this.reducer.applySnapshot({
|
|
316
|
+
spaceId: this.spaceId,
|
|
317
|
+
sessionId: this.sessionId,
|
|
318
|
+
turnId,
|
|
319
|
+
seq,
|
|
320
|
+
contentBlocks: content,
|
|
321
|
+
anchorUserMessageId,
|
|
322
|
+
appendPath: stringField(current, "appendPath"),
|
|
323
|
+
});
|
|
324
|
+
this.handleAppliedState(handlers, "snapshot", result, event, messageId, messageOrdinal);
|
|
325
|
+
}
|
|
326
|
+
handlePatch(event, handlers) {
|
|
327
|
+
const payload = event.payload;
|
|
328
|
+
const seq = typeof payload.seq === "number" ? payload.seq : null;
|
|
329
|
+
const baseSeq = typeof payload.baseSeq === "number" ? payload.baseSeq : null;
|
|
330
|
+
if (seq === null || baseSeq === null || !Array.isArray(payload.ops)) {
|
|
331
|
+
this.emit(handlers, {
|
|
332
|
+
type: "out_of_sync",
|
|
333
|
+
source: "patch",
|
|
334
|
+
reason: "invalid",
|
|
335
|
+
state: this.reducer.get({ spaceId: this.spaceId, sessionId: this.sessionId }),
|
|
336
|
+
rawEvent: event,
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const turnId = typeof payload.turnId === "string" ? payload.turnId : null;
|
|
341
|
+
const anchorUserMessageId = typeof payload.anchorUserMessageId === "string"
|
|
342
|
+
? payload.anchorUserMessageId
|
|
343
|
+
: null;
|
|
344
|
+
const messageOrdinal = typeof payload.messageOrdinal === "number" ? payload.messageOrdinal : null;
|
|
345
|
+
const messageId = this.prepareMessageBoundary({
|
|
346
|
+
turnId,
|
|
347
|
+
messageId: typeof payload.messageId === "string" ? payload.messageId : null,
|
|
348
|
+
messageOrdinal,
|
|
349
|
+
anchorUserMessageId,
|
|
350
|
+
});
|
|
351
|
+
const input = {
|
|
352
|
+
spaceId: this.spaceId,
|
|
353
|
+
sessionId: this.sessionId,
|
|
354
|
+
turnId,
|
|
355
|
+
seq,
|
|
356
|
+
baseSeq,
|
|
357
|
+
ops: payload.ops,
|
|
358
|
+
anchorUserMessageId,
|
|
359
|
+
};
|
|
360
|
+
const result = this.reducer.applyPatch(input);
|
|
361
|
+
this.handleAppliedState(handlers, "patch", result, event, messageId, messageOrdinal);
|
|
362
|
+
}
|
|
363
|
+
handleProgress(event, handlers) {
|
|
364
|
+
const payload = event.payload;
|
|
365
|
+
if (!isContentBlockArray(payload.content))
|
|
366
|
+
return;
|
|
367
|
+
const current = this.progressState ??
|
|
368
|
+
this.reducer.get({ spaceId: this.spaceId, sessionId: this.sessionId });
|
|
369
|
+
const turnId = typeof payload.turnId === "string" ? payload.turnId : current.turnId;
|
|
370
|
+
const anchorUserMessageId = typeof payload.anchorUserMessageId === "string"
|
|
371
|
+
? payload.anchorUserMessageId
|
|
372
|
+
: current.anchorUserMessageId;
|
|
373
|
+
const messageOrdinal = typeof payload.messageOrdinal === "number"
|
|
374
|
+
? payload.messageOrdinal
|
|
375
|
+
: this.messageOrdinal;
|
|
376
|
+
const messageId = this.prepareMessageBoundary({
|
|
377
|
+
turnId,
|
|
378
|
+
messageId: typeof payload.messageId === "string" ? payload.messageId : this.messageId,
|
|
379
|
+
messageOrdinal,
|
|
380
|
+
anchorUserMessageId,
|
|
381
|
+
});
|
|
382
|
+
const base = this.progressState?.turnId === turnId ? this.progressState : current;
|
|
383
|
+
const state = {
|
|
384
|
+
...base,
|
|
385
|
+
spaceId: this.spaceId,
|
|
386
|
+
sessionId: this.sessionId,
|
|
387
|
+
status: "streaming",
|
|
388
|
+
contentBlocks: mergeStreamingDeltaBlocks(base.contentBlocks, payload.content),
|
|
389
|
+
anchorUserMessageId,
|
|
390
|
+
turnId,
|
|
391
|
+
};
|
|
392
|
+
this.progressState = state;
|
|
393
|
+
this.messageId = messageId;
|
|
394
|
+
this.messageOrdinal = messageOrdinal;
|
|
395
|
+
this.emit(handlers, {
|
|
396
|
+
type: "state",
|
|
397
|
+
source: "progress",
|
|
398
|
+
state,
|
|
399
|
+
messageId,
|
|
400
|
+
messageOrdinal,
|
|
401
|
+
intermediateMessages: [...this.intermediateMessages],
|
|
402
|
+
rawEvent: event,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
handlePersisted(event, handlers) {
|
|
406
|
+
const message = event.payload.message;
|
|
407
|
+
if (!isRecord(message))
|
|
408
|
+
return;
|
|
409
|
+
const commit = parseAssistantMessageCommit(message);
|
|
410
|
+
if (commit.kind === "intermediate") {
|
|
411
|
+
const intermediate = messageRecordToIntermediate(commit.message);
|
|
412
|
+
if (intermediate) {
|
|
413
|
+
this.addIntermediateMessage(intermediate);
|
|
414
|
+
}
|
|
415
|
+
this.reducer.reset({ spaceId: this.spaceId, sessionId: this.sessionId });
|
|
416
|
+
this.resetCurrentMessage();
|
|
417
|
+
}
|
|
418
|
+
if (commit.kind === "final") {
|
|
419
|
+
this.reducer.complete({
|
|
420
|
+
spaceId: this.spaceId,
|
|
421
|
+
sessionId: this.sessionId,
|
|
422
|
+
turnId: getTurnIdFromMessage(commit.message),
|
|
423
|
+
});
|
|
424
|
+
this.resetCurrentMessage();
|
|
425
|
+
}
|
|
426
|
+
if (commit.kind === "error") {
|
|
427
|
+
this.reducer.fail({
|
|
428
|
+
spaceId: this.spaceId,
|
|
429
|
+
sessionId: this.sessionId,
|
|
430
|
+
turnId: getTurnIdFromMessage(commit.message),
|
|
431
|
+
});
|
|
432
|
+
this.resetCurrentMessage();
|
|
433
|
+
}
|
|
434
|
+
this.emit(handlers, {
|
|
435
|
+
type: "commit",
|
|
436
|
+
commit,
|
|
437
|
+
rawEvent: event,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
handleFinalized(event, handlers) {
|
|
441
|
+
const turn = event.payload.turn;
|
|
442
|
+
if (!isRecord(turn))
|
|
443
|
+
return;
|
|
444
|
+
const typedTurn = turn;
|
|
445
|
+
if (typedTurn.status === "interrupted") {
|
|
446
|
+
this.reducer.interrupt({
|
|
447
|
+
spaceId: this.spaceId,
|
|
448
|
+
sessionId: this.sessionId,
|
|
449
|
+
turnId: typedTurn.id,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
this.reducer.complete({
|
|
454
|
+
spaceId: this.spaceId,
|
|
455
|
+
sessionId: this.sessionId,
|
|
456
|
+
turnId: typedTurn.id,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
this.resetCurrentMessage();
|
|
460
|
+
this.emit(handlers, {
|
|
461
|
+
type: "finalized",
|
|
462
|
+
turn: typedTurn,
|
|
463
|
+
rawEvent: event,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
handleEvent(event, handlers) {
|
|
467
|
+
switch (event.type) {
|
|
468
|
+
case "session.turn.snapshot":
|
|
469
|
+
this.handleSnapshot(event, handlers);
|
|
470
|
+
return;
|
|
471
|
+
case "session.turn.patch":
|
|
472
|
+
this.handlePatch(event, handlers);
|
|
473
|
+
return;
|
|
474
|
+
case "session.turn.progress":
|
|
475
|
+
this.handleProgress(event, handlers);
|
|
476
|
+
return;
|
|
477
|
+
case "session.message.persisted":
|
|
478
|
+
this.handlePersisted(event, handlers);
|
|
479
|
+
return;
|
|
480
|
+
case "session.turn.finalized":
|
|
481
|
+
this.handleFinalized(event, handlers);
|
|
482
|
+
return;
|
|
483
|
+
case "session.turn.updated": {
|
|
484
|
+
const turn = event.payload.turn;
|
|
485
|
+
if (!isRecord(turn))
|
|
486
|
+
return;
|
|
487
|
+
this.emit(handlers, {
|
|
488
|
+
type: "turn_updated",
|
|
489
|
+
turn: turn,
|
|
490
|
+
rawEvent: event,
|
|
491
|
+
});
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
case "session.turn.error": {
|
|
495
|
+
const message = typeof event.payload.error === "string" && event.payload.error.trim()
|
|
496
|
+
? event.payload.error.trim()
|
|
497
|
+
: "Generation failed";
|
|
498
|
+
this.reducer.fail({ spaceId: this.spaceId, sessionId: this.sessionId });
|
|
499
|
+
this.resetCurrentMessage();
|
|
500
|
+
this.emit(handlers, {
|
|
501
|
+
type: "error",
|
|
502
|
+
message,
|
|
503
|
+
rawEvent: event,
|
|
504
|
+
});
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
default:
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
export function createSessionGenerationStreamClient(input) {
|
|
513
|
+
return new SessionGenerationStreamClient(input.websocketClient, input.spaceId, input.sessionId);
|
|
514
|
+
}
|
|
@@ -20,9 +20,27 @@ function getStreamIndex(block) {
|
|
|
20
20
|
const value = block._meta?.streamIndex;
|
|
21
21
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
22
22
|
}
|
|
23
|
+
function blockIdentityCompatible(prev, next) {
|
|
24
|
+
if (prev.type !== next.type)
|
|
25
|
+
return false;
|
|
26
|
+
if (prev.type === "tool_use" && next.type === "tool_use")
|
|
27
|
+
return prev.id === next.id && prev.name === next.name;
|
|
28
|
+
if (prev.type === "tool_result" && next.type === "tool_result")
|
|
29
|
+
return prev.tool_use_id === next.tool_use_id;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
23
32
|
function findBlockByStreamIndex(blocks, streamIndex) {
|
|
24
33
|
return blocks.findIndex((block) => getStreamIndex(block) === streamIndex);
|
|
25
34
|
}
|
|
35
|
+
function findBlockForReplacement(blocks, streamIndex, nextBlock) {
|
|
36
|
+
const compatibleIndex = blocks.findIndex((block) => getStreamIndex(block) === streamIndex && blockIdentityCompatible(block, nextBlock));
|
|
37
|
+
if (compatibleIndex >= 0)
|
|
38
|
+
return compatibleIndex;
|
|
39
|
+
const sameStreamIndex = blocks.findIndex((block) => getStreamIndex(block) === streamIndex);
|
|
40
|
+
if (sameStreamIndex >= 0 && nextBlock.type !== "tool_use" && nextBlock.type !== "tool_result")
|
|
41
|
+
return sameStreamIndex;
|
|
42
|
+
return -1;
|
|
43
|
+
}
|
|
26
44
|
function sortBlocksByStreamIndex(blocks) {
|
|
27
45
|
return [...blocks].sort((a, b) => {
|
|
28
46
|
const aIndex = getStreamIndex(a);
|
|
@@ -227,7 +245,7 @@ function applyPatchOpsToBlocks(current, ops, initialAppendPath) {
|
|
|
227
245
|
const streamIndex = Number(match[1]);
|
|
228
246
|
const block = cloneBlock(op.v);
|
|
229
247
|
block._meta = { ...(block._meta ?? {}), streamIndex };
|
|
230
|
-
const blockIndex =
|
|
248
|
+
const blockIndex = findBlockForReplacement(next, streamIndex, block);
|
|
231
249
|
if (blockIndex >= 0) {
|
|
232
250
|
next[blockIndex] = block;
|
|
233
251
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SessionBindingRecord as ProtocolSessionBindingRecord, SessionRecord as ProtocolSessionRecord, SessionTurnIndexItem, SessionTurnRecord } from "@neta-art/cohub-protocol/model";
|
|
1
|
+
import type { SessionBindingRecord as ProtocolSessionBindingRecord, SessionRecord as ProtocolSessionRecord, SessionForkRecord, SessionTurnIndexItem, SessionTurnRecord, SessionTurnSegmentRecord } from "@neta-art/cohub-protocol/model";
|
|
2
2
|
import type { ChannelConfig } from "@neta-art/cohub-protocol/gateway";
|
|
3
3
|
import type { ContentBlock, Usage } from "@neta-art/cohub-protocol/core";
|
|
4
4
|
import type { MessageRecord } from "@neta-art/cohub-protocol/model";
|
|
@@ -24,7 +24,7 @@ export type UserRulesResponse = {
|
|
|
24
24
|
source: "config-space";
|
|
25
25
|
path: string;
|
|
26
26
|
};
|
|
27
|
-
export type { ContentBlock, MessageRecord, SessionTurnRecord, SessionTurnIndexItem };
|
|
27
|
+
export type { ContentBlock, MessageRecord, SessionTurnRecord, SessionTurnIndexItem, SessionForkRecord, SessionTurnSegmentRecord };
|
|
28
28
|
export type SpaceFsEntry = {
|
|
29
29
|
name: string;
|
|
30
30
|
path: string;
|
|
@@ -279,6 +279,32 @@ export type SpaceChannelBindingInput = {
|
|
|
279
279
|
channelId: string;
|
|
280
280
|
config?: ChannelConfig | null;
|
|
281
281
|
};
|
|
282
|
+
export type GlobalSearchResult = {
|
|
283
|
+
type: "turn" | "session" | "space";
|
|
284
|
+
id: string;
|
|
285
|
+
spaceId: string;
|
|
286
|
+
sessionId: string | null;
|
|
287
|
+
turnId: string | null;
|
|
288
|
+
sequence: number | null;
|
|
289
|
+
title: string;
|
|
290
|
+
excerpt: string | null;
|
|
291
|
+
spaceName: string | null;
|
|
292
|
+
sessionTitle: string | null;
|
|
293
|
+
matchedField: "userText" | "title" | "name" | "description";
|
|
294
|
+
href: string;
|
|
295
|
+
score: number;
|
|
296
|
+
textScore: number;
|
|
297
|
+
recencyScore: number;
|
|
298
|
+
typePriorityScore: number;
|
|
299
|
+
updatedAt: string | null;
|
|
300
|
+
source: "remote";
|
|
301
|
+
};
|
|
302
|
+
export type GlobalSearchResponse = {
|
|
303
|
+
items: GlobalSearchResult[];
|
|
304
|
+
query: string;
|
|
305
|
+
source: "remote";
|
|
306
|
+
degraded?: boolean;
|
|
307
|
+
};
|
|
282
308
|
export type SpaceSessionsResponse = {
|
|
283
309
|
sessions: SessionRecord[];
|
|
284
310
|
pageInfo?: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
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.
|
|
47
|
+
"@neta-art/cohub-protocol": "1.5.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"typescript": "^6.0.3"
|