hci-atrium 0.1.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/LICENSE +21 -0
- package/README.md +182 -0
- package/dist/atrium.d.ts +522 -0
- package/dist/atrium.d.ts.map +1 -0
- package/dist/atrium.js +806 -0
- package/dist/atrium.js.map +1 -0
- package/dist/auth.d.ts +85 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +369 -0
- package/dist/auth.js.map +1 -0
- package/dist/by.d.ts +84 -0
- package/dist/by.d.ts.map +1 -0
- package/dist/by.js +87 -0
- package/dist/by.js.map +1 -0
- package/dist/decider.d.ts +40 -0
- package/dist/decider.d.ts.map +1 -0
- package/dist/decider.js +76 -0
- package/dist/decider.js.map +1 -0
- package/dist/discovery.d.ts +15 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +38 -0
- package/dist/discovery.js.map +1 -0
- package/dist/env.d.ts +11 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +28 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +67 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +86 -0
- package/dist/errors.js.map +1 -0
- package/dist/files.d.ts +7 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +2 -0
- package/dist/files.js.map +1 -0
- package/dist/http.d.ts +11 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +21 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +456 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +487 -0
- package/dist/models.js.map +1 -0
- package/dist/pkce.d.ts +16 -0
- package/dist/pkce.d.ts.map +1 -0
- package/dist/pkce.js +40 -0
- package/dist/pkce.js.map +1 -0
- package/dist/react/index.d.ts +37 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +89 -0
- package/dist/react/index.js.map +1 -0
- package/dist/rooms/armed-set.d.ts +33 -0
- package/dist/rooms/armed-set.d.ts.map +1 -0
- package/dist/rooms/armed-set.js +65 -0
- package/dist/rooms/armed-set.js.map +1 -0
- package/dist/rooms/brokers.d.ts +62 -0
- package/dist/rooms/brokers.d.ts.map +1 -0
- package/dist/rooms/brokers.js +109 -0
- package/dist/rooms/brokers.js.map +1 -0
- package/dist/rooms/codec.d.ts +100 -0
- package/dist/rooms/codec.d.ts.map +1 -0
- package/dist/rooms/codec.js +101 -0
- package/dist/rooms/codec.js.map +1 -0
- package/dist/rooms/index.d.ts +40 -0
- package/dist/rooms/index.d.ts.map +1 -0
- package/dist/rooms/index.js +45 -0
- package/dist/rooms/index.js.map +1 -0
- package/dist/rooms/offer-capture.d.ts +96 -0
- package/dist/rooms/offer-capture.d.ts.map +1 -0
- package/dist/rooms/offer-capture.js +338 -0
- package/dist/rooms/offer-capture.js.map +1 -0
- package/dist/rooms/offer-transcription.d.ts +63 -0
- package/dist/rooms/offer-transcription.d.ts.map +1 -0
- package/dist/rooms/offer-transcription.js +231 -0
- package/dist/rooms/offer-transcription.js.map +1 -0
- package/dist/rooms/participant.d.ts +35 -0
- package/dist/rooms/participant.d.ts.map +1 -0
- package/dist/rooms/participant.js +53 -0
- package/dist/rooms/participant.js.map +1 -0
- package/dist/rooms/recorder.d.ts +74 -0
- package/dist/rooms/recorder.d.ts.map +1 -0
- package/dist/rooms/recorder.js +157 -0
- package/dist/rooms/recorder.js.map +1 -0
- package/dist/rooms/room.d.ts +542 -0
- package/dist/rooms/room.d.ts.map +1 -0
- package/dist/rooms/room.js +1357 -0
- package/dist/rooms/room.js.map +1 -0
- package/dist/rooms/snippet.d.ts +48 -0
- package/dist/rooms/snippet.d.ts.map +1 -0
- package/dist/rooms/snippet.js +70 -0
- package/dist/rooms/snippet.js.map +1 -0
- package/dist/rooms/sse.d.ts +102 -0
- package/dist/rooms/sse.d.ts.map +1 -0
- package/dist/rooms/sse.js +347 -0
- package/dist/rooms/sse.js.map +1 -0
- package/dist/rooms/standing-source.d.ts +190 -0
- package/dist/rooms/standing-source.d.ts.map +1 -0
- package/dist/rooms/standing-source.js +431 -0
- package/dist/rooms/standing-source.js.map +1 -0
- package/dist/rooms/transcriber.d.ts +81 -0
- package/dist/rooms/transcriber.d.ts.map +1 -0
- package/dist/rooms/transcriber.js +111 -0
- package/dist/rooms/transcriber.js.map +1 -0
- package/dist/rooms/transport-shared.d.ts +28 -0
- package/dist/rooms/transport-shared.d.ts.map +1 -0
- package/dist/rooms/transport-shared.js +42 -0
- package/dist/rooms/transport-shared.js.map +1 -0
- package/dist/rooms/transport.d.ts +161 -0
- package/dist/rooms/transport.d.ts.map +1 -0
- package/dist/rooms/transport.js +258 -0
- package/dist/rooms/transport.js.map +1 -0
- package/dist/shaping/assets.d.ts +42 -0
- package/dist/shaping/assets.d.ts.map +1 -0
- package/dist/shaping/assets.js +77 -0
- package/dist/shaping/assets.js.map +1 -0
- package/dist/shaping/chat.d.ts +39 -0
- package/dist/shaping/chat.d.ts.map +1 -0
- package/dist/shaping/chat.js +62 -0
- package/dist/shaping/chat.js.map +1 -0
- package/dist/shaping/index.d.ts +14 -0
- package/dist/shaping/index.d.ts.map +1 -0
- package/dist/shaping/index.js +13 -0
- package/dist/shaping/index.js.map +1 -0
- package/dist/shaping/library.d.ts +84 -0
- package/dist/shaping/library.d.ts.map +1 -0
- package/dist/shaping/library.js +288 -0
- package/dist/shaping/library.js.map +1 -0
- package/dist/shaping/request.d.ts +47 -0
- package/dist/shaping/request.d.ts.map +1 -0
- package/dist/shaping/request.js +25 -0
- package/dist/shaping/request.js.map +1 -0
- package/dist/shaping/rooms.d.ts +128 -0
- package/dist/shaping/rooms.d.ts.map +1 -0
- package/dist/shaping/rooms.js +330 -0
- package/dist/shaping/rooms.js.map +1 -0
- package/dist/shaping/sources.d.ts +29 -0
- package/dist/shaping/sources.d.ts.map +1 -0
- package/dist/shaping/sources.js +51 -0
- package/dist/shaping/sources.js.map +1 -0
- package/dist/shaping/structured.d.ts +47 -0
- package/dist/shaping/structured.d.ts.map +1 -0
- package/dist/shaping/structured.js +115 -0
- package/dist/shaping/structured.js.map +1 -0
- package/dist/storage.d.ts +36 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +86 -0
- package/dist/storage.js.map +1 -0
- package/dist/transcribe-stream.d.ts +81 -0
- package/dist/transcribe-stream.d.ts.map +1 -0
- package/dist/transcribe-stream.js +253 -0
- package/dist/transcribe-stream.js.map +1 -0
- package/dist/transcribe.d.ts +57 -0
- package/dist/transcribe.d.ts.map +1 -0
- package/dist/transcribe.js +267 -0
- package/dist/transcribe.js.map +1 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured-output plumbing — the mirror of ``_structured.py``, using **Zod** as the OpenAI TS SDK
|
|
3
|
+
* does (decision 3). Zod is an *optional* peer dependency: nothing here imports it at module top;
|
|
4
|
+
* a schema is duck-typed at runtime and the module is loaded lazily only to derive its JSON Schema.
|
|
5
|
+
*
|
|
6
|
+
* The flow (client-orchestrated, like Python's): send the schema as OpenAI's ``json_schema``
|
|
7
|
+
* response_format, validate the reply, and on failure run ONE repair turn before throwing
|
|
8
|
+
* {@link StructuredOutputError}. The pure pieces (JSON extraction, validation, repair messages) live
|
|
9
|
+
* here; the client owns the two ``chat`` calls.
|
|
10
|
+
*/
|
|
11
|
+
import { InvalidArgumentError, StructuredOutputError } from "../errors.js";
|
|
12
|
+
/** A safe, universally-supported response_format for the repair turn: plain JSON mode. */
|
|
13
|
+
export const JSON_OBJECT = { type: "json_object" };
|
|
14
|
+
// Sentinel: no JSON value at all in the reply — distinct from a parsed literal ``null``.
|
|
15
|
+
const NO_JSON = Symbol("no-json");
|
|
16
|
+
/** True when ``value`` looks like a Zod schema (the structured-output path). */
|
|
17
|
+
export function isZodSchema(value) {
|
|
18
|
+
return (typeof value === "object" &&
|
|
19
|
+
value !== null &&
|
|
20
|
+
"safeParse" in value &&
|
|
21
|
+
typeof value.safeParse === "function");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build the OpenAI ``json_schema`` response_format for a Zod schema. Async because it lazily loads
|
|
25
|
+
* Zod (v4's ``z.toJSONSchema``) — never imported at module top so the SDK installs without Zod.
|
|
26
|
+
* Throws {@link InvalidArgumentError} when Zod (v4+) isn't resolvable.
|
|
27
|
+
*/
|
|
28
|
+
export async function responseFormatParam(schema, name = "response") {
|
|
29
|
+
// Non-literal specifier: TS treats the import as ``any`` and skips resolving "zod" at build time,
|
|
30
|
+
// so the optional peer dep need not be installed to compile the SDK.
|
|
31
|
+
const specifier = "zod";
|
|
32
|
+
let jsonSchema;
|
|
33
|
+
try {
|
|
34
|
+
const zod = (await import(specifier));
|
|
35
|
+
if (typeof zod.toJSONSchema !== "function") {
|
|
36
|
+
throw new Error("z.toJSONSchema is unavailable (Zod v4+ required)");
|
|
37
|
+
}
|
|
38
|
+
jsonSchema = zod.toJSONSchema(schema);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
throw new InvalidArgumentError("structured output needs Zod v4+ installed (npm i zod); " +
|
|
42
|
+
`could not derive a JSON schema: ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
return { type: "json_schema", json_schema: { name, schema: jsonSchema } };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse a JSON value out of a model reply — the whole string, else the outermost ``{…}`` or
|
|
48
|
+
* ``[…]`` (tolerating code fences / surrounding chatter). {@link NO_JSON} when nothing parses.
|
|
49
|
+
*/
|
|
50
|
+
function extractJson(text) {
|
|
51
|
+
const stripped = text.trim();
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(stripped);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// fall through to the bracket-slice fallback
|
|
57
|
+
}
|
|
58
|
+
// Try the EARLIEST opener first (it's the outermost structure).
|
|
59
|
+
const slices = [];
|
|
60
|
+
for (const [opener, closer] of [
|
|
61
|
+
["{", "}"],
|
|
62
|
+
["[", "]"],
|
|
63
|
+
]) {
|
|
64
|
+
const start = stripped.indexOf(opener);
|
|
65
|
+
const end = stripped.lastIndexOf(closer);
|
|
66
|
+
if (start !== -1 && end > start) {
|
|
67
|
+
slices.push([start, stripped.slice(start, end + 1)]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
slices.sort((a, b) => a[0] - b[0]);
|
|
71
|
+
for (const [, blob] of slices) {
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(blob);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// try the next slice
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return NO_JSON;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Validate ``text`` into ``schema``. Returns ``[value, ""]`` on success or ``[null, detail]`` with
|
|
83
|
+
* a human-readable reason on failure (never throws).
|
|
84
|
+
*/
|
|
85
|
+
export function tryParse(schema, text) {
|
|
86
|
+
const payload = extractJson(text);
|
|
87
|
+
if (payload === NO_JSON) {
|
|
88
|
+
return [null, "no JSON value found in the reply"];
|
|
89
|
+
}
|
|
90
|
+
const result = schema.safeParse(payload);
|
|
91
|
+
if (result.success) {
|
|
92
|
+
return [result.data, ""];
|
|
93
|
+
}
|
|
94
|
+
return [null, String(result.error?.message ?? result.error)];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The message list for the single repair turn: the prior turns, the model's bad reply, and a
|
|
98
|
+
* correction quoting the error and the JSON schema to conform to.
|
|
99
|
+
*/
|
|
100
|
+
export function repairMessages(messages, badReply, detail, jsonSchema) {
|
|
101
|
+
const correction = "Your previous reply was not valid JSON for the required schema.\n" +
|
|
102
|
+
`Error: ${detail}\n` +
|
|
103
|
+
"Return ONLY a JSON object matching this schema — no prose, no code fences:\n" +
|
|
104
|
+
JSON.stringify(jsonSchema);
|
|
105
|
+
return [
|
|
106
|
+
...messages,
|
|
107
|
+
{ role: "assistant", content: badReply },
|
|
108
|
+
{ role: "user", content: correction },
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
/** Build the terminal error raised when the repair turn still didn't yield valid output. */
|
|
112
|
+
export function structuredError(schemaName, raw, detail) {
|
|
113
|
+
return new StructuredOutputError(schemaName, raw, detail);
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=structured.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structured.js","sourceRoot":"","sources":["../../src/shaping/structured.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-storage helpers, namespaced per ``(baseUrl, clientId)`` so multiple ``Atrium`` instances
|
|
3
|
+
* never collide.
|
|
4
|
+
*
|
|
5
|
+
* Every access is guarded: on a server, in private-mode browsers, or wherever storage throws, the
|
|
6
|
+
* stores degrade to no-ops rather than throwing — importing and constructing the SDK stays safe.
|
|
7
|
+
*/
|
|
8
|
+
/** Persisted OAuth tokens. ``expiresAt`` is an epoch-millisecond timestamp. */
|
|
9
|
+
export interface StoredTokens {
|
|
10
|
+
accessToken: string;
|
|
11
|
+
refreshToken: string | null;
|
|
12
|
+
expiresAt: number;
|
|
13
|
+
}
|
|
14
|
+
/** The transient PKCE state stashed across the authorize redirect. */
|
|
15
|
+
export interface PkceStash {
|
|
16
|
+
verifier: string;
|
|
17
|
+
state: string;
|
|
18
|
+
redirectUri: string;
|
|
19
|
+
}
|
|
20
|
+
/** Long-lived token cache in ``localStorage``. */
|
|
21
|
+
export declare class TokenStore {
|
|
22
|
+
private readonly key;
|
|
23
|
+
constructor(baseUrl: string, clientId: string);
|
|
24
|
+
load(): StoredTokens | null;
|
|
25
|
+
save(tokens: StoredTokens): void;
|
|
26
|
+
clear(): void;
|
|
27
|
+
}
|
|
28
|
+
/** Short-lived PKCE stash in ``sessionStorage`` (survives the authorize redirect, not a new tab). */
|
|
29
|
+
export declare class PkceStore {
|
|
30
|
+
private readonly key;
|
|
31
|
+
constructor(baseUrl: string, clientId: string);
|
|
32
|
+
save(stash: PkceStash): void;
|
|
33
|
+
load(): PkceStash | null;
|
|
34
|
+
clear(): void;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,sEAAsE;AACtE,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;CACpB;AAqBD,kDAAkD;AAClD,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;gBAEhB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAI7C,IAAI,IAAI,YAAY,GAAG,IAAI;IAiB3B,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAIhC,KAAK,IAAI,IAAI;CAGd;AAED,qGAAqG;AACrG,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;gBAEhB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAI7C,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAI5B,IAAI,IAAI,SAAS,GAAG,IAAI;IAiBxB,KAAK,IAAI,IAAI;CAGd"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-storage helpers, namespaced per ``(baseUrl, clientId)`` so multiple ``Atrium`` instances
|
|
3
|
+
* never collide.
|
|
4
|
+
*
|
|
5
|
+
* Every access is guarded: on a server, in private-mode browsers, or wherever storage throws, the
|
|
6
|
+
* stores degrade to no-ops rather than throwing — importing and constructing the SDK stays safe.
|
|
7
|
+
*/
|
|
8
|
+
function safeStorage(kind) {
|
|
9
|
+
try {
|
|
10
|
+
const store = kind === "local" ? globalThis.localStorage : globalThis.sessionStorage;
|
|
11
|
+
if (!store) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const probe = "__atrium_probe__";
|
|
15
|
+
store.setItem(probe, "1");
|
|
16
|
+
store.removeItem(probe);
|
|
17
|
+
return store;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function namespaced(prefix, baseUrl, clientId) {
|
|
24
|
+
return `${prefix}:${baseUrl}|${clientId}`;
|
|
25
|
+
}
|
|
26
|
+
/** Long-lived token cache in ``localStorage``. */
|
|
27
|
+
export class TokenStore {
|
|
28
|
+
key;
|
|
29
|
+
constructor(baseUrl, clientId) {
|
|
30
|
+
this.key = namespaced("atrium.tokens", baseUrl, clientId);
|
|
31
|
+
}
|
|
32
|
+
load() {
|
|
33
|
+
const store = safeStorage("local");
|
|
34
|
+
if (!store) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const raw = store.getItem(this.key);
|
|
38
|
+
if (!raw) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
return typeof parsed?.accessToken === "string" ? parsed : null;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
save(tokens) {
|
|
50
|
+
safeStorage("local")?.setItem(this.key, JSON.stringify(tokens));
|
|
51
|
+
}
|
|
52
|
+
clear() {
|
|
53
|
+
safeStorage("local")?.removeItem(this.key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Short-lived PKCE stash in ``sessionStorage`` (survives the authorize redirect, not a new tab). */
|
|
57
|
+
export class PkceStore {
|
|
58
|
+
key;
|
|
59
|
+
constructor(baseUrl, clientId) {
|
|
60
|
+
this.key = namespaced("atrium.pkce", baseUrl, clientId);
|
|
61
|
+
}
|
|
62
|
+
save(stash) {
|
|
63
|
+
safeStorage("session")?.setItem(this.key, JSON.stringify(stash));
|
|
64
|
+
}
|
|
65
|
+
load() {
|
|
66
|
+
const store = safeStorage("session");
|
|
67
|
+
if (!store) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const raw = store.getItem(this.key);
|
|
71
|
+
if (!raw) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(raw);
|
|
76
|
+
return typeof parsed?.verifier === "string" ? parsed : null;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
clear() {
|
|
83
|
+
safeStorage("session")?.removeItem(this.key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A narrow WebSocket client for the live transcription route (``WS /v1/audio/transcriptions/stream``)
|
|
3
|
+
* — the TypeScript port of Python's ``hci_atrium._transcribe_stream``.
|
|
4
|
+
*
|
|
5
|
+
* That route speaks the ``atrium.stt.v1`` *superset* dialect: a JSON ``start`` handshake, binary audio
|
|
6
|
+
* frames, a ``stop``, and ``ready`` / ``partial`` / ``final`` / ``done`` / ``error`` events down. The
|
|
7
|
+
* dispatch brain (streaming relay vs rolling-buffer re-transcription) lives in the backend; this
|
|
8
|
+
* client is dumb by design — connect, ``start``, send bytes / stop, iterate events, close.
|
|
9
|
+
*
|
|
10
|
+
* **Auth divergence from Python (browser-safe):** Python rides an ``Authorization: Bearer`` header on
|
|
11
|
+
* the handshake, which browsers can't set on a ``WebSocket``. So this client carries the credential
|
|
12
|
+
* in-band as an ``api_key`` field on the ``start`` frame — the backend's browser-safe first-frame path:
|
|
13
|
+
* it accepts the socket, reads ``start``, pops ``api_key`` (never logged), and validates it exactly
|
|
14
|
+
* like the header path. This keeps the long-lived credential out of the URL (and so out of every proxy
|
|
15
|
+
* and access log on the path), unlike the legacy ``?api_key=`` query the backend still tolerates. An
|
|
16
|
+
* invalid/missing key closes ``4401`` — arriving *after* the socket opened, but still surfaced as
|
|
17
|
+
* {@link AuthError}, never a silent fallback.
|
|
18
|
+
*/
|
|
19
|
+
import type { WebSocketCtor } from "./rooms/transport.js";
|
|
20
|
+
/**
|
|
21
|
+
* Internal signal: the stream route isn't reachable *as a stream* for this caller — a refused
|
|
22
|
+
* connection, a proxy that strips the WebSocket upgrade, a non-``ready`` first frame, or a handshake
|
|
23
|
+
* timeout. The caller degrades to per-clip batch transcription. **Not** raised for an auth rejection
|
|
24
|
+
* ({@link AUTH_CLOSE_CODE}), which surfaces as {@link AuthError} since a silent fallback would just
|
|
25
|
+
* mask a fixable credential problem.
|
|
26
|
+
*/
|
|
27
|
+
export declare class StreamUnavailable extends Error {
|
|
28
|
+
constructor(message: string);
|
|
29
|
+
}
|
|
30
|
+
/** Options for opening a {@link TranscriptionStream}. */
|
|
31
|
+
export interface TranscriptionStreamOptions {
|
|
32
|
+
/** Bearer/API-key credential — rides the ``start`` frame's ``api_key`` field (browsers can't set WS
|
|
33
|
+
* headers; the in-band path keeps it out of the URL and so out of proxy/access logs). */
|
|
34
|
+
token: string;
|
|
35
|
+
model?: string;
|
|
36
|
+
language?: string;
|
|
37
|
+
/** ``"pcm16"`` for a raw headerless stream (needs ``sampleRate``), else a container format. */
|
|
38
|
+
format: string;
|
|
39
|
+
sampleRate?: number;
|
|
40
|
+
/** Injected ``WebSocket`` constructor (defaults to the runtime global) — the test seam / Node <22. */
|
|
41
|
+
WebSocketCtor?: WebSocketCtor;
|
|
42
|
+
}
|
|
43
|
+
/** One open live-transcription WebSocket. Construct via {@link TranscriptionStream.open}. */
|
|
44
|
+
export declare class TranscriptionStream {
|
|
45
|
+
private readonly ws;
|
|
46
|
+
private readonly inbox;
|
|
47
|
+
private closedItem;
|
|
48
|
+
private notify;
|
|
49
|
+
private opened;
|
|
50
|
+
private openResolve;
|
|
51
|
+
private openReject;
|
|
52
|
+
private constructor();
|
|
53
|
+
/**
|
|
54
|
+
* Connect, send ``start``, and wait for the server's ``ready`` frame. Rejects with {@link AuthError}
|
|
55
|
+
* on a rejected credential ({@link AUTH_CLOSE_CODE}) and {@link StreamUnavailable} for every other
|
|
56
|
+
* handshake failure — the caller turns the latter into a per-clip batch fallback.
|
|
57
|
+
*/
|
|
58
|
+
static open(baseUrl: string, opts: TranscriptionStreamOptions): Promise<TranscriptionStream>;
|
|
59
|
+
/** Send one binary audio frame (a PCM16 slice, or a self-contained container clip). */
|
|
60
|
+
sendBytes(data: ArrayBuffer): void;
|
|
61
|
+
/** Signal end-of-input — the server flushes, sends ``done``, and closes. */
|
|
62
|
+
sendStop(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Iterate inbound JSON events until the connection closes. Binary frames and unparseable text are
|
|
65
|
+
* skipped; unknown ``type``s are yielded as-is (the caller ignores what it doesn't know).
|
|
66
|
+
*
|
|
67
|
+
* The close code is *not* swallowed: a mid-session {@link AUTH_CLOSE_CODE} throws {@link AuthError},
|
|
68
|
+
* a clean ``1000`` returns normally (the caller then checks whether a ``done`` actually arrived), and
|
|
69
|
+
* any other (abnormal) close throws {@link AtriumError} — a server crash / proxy kill / dropped
|
|
70
|
+
* connection must never read as a clean end.
|
|
71
|
+
*/
|
|
72
|
+
events(): AsyncGenerator<Record<string, unknown>>;
|
|
73
|
+
/** Close the underlying socket (never throws). */
|
|
74
|
+
close(): void;
|
|
75
|
+
private attach;
|
|
76
|
+
private push;
|
|
77
|
+
private recv;
|
|
78
|
+
private recvWithin;
|
|
79
|
+
private waitOpen;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=transcribe-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcribe-stream.d.ts","sourceRoot":"","sources":["../src/transcribe-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,sBAAsB,CAAA;AAYxE;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAK5B;AAED,yDAAyD;AACzD,MAAM,WAAW,0BAA0B;IACzC;6FACyF;IACzF,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+FAA+F;IAC/F,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sGAAsG;IACtG,aAAa,CAAC,EAAE,aAAa,CAAA;CAC9B;AAaD,6FAA6F;AAC7F,qBAAa,mBAAmB;IAQV,OAAO,CAAC,QAAQ,CAAC,EAAE;IAPvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,UAAU,CAA2C;IAE7D,OAAO;IAEP;;;;OAIG;WACU,IAAI,CACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,mBAAmB,CAAC;IAkD/B,uFAAuF;IACvF,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAIlC,4EAA4E;IAC5E,QAAQ,IAAI,IAAI;IAIhB;;;;;;;;OAQG;IACI,MAAM,IAAI,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAgBxD,kDAAkD;IAClD,KAAK,IAAI,IAAI;IAUb,OAAO,CAAC,MAAM;IAwBd,OAAO,CAAC,IAAI;YAUE,IAAI;IASlB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,QAAQ;CAajB"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A narrow WebSocket client for the live transcription route (``WS /v1/audio/transcriptions/stream``)
|
|
3
|
+
* — the TypeScript port of Python's ``hci_atrium._transcribe_stream``.
|
|
4
|
+
*
|
|
5
|
+
* That route speaks the ``atrium.stt.v1`` *superset* dialect: a JSON ``start`` handshake, binary audio
|
|
6
|
+
* frames, a ``stop``, and ``ready`` / ``partial`` / ``final`` / ``done`` / ``error`` events down. The
|
|
7
|
+
* dispatch brain (streaming relay vs rolling-buffer re-transcription) lives in the backend; this
|
|
8
|
+
* client is dumb by design — connect, ``start``, send bytes / stop, iterate events, close.
|
|
9
|
+
*
|
|
10
|
+
* **Auth divergence from Python (browser-safe):** Python rides an ``Authorization: Bearer`` header on
|
|
11
|
+
* the handshake, which browsers can't set on a ``WebSocket``. So this client carries the credential
|
|
12
|
+
* in-band as an ``api_key`` field on the ``start`` frame — the backend's browser-safe first-frame path:
|
|
13
|
+
* it accepts the socket, reads ``start``, pops ``api_key`` (never logged), and validates it exactly
|
|
14
|
+
* like the header path. This keeps the long-lived credential out of the URL (and so out of every proxy
|
|
15
|
+
* and access log on the path), unlike the legacy ``?api_key=`` query the backend still tolerates. An
|
|
16
|
+
* invalid/missing key closes ``4401`` — arriving *after* the socket opened, but still surfaced as
|
|
17
|
+
* {@link AuthError}, never a silent fallback.
|
|
18
|
+
*/
|
|
19
|
+
import { AtriumError, AuthError } from "./errors.js";
|
|
20
|
+
const STREAM_PATH = "/v1/audio/transcriptions/stream";
|
|
21
|
+
/** The server closes with this application code when the credential is rejected — distinct from a
|
|
22
|
+
* "route not here" failure because retrying won't heal it (so it surfaces as {@link AuthError}, not a
|
|
23
|
+
* {@link StreamUnavailable} fallback). */
|
|
24
|
+
const AUTH_CLOSE_CODE = 4401;
|
|
25
|
+
/** Budget for the whole handshake (connect + the ``ready`` reply). Python leans on ``websockets``'
|
|
26
|
+
* default ``open_timeout`` (~10s); a browser ``WebSocket`` can't separate connect from the first read,
|
|
27
|
+
* so one budget covers both — a hung route degrades to the per-clip fallback rather than hanging. */
|
|
28
|
+
const HANDSHAKE_TIMEOUT_MS = 10_000;
|
|
29
|
+
/**
|
|
30
|
+
* Internal signal: the stream route isn't reachable *as a stream* for this caller — a refused
|
|
31
|
+
* connection, a proxy that strips the WebSocket upgrade, a non-``ready`` first frame, or a handshake
|
|
32
|
+
* timeout. The caller degrades to per-clip batch transcription. **Not** raised for an auth rejection
|
|
33
|
+
* ({@link AUTH_CLOSE_CODE}), which surfaces as {@link AuthError} since a silent fallback would just
|
|
34
|
+
* mask a fixable credential problem.
|
|
35
|
+
*/
|
|
36
|
+
export class StreamUnavailable extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "StreamUnavailable";
|
|
40
|
+
Object.setPrototypeOf(this, StreamUnavailable.prototype);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function toWsScheme(baseUrl) {
|
|
44
|
+
return baseUrl.replace(/^http/, "ws").replace(/\/+$/, "");
|
|
45
|
+
}
|
|
46
|
+
function streamUrl(baseUrl) {
|
|
47
|
+
// No query params: the credential rides the ``start`` frame, everything else the ``start`` message.
|
|
48
|
+
return `${toWsScheme(baseUrl)}${STREAM_PATH}`;
|
|
49
|
+
}
|
|
50
|
+
/** One open live-transcription WebSocket. Construct via {@link TranscriptionStream.open}. */
|
|
51
|
+
export class TranscriptionStream {
|
|
52
|
+
ws;
|
|
53
|
+
inbox = [];
|
|
54
|
+
closedItem = null;
|
|
55
|
+
notify = null;
|
|
56
|
+
opened = false;
|
|
57
|
+
openResolve = null;
|
|
58
|
+
openReject = null;
|
|
59
|
+
constructor(ws) {
|
|
60
|
+
this.ws = ws;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Connect, send ``start``, and wait for the server's ``ready`` frame. Rejects with {@link AuthError}
|
|
64
|
+
* on a rejected credential ({@link AUTH_CLOSE_CODE}) and {@link StreamUnavailable} for every other
|
|
65
|
+
* handshake failure — the caller turns the latter into a per-clip batch fallback.
|
|
66
|
+
*/
|
|
67
|
+
static async open(baseUrl, opts) {
|
|
68
|
+
const Ctor = opts.WebSocketCtor ?? globalThis.WebSocket;
|
|
69
|
+
if (!Ctor) {
|
|
70
|
+
throw new Error("Atrium: no WebSocket implementation — pass { webSocketCtor } (Node <22 has none global)");
|
|
71
|
+
}
|
|
72
|
+
const ws = new Ctor(streamUrl(baseUrl));
|
|
73
|
+
const stream = new TranscriptionStream(ws);
|
|
74
|
+
stream.attach();
|
|
75
|
+
try {
|
|
76
|
+
await stream.waitOpen();
|
|
77
|
+
}
|
|
78
|
+
catch (reason) {
|
|
79
|
+
stream.close();
|
|
80
|
+
if (isCloseItem(reason) && reason.code === AUTH_CLOSE_CODE) {
|
|
81
|
+
throw new AuthError(401, "transcription stream rejected the credential (close 4401)");
|
|
82
|
+
}
|
|
83
|
+
throw new StreamUnavailable(`transcription stream handshake failed: ${describe(reason)}`);
|
|
84
|
+
}
|
|
85
|
+
// The credential rides here (the browser-safe first-frame path), so the server pops it before any
|
|
86
|
+
// processing. A rejected key closes 4401 *after* the socket opened — caught below as AuthError.
|
|
87
|
+
const start = {
|
|
88
|
+
type: "start",
|
|
89
|
+
protocol: 1,
|
|
90
|
+
format: opts.format,
|
|
91
|
+
api_key: opts.token,
|
|
92
|
+
};
|
|
93
|
+
if (opts.language != null)
|
|
94
|
+
start.language = opts.language;
|
|
95
|
+
if (opts.model != null)
|
|
96
|
+
start.model = opts.model;
|
|
97
|
+
if (opts.format === "pcm16")
|
|
98
|
+
start.sample_rate = opts.sampleRate;
|
|
99
|
+
ws.send(JSON.stringify(start));
|
|
100
|
+
const first = await stream.recvWithin(HANDSHAKE_TIMEOUT_MS);
|
|
101
|
+
if (first.kind === "close") {
|
|
102
|
+
stream.close();
|
|
103
|
+
// A post-open 4401 is the rejected first-frame credential — AuthError, never a degrade-to-batch
|
|
104
|
+
// fallback (a fixable credential problem must not silently mask itself).
|
|
105
|
+
if (first.code === AUTH_CLOSE_CODE) {
|
|
106
|
+
throw new AuthError(401, "transcription stream rejected the credential (close 4401)");
|
|
107
|
+
}
|
|
108
|
+
throw new StreamUnavailable(`stream closed during handshake (close ${first.code ?? "?"})`);
|
|
109
|
+
}
|
|
110
|
+
const msg = parseJson(first.data);
|
|
111
|
+
if (msg && msg.type === "ready")
|
|
112
|
+
return stream;
|
|
113
|
+
stream.close();
|
|
114
|
+
throw new StreamUnavailable(`expected a 'ready' frame, got: ${JSON.stringify(first.data)}`);
|
|
115
|
+
}
|
|
116
|
+
/** Send one binary audio frame (a PCM16 slice, or a self-contained container clip). */
|
|
117
|
+
sendBytes(data) {
|
|
118
|
+
this.ws.send(data);
|
|
119
|
+
}
|
|
120
|
+
/** Signal end-of-input — the server flushes, sends ``done``, and closes. */
|
|
121
|
+
sendStop() {
|
|
122
|
+
this.ws.send(JSON.stringify({ type: "stop" }));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Iterate inbound JSON events until the connection closes. Binary frames and unparseable text are
|
|
126
|
+
* skipped; unknown ``type``s are yielded as-is (the caller ignores what it doesn't know).
|
|
127
|
+
*
|
|
128
|
+
* The close code is *not* swallowed: a mid-session {@link AUTH_CLOSE_CODE} throws {@link AuthError},
|
|
129
|
+
* a clean ``1000`` returns normally (the caller then checks whether a ``done`` actually arrived), and
|
|
130
|
+
* any other (abnormal) close throws {@link AtriumError} — a server crash / proxy kill / dropped
|
|
131
|
+
* connection must never read as a clean end.
|
|
132
|
+
*/
|
|
133
|
+
async *events() {
|
|
134
|
+
while (true) {
|
|
135
|
+
const item = await this.recv();
|
|
136
|
+
if (item.kind === "close") {
|
|
137
|
+
if (item.code === AUTH_CLOSE_CODE) {
|
|
138
|
+
throw new AuthError(401, "transcription stream rejected the credential (close 4401)");
|
|
139
|
+
}
|
|
140
|
+
if (item.code === 1000)
|
|
141
|
+
return;
|
|
142
|
+
throw new AtriumError(`transcription stream closed abnormally (close ${item.code ?? "?"})`);
|
|
143
|
+
}
|
|
144
|
+
if (typeof item.data !== "string")
|
|
145
|
+
continue; // binary — skip
|
|
146
|
+
const msg = parseJson(item.data);
|
|
147
|
+
if (msg)
|
|
148
|
+
yield msg;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Close the underlying socket (never throws). */
|
|
152
|
+
close() {
|
|
153
|
+
try {
|
|
154
|
+
this.ws.close();
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Closing a half-broken socket must never mask the real error.
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// --- internals ---------------------------------------------------------------------------------
|
|
161
|
+
attach() {
|
|
162
|
+
this.ws.binaryType = "arraybuffer";
|
|
163
|
+
this.ws.onopen = () => {
|
|
164
|
+
this.opened = true;
|
|
165
|
+
const resolve = this.openResolve;
|
|
166
|
+
this.openResolve = null;
|
|
167
|
+
this.openReject = null;
|
|
168
|
+
resolve?.();
|
|
169
|
+
};
|
|
170
|
+
this.ws.onmessage = (ev) => this.push({ kind: "message", data: ev.data });
|
|
171
|
+
this.ws.onclose = (ev) => {
|
|
172
|
+
const code = ev?.code;
|
|
173
|
+
const item = { kind: "close", code };
|
|
174
|
+
this.push(item);
|
|
175
|
+
if (!this.opened) {
|
|
176
|
+
const reject = this.openReject;
|
|
177
|
+
this.openResolve = null;
|
|
178
|
+
this.openReject = null;
|
|
179
|
+
reject?.(item);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
this.ws.onerror = () => { };
|
|
183
|
+
}
|
|
184
|
+
push(item) {
|
|
185
|
+
// A close is sticky (delivered once buffered messages drain, then repeatedly) so a late reader
|
|
186
|
+
// still sees the end; messages queue in order.
|
|
187
|
+
if (item.kind === "close")
|
|
188
|
+
this.closedItem = item;
|
|
189
|
+
else
|
|
190
|
+
this.inbox.push(item);
|
|
191
|
+
const wake = this.notify;
|
|
192
|
+
this.notify = null;
|
|
193
|
+
wake?.();
|
|
194
|
+
}
|
|
195
|
+
async recv() {
|
|
196
|
+
while (this.inbox.length === 0 && this.closedItem === null) {
|
|
197
|
+
await new Promise((resolve) => {
|
|
198
|
+
this.notify = resolve;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return this.inbox.shift() ?? this.closedItem;
|
|
202
|
+
}
|
|
203
|
+
recvWithin(ms) {
|
|
204
|
+
return withTimeout(this.recv(), ms, () => new StreamUnavailable("handshake timed out"));
|
|
205
|
+
}
|
|
206
|
+
waitOpen() {
|
|
207
|
+
if (this.opened)
|
|
208
|
+
return Promise.resolve();
|
|
209
|
+
if (this.closedItem)
|
|
210
|
+
return Promise.reject(this.closedItem);
|
|
211
|
+
const gate = new Promise((resolve, reject) => {
|
|
212
|
+
this.openResolve = resolve;
|
|
213
|
+
this.openReject = reject;
|
|
214
|
+
});
|
|
215
|
+
return withTimeout(gate, HANDSHAKE_TIMEOUT_MS, () => new StreamUnavailable("handshake timed out"));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function withTimeout(promise, ms, onTimeout) {
|
|
219
|
+
return new Promise((resolve, reject) => {
|
|
220
|
+
const timer = setTimeout(() => reject(onTimeout()), ms);
|
|
221
|
+
promise.then((value) => {
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
resolve(value);
|
|
224
|
+
}, (reason) => {
|
|
225
|
+
clearTimeout(timer);
|
|
226
|
+
reject(reason);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function isCloseItem(value) {
|
|
231
|
+
return typeof value === "object" && value !== null && value.kind === "close";
|
|
232
|
+
}
|
|
233
|
+
function describe(reason) {
|
|
234
|
+
if (isCloseItem(reason))
|
|
235
|
+
return `closed before ready (close ${reason.code ?? "?"})`;
|
|
236
|
+
if (reason instanceof Error)
|
|
237
|
+
return reason.message;
|
|
238
|
+
return String(reason);
|
|
239
|
+
}
|
|
240
|
+
function parseJson(raw) {
|
|
241
|
+
if (typeof raw !== "string")
|
|
242
|
+
return null;
|
|
243
|
+
try {
|
|
244
|
+
const value = JSON.parse(raw);
|
|
245
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
246
|
+
? value
|
|
247
|
+
: null;
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=transcribe-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcribe-stream.js","sourceRoot":"","sources":["../src/transcribe-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAGpD,MAAM,WAAW,GAAG,iCAAiC,CAAA;AACrD;;0CAE0C;AAC1C,MAAM,eAAe,GAAG,IAAI,CAAA;AAC5B;;qGAEqG;AACrG,MAAM,oBAAoB,GAAG,MAAM,CAAA;AAEnC;;;;;;GAMG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;QAC/B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAA;IAC1D,CAAC;CACF;AAgBD,SAAS,UAAU,CAAC,OAAe;IACjC,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AAC3D,CAAC;AAED,SAAS,SAAS,CAAC,OAAe;IAChC,oGAAoG;IACpG,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,WAAW,EAAE,CAAA;AAC/C,CAAC;AAID,6FAA6F;AAC7F,MAAM,OAAO,mBAAmB;IAQO;IAPpB,KAAK,GAAc,EAAE,CAAA;IAC9B,UAAU,GAAmB,IAAI,CAAA;IACjC,MAAM,GAAwB,IAAI,CAAA;IAClC,MAAM,GAAG,KAAK,CAAA;IACd,WAAW,GAAwB,IAAI,CAAA;IACvC,UAAU,GAAuC,IAAI,CAAA;IAE7D,YAAqC,EAAiB;QAAjB,OAAE,GAAF,EAAE,CAAe;IAAG,CAAC;IAE1D;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,CACf,OAAe,EACf,IAAgC;QAEhC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,IAAK,UAA4C,CAAC,SAAS,CAAA;QAC1F,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAA;QACH,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;QACvC,MAAM,MAAM,GAAG,IAAI,mBAAmB,CAAC,EAAE,CAAC,CAAA;QAC1C,MAAM,CAAC,MAAM,EAAE,CAAA;QAEf,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAA;QACzB,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YAChB,MAAM,CAAC,KAAK,EAAE,CAAA;YACd,IAAI,WAAW,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBAC3D,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,2DAA2D,CAAC,CAAA;YACvF,CAAC;YACD,MAAM,IAAI,iBAAiB,CAAC,0CAA0C,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAC3F,CAAC;QAED,kGAAkG;QAClG,gGAAgG;QAChG,MAAM,KAAK,GAA4B;YACrC,IAAI,EAAE,OAAO;YACb,QAAQ,EAAE,CAAC;YACX,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,OAAO,EAAE,IAAI,CAAC,KAAK;SACpB,CAAA;QACD,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI;YAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAA;QACzD,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI;YAAE,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;QAChD,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO;YAAE,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,UAAU,CAAA;QAChE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;QAE9B,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAA;QAC3D,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC3B,MAAM,CAAC,KAAK,EAAE,CAAA;YACd,gGAAgG;YAChG,yEAAyE;YACzE,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBACnC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,2DAA2D,CAAC,CAAA;YACvF,CAAC;YACD,MAAM,IAAI,iBAAiB,CAAC,yCAAyC,KAAK,CAAC,IAAI,IAAI,GAAG,GAAG,CAAC,CAAA;QAC5F,CAAC;QACD,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,MAAM,CAAA;QAC9C,MAAM,CAAC,KAAK,EAAE,CAAA;QACd,MAAM,IAAI,iBAAiB,CAAC,kCAAkC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC7F,CAAC;IAED,uFAAuF;IACvF,SAAS,CAAC,IAAiB;QACzB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACpB,CAAC;IAED,4EAA4E;IAC5E,QAAQ;QACN,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;IAChD,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,CAAC,MAAM;QACX,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;YAC9B,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC1B,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;oBAClC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,2DAA2D,CAAC,CAAA;gBACvF,CAAC;gBACD,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;oBAAE,OAAM;gBAC9B,MAAM,IAAI,WAAW,CAAC,iDAAiD,IAAI,CAAC,IAAI,IAAI,GAAG,GAAG,CAAC,CAAA;YAC7F,CAAC;YACD,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;gBAAE,SAAQ,CAAC,gBAAgB;YAC5D,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAChC,IAAI,GAAG;gBAAE,MAAM,GAAG,CAAA;QACpB,CAAC;IACH,CAAC;IAED,kDAAkD;IAClD,KAAK;QACH,IAAI,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAA;QACjB,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;IACH,CAAC;IAED,kGAAkG;IAE1F,MAAM;QACZ,IAAI,CAAC,EAAE,CAAC,UAAU,GAAG,aAAa,CAAA;QAClC,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,GAAG,EAAE;YACpB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;YAClB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAA;YAChC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;YACvB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;YACtB,OAAO,EAAE,EAAE,CAAA;QACb,CAAC,CAAA;QACD,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;QACzE,IAAI,CAAC,EAAE,CAAC,OAAO,GAAG,CAAC,EAAE,EAAE,EAAE;YACvB,MAAM,IAAI,GAAI,EAAoC,EAAE,IAAI,CAAA;YACxD,MAAM,IAAI,GAAY,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;YAC7C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACf,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACjB,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAA;gBAC9B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;gBACvB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;gBACtB,MAAM,EAAE,CAAC,IAAI,CAAC,CAAA;YAChB,CAAC;QACH,CAAC,CAAA;QACD,IAAI,CAAC,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE,GAAE,CAAC,CAAA;IAC5B,CAAC;IAEO,IAAI,CAAC,IAAa;QACxB,+FAA+F;QAC/F,+CAA+C;QAC/C,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YAAE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;;YAC5C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAA;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;QAClB,IAAI,EAAE,EAAE,CAAA;IACV,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC3D,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAClC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAA;YACvB,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,IAAK,IAAI,CAAC,UAAsB,CAAA;IAC3D,CAAC;IAEO,UAAU,CAAC,EAAU;QAC3B,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,iBAAiB,CAAC,qBAAqB,CAAC,CAAC,CAAA;IACzF,CAAC;IAEO,QAAQ;QACd,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;QACzC,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC3D,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACjD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAA;YAC1B,IAAI,CAAC,UAAU,GAAG,MAAM,CAAA;QAC1B,CAAC,CAAC,CAAA;QACF,OAAO,WAAW,CAChB,IAAI,EACJ,oBAAoB,EACpB,GAAG,EAAE,CAAC,IAAI,iBAAiB,CAAC,qBAAqB,CAAC,CACnD,CAAA;IACH,CAAC;CACF;AAED,SAAS,WAAW,CAAI,OAAmB,EAAE,EAAU,EAAE,SAAsB;IAC7E,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;QACvD,OAAO,CAAC,IAAI,CACV,CAAC,KAAK,EAAE,EAAE;YACR,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC,EACD,CAAC,MAAM,EAAE,EAAE;YACT,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,MAAM,CAAC,MAAM,CAAC,CAAA;QAChB,CAAC,CACF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAK,KAAiB,CAAC,IAAI,KAAK,OAAO,CAAA;AAC3F,CAAC;AAED,SAAS,QAAQ,CAAC,MAAe;IAC/B,IAAI,WAAW,CAAC,MAAM,CAAC;QAAE,OAAO,8BAA8B,MAAM,CAAC,IAAI,IAAI,GAAG,GAAG,CAAA;IACnF,IAAI,MAAM,YAAY,KAAK;QAAE,OAAO,MAAM,CAAC,OAAO,CAAA;IAClD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAA;AACvB,CAAC;AAED,SAAS,SAAS,CAAC,GAAY;IAC7B,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACxC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC7B,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAChE,CAAC,CAAE,KAAiC;YACpC,CAAC,CAAC,IAAI,CAAA;IACV,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
|