@convai/web-sdk 1.8.0-beta.4 → 1.8.0-beta.6
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 +64 -3
- package/dist/core/CharacterRoster.d.ts +84 -0
- package/dist/core/CharacterRoster.d.ts.map +1 -0
- package/dist/core/CharacterRoster.js +348 -0
- package/dist/core/CharacterRoster.js.map +1 -0
- package/dist/core/CharacterVersionManager.d.ts +100 -0
- package/dist/core/CharacterVersionManager.d.ts.map +1 -0
- package/dist/core/CharacterVersionManager.js +227 -0
- package/dist/core/CharacterVersionManager.js.map +1 -0
- package/dist/core/ConvaiClient.d.ts +109 -1
- package/dist/core/ConvaiClient.d.ts.map +1 -1
- package/dist/core/ConvaiClient.js +642 -49
- package/dist/core/ConvaiClient.js.map +1 -1
- package/dist/core/ConvaiRoomError.d.ts +48 -0
- package/dist/core/ConvaiRoomError.d.ts.map +1 -0
- package/dist/core/ConvaiRoomError.js +111 -0
- package/dist/core/ConvaiRoomError.js.map +1 -0
- package/dist/core/MessageHandler.d.ts +14 -0
- package/dist/core/MessageHandler.d.ts.map +1 -1
- package/dist/core/MessageHandler.js +111 -5
- package/dist/core/MessageHandler.js.map +1 -1
- package/dist/core/SSESession.d.ts +15 -0
- package/dist/core/SSESession.d.ts.map +1 -1
- package/dist/core/SSESession.js +92 -3
- package/dist/core/SSESession.js.map +1 -1
- package/dist/core/characterReference.d.ts +40 -0
- package/dist/core/characterReference.d.ts.map +1 -0
- package/dist/core/characterReference.js +72 -0
- package/dist/core/characterReference.js.map +1 -0
- package/dist/core/connectRequest.d.ts +1 -0
- package/dist/core/connectRequest.d.ts.map +1 -1
- package/dist/core/connectRequest.js +11 -0
- package/dist/core/connectRequest.js.map +1 -1
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/rosterRequest.d.ts +57 -0
- package/dist/core/rosterRequest.d.ts.map +1 -0
- package/dist/core/rosterRequest.js +210 -0
- package/dist/core/rosterRequest.js.map +1 -0
- package/dist/core/types.d.ts +717 -7
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/react/components/rtc-widget/components/MarkdownRenderer.d.ts.map +1 -1
- package/dist/react/components/rtc-widget/components/MarkdownRenderer.js +39 -44
- package/dist/react/components/rtc-widget/components/MarkdownRenderer.js.map +1 -1
- package/dist/react/hooks/useConvaiClient.d.ts.map +1 -1
- package/dist/react/hooks/useConvaiClient.js +15 -2
- package/dist/react/hooks/useConvaiClient.js.map +1 -1
- package/dist/utils/inlineMarkdown.d.ts +38 -0
- package/dist/utils/inlineMarkdown.d.ts.map +1 -0
- package/dist/utils/inlineMarkdown.js +106 -0
- package/dist/utils/inlineMarkdown.js.map +1 -0
- package/dist/vanilla/AudioRenderer.d.ts.map +1 -1
- package/dist/vanilla/AudioRenderer.js +6 -0
- package/dist/vanilla/AudioRenderer.js.map +1 -1
- package/dist/vanilla/ConvaiWidget.d.ts.map +1 -1
- package/dist/vanilla/ConvaiWidget.js +71 -57
- package/dist/vanilla/ConvaiWidget.js.map +1 -1
- package/dist/vanilla/index.d.ts +2 -0
- package/dist/vanilla/index.d.ts.map +1 -1
- package/dist/vanilla/index.js +2 -0
- package/dist/vanilla/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character references.
|
|
3
|
+
*
|
|
4
|
+
* The runtime accepts a bare character UUID or a UUID followed by a version
|
|
5
|
+
* selector: `<uuid>-draft`, `<uuid>-latest`, or `<uuid>-1.2[.3]`. The SDK
|
|
6
|
+
* keeps the two halves apart — the bare UUID still names the character for
|
|
7
|
+
* memory, character info and analytics, while the joined reference is what
|
|
8
|
+
* goes on the wire to `/connect` and the interaction API.
|
|
9
|
+
*/
|
|
10
|
+
const UUID_PATTERN = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
|
|
11
|
+
const VERSION_TAG_PATTERN = "(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:\\.(?:0|[1-9]\\d*))?";
|
|
12
|
+
const UUID_RE = new RegExp(`^${UUID_PATTERN}$`);
|
|
13
|
+
const SELECTOR_RE = new RegExp(`^(?:draft|latest|${VERSION_TAG_PATTERN})$`);
|
|
14
|
+
const REFERENCE_RE = new RegExp(`^(${UUID_PATTERN})(?:-(draft|latest|${VERSION_TAG_PATTERN}))?$`);
|
|
15
|
+
/** True when `value` is `draft`, `latest`, or a `major.minor[.patch]` tag. */
|
|
16
|
+
export function isCharacterVersionSelector(value) {
|
|
17
|
+
return typeof value === "string" && SELECTOR_RE.test(value);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Split a character id that may already carry a selector suffix. Returns null
|
|
21
|
+
* when the value is neither a UUID nor a versioned reference — callers that
|
|
22
|
+
* accept opaque ids (legacy non-UUID characters) fall back to the raw string.
|
|
23
|
+
*/
|
|
24
|
+
export function parseCharacterReference(value) {
|
|
25
|
+
const match = REFERENCE_RE.exec(value.trim());
|
|
26
|
+
if (!match)
|
|
27
|
+
return null;
|
|
28
|
+
const characterId = match[1].toLowerCase();
|
|
29
|
+
const version = match[2] ?? null;
|
|
30
|
+
return {
|
|
31
|
+
characterId,
|
|
32
|
+
version,
|
|
33
|
+
reference: version ? `${characterId}-${version}` : characterId,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Combine a `characterId` and an optional `characterVersion` config field into
|
|
38
|
+
* the reference sent to the runtime.
|
|
39
|
+
*
|
|
40
|
+
* - `characterId` may be a bare UUID or already carry a selector.
|
|
41
|
+
* - `characterVersion` must be a valid selector when provided.
|
|
42
|
+
* - Supplying both is allowed only when they agree.
|
|
43
|
+
* - A non-UUID `characterId` cannot be versioned.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveCharacterReference(characterId, characterVersion) {
|
|
46
|
+
const parsed = parseCharacterReference(characterId);
|
|
47
|
+
if (characterVersion != null && characterVersion !== "") {
|
|
48
|
+
if (!isCharacterVersionSelector(characterVersion)) {
|
|
49
|
+
throw new Error(`characterVersion must be "draft", "latest", or a major.minor[.patch] tag; received ${JSON.stringify(characterVersion)}`);
|
|
50
|
+
}
|
|
51
|
+
if (!parsed) {
|
|
52
|
+
throw new Error("characterVersion requires characterId to be a character UUID");
|
|
53
|
+
}
|
|
54
|
+
if (parsed.version && parsed.version !== characterVersion) {
|
|
55
|
+
throw new Error(`characterId already selects version "${parsed.version}" but characterVersion is "${characterVersion}"`);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
characterId: parsed.characterId,
|
|
59
|
+
version: characterVersion,
|
|
60
|
+
reference: `${parsed.characterId}-${characterVersion}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (parsed)
|
|
64
|
+
return parsed;
|
|
65
|
+
// Not a UUID: pass through untouched so legacy ids keep working.
|
|
66
|
+
return { characterId, version: null, reference: characterId };
|
|
67
|
+
}
|
|
68
|
+
/** True when `value` is a bare character UUID. */
|
|
69
|
+
export function isCharacterUuid(value) {
|
|
70
|
+
return UUID_RE.test(value);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=characterReference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"characterReference.js","sourceRoot":"","sources":["../../src/core/characterReference.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,MAAM,YAAY,GAChB,6EAA6E,CAAC;AAChF,MAAM,mBAAmB,GAAG,0DAA0D,CAAC;AAEvF,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC;AAChD,MAAM,WAAW,GAAG,IAAI,MAAM,CAAC,oBAAoB,mBAAmB,IAAI,CAAC,CAAC;AAC5E,MAAM,YAAY,GAAG,IAAI,MAAM,CAC7B,KAAK,YAAY,sBAAsB,mBAAmB,MAAM,CACjE,CAAC;AAWF,8EAA8E;AAC9E,MAAM,UAAU,0BAA0B,CAAC,KAAc;IACvD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAI,KAAK,CAAC,CAAC,CAA0C,IAAI,IAAI,CAAC;IAC3E,OAAO;QACL,WAAW;QACX,OAAO;QACP,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,WAAW;KAC/D,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CACvC,WAAmB,EACnB,gBAAkD;IAElD,MAAM,MAAM,GAAG,uBAAuB,CAAC,WAAW,CAAC,CAAC;IAEpD,IAAI,gBAAgB,IAAI,IAAI,IAAI,gBAAgB,KAAK,EAAE,EAAE,CAAC;QACxD,IAAI,CAAC,0BAA0B,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CACb,sFAAsF,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,CACzH,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,8DAA8D,CAC/D,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,KAAK,gBAAgB,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CACb,wCAAwC,MAAM,CAAC,OAAO,8BAA8B,gBAAgB,GAAG,CACxG,CAAC;QACJ,CAAC;QACD,OAAO;YACL,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,OAAO,EAAE,gBAAgB;YACzB,SAAS,EAAE,GAAG,MAAM,CAAC,WAAW,IAAI,gBAAgB,EAAE;SACvD,CAAC;IACJ,CAAC;IAED,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,iEAAiE;IACjE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AAChE,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC","sourcesContent":["/**\n * Character references.\n *\n * The runtime accepts a bare character UUID or a UUID followed by a version\n * selector: `<uuid>-draft`, `<uuid>-latest`, or `<uuid>-1.2[.3]`. The SDK\n * keeps the two halves apart — the bare UUID still names the character for\n * memory, character info and analytics, while the joined reference is what\n * goes on the wire to `/connect` and the interaction API.\n */\n\n/** A version selector: the editable draft, the promoted latest, or an immutable tag. */\nexport type CharacterVersionSelector = \"draft\" | \"latest\" | (string & {});\n\nconst UUID_PATTERN =\n \"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\";\nconst VERSION_TAG_PATTERN = \"(?:0|[1-9]\\\\d*)\\\\.(?:0|[1-9]\\\\d*)(?:\\\\.(?:0|[1-9]\\\\d*))?\";\n\nconst UUID_RE = new RegExp(`^${UUID_PATTERN}$`);\nconst SELECTOR_RE = new RegExp(`^(?:draft|latest|${VERSION_TAG_PATTERN})$`);\nconst REFERENCE_RE = new RegExp(\n `^(${UUID_PATTERN})(?:-(draft|latest|${VERSION_TAG_PATTERN}))?$`,\n);\n\nexport interface CharacterReference {\n /** The bare character UUID, lower-cased. */\n characterId: string;\n /** The selector, or null for a bare reference. */\n version: CharacterVersionSelector | null;\n /** What goes on the wire: the UUID, or `<uuid>-<selector>`. */\n reference: string;\n}\n\n/** True when `value` is `draft`, `latest`, or a `major.minor[.patch]` tag. */\nexport function isCharacterVersionSelector(value: unknown): value is CharacterVersionSelector {\n return typeof value === \"string\" && SELECTOR_RE.test(value);\n}\n\n/**\n * Split a character id that may already carry a selector suffix. Returns null\n * when the value is neither a UUID nor a versioned reference — callers that\n * accept opaque ids (legacy non-UUID characters) fall back to the raw string.\n */\nexport function parseCharacterReference(value: string): CharacterReference | null {\n const match = REFERENCE_RE.exec(value.trim());\n if (!match) return null;\n const characterId = match[1].toLowerCase();\n const version = (match[2] as CharacterVersionSelector | undefined) ?? null;\n return {\n characterId,\n version,\n reference: version ? `${characterId}-${version}` : characterId,\n };\n}\n\n/**\n * Combine a `characterId` and an optional `characterVersion` config field into\n * the reference sent to the runtime.\n *\n * - `characterId` may be a bare UUID or already carry a selector.\n * - `characterVersion` must be a valid selector when provided.\n * - Supplying both is allowed only when they agree.\n * - A non-UUID `characterId` cannot be versioned.\n */\nexport function resolveCharacterReference(\n characterId: string,\n characterVersion?: CharacterVersionSelector | null,\n): CharacterReference {\n const parsed = parseCharacterReference(characterId);\n\n if (characterVersion != null && characterVersion !== \"\") {\n if (!isCharacterVersionSelector(characterVersion)) {\n throw new Error(\n `characterVersion must be \"draft\", \"latest\", or a major.minor[.patch] tag; received ${JSON.stringify(characterVersion)}`,\n );\n }\n if (!parsed) {\n throw new Error(\n \"characterVersion requires characterId to be a character UUID\",\n );\n }\n if (parsed.version && parsed.version !== characterVersion) {\n throw new Error(\n `characterId already selects version \"${parsed.version}\" but characterVersion is \"${characterVersion}\"`,\n );\n }\n return {\n characterId: parsed.characterId,\n version: characterVersion,\n reference: `${parsed.characterId}-${characterVersion}`,\n };\n }\n\n if (parsed) return parsed;\n\n // Not a UUID: pass through untouched so legacy ids keep working.\n return { characterId, version: null, reference: characterId };\n}\n\n/** True when `value` is a bare character UUID. */\nexport function isCharacterUuid(value: string): boolean {\n return UUID_RE.test(value);\n}\n"]}
|
|
@@ -21,4 +21,5 @@ export interface BlendshapeConnectRequestConfig {
|
|
|
21
21
|
export declare function buildBlendshapeConnectConfig(config: Pick<ConvaiConfig, "enableLipsync" | "blendshapeConfig">): BlendshapeConnectRequestConfig;
|
|
22
22
|
export declare function buildEmotionConnectConfig(config: Pick<ConvaiConfig, "enableEmotion" | "emotionConfig">): Record<string, unknown> | undefined;
|
|
23
23
|
export declare function buildActionConnectConfig(config: Pick<ConvaiConfig, "actionConfig">): ConvaiConfig["actionConfig"] | undefined;
|
|
24
|
+
export declare function serializeActionProtocolCapabilities(capabilities: ConvaiConfig["capabilities"]): Record<string, number> | undefined;
|
|
24
25
|
//# sourceMappingURL=connectRequest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectRequest.d.ts","sourceRoot":"","sources":["../../src/core/connectRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,WAAW,8BAA8B;IAC7C,mBAAmB,EAAE,WAAW,GAAG,cAAc,CAAC;IAClD,iBAAiB,EAAE;QACjB,eAAe,EAAE,IAAI,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,OAAO,GAAG,KAAK,GAAG,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;QAChE,sBAAsB,EAAE,MAAM,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC;QACnB,oBAAoB,CAAC,EAAE,IAAI,CAAC;KAC7B,CAAC;CACH;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,kBAAkB,CAAC,GAC/D,8BAA8B,CAuBhC;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,eAAe,CAAC,GAC5D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAYrC;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,GACzC,YAAY,CAAC,cAAc,CAAC,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"connectRequest.d.ts","sourceRoot":"","sources":["../../src/core/connectRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,WAAW,8BAA8B;IAC7C,mBAAmB,EAAE,WAAW,GAAG,cAAc,CAAC;IAClD,iBAAiB,EAAE;QACjB,eAAe,EAAE,IAAI,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,OAAO,GAAG,KAAK,GAAG,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;QAChE,sBAAsB,EAAE,MAAM,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC;QACnB,oBAAoB,CAAC,EAAE,IAAI,CAAC;KAC7B,CAAC;CACH;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,kBAAkB,CAAC,GAC/D,8BAA8B,CAuBhC;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,eAAe,CAAC,GAC5D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAYrC;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,GACzC,YAAY,CAAC,cAAc,CAAC,GAAG,SAAS,CAY1C;AAED,wBAAgB,mCAAmC,CACjD,YAAY,EAAE,YAAY,CAAC,cAAc,CAAC,GACzC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAQpC"}
|
|
@@ -52,6 +52,17 @@ export function buildActionConnectConfig(config) {
|
|
|
52
52
|
...(actionConfig.current_attention_object && {
|
|
53
53
|
current_attention_object: actionConfig.current_attention_object,
|
|
54
54
|
}),
|
|
55
|
+
...(actionConfig.tools && { tools: actionConfig.tools }),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function serializeActionProtocolCapabilities(capabilities) {
|
|
59
|
+
if (!capabilities)
|
|
60
|
+
return undefined;
|
|
61
|
+
return {
|
|
62
|
+
action_protocol_version: capabilities.actionProtocolVersion,
|
|
63
|
+
...(capabilities.modelOutputVersion !== undefined
|
|
64
|
+
? { model_output_version: capabilities.modelOutputVersion }
|
|
65
|
+
: {}),
|
|
55
66
|
};
|
|
56
67
|
}
|
|
57
68
|
//# sourceMappingURL=connectRequest.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectRequest.js","sourceRoot":"","sources":["../../src/core/connectRequest.ts"],"names":[],"mappings":"AAcA;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAAgE;IAEhE,MAAM,kBAAkB,GACtB,MAAM,CAAC,aAAa,KAAK,IAAI;QAC7B,MAAM,CAAC,gBAAgB,EAAE,oBAAoB,KAAK,KAAK,CAAC;IAE1D,OAAO;QACL,mBAAmB,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc;QACxE,iBAAiB,EAAE;YACjB,eAAe,EAAE,IAAI;YACrB,UAAU,EAAE,EAAE;YACd,MAAM,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,IAAI,KAAK;YAChD,sBAAsB,EACpB,MAAM,CAAC,gBAAgB,EAAE,sBAAsB,IAAI,GAAG;YACxD,yEAAyE;YACzE,qEAAqE;YACrE,wEAAwE;YACxE,gBAAgB;YAChB,UAAU,EAAE,kBAAkB;gBAC5B,CAAC,CAAC,MAAM,CAAC,gBAAgB,EAAE,UAAU,IAAI,EAAE;gBAC3C,CAAC,CAAC,EAAE;YACN,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,oBAAoB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,MAA6D;IAE7D,IAAI,MAAM,CAAC,aAAa,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACpD,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;IAC3C,IAAI,aAAa,EAAE,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACzC,OAAO;YACL,QAAQ,EAAE,aAAa,CAAC,QAAQ;YAChC,kBAAkB,EAAE,aAAa,CAAC,kBAAkB,IAAI,CAAC;YACzD,uBAAuB,EAAE,aAAa,CAAC,uBAAuB,IAAI,IAAI;YACtE,wBAAwB,EAAE,aAAa,CAAC,wBAAwB,IAAI,IAAI;SACzE,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,IAAI,KAAK,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,MAA0C;IAE1C,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;IACzC,IAAI,CAAC,YAAY;QAAE,OAAO,SAAS,CAAC;IACpC,OAAO;QACL,OAAO,EAAE,YAAY,CAAC,OAAO;QAC7B,OAAO,EAAE,YAAY,CAAC,OAAO;QAC7B,UAAU,EAAE,YAAY,CAAC,UAAU;QACnC,GAAG,CAAC,YAAY,CAAC,wBAAwB,IAAI;YAC3C,wBAAwB,EAAE,YAAY,CAAC,wBAAwB;SAChE,CAAC;
|
|
1
|
+
{"version":3,"file":"connectRequest.js","sourceRoot":"","sources":["../../src/core/connectRequest.ts"],"names":[],"mappings":"AAcA;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAAgE;IAEhE,MAAM,kBAAkB,GACtB,MAAM,CAAC,aAAa,KAAK,IAAI;QAC7B,MAAM,CAAC,gBAAgB,EAAE,oBAAoB,KAAK,KAAK,CAAC;IAE1D,OAAO;QACL,mBAAmB,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc;QACxE,iBAAiB,EAAE;YACjB,eAAe,EAAE,IAAI;YACrB,UAAU,EAAE,EAAE;YACd,MAAM,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,IAAI,KAAK;YAChD,sBAAsB,EACpB,MAAM,CAAC,gBAAgB,EAAE,sBAAsB,IAAI,GAAG;YACxD,yEAAyE;YACzE,qEAAqE;YACrE,wEAAwE;YACxE,gBAAgB;YAChB,UAAU,EAAE,kBAAkB;gBAC5B,CAAC,CAAC,MAAM,CAAC,gBAAgB,EAAE,UAAU,IAAI,EAAE;gBAC3C,CAAC,CAAC,EAAE;YACN,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,oBAAoB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,MAA6D;IAE7D,IAAI,MAAM,CAAC,aAAa,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACpD,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;IAC3C,IAAI,aAAa,EAAE,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACzC,OAAO;YACL,QAAQ,EAAE,aAAa,CAAC,QAAQ;YAChC,kBAAkB,EAAE,aAAa,CAAC,kBAAkB,IAAI,CAAC;YACzD,uBAAuB,EAAE,aAAa,CAAC,uBAAuB,IAAI,IAAI;YACtE,wBAAwB,EAAE,aAAa,CAAC,wBAAwB,IAAI,IAAI;SACzE,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,IAAI,KAAK,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,MAA0C;IAE1C,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;IACzC,IAAI,CAAC,YAAY;QAAE,OAAO,SAAS,CAAC;IACpC,OAAO;QACL,OAAO,EAAE,YAAY,CAAC,OAAO;QAC7B,OAAO,EAAE,YAAY,CAAC,OAAO;QAC7B,UAAU,EAAE,YAAY,CAAC,UAAU;QACnC,GAAG,CAAC,YAAY,CAAC,wBAAwB,IAAI;YAC3C,wBAAwB,EAAE,YAAY,CAAC,wBAAwB;SAChE,CAAC;QACF,GAAG,CAAC,YAAY,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC,KAAK,EAAE,CAAC;KACzD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mCAAmC,CACjD,YAA0C;IAE1C,IAAI,CAAC,YAAY;QAAE,OAAO,SAAS,CAAC;IACpC,OAAO;QACL,uBAAuB,EAAE,YAAY,CAAC,qBAAqB;QAC3D,GAAG,CAAC,YAAY,CAAC,kBAAkB,KAAK,SAAS;YAC/C,CAAC,CAAC,EAAE,oBAAoB,EAAE,YAAY,CAAC,kBAAkB,EAAE;YAC3D,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC","sourcesContent":["import type { ConvaiConfig } from \"./types\";\n\nexport interface BlendshapeConnectRequestConfig {\n blendshape_provider: \"neurosync\" | \"not_provided\";\n blendshape_config: {\n enable_chunking: true;\n chunk_size: number;\n format: \"arkit\" | \"mha\" | \"cc4_extended\" | \"cc5_hd\" | \"visemes\";\n frames_buffer_duration: number;\n output_fps: number;\n deliver_chunks_ahead?: true;\n };\n}\n\n/**\n * Build the lipsync part of the /connect request.\n *\n * Ahead-delivery changes the client contract because frames can arrive before\n * bot-started-speaking and cancellation must be handled with owner metadata.\n * It is the default now that server support is verified; callers can fall back\n * to the legacy paced path with deliver_chunks_ahead: false.\n */\nexport function buildBlendshapeConnectConfig(\n config: Pick<ConvaiConfig, \"enableLipsync\" | \"blendshapeConfig\">,\n): BlendshapeConnectRequestConfig {\n const deliverChunksAhead =\n config.enableLipsync === true &&\n config.blendshapeConfig?.deliver_chunks_ahead !== false;\n\n return {\n blendshape_provider: config.enableLipsync ? \"neurosync\" : \"not_provided\",\n blendshape_config: {\n enable_chunking: true,\n chunk_size: 10,\n format: config.blendshapeConfig?.format || \"mha\",\n frames_buffer_duration:\n config.blendshapeConfig?.frames_buffer_duration ?? 0.1,\n // In the legacy paced path, output_fps is a delivery cadence and the SDK\n // player keeps its 60fps visual timeline. Only ahead-delivery treats\n // output_fps as a client-visible timeline override because chunks carry\n // fps metadata.\n output_fps: deliverChunksAhead\n ? config.blendshapeConfig?.output_fps ?? 60\n : 90,\n ...(deliverChunksAhead ? { deliver_chunks_ahead: true } : {}),\n },\n };\n}\n\nexport function buildEmotionConnectConfig(\n config: Pick<ConvaiConfig, \"enableEmotion\" | \"emotionConfig\">,\n): Record<string, unknown> | undefined {\n if (config.enableEmotion !== true) return undefined;\n const emotionConfig = config.emotionConfig;\n if (emotionConfig?.provider === \"nrclex\") {\n return {\n provider: emotionConfig.provider,\n min_word_threshold: emotionConfig.min_word_threshold ?? 3,\n low_intensity_threshold: emotionConfig.low_intensity_threshold ?? 0.33,\n high_intensity_threshold: emotionConfig.high_intensity_threshold ?? 0.66,\n };\n }\n return { provider: emotionConfig?.provider ?? \"llm\" };\n}\n\nexport function buildActionConnectConfig(\n config: Pick<ConvaiConfig, \"actionConfig\">,\n): ConvaiConfig[\"actionConfig\"] | undefined {\n const actionConfig = config.actionConfig;\n if (!actionConfig) return undefined;\n return {\n actions: actionConfig.actions,\n objects: actionConfig.objects,\n characters: actionConfig.characters,\n ...(actionConfig.current_attention_object && {\n current_attention_object: actionConfig.current_attention_object,\n }),\n ...(actionConfig.tools && { tools: actionConfig.tools }),\n };\n}\n\nexport function serializeActionProtocolCapabilities(\n capabilities: ConvaiConfig[\"capabilities\"],\n): Record<string, number> | undefined {\n if (!capabilities) return undefined;\n return {\n action_protocol_version: capabilities.actionProtocolVersion,\n ...(capabilities.modelOutputVersion !== undefined\n ? { model_output_version: capabilities.modelOutputVersion }\n : {}),\n };\n}\n"]}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -7,6 +7,11 @@ export { VideoManager } from './VideoManager.js';
|
|
|
7
7
|
export { ScreenShareManager } from './ScreenShareManager.js';
|
|
8
8
|
export { MessageHandler } from './MessageHandler.js';
|
|
9
9
|
export { MemoryManager } from './MemoryManager.js';
|
|
10
|
+
export { CharacterRoster } from './CharacterRoster.js';
|
|
11
|
+
export { ConvaiRoomError } from './ConvaiRoomError.js';
|
|
12
|
+
export { MULTI_CHARACTER_MAX_CHARACTERS, isRosterConfig, } from './rosterRequest.js';
|
|
13
|
+
export { CharacterVersionManager, CharacterApiError, DEFAULT_CHARACTER_API_URL, } from './CharacterVersionManager.js';
|
|
14
|
+
export { parseCharacterReference, resolveCharacterReference, isCharacterVersionSelector, isCharacterUuid, } from './characterReference.js';
|
|
10
15
|
export { BlendshapeQueue } from './BlendshapeQueue.js';
|
|
11
16
|
export type { TurnStats, BlendshapeChunkMetadata } from './BlendshapeQueue.js';
|
|
12
17
|
export { EventEmitter } from './EventEmitter.js';
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAGrD,YAAY,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGvE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAGrD,YAAY,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGvE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EACL,8BAA8B,EAC9B,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,0BAA0B,EAC1B,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAG5E,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/core/index.js
CHANGED
|
@@ -10,6 +10,12 @@ export { VideoManager } from './VideoManager.js';
|
|
|
10
10
|
export { ScreenShareManager } from './ScreenShareManager.js';
|
|
11
11
|
export { MessageHandler } from './MessageHandler.js';
|
|
12
12
|
export { MemoryManager } from './MemoryManager.js';
|
|
13
|
+
export { CharacterRoster } from './CharacterRoster.js';
|
|
14
|
+
export { ConvaiRoomError } from './ConvaiRoomError.js';
|
|
15
|
+
export { MULTI_CHARACTER_MAX_CHARACTERS, isRosterConfig, } from './rosterRequest.js';
|
|
16
|
+
export { CharacterVersionManager, CharacterApiError, DEFAULT_CHARACTER_API_URL, } from './CharacterVersionManager.js';
|
|
17
|
+
// Character reference helpers (version selectors)
|
|
18
|
+
export { parseCharacterReference, resolveCharacterReference, isCharacterVersionSelector, isCharacterUuid, } from './characterReference.js';
|
|
13
19
|
// Blendshape queue for lipsync
|
|
14
20
|
export { BlendshapeQueue } from './BlendshapeQueue.js';
|
|
15
21
|
// Event Emitter (for advanced usage)
|
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,cAAc;AACd,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,4CAA4C;AAC5C,cAAc,SAAS,CAAC;AAExB,mBAAmB;AACnB,OAAO,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAKrD,gCAAgC;AAChC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,cAAc;AACd,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,4CAA4C;AAC5C,cAAc,SAAS,CAAC;AAExB,mBAAmB;AACnB,OAAO,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAKrD,gCAAgC;AAChC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EACL,8BAA8B,EAC9B,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,2BAA2B,CAAC;AAEnC,kDAAkD;AAClD,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,0BAA0B,EAC1B,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAE9B,+BAA+B;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,qCAAqC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["// Main client\nexport { ConvaiClient } from './ConvaiClient';\n\n// Types (including IConvaiClient interface)\nexport * from './types';\n\n// Helper functions\nexport { getDisconnectReasonMessage } from './types';\n\n// Type alias for easier usage\nexport type { ConvaiClient as ConvaiClientType } from './ConvaiClient';\n\n// Managers (for advanced usage)\nexport { AudioManager } from './AudioManager';\nexport { VideoManager } from './VideoManager';\nexport { ScreenShareManager } from './ScreenShareManager';\nexport { MessageHandler } from './MessageHandler';\nexport { MemoryManager } from './MemoryManager';\nexport { CharacterRoster } from './CharacterRoster';\nexport { ConvaiRoomError } from './ConvaiRoomError';\nexport {\n MULTI_CHARACTER_MAX_CHARACTERS,\n isRosterConfig,\n} from './rosterRequest';\nexport {\n CharacterVersionManager,\n CharacterApiError,\n DEFAULT_CHARACTER_API_URL,\n} from './CharacterVersionManager';\n\n// Character reference helpers (version selectors)\nexport {\n parseCharacterReference,\n resolveCharacterReference,\n isCharacterVersionSelector,\n isCharacterUuid,\n} from './characterReference';\n\n// Blendshape queue for lipsync\nexport { BlendshapeQueue } from './BlendshapeQueue';\nexport type { TurnStats, BlendshapeChunkMetadata } from './BlendshapeQueue';\n\n// Event Emitter (for advanced usage)\nexport { EventEmitter } from './EventEmitter';\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ConvaiConfig, JoinRoomOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Validation and request-body construction for multi-character rooms.
|
|
4
|
+
*
|
|
5
|
+
* Pure: no transport, no client state. Every rule below runs before a request
|
|
6
|
+
* is built, so an illegal topology costs nothing and reports the rule it broke
|
|
7
|
+
* rather than a 422 from the runtime.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Server-wide roster cap. Checked client-side only to fail fast with a clear
|
|
11
|
+
* message — the server stays authoritative, and a plan may configure a lower
|
|
12
|
+
* `max_characters` that surfaces as 403 `MULTI_CHARACTER_ROSTER_LIMIT_EXCEEDED`.
|
|
13
|
+
* This is an operational limit, not a protocol maximum, so it must not leak
|
|
14
|
+
* into any public type.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MULTI_CHARACTER_MAX_CHARACTERS = 50;
|
|
17
|
+
/** True when this config asks for a multi-character room. */
|
|
18
|
+
export declare function isRosterConfig(config: Pick<ConvaiConfig, "characters">): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Validate a roster create config.
|
|
21
|
+
*
|
|
22
|
+
* Throws on the first broken rule. Ordered so the most structural problems
|
|
23
|
+
* (which topology did you mean?) report before the detail ones.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateRosterConfig(config: ConvaiConfig): void;
|
|
26
|
+
/**
|
|
27
|
+
* Reject a config that negotiates v2 output protocols alongside room options
|
|
28
|
+
* the runtime cannot combine them with.
|
|
29
|
+
*
|
|
30
|
+
* Verified against staging 2026-09-08: `capabilities.actionProtocolVersion: 2`
|
|
31
|
+
* or `modelOutputVersion: 2` is answered with 422
|
|
32
|
+
* `unsupported_action_protocol_topology` / `unsupported_model_output_topology`
|
|
33
|
+
* unless the request uses `mode='create'`, a singular `characterId`, no
|
|
34
|
+
* `sharedSessionKey`, and `maxNumParticipants` of 1. Version 1 carries no such
|
|
35
|
+
* constraint.
|
|
36
|
+
*
|
|
37
|
+
* The published contract mentions only the multi-character half of this. The
|
|
38
|
+
* `sharedSessionKey` and `maxNumParticipants` constraints bite in
|
|
39
|
+
* single-character sessions too, which is why this runs on every connect
|
|
40
|
+
* rather than only the roster path.
|
|
41
|
+
*/
|
|
42
|
+
export declare function validateProtocolTopology(config: ConvaiConfig): void;
|
|
43
|
+
/** Validate join options. */
|
|
44
|
+
export declare function validateJoinOptions(options: JoinRoomOptions): void;
|
|
45
|
+
/**
|
|
46
|
+
* The roster half of a create request. Merged into the existing single-character
|
|
47
|
+
* body by the caller, so every current option (blendshape, TTS/STT, dynamic
|
|
48
|
+
* info, action, scene, VAD, vision) continues to apply to every member.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildRosterConnectFields(config: ConvaiConfig): Record<string, unknown>;
|
|
51
|
+
/**
|
|
52
|
+
* The complete join request body. A join carries a locator and the human, and
|
|
53
|
+
* nothing else — the roster is loaded from persisted room state, and resending
|
|
54
|
+
* topology is rejected.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildJoinRequestBody(options: JoinRoomOptions): Record<string, unknown>;
|
|
57
|
+
//# sourceMappingURL=rosterRequest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rosterRequest.d.ts","sourceRoot":"","sources":["../../src/core/rosterRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,YAAY,EACZ,eAAe,EAChB,MAAM,SAAS,CAAC;AAGjB;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAKjD,6DAA6D;AAC7D,wBAAgB,cAAc,CAC5B,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,GACvC,OAAO,CAET;AAeD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CA8E/D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CA+BnE;AAED,6BAA6B;AAC7B,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAuBlE;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0BzB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,eAAe,GACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAezB"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { isCharacterUuid } from "./characterReference.js";
|
|
2
|
+
/**
|
|
3
|
+
* Validation and request-body construction for multi-character rooms.
|
|
4
|
+
*
|
|
5
|
+
* Pure: no transport, no client state. Every rule below runs before a request
|
|
6
|
+
* is built, so an illegal topology costs nothing and reports the rule it broke
|
|
7
|
+
* rather than a 422 from the runtime.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Server-wide roster cap. Checked client-side only to fail fast with a clear
|
|
11
|
+
* message — the server stays authoritative, and a plan may configure a lower
|
|
12
|
+
* `max_characters` that surfaces as 403 `MULTI_CHARACTER_ROSTER_LIMIT_EXCEEDED`.
|
|
13
|
+
* This is an operational limit, not a protocol maximum, so it must not leak
|
|
14
|
+
* into any public type.
|
|
15
|
+
*/
|
|
16
|
+
export const MULTI_CHARACTER_MAX_CHARACTERS = 50;
|
|
17
|
+
/** `shared_session_key` and `connect_attempt_id` share one charset. */
|
|
18
|
+
const KEY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
19
|
+
/** True when this config asks for a multi-character room. */
|
|
20
|
+
export function isRosterConfig(config) {
|
|
21
|
+
return Array.isArray(config.characters) && config.characters.length > 0;
|
|
22
|
+
}
|
|
23
|
+
function assertKey(value, field) {
|
|
24
|
+
if (value === undefined)
|
|
25
|
+
return;
|
|
26
|
+
if (!KEY_PATTERN.test(value)) {
|
|
27
|
+
throw new Error(`${field} must be 1-128 characters of letters, digits, hyphen or underscore`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function nonblank(value) {
|
|
31
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate a roster create config.
|
|
35
|
+
*
|
|
36
|
+
* Throws on the first broken rule. Ordered so the most structural problems
|
|
37
|
+
* (which topology did you mean?) report before the detail ones.
|
|
38
|
+
*/
|
|
39
|
+
export function validateRosterConfig(config) {
|
|
40
|
+
const characters = config.characters ?? [];
|
|
41
|
+
if (config.characterId) {
|
|
42
|
+
throw new Error("Pass either characterId or characters, not both");
|
|
43
|
+
}
|
|
44
|
+
if (characters.length === 0) {
|
|
45
|
+
throw new Error("characters must contain at least one entry");
|
|
46
|
+
}
|
|
47
|
+
if (characters.length > MULTI_CHARACTER_MAX_CHARACTERS) {
|
|
48
|
+
throw new Error(`characters accepts at most ${MULTI_CHARACTER_MAX_CHARACTERS} entries; ` +
|
|
49
|
+
`your plan may allow fewer`);
|
|
50
|
+
}
|
|
51
|
+
const seenSessions = new Set();
|
|
52
|
+
characters.forEach((spec, index) => {
|
|
53
|
+
if (!spec || !nonblank(spec.characterId)) {
|
|
54
|
+
throw new Error(`characters[${index}] requires a characterId`);
|
|
55
|
+
}
|
|
56
|
+
// A roster entry is typed as a bare UUID on the runtime. The version
|
|
57
|
+
// selectors that single-character `characterId` has accepted since
|
|
58
|
+
// 1.8.0-beta.5 are a 422 here, and the failure would otherwise only show
|
|
59
|
+
// up as an opaque validation error from the server.
|
|
60
|
+
if (!isCharacterUuid(spec.characterId.trim())) {
|
|
61
|
+
throw new Error(`characters[${index}].characterId must be a bare character UUID; ` +
|
|
62
|
+
`version selectors are not supported inside a roster`);
|
|
63
|
+
}
|
|
64
|
+
if (spec.characterSessionId !== undefined) {
|
|
65
|
+
if (!nonblank(spec.characterSessionId)) {
|
|
66
|
+
throw new Error(`characters[${index}].characterSessionId must be nonblank when supplied`);
|
|
67
|
+
}
|
|
68
|
+
if (seenSessions.has(spec.characterSessionId)) {
|
|
69
|
+
throw new Error(`characters[${index}].characterSessionId is repeated; resume ids must be unique within the roster`);
|
|
70
|
+
}
|
|
71
|
+
seenSessions.add(spec.characterSessionId);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
if (config.characterSessionId) {
|
|
75
|
+
throw new Error("characterSessionId is not valid with characters; use characters[].characterSessionId");
|
|
76
|
+
}
|
|
77
|
+
if (!nonblank(config.endUserId)) {
|
|
78
|
+
throw new Error("characters requires a nonblank endUserId");
|
|
79
|
+
}
|
|
80
|
+
if (config.transport && config.transport !== "livekit") {
|
|
81
|
+
throw new Error("Multi-character rooms require the LiveKit transport");
|
|
82
|
+
}
|
|
83
|
+
if (config.interactionApiUrl) {
|
|
84
|
+
throw new Error("Multi-character rooms require the LiveKit transport; interactionApiUrl selects SSE");
|
|
85
|
+
}
|
|
86
|
+
if (config.characterVersion) {
|
|
87
|
+
throw new Error("characterVersion applies to a single character; roster entries take bare UUIDs");
|
|
88
|
+
}
|
|
89
|
+
assertKey(config.sharedSessionKey, "sharedSessionKey");
|
|
90
|
+
assertKey(config.connectAttemptId, "connectAttemptId");
|
|
91
|
+
if (config.maxNumParticipants !== undefined &&
|
|
92
|
+
(!Number.isInteger(config.maxNumParticipants) ||
|
|
93
|
+
config.maxNumParticipants < 1)) {
|
|
94
|
+
throw new Error("maxNumParticipants must be a positive integer");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Reject a config that negotiates v2 output protocols alongside room options
|
|
99
|
+
* the runtime cannot combine them with.
|
|
100
|
+
*
|
|
101
|
+
* Verified against staging 2026-09-08: `capabilities.actionProtocolVersion: 2`
|
|
102
|
+
* or `modelOutputVersion: 2` is answered with 422
|
|
103
|
+
* `unsupported_action_protocol_topology` / `unsupported_model_output_topology`
|
|
104
|
+
* unless the request uses `mode='create'`, a singular `characterId`, no
|
|
105
|
+
* `sharedSessionKey`, and `maxNumParticipants` of 1. Version 1 carries no such
|
|
106
|
+
* constraint.
|
|
107
|
+
*
|
|
108
|
+
* The published contract mentions only the multi-character half of this. The
|
|
109
|
+
* `sharedSessionKey` and `maxNumParticipants` constraints bite in
|
|
110
|
+
* single-character sessions too, which is why this runs on every connect
|
|
111
|
+
* rather than only the roster path.
|
|
112
|
+
*/
|
|
113
|
+
export function validateProtocolTopology(config) {
|
|
114
|
+
const negotiatesV2 = config.capabilities?.actionProtocolVersion === 2 ||
|
|
115
|
+
config.capabilities?.modelOutputVersion === 2;
|
|
116
|
+
if (!negotiatesV2) {
|
|
117
|
+
// Tools need v2, so without it they are refused whatever the topology.
|
|
118
|
+
if (config.actionConfig?.tools?.length) {
|
|
119
|
+
throw new Error("actionConfig.tools requires capabilities.actionProtocolVersion: 2");
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const offenders = [];
|
|
124
|
+
if (isRosterConfig(config))
|
|
125
|
+
offenders.push("characters");
|
|
126
|
+
if (config.sharedSessionKey)
|
|
127
|
+
offenders.push("sharedSessionKey");
|
|
128
|
+
if (config.maxNumParticipants !== undefined && config.maxNumParticipants !== 1) {
|
|
129
|
+
offenders.push("maxNumParticipants greater than 1");
|
|
130
|
+
}
|
|
131
|
+
if (offenders.length === 0)
|
|
132
|
+
return;
|
|
133
|
+
throw new Error(`Negotiated v2 output protocols require a single-character room: ` +
|
|
134
|
+
`remove ${offenders.join(", ")}, or drop capabilities to version 1. ` +
|
|
135
|
+
(offenders.includes("characters")
|
|
136
|
+
? "Multi-character rooms cannot use action protocol v2, and therefore cannot use client tools. "
|
|
137
|
+
: "") +
|
|
138
|
+
"The runtime rejects this combination with unsupported_action_protocol_topology.");
|
|
139
|
+
}
|
|
140
|
+
/** Validate join options. */
|
|
141
|
+
export function validateJoinOptions(options) {
|
|
142
|
+
const locators = Number(nonblank(options?.roomSessionId)) +
|
|
143
|
+
Number(nonblank(options?.sharedSessionKey));
|
|
144
|
+
if (locators !== 1) {
|
|
145
|
+
throw new Error("joinRoom requires exactly one of roomSessionId or sharedSessionKey");
|
|
146
|
+
}
|
|
147
|
+
if (!nonblank(options.endUserId)) {
|
|
148
|
+
throw new Error("joinRoom requires a nonblank endUserId");
|
|
149
|
+
}
|
|
150
|
+
// Guard the shape even though the type forbids it: a plain-JS caller, or a
|
|
151
|
+
// config object spread in wholesale, would otherwise send topology that the
|
|
152
|
+
// runtime rejects with a less specific message.
|
|
153
|
+
const stray = options;
|
|
154
|
+
if (stray.characters || stray.characterId || stray.characterSessionId) {
|
|
155
|
+
throw new Error("joinRoom must not carry character topology; the room's roster is server-owned");
|
|
156
|
+
}
|
|
157
|
+
assertKey(options.sharedSessionKey, "sharedSessionKey");
|
|
158
|
+
assertKey(options.connectAttemptId, "connectAttemptId");
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The roster half of a create request. Merged into the existing single-character
|
|
162
|
+
* body by the caller, so every current option (blendshape, TTS/STT, dynamic
|
|
163
|
+
* info, action, scene, VAD, vision) continues to apply to every member.
|
|
164
|
+
*/
|
|
165
|
+
export function buildRosterConnectFields(config) {
|
|
166
|
+
const characters = (config.characters ?? []).map((spec) => ({
|
|
167
|
+
character_id: spec.characterId.trim(),
|
|
168
|
+
...(spec.characterSessionId
|
|
169
|
+
? { character_session_id: spec.characterSessionId }
|
|
170
|
+
: {}),
|
|
171
|
+
}));
|
|
172
|
+
return {
|
|
173
|
+
// Both carry server defaults that already match, but a roster room is
|
|
174
|
+
// rejected outright without them, so they are stated rather than assumed.
|
|
175
|
+
mode: "create",
|
|
176
|
+
spawn_agent: true,
|
|
177
|
+
characters,
|
|
178
|
+
...(config.sharedSessionKey
|
|
179
|
+
? { shared_session_key: config.sharedSessionKey }
|
|
180
|
+
: {}),
|
|
181
|
+
...(config.connectAttemptId
|
|
182
|
+
? { connect_attempt_id: config.connectAttemptId }
|
|
183
|
+
: {}),
|
|
184
|
+
...(config.maxNumParticipants !== undefined
|
|
185
|
+
? { max_num_participants: config.maxNumParticipants }
|
|
186
|
+
: {}),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The complete join request body. A join carries a locator and the human, and
|
|
191
|
+
* nothing else — the roster is loaded from persisted room state, and resending
|
|
192
|
+
* topology is rejected.
|
|
193
|
+
*/
|
|
194
|
+
export function buildJoinRequestBody(options) {
|
|
195
|
+
return {
|
|
196
|
+
mode: "join",
|
|
197
|
+
transport: "livekit",
|
|
198
|
+
...(options.roomSessionId
|
|
199
|
+
? { room_session_id: options.roomSessionId }
|
|
200
|
+
: { shared_session_key: options.sharedSessionKey }),
|
|
201
|
+
end_user_id: options.endUserId,
|
|
202
|
+
...(options.endUserMetadata
|
|
203
|
+
? { end_user_metadata: options.endUserMetadata }
|
|
204
|
+
: {}),
|
|
205
|
+
...(options.connectAttemptId
|
|
206
|
+
? { connect_attempt_id: options.connectAttemptId }
|
|
207
|
+
: {}),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=rosterRequest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rosterRequest.js","sourceRoot":"","sources":["../../src/core/rosterRequest.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ConvaiCharacterSpec,\n ConvaiConfig,\n JoinRoomOptions,\n} from \"./types\";\nimport { isCharacterUuid } from \"./characterReference\";\n\n/**\n * Validation and request-body construction for multi-character rooms.\n *\n * Pure: no transport, no client state. Every rule below runs before a request\n * is built, so an illegal topology costs nothing and reports the rule it broke\n * rather than a 422 from the runtime.\n */\n\n/**\n * Server-wide roster cap. Checked client-side only to fail fast with a clear\n * message — the server stays authoritative, and a plan may configure a lower\n * `max_characters` that surfaces as 403 `MULTI_CHARACTER_ROSTER_LIMIT_EXCEEDED`.\n * This is an operational limit, not a protocol maximum, so it must not leak\n * into any public type.\n */\nexport const MULTI_CHARACTER_MAX_CHARACTERS = 50;\n\n/** `shared_session_key` and `connect_attempt_id` share one charset. */\nconst KEY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;\n\n/** True when this config asks for a multi-character room. */\nexport function isRosterConfig(\n config: Pick<ConvaiConfig, \"characters\">,\n): boolean {\n return Array.isArray(config.characters) && config.characters.length > 0;\n}\n\nfunction assertKey(value: string | undefined, field: string): void {\n if (value === undefined) return;\n if (!KEY_PATTERN.test(value)) {\n throw new Error(\n `${field} must be 1-128 characters of letters, digits, hyphen or underscore`,\n );\n }\n}\n\nfunction nonblank(value: string | undefined | null): boolean {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\n/**\n * Validate a roster create config.\n *\n * Throws on the first broken rule. Ordered so the most structural problems\n * (which topology did you mean?) report before the detail ones.\n */\nexport function validateRosterConfig(config: ConvaiConfig): void {\n const characters = config.characters ?? [];\n\n if (config.characterId) {\n throw new Error(\"Pass either characterId or characters, not both\");\n }\n if (characters.length === 0) {\n throw new Error(\"characters must contain at least one entry\");\n }\n if (characters.length > MULTI_CHARACTER_MAX_CHARACTERS) {\n throw new Error(\n `characters accepts at most ${MULTI_CHARACTER_MAX_CHARACTERS} entries; ` +\n `your plan may allow fewer`,\n );\n }\n\n const seenSessions = new Set<string>();\n characters.forEach((spec, index) => {\n if (!spec || !nonblank(spec.characterId)) {\n throw new Error(\n `characters[${index}] requires a characterId`,\n );\n }\n // A roster entry is typed as a bare UUID on the runtime. The version\n // selectors that single-character `characterId` has accepted since\n // 1.8.0-beta.5 are a 422 here, and the failure would otherwise only show\n // up as an opaque validation error from the server.\n if (!isCharacterUuid(spec.characterId.trim())) {\n throw new Error(\n `characters[${index}].characterId must be a bare character UUID; ` +\n `version selectors are not supported inside a roster`,\n );\n }\n if (spec.characterSessionId !== undefined) {\n if (!nonblank(spec.characterSessionId)) {\n throw new Error(\n `characters[${index}].characterSessionId must be nonblank when supplied`,\n );\n }\n if (seenSessions.has(spec.characterSessionId)) {\n throw new Error(\n `characters[${index}].characterSessionId is repeated; resume ids must be unique within the roster`,\n );\n }\n seenSessions.add(spec.characterSessionId);\n }\n });\n\n if (config.characterSessionId) {\n throw new Error(\n \"characterSessionId is not valid with characters; use characters[].characterSessionId\",\n );\n }\n if (!nonblank(config.endUserId)) {\n throw new Error(\"characters requires a nonblank endUserId\");\n }\n if (config.transport && config.transport !== \"livekit\") {\n throw new Error(\"Multi-character rooms require the LiveKit transport\");\n }\n if (config.interactionApiUrl) {\n throw new Error(\n \"Multi-character rooms require the LiveKit transport; interactionApiUrl selects SSE\",\n );\n }\n if (config.characterVersion) {\n throw new Error(\n \"characterVersion applies to a single character; roster entries take bare UUIDs\",\n );\n }\n assertKey(config.sharedSessionKey, \"sharedSessionKey\");\n assertKey(config.connectAttemptId, \"connectAttemptId\");\n if (\n config.maxNumParticipants !== undefined &&\n (!Number.isInteger(config.maxNumParticipants) ||\n config.maxNumParticipants < 1)\n ) {\n throw new Error(\"maxNumParticipants must be a positive integer\");\n }\n}\n\n/**\n * Reject a config that negotiates v2 output protocols alongside room options\n * the runtime cannot combine them with.\n *\n * Verified against staging 2026-09-08: `capabilities.actionProtocolVersion: 2`\n * or `modelOutputVersion: 2` is answered with 422\n * `unsupported_action_protocol_topology` / `unsupported_model_output_topology`\n * unless the request uses `mode='create'`, a singular `characterId`, no\n * `sharedSessionKey`, and `maxNumParticipants` of 1. Version 1 carries no such\n * constraint.\n *\n * The published contract mentions only the multi-character half of this. The\n * `sharedSessionKey` and `maxNumParticipants` constraints bite in\n * single-character sessions too, which is why this runs on every connect\n * rather than only the roster path.\n */\nexport function validateProtocolTopology(config: ConvaiConfig): void {\n const negotiatesV2 =\n config.capabilities?.actionProtocolVersion === 2 ||\n config.capabilities?.modelOutputVersion === 2;\n\n if (!negotiatesV2) {\n // Tools need v2, so without it they are refused whatever the topology.\n if (config.actionConfig?.tools?.length) {\n throw new Error(\n \"actionConfig.tools requires capabilities.actionProtocolVersion: 2\",\n );\n }\n return;\n }\n\n const offenders: string[] = [];\n if (isRosterConfig(config)) offenders.push(\"characters\");\n if (config.sharedSessionKey) offenders.push(\"sharedSessionKey\");\n if (config.maxNumParticipants !== undefined && config.maxNumParticipants !== 1) {\n offenders.push(\"maxNumParticipants greater than 1\");\n }\n if (offenders.length === 0) return;\n\n throw new Error(\n `Negotiated v2 output protocols require a single-character room: ` +\n `remove ${offenders.join(\", \")}, or drop capabilities to version 1. ` +\n (offenders.includes(\"characters\")\n ? \"Multi-character rooms cannot use action protocol v2, and therefore cannot use client tools. \"\n : \"\") +\n \"The runtime rejects this combination with unsupported_action_protocol_topology.\",\n );\n}\n\n/** Validate join options. */\nexport function validateJoinOptions(options: JoinRoomOptions): void {\n const locators =\n Number(nonblank(options?.roomSessionId)) +\n Number(nonblank(options?.sharedSessionKey));\n if (locators !== 1) {\n throw new Error(\n \"joinRoom requires exactly one of roomSessionId or sharedSessionKey\",\n );\n }\n if (!nonblank(options.endUserId)) {\n throw new Error(\"joinRoom requires a nonblank endUserId\");\n }\n // Guard the shape even though the type forbids it: a plain-JS caller, or a\n // config object spread in wholesale, would otherwise send topology that the\n // runtime rejects with a less specific message.\n const stray = options as unknown as Record<string, unknown>;\n if (stray.characters || stray.characterId || stray.characterSessionId) {\n throw new Error(\n \"joinRoom must not carry character topology; the room's roster is server-owned\",\n );\n }\n assertKey(options.sharedSessionKey, \"sharedSessionKey\");\n assertKey(options.connectAttemptId, \"connectAttemptId\");\n}\n\n/**\n * The roster half of a create request. Merged into the existing single-character\n * body by the caller, so every current option (blendshape, TTS/STT, dynamic\n * info, action, scene, VAD, vision) continues to apply to every member.\n */\nexport function buildRosterConnectFields(\n config: ConvaiConfig,\n): Record<string, unknown> {\n const characters = (config.characters ?? []).map(\n (spec: ConvaiCharacterSpec) => ({\n character_id: spec.characterId.trim(),\n ...(spec.characterSessionId\n ? { character_session_id: spec.characterSessionId }\n : {}),\n }),\n );\n\n return {\n // Both carry server defaults that already match, but a roster room is\n // rejected outright without them, so they are stated rather than assumed.\n mode: \"create\",\n spawn_agent: true,\n characters,\n ...(config.sharedSessionKey\n ? { shared_session_key: config.sharedSessionKey }\n : {}),\n ...(config.connectAttemptId\n ? { connect_attempt_id: config.connectAttemptId }\n : {}),\n ...(config.maxNumParticipants !== undefined\n ? { max_num_participants: config.maxNumParticipants }\n : {}),\n };\n}\n\n/**\n * The complete join request body. A join carries a locator and the human, and\n * nothing else — the roster is loaded from persisted room state, and resending\n * topology is rejected.\n */\nexport function buildJoinRequestBody(\n options: JoinRoomOptions,\n): Record<string, unknown> {\n return {\n mode: \"join\",\n transport: \"livekit\",\n ...(options.roomSessionId\n ? { room_session_id: options.roomSessionId }\n : { shared_session_key: options.sharedSessionKey }),\n end_user_id: options.endUserId,\n ...(options.endUserMetadata\n ? { end_user_metadata: options.endUserMetadata }\n : {}),\n ...(options.connectAttemptId\n ? { connect_attempt_id: options.connectAttemptId }\n : {}),\n };\n}\n"]}
|