@neta-art/cohub 2.7.1 → 2.9.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,4 +1,4 @@
1
- import { r as getRealtimeSpaceRoom } from "./types.js";
1
+ import { a as getRealtimeSpaceRoom, n as HttpTransport, t as HttpError } from "./transport.js";
2
2
  import { a as resolveApiBaseUrl } from "./environment.js";
3
3
  //#region src/apis/channels.ts
4
4
  var ChannelsApi = class {
@@ -99,7 +99,20 @@ function sleep(ms, signal) {
99
99
  });
100
100
  }
101
101
  function isGenerationTaskResult(value) {
102
- return !!value && typeof value === "object" && !Array.isArray(value) && typeof value.model === "string" && Array.isArray(value.output);
102
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
103
+ const record = value;
104
+ if (typeof record.model !== "string" || !Array.isArray(record.output)) return false;
105
+ if (record.requestId !== void 0 && typeof record.requestId !== "string") return false;
106
+ if (record.cost !== void 0 && (typeof record.cost !== "number" || !Number.isFinite(record.cost))) return false;
107
+ if (record.billing !== void 0 && record.billing !== null) {
108
+ if (!record.billing || typeof record.billing !== "object" || Array.isArray(record.billing)) return false;
109
+ const billing = record.billing;
110
+ if (typeof billing.amountUsd !== "number" || !Number.isFinite(billing.amountUsd)) return false;
111
+ if (typeof billing.usageType !== "string") return false;
112
+ if (billing.status !== "recorded" && billing.status !== "overage" && billing.status !== "skipped") return false;
113
+ if (billing.reason !== void 0 && billing.reason !== null && typeof billing.reason !== "string") return false;
114
+ }
115
+ return true;
103
116
  }
104
117
  var GenerationsApi = class {
105
118
  transport;
@@ -295,113 +308,6 @@ function ensureRealtimeConnected(websocketClient) {
295
308
  });
296
309
  }
297
310
  //#endregion
298
- //#region src/transport.ts
299
- const responseBodyForError = async (response) => {
300
- return (response.headers.get("content-type") ?? "").includes("application/json") ? await response.json().catch(() => null) : await response.text().catch(() => response.statusText);
301
- };
302
- const messageFromErrorBody = (body, fallback) => {
303
- if (typeof body === "string") return body.trim() || fallback;
304
- if (body && typeof body === "object") {
305
- const errorBody = body;
306
- if (typeof errorBody.message === "string" && errorBody.message.trim()) return errorBody.message;
307
- if (typeof errorBody.error?.message === "string" && errorBody.error.message.trim()) return errorBody.error.message;
308
- }
309
- return fallback;
310
- };
311
- function errorCodeFromBody(body) {
312
- if (!body || typeof body !== "object") return null;
313
- const errorBody = body;
314
- if (typeof errorBody.code === "string" && errorBody.code.trim()) return errorBody.code;
315
- if (typeof errorBody.error?.code === "string" && errorBody.error.code.trim()) return errorBody.error.code;
316
- return null;
317
- }
318
- var HttpError = class extends Error {
319
- status;
320
- body;
321
- code;
322
- constructor(message, status, body) {
323
- super(message);
324
- this.name = "HttpError";
325
- this.status = status;
326
- this.body = body;
327
- this.code = errorCodeFromBody(body);
328
- }
329
- };
330
- var HttpTransport = class {
331
- baseUrl;
332
- fetcher;
333
- getAccessToken;
334
- onUnauthorized;
335
- constructor(options = {}) {
336
- this.baseUrl = resolveApiBaseUrl(options);
337
- this.fetcher = options.fetch ?? fetch;
338
- this.getAccessToken = options.getAccessToken;
339
- this.onUnauthorized = options.onUnauthorized;
340
- }
341
- async withAuthorization(init, tokenOverride) {
342
- const headers = new Headers(init?.headers);
343
- const token = tokenOverride ?? (this.getAccessToken ? await this.getAccessToken() : null);
344
- if (token) headers.set("Authorization", `Bearer ${token}`);
345
- else headers.delete("Authorization");
346
- return {
347
- ...init,
348
- headers
349
- };
350
- }
351
- async send(path, init) {
352
- const fetcher = init?.fetch ?? this.fetcher;
353
- const url = this.baseUrl ? `${this.baseUrl}${path}` : path;
354
- const response = await fetcher(url, await this.withAuthorization(init));
355
- const getAccessToken = this.getAccessToken;
356
- if (response.status === 401 && getAccessToken) {
357
- const refreshedToken = await (async () => {
358
- try {
359
- return await getAccessToken({ forceRefresh: true });
360
- } catch {
361
- return null;
362
- }
363
- })();
364
- if (refreshedToken) {
365
- const retryResponse = await fetcher(url, await this.withAuthorization(init, refreshedToken));
366
- if (retryResponse.status !== 401) {
367
- if (!retryResponse.ok) {
368
- const body = await responseBodyForError(retryResponse);
369
- throw new HttpError(messageFromErrorBody(body, retryResponse.statusText), retryResponse.status, body);
370
- }
371
- return retryResponse;
372
- }
373
- }
374
- }
375
- if (response.status === 401) {
376
- await this.onUnauthorized?.();
377
- throw new HttpError("unauthorized", 401, null);
378
- }
379
- if (!response.ok) {
380
- const body = await responseBodyForError(response);
381
- throw new HttpError(messageFromErrorBody(body, response.statusText), response.status, body);
382
- }
383
- return response;
384
- }
385
- async request(path, init) {
386
- const response = await this.send(path, init);
387
- if (response.status === 204) return null;
388
- return response.json();
389
- }
390
- async raw(path, init) {
391
- const response = await this.send(path, init);
392
- return {
393
- response,
394
- blob: () => response.blob(),
395
- arrayBuffer: () => response.arrayBuffer(),
396
- text: () => response.text(),
397
- json: () => response.json()
398
- };
399
- }
400
- async blob(path, init) {
401
- return (await this.raw(path, init)).blob();
402
- }
403
- };
404
- //#endregion
405
311
  //#region src/session-patch-reducer.ts
406
312
  const blockSubPathPattern = /^\/message\/content\/blocks\/(\d+)\/(.+)$/;
407
313
  const blockPathPattern = /^\/message\/content\/blocks\/(\d+)$/;
@@ -1455,6 +1361,15 @@ var SpaceFilesApi = class {
1455
1361
  const params = new URLSearchParams({ path });
1456
1362
  return this.transport.request(`/api/spaces/${this.spaceId}/fs/file?${params.toString()}`, { fetch: customFetch });
1457
1363
  }
1364
+ /** Pending workspace changes vs the space head checkpoint. */
1365
+ diff(customFetch) {
1366
+ return this.transport.request(`/api/spaces/${this.spaceId}/fs/diff`, { fetch: customFetch });
1367
+ }
1368
+ /** Per-file pending workspace diff vs the space head checkpoint. */
1369
+ diffFile(path, customFetch) {
1370
+ const params = new URLSearchParams({ path });
1371
+ return this.transport.request(`/api/spaces/${this.spaceId}/fs/diff/file?${params.toString()}`, { fetch: customFetch });
1372
+ }
1458
1373
  readMany(paths, customFetch) {
1459
1374
  return this.transport.request(`/api/spaces/${this.spaceId}/fs/files`, {
1460
1375
  method: "POST",
@@ -2222,16 +2137,80 @@ var SpaceCheckpointFilesApi = class {
2222
2137
  return this.transport.request(`/api/spaces/${this.spaceId}/checkpoints/${this.checkpointId}/fs/file?${params.toString()}`, { fetch: customFetch });
2223
2138
  }
2224
2139
  };
2140
+ async function hydrateCheckpointDiffSummary(summary, customFetch) {
2141
+ if (summary.delivery !== "url" || !summary.url) return {
2142
+ ...summary,
2143
+ delivery: summary.delivery ?? "inline"
2144
+ };
2145
+ const response = await (customFetch ?? fetch)(summary.url);
2146
+ if (!response.ok) throw new HttpError(`Failed to load checkpoint diff (${response.status})`, response.status, null);
2147
+ const body = await response.json();
2148
+ return {
2149
+ ...body,
2150
+ delivery: "inline",
2151
+ url: summary.url,
2152
+ precomputed: summary.precomputed ?? body.precomputed ?? true,
2153
+ headCheckpointId: body.headCheckpointId || summary.headCheckpointId,
2154
+ headCommitHash: body.headCommitHash || summary.headCommitHash,
2155
+ baseCheckpointId: body.baseCheckpointId ?? summary.baseCheckpointId,
2156
+ baseCommitHash: body.baseCommitHash ?? summary.baseCommitHash
2157
+ };
2158
+ }
2159
+ async function hydrateCheckpointDiffFile(file, customFetch) {
2160
+ if (file.delivery !== "url" || !file.url) return {
2161
+ ...file,
2162
+ delivery: file.delivery ?? "inline"
2163
+ };
2164
+ if (file.kind !== "text") return {
2165
+ ...file,
2166
+ delivery: file.delivery ?? "url"
2167
+ };
2168
+ const response = await (customFetch ?? fetch)(file.url);
2169
+ if (!response.ok) throw new HttpError(`Failed to load file diff (${response.status})`, response.status, null);
2170
+ const body = await response.json();
2171
+ return {
2172
+ ...body,
2173
+ path: body.path || file.path,
2174
+ oldPath: body.oldPath ?? file.oldPath ?? null,
2175
+ status: body.status ?? file.status,
2176
+ kind: body.kind ?? file.kind,
2177
+ delivery: "inline",
2178
+ url: file.url
2179
+ };
2180
+ }
2181
+ var SpaceCheckpointDiffApi = class {
2182
+ transport;
2183
+ spaceId;
2184
+ checkpointId;
2185
+ constructor(transport, spaceId, checkpointId) {
2186
+ this.transport = transport;
2187
+ this.spaceId = spaceId;
2188
+ this.checkpointId = checkpointId;
2189
+ }
2190
+ async summary(options, customFetch) {
2191
+ const params = new URLSearchParams();
2192
+ if (options?.base) params.set("base", options.base);
2193
+ const query = params.toString();
2194
+ return hydrateCheckpointDiffSummary(await this.transport.request(`/api/spaces/${this.spaceId}/checkpoints/${this.checkpointId}/fs/diff${query ? `?${query}` : ""}`, { fetch: customFetch }), customFetch);
2195
+ }
2196
+ async file(path, options, customFetch) {
2197
+ const params = new URLSearchParams({ path });
2198
+ if (options?.base) params.set("base", options.base);
2199
+ return hydrateCheckpointDiffFile(await this.transport.request(`/api/spaces/${this.spaceId}/checkpoints/${this.checkpointId}/fs/diff/file?${params.toString()}`, { fetch: customFetch }), customFetch);
2200
+ }
2201
+ };
2225
2202
  var SpaceCheckpointApi = class {
2226
2203
  transport;
2227
2204
  spaceId;
2228
2205
  id;
2229
2206
  files;
2207
+ diff;
2230
2208
  constructor(transport, spaceId, id) {
2231
2209
  this.transport = transport;
2232
2210
  this.spaceId = spaceId;
2233
2211
  this.id = id;
2234
2212
  this.files = new SpaceCheckpointFilesApi(transport, spaceId, id);
2213
+ this.diff = new SpaceCheckpointDiffApi(transport, spaceId, id);
2235
2214
  }
2236
2215
  get(customFetch) {
2237
2216
  return this.transport.request(`/api/spaces/${this.spaceId}/checkpoints/${this.id}`, { fetch: customFetch });
@@ -2306,6 +2285,122 @@ var SpaceClient = class {
2306
2285
  body: JSON.stringify(input)
2307
2286
  });
2308
2287
  }
2288
+ /**
2289
+ * Raw LLM completion. Caller fully controls messages and optional system prompt file.
2290
+ * Non-streaming JSON response. Use `streamCompletion` for SSE.
2291
+ */
2292
+ completion(input) {
2293
+ return this.transport.request(`/api/spaces/${this.id}/completions`, {
2294
+ method: "POST",
2295
+ headers: { "Content-Type": "application/json" },
2296
+ body: JSON.stringify({
2297
+ ...input,
2298
+ stream: false
2299
+ })
2300
+ });
2301
+ }
2302
+ /** Raw LLM completion with SSE events. Yields deltas; returns the final aggregated result. */
2303
+ async *streamCompletion(input, options) {
2304
+ const signal = options?.signal;
2305
+ if (signal?.aborted) throw signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("aborted");
2306
+ const raw = await this.transport.raw(`/api/spaces/${this.id}/completions`, {
2307
+ method: "POST",
2308
+ headers: {
2309
+ "Content-Type": "application/json",
2310
+ Accept: "text/event-stream"
2311
+ },
2312
+ body: JSON.stringify({
2313
+ ...input,
2314
+ stream: true
2315
+ }),
2316
+ signal
2317
+ });
2318
+ if (!(raw.response.headers.get("content-type") ?? "").includes("text/event-stream")) {
2319
+ const body = await raw.json().catch(() => null);
2320
+ if (body && typeof body === "object" && "completionId" in body) {
2321
+ const result = body;
2322
+ yield {
2323
+ type: "done",
2324
+ completionId: result.completionId,
2325
+ message: result.message,
2326
+ usage: result.usage
2327
+ };
2328
+ return result;
2329
+ }
2330
+ throw new HttpError(body && typeof body === "object" && typeof body.message === "string" ? body.message : "Unexpected completion response", raw.response.status, body);
2331
+ }
2332
+ if (!raw.response.body) throw new HttpError("Empty completion stream", 502, null);
2333
+ const reader = raw.response.body.getReader();
2334
+ const decoder = new TextDecoder();
2335
+ let buffer = "";
2336
+ let result = null;
2337
+ let meta = null;
2338
+ let lastUsage = null;
2339
+ let readerReleased = false;
2340
+ const releaseReader = async () => {
2341
+ if (readerReleased) return;
2342
+ readerReleased = true;
2343
+ try {
2344
+ await reader.cancel();
2345
+ } catch {}
2346
+ };
2347
+ const onAbort = () => {
2348
+ releaseReader();
2349
+ };
2350
+ signal?.addEventListener("abort", onAbort, { once: true });
2351
+ const consume = (event) => {
2352
+ if (event.type === "meta") meta = event;
2353
+ if (event.type === "usage") lastUsage = event.usage;
2354
+ if (event.type === "done") result = {
2355
+ completionId: event.completionId,
2356
+ provider: meta?.provider ?? "",
2357
+ model: meta?.model ?? "",
2358
+ systemPromptPath: meta?.systemPromptPath ?? null,
2359
+ message: event.message,
2360
+ usage: event.usage ?? lastUsage
2361
+ };
2362
+ if (event.type === "error") throw new HttpError(event.message, 502, event);
2363
+ };
2364
+ const parseDataLine = (chunk) => {
2365
+ const dataLine = chunk.split("\n").map((line) => line.trimEnd()).find((line) => line.startsWith("data:"));
2366
+ if (!dataLine) return null;
2367
+ const payload = dataLine.slice(5).trim();
2368
+ if (!payload || payload === "[DONE]") return null;
2369
+ try {
2370
+ return JSON.parse(payload);
2371
+ } catch {
2372
+ return null;
2373
+ }
2374
+ };
2375
+ try {
2376
+ while (true) {
2377
+ if (signal?.aborted) throw signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("aborted");
2378
+ const { done, value } = await reader.read();
2379
+ if (done) break;
2380
+ buffer += decoder.decode(value, { stream: true });
2381
+ const chunks = buffer.split("\n\n");
2382
+ buffer = chunks.pop() ?? "";
2383
+ for (const chunk of chunks) {
2384
+ const event = parseDataLine(chunk);
2385
+ if (!event) continue;
2386
+ consume(event);
2387
+ yield event;
2388
+ }
2389
+ }
2390
+ if (buffer.trim()) {
2391
+ const event = parseDataLine(buffer);
2392
+ if (event) {
2393
+ consume(event);
2394
+ yield event;
2395
+ }
2396
+ }
2397
+ if (!result) throw new HttpError("Completion stream ended without a result", 502, null);
2398
+ return result;
2399
+ } finally {
2400
+ signal?.removeEventListener("abort", onAbort);
2401
+ await releaseReader();
2402
+ }
2403
+ }
2309
2404
  update(input) {
2310
2405
  return this.transport.request(`/api/spaces/${this.id}`, {
2311
2406
  method: "PATCH",
@@ -2613,4 +2708,4 @@ var CohubHttpClient = class {
2613
2708
  };
2614
2709
  const createHttpClient = (options) => new CohubHttpClient(options);
2615
2710
  //#endregion
2616
- export { ModelsApi as C, ChannelsApi as E, PromptsApi as S, CronJobsApi as T, ensureRealtimeConnected as _, UserApi as a, SearchApi as b, SpacesApi as c, createSessionGenerationStreamClient as d, parseAssistantMessageCommit as f, HttpTransport as g, HttpError as h, WorksApi as i, PublicInviteApi as l, createSessionPatchReducer as m, createHttpClient as n, TasksApi as o, SessionPatchReducer as p, WorkCommerceApi as r, SpaceClient as s, CohubHttpClient as t, SessionGenerationStreamClient as u, SessionAccessApi as v, GenerationsApi as w, PublicAssetsApi as x, ReferencesApi as y };
2711
+ export { CronJobsApi as C, GenerationsApi as S, ReferencesApi as _, UserApi as a, PromptsApi as b, SpacesApi as c, createSessionGenerationStreamClient as d, parseAssistantMessageCommit as f, SessionAccessApi as g, ensureRealtimeConnected as h, WorksApi as i, PublicInviteApi as l, createSessionPatchReducer as m, createHttpClient as n, TasksApi as o, SessionPatchReducer as p, WorkCommerceApi as r, SpaceClient as s, CohubHttpClient as t, SessionGenerationStreamClient as u, SearchApi as v, ChannelsApi as w, ModelsApi as x, PublicAssetsApi as y };
@@ -0,0 +1,143 @@
1
+ import { a as resolveApiBaseUrl } from "./environment.js";
2
+ //#region ../protocol/src/realtime/types.ts
3
+ const WS_COMPACT_STREAM_CAPABILITY = "session.compact_stream.v1";
4
+ const WS_ROOM_SUBSCRIPTION_CAPABILITY = "realtime.rooms.v1";
5
+ const getRealtimeSpaceRoom = (spaceId) => `space:${spaceId}`;
6
+ const parseRealtimeRoom = (room) => {
7
+ const trimmed = room.trim();
8
+ const separatorIndex = trimmed.indexOf(":");
9
+ if (separatorIndex <= 0) return null;
10
+ const kind = trimmed.slice(0, separatorIndex);
11
+ const id = trimmed.slice(separatorIndex + 1).trim();
12
+ if (!id) return null;
13
+ if (kind !== "space" && kind !== "user") return null;
14
+ return {
15
+ kind,
16
+ id
17
+ };
18
+ };
19
+ const normalizeRealtimeRooms = (rooms) => {
20
+ const normalized = /* @__PURE__ */ new Set();
21
+ for (const room of rooms) {
22
+ const parsed = typeof room === "string" ? parseRealtimeRoom(room) : null;
23
+ if (!parsed) continue;
24
+ normalized.add(`${parsed.kind}:${parsed.id}`);
25
+ }
26
+ return [...normalized];
27
+ };
28
+ const getNonEmptyString = (value) => typeof value === "string" && value.trim() ? value : null;
29
+ const getSessionTurnPatchStreamKey = (input, options = {}) => {
30
+ const turnId = getNonEmptyString(input.turnId);
31
+ const messageKey = getNonEmptyString(input.messageId) ?? getNonEmptyString(input.sourceMessageId) ?? getNonEmptyString(input.anchorUserMessageId) ?? (typeof input.messageOrdinal === "number" && Number.isFinite(input.messageOrdinal) ? `ordinal:${input.messageOrdinal}` : null);
32
+ if (turnId && messageKey) return `${turnId}:${messageKey}`;
33
+ const streamKey = messageKey ?? turnId;
34
+ if (streamKey) return streamKey;
35
+ return options.includeSessionFallback ? getNonEmptyString(input.sessionId) : null;
36
+ };
37
+ //#endregion
38
+ //#region src/transport.ts
39
+ const responseBodyForError = async (response) => {
40
+ return (response.headers.get("content-type") ?? "").includes("application/json") ? await response.json().catch(() => null) : await response.text().catch(() => response.statusText);
41
+ };
42
+ const messageFromErrorBody = (body, fallback) => {
43
+ if (typeof body === "string") return body.trim() || fallback;
44
+ if (body && typeof body === "object") {
45
+ const errorBody = body;
46
+ if (typeof errorBody.message === "string" && errorBody.message.trim()) return errorBody.message;
47
+ }
48
+ return fallback;
49
+ };
50
+ function errorCodeFromBody(body) {
51
+ if (!body || typeof body !== "object") return null;
52
+ const errorBody = body;
53
+ if (typeof errorBody.code === "string" && errorBody.code.trim()) return errorBody.code;
54
+ return null;
55
+ }
56
+ var HttpError = class extends Error {
57
+ status;
58
+ body;
59
+ code;
60
+ constructor(message, status, body) {
61
+ super(message);
62
+ this.name = "HttpError";
63
+ this.status = status;
64
+ this.body = body;
65
+ this.code = errorCodeFromBody(body);
66
+ }
67
+ };
68
+ var HttpTransport = class {
69
+ baseUrl;
70
+ fetcher;
71
+ getAccessToken;
72
+ onUnauthorized;
73
+ constructor(options = {}) {
74
+ this.baseUrl = resolveApiBaseUrl(options);
75
+ this.fetcher = options.fetch ?? fetch;
76
+ this.getAccessToken = options.getAccessToken;
77
+ this.onUnauthorized = options.onUnauthorized;
78
+ }
79
+ async withAuthorization(init, tokenOverride) {
80
+ const headers = new Headers(init?.headers);
81
+ const token = tokenOverride ?? (this.getAccessToken ? await this.getAccessToken() : null);
82
+ if (token) headers.set("Authorization", `Bearer ${token}`);
83
+ else headers.delete("Authorization");
84
+ return {
85
+ ...init,
86
+ headers
87
+ };
88
+ }
89
+ async send(path, init) {
90
+ const fetcher = init?.fetch ?? this.fetcher;
91
+ const url = this.baseUrl ? `${this.baseUrl}${path}` : path;
92
+ const response = await fetcher(url, await this.withAuthorization(init));
93
+ const getAccessToken = this.getAccessToken;
94
+ if (response.status === 401 && getAccessToken) {
95
+ const refreshedToken = await (async () => {
96
+ try {
97
+ return await getAccessToken({ forceRefresh: true });
98
+ } catch {
99
+ return null;
100
+ }
101
+ })();
102
+ if (refreshedToken) {
103
+ const retryResponse = await fetcher(url, await this.withAuthorization(init, refreshedToken));
104
+ if (retryResponse.status !== 401) {
105
+ if (!retryResponse.ok) {
106
+ const body = await responseBodyForError(retryResponse);
107
+ throw new HttpError(messageFromErrorBody(body, retryResponse.statusText), retryResponse.status, body);
108
+ }
109
+ return retryResponse;
110
+ }
111
+ }
112
+ }
113
+ if (response.status === 401) {
114
+ await this.onUnauthorized?.();
115
+ throw new HttpError("unauthorized", 401, null);
116
+ }
117
+ if (!response.ok) {
118
+ const body = await responseBodyForError(response);
119
+ throw new HttpError(messageFromErrorBody(body, response.statusText), response.status, body);
120
+ }
121
+ return response;
122
+ }
123
+ async request(path, init) {
124
+ const response = await this.send(path, init);
125
+ if (response.status === 204) return null;
126
+ return response.json();
127
+ }
128
+ async raw(path, init) {
129
+ const response = await this.send(path, init);
130
+ return {
131
+ response,
132
+ blob: () => response.blob(),
133
+ arrayBuffer: () => response.arrayBuffer(),
134
+ text: () => response.text(),
135
+ json: () => response.json()
136
+ };
137
+ }
138
+ async blob(path, init) {
139
+ return (await this.raw(path, init)).blob();
140
+ }
141
+ };
142
+ //#endregion
143
+ export { getRealtimeSpaceRoom as a, WS_ROOM_SUBSCRIPTION_CAPABILITY as i, HttpTransport as n, getSessionTurnPatchStreamKey as o, WS_COMPACT_STREAM_CAPABILITY as r, normalizeRealtimeRooms as s, HttpError as t };