@neta-art/cohub 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -16
- package/dist/apis/invitations.d.ts +20 -0
- package/dist/apis/invitations.js +36 -0
- package/dist/apis/models.d.ts +4 -5
- package/dist/apis/models.js +5 -13
- package/dist/apis/spaces.d.ts +65 -4
- package/dist/apis/spaces.js +156 -8
- package/dist/client.d.ts +2 -0
- package/dist/client.js +35 -5
- package/dist/environment.d.ts +22 -0
- package/dist/environment.js +37 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +8 -3
- package/dist/index.d.ts +5 -1
- package/dist/index.js +2 -0
- package/dist/realtime.js +3 -0
- package/dist/session-patch-reducer.d.ts +49 -0
- package/dist/session-patch-reducer.js +273 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +2 -1
- package/dist/types.d.ts +70 -0
- package/dist/websocket.d.ts +18 -1
- package/dist/websocket.js +167 -43
- package/package.json +7 -7
|
@@ -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,
|
|
25
|
-
this.models = new ModelsApi(
|
|
26
|
-
this.prompts = new PromptsApi(options.fetch ?? fetch,
|
|
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();
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { CohubEnvironment } from "./environment.js";
|
|
1
2
|
import type { WebsocketClientOptions } from "./websocket.js";
|
|
2
3
|
export type Fetch = typeof globalThis.fetch;
|
|
3
4
|
export type CohubClientOptions = {
|
|
5
|
+
env?: CohubEnvironment;
|
|
4
6
|
baseUrl?: string;
|
|
5
7
|
getAccessToken?: () => Promise<string | null> | string | null;
|
|
6
8
|
onUnauthorized?: () => Promise<void> | void;
|
package/dist/transport.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveApiBaseUrl } from "./environment.js";
|
|
1
2
|
export class HttpError extends Error {
|
|
2
3
|
status;
|
|
3
4
|
body;
|
|
@@ -14,7 +15,7 @@ export class HttpTransport {
|
|
|
14
15
|
getAccessToken;
|
|
15
16
|
onUnauthorized;
|
|
16
17
|
constructor(options = {}) {
|
|
17
|
-
this.baseUrl = options
|
|
18
|
+
this.baseUrl = resolveApiBaseUrl(options);
|
|
18
19
|
this.fetcher = options.fetch ?? fetch;
|
|
19
20
|
this.getAccessToken = options.getAccessToken;
|
|
20
21
|
this.onUnauthorized = options.onUnauthorized;
|
package/dist/types.d.ts
CHANGED
|
@@ -40,6 +40,22 @@ export type SpaceFsMoveInput = {
|
|
|
40
40
|
fromPath: string;
|
|
41
41
|
toPath: string;
|
|
42
42
|
};
|
|
43
|
+
export type SpaceFsUploadEntry = {
|
|
44
|
+
path: string;
|
|
45
|
+
name: string;
|
|
46
|
+
size: number;
|
|
47
|
+
mimeType: string | null;
|
|
48
|
+
mtimeMs: number;
|
|
49
|
+
};
|
|
50
|
+
export type SpaceFsUploadError = {
|
|
51
|
+
name: string;
|
|
52
|
+
code: "file_too_large" | "name_invalid" | "write_failed";
|
|
53
|
+
message: string;
|
|
54
|
+
};
|
|
55
|
+
export type SpaceFsUploadResponse = {
|
|
56
|
+
uploaded: SpaceFsUploadEntry[];
|
|
57
|
+
errors: SpaceFsUploadError[];
|
|
58
|
+
};
|
|
43
59
|
export type SessionBindingRecord = ProtocolSessionBindingRecord;
|
|
44
60
|
export type SessionRecord = ProtocolSessionRecord & {
|
|
45
61
|
bindings?: SessionBindingRecord[];
|
|
@@ -49,6 +65,10 @@ export type SessionRecord = ProtocolSessionRecord & {
|
|
|
49
65
|
totalOutputTokens?: number;
|
|
50
66
|
totalCost?: string | number | null;
|
|
51
67
|
};
|
|
68
|
+
export type SpaceGitInfo = {
|
|
69
|
+
giteaHost: string;
|
|
70
|
+
giteaUsername: string;
|
|
71
|
+
};
|
|
52
72
|
export type SpaceRecord = {
|
|
53
73
|
id: string;
|
|
54
74
|
userUuid: string;
|
|
@@ -69,6 +89,7 @@ export type SpaceRecord = {
|
|
|
69
89
|
status: string;
|
|
70
90
|
}[];
|
|
71
91
|
accessLevel?: "minimal";
|
|
92
|
+
gitInfo?: SpaceGitInfo | null;
|
|
72
93
|
};
|
|
73
94
|
export type SpaceBootstrapSource = {
|
|
74
95
|
type: "blank";
|
|
@@ -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
|
+
};
|
package/dist/websocket.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ChannelEnvelope } from "@neta-art/cohub-protocol/realtime";
|
|
2
2
|
import type { ContentBlock } from "@neta-art/cohub-protocol/core";
|
|
3
|
+
import type { CohubEnvironment } from "./environment.js";
|
|
3
4
|
export type WebsocketEventPayload = ChannelEnvelope;
|
|
4
5
|
export type WebSocketLike = {
|
|
5
6
|
readonly readyState: number;
|
|
@@ -12,6 +13,7 @@ export type WebSocketLike = {
|
|
|
12
13
|
};
|
|
13
14
|
export type WebSocketConstructor = new (url: string) => WebSocketLike;
|
|
14
15
|
export type WebsocketClientOptions = {
|
|
16
|
+
env?: CohubEnvironment;
|
|
15
17
|
url?: string;
|
|
16
18
|
autoReconnect?: boolean;
|
|
17
19
|
reconnectBaseDelayMs?: number;
|
|
@@ -22,8 +24,18 @@ export type WebsocketClientOptions = {
|
|
|
22
24
|
getAccessToken?: () => Promise<string | null> | string | null;
|
|
23
25
|
WebSocketImpl?: WebSocketConstructor;
|
|
24
26
|
};
|
|
25
|
-
export type WebsocketClientState = "idle" | "connecting" | "open" | "closed";
|
|
27
|
+
export type WebsocketClientState = "idle" | "connecting" | "reconnecting" | "open" | "closed";
|
|
26
28
|
export type WebsocketClientEvents = {
|
|
29
|
+
connecting: {
|
|
30
|
+
isReconnect: boolean;
|
|
31
|
+
attempt: number;
|
|
32
|
+
};
|
|
33
|
+
reconnecting: {
|
|
34
|
+
attempt: number;
|
|
35
|
+
delayMs: number;
|
|
36
|
+
reason?: string;
|
|
37
|
+
code?: number;
|
|
38
|
+
};
|
|
27
39
|
open: {
|
|
28
40
|
connectionId?: string | null;
|
|
29
41
|
};
|
|
@@ -34,6 +46,7 @@ export type WebsocketClientEvents = {
|
|
|
34
46
|
};
|
|
35
47
|
error: {
|
|
36
48
|
error: unknown;
|
|
49
|
+
recoverable: boolean;
|
|
37
50
|
};
|
|
38
51
|
event: WebsocketEventPayload;
|
|
39
52
|
ready: {
|
|
@@ -75,6 +88,7 @@ export declare class WebsocketClient {
|
|
|
75
88
|
private ws;
|
|
76
89
|
private pingTimer;
|
|
77
90
|
private reconnectTimer;
|
|
91
|
+
private reconnectTimerResolver;
|
|
78
92
|
private reconnectAttempt;
|
|
79
93
|
private manuallyClosed;
|
|
80
94
|
private connectPromise;
|
|
@@ -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;
|