@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.
- package/dist/chunks/http.d.ts +134 -1419
- package/dist/chunks/http.js +205 -110
- package/dist/chunks/transport.js +143 -0
- package/dist/chunks/websocket.d.ts +1593 -8
- package/dist/chunks/websocket.js +818 -0
- package/dist/http.d.ts +3 -3
- package/dist/http.js +2 -1
- package/dist/index.d.ts +21 -3
- package/dist/index.js +103 -27
- package/dist/websocket.js +1 -780
- package/docs/work-runtime-guide.md +35 -3
- package/package.json +1 -1
- package/dist/chunks/types.js +0 -37
package/dist/chunks/http.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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 {
|
|
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 };
|