@livekit/agents-plugin-protoface 1.5.3
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 +201 -0
- package/README.md +15 -0
- package/dist/api.cjs +153 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.d.cts +42 -0
- package/dist/api.d.ts +42 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +133 -0
- package/dist/api.js.map +1 -0
- package/dist/api.test.cjs +55 -0
- package/dist/api.test.cjs.map +1 -0
- package/dist/api.test.d.cts +2 -0
- package/dist/api.test.d.ts +2 -0
- package/dist/api.test.d.ts.map +1 -0
- package/dist/api.test.js +54 -0
- package/dist/api.test.js.map +1 -0
- package/dist/avatar.cjs +160 -0
- package/dist/avatar.cjs.map +1 -0
- package/dist/avatar.d.cts +54 -0
- package/dist/avatar.d.ts +54 -0
- package/dist/avatar.d.ts.map +1 -0
- package/dist/avatar.js +139 -0
- package/dist/avatar.js.map +1 -0
- package/dist/avatar.test.cjs +107 -0
- package/dist/avatar.test.cjs.map +1 -0
- package/dist/avatar.test.d.cts +2 -0
- package/dist/avatar.test.d.ts +2 -0
- package/dist/avatar.test.d.ts.map +1 -0
- package/dist/avatar.test.js +106 -0
- package/dist/avatar.test.js.map +1 -0
- package/dist/index.cjs +36 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/log.cjs +30 -0
- package/dist/log.cjs.map +1 -0
- package/dist/log.d.cts +3 -0
- package/dist/log.d.ts +3 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +6 -0
- package/dist/log.js.map +1 -0
- package/package.json +51 -0
- package/src/api.test.ts +65 -0
- package/src/api.ts +182 -0
- package/src/avatar.test.ts +132 -0
- package/src/avatar.ts +204 -0
- package/src/index.ts +19 -0
- package/src/log.ts +7 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_api = require("./api.cjs");
|
|
4
|
+
(0, import_vitest.describe)("ProtofaceAPI", () => {
|
|
5
|
+
(0, import_vitest.afterEach)(() => {
|
|
6
|
+
import_vitest.vi.unstubAllEnvs();
|
|
7
|
+
import_vitest.vi.unstubAllGlobals();
|
|
8
|
+
});
|
|
9
|
+
(0, import_vitest.it)("requires an API key", () => {
|
|
10
|
+
import_vitest.vi.stubEnv("PROTOFACE_API_KEY", "");
|
|
11
|
+
(0, import_vitest.expect)(() => new import_api.ProtofaceAPI()).toThrow(import_api.ProtofaceException);
|
|
12
|
+
});
|
|
13
|
+
(0, import_vitest.it)("uses env configuration and sends the expected start session request", async () => {
|
|
14
|
+
import_vitest.vi.stubEnv("PROTOFACE_API_KEY", "sk_test");
|
|
15
|
+
import_vitest.vi.stubEnv("PROTOFACE_API_URL", "https://api.example.test/");
|
|
16
|
+
const fetchMock = import_vitest.vi.fn(async () => new Response('{"id":"sess_test"}', { status: 200 }));
|
|
17
|
+
import_vitest.vi.stubGlobal("fetch", fetchMock);
|
|
18
|
+
const client = new import_api.ProtofaceAPI();
|
|
19
|
+
const session = await client.startSession({
|
|
20
|
+
avatarId: "av_test",
|
|
21
|
+
transport: { type: "livekit" },
|
|
22
|
+
maxDurationMs: 12e4
|
|
23
|
+
});
|
|
24
|
+
(0, import_vitest.expect)(session).toEqual({ id: "sess_test" });
|
|
25
|
+
(0, import_vitest.expect)(fetchMock).toHaveBeenCalledTimes(1);
|
|
26
|
+
(0, import_vitest.expect)(fetchMock).toHaveBeenCalledWith(
|
|
27
|
+
"https://api.example.test/v1/sessions",
|
|
28
|
+
import_vitest.expect.objectContaining({
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: import_vitest.expect.objectContaining({
|
|
31
|
+
accept: "application/json",
|
|
32
|
+
authorization: "Bearer sk_test",
|
|
33
|
+
"content-type": "application/json",
|
|
34
|
+
"user-agent": "@livekit/agents-plugin-protoface/0.0.0-test"
|
|
35
|
+
}),
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
avatar_id: "av_test",
|
|
38
|
+
transport: { type: "livekit" },
|
|
39
|
+
max_duration_seconds: 120
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
(0, import_vitest.it)("ends a session", async () => {
|
|
45
|
+
const fetchMock = import_vitest.vi.fn(async () => new Response("{}", { status: 200 }));
|
|
46
|
+
import_vitest.vi.stubGlobal("fetch", fetchMock);
|
|
47
|
+
const client = new import_api.ProtofaceAPI({ apiKey: "sk_test", apiUrl: "https://api.example.test" });
|
|
48
|
+
await (0, import_vitest.expect)(client.endSession("sess_test")).resolves.toEqual({});
|
|
49
|
+
(0, import_vitest.expect)(fetchMock).toHaveBeenCalledWith(
|
|
50
|
+
"https://api.example.test/v1/sessions/sess_test/end",
|
|
51
|
+
import_vitest.expect.objectContaining({ method: "POST" })
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=api.test.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { afterEach, describe, expect, it, vi } from 'vitest';\nimport { ProtofaceAPI, ProtofaceException } from './api.js';\n\ndescribe('ProtofaceAPI', () => {\n afterEach(() => {\n vi.unstubAllEnvs();\n vi.unstubAllGlobals();\n });\n\n it('requires an API key', () => {\n vi.stubEnv('PROTOFACE_API_KEY', '');\n\n expect(() => new ProtofaceAPI()).toThrow(ProtofaceException);\n });\n\n it('uses env configuration and sends the expected start session request', async () => {\n vi.stubEnv('PROTOFACE_API_KEY', 'sk_test');\n vi.stubEnv('PROTOFACE_API_URL', 'https://api.example.test/');\n const fetchMock = vi.fn(async () => new Response('{\"id\":\"sess_test\"}', { status: 200 }));\n vi.stubGlobal('fetch', fetchMock);\n\n const client = new ProtofaceAPI();\n const session = await client.startSession({\n avatarId: 'av_test',\n transport: { type: 'livekit' },\n maxDurationMs: 120_000,\n });\n\n expect(session).toEqual({ id: 'sess_test' });\n expect(fetchMock).toHaveBeenCalledTimes(1);\n expect(fetchMock).toHaveBeenCalledWith(\n 'https://api.example.test/v1/sessions',\n expect.objectContaining({\n method: 'POST',\n headers: expect.objectContaining({\n accept: 'application/json',\n authorization: 'Bearer sk_test',\n 'content-type': 'application/json',\n 'user-agent': '@livekit/agents-plugin-protoface/0.0.0-test',\n }),\n body: JSON.stringify({\n avatar_id: 'av_test',\n transport: { type: 'livekit' },\n max_duration_seconds: 120,\n }),\n }),\n );\n });\n\n it('ends a session', async () => {\n const fetchMock = vi.fn(async () => new Response('{}', { status: 200 }));\n vi.stubGlobal('fetch', fetchMock);\n\n const client = new ProtofaceAPI({ apiKey: 'sk_test', apiUrl: 'https://api.example.test' });\n await expect(client.endSession('sess_test')).resolves.toEqual({});\n\n expect(fetchMock).toHaveBeenCalledWith(\n 'https://api.example.test/v1/sessions/sess_test/end',\n expect.objectContaining({ method: 'POST' }),\n );\n });\n});\n"],"mappings":";AAGA,oBAAoD;AACpD,iBAAiD;AAAA,IAEjD,wBAAS,gBAAgB,MAAM;AAC7B,+BAAU,MAAM;AACd,qBAAG,cAAc;AACjB,qBAAG,iBAAiB;AAAA,EACtB,CAAC;AAED,wBAAG,uBAAuB,MAAM;AAC9B,qBAAG,QAAQ,qBAAqB,EAAE;AAElC,8BAAO,MAAM,IAAI,wBAAa,CAAC,EAAE,QAAQ,6BAAkB;AAAA,EAC7D,CAAC;AAED,wBAAG,uEAAuE,YAAY;AACpF,qBAAG,QAAQ,qBAAqB,SAAS;AACzC,qBAAG,QAAQ,qBAAqB,2BAA2B;AAC3D,UAAM,YAAY,iBAAG,GAAG,YAAY,IAAI,SAAS,sBAAsB,EAAE,QAAQ,IAAI,CAAC,CAAC;AACvF,qBAAG,WAAW,SAAS,SAAS;AAEhC,UAAM,SAAS,IAAI,wBAAa;AAChC,UAAM,UAAU,MAAM,OAAO,aAAa;AAAA,MACxC,UAAU;AAAA,MACV,WAAW,EAAE,MAAM,UAAU;AAAA,MAC7B,eAAe;AAAA,IACjB,CAAC;AAED,8BAAO,OAAO,EAAE,QAAQ,EAAE,IAAI,YAAY,CAAC;AAC3C,8BAAO,SAAS,EAAE,sBAAsB,CAAC;AACzC,8BAAO,SAAS,EAAE;AAAA,MAChB;AAAA,MACA,qBAAO,iBAAiB;AAAA,QACtB,QAAQ;AAAA,QACR,SAAS,qBAAO,iBAAiB;AAAA,UAC/B,QAAQ;AAAA,UACR,eAAe;AAAA,UACf,gBAAgB;AAAA,UAChB,cAAc;AAAA,QAChB,CAAC;AAAA,QACD,MAAM,KAAK,UAAU;AAAA,UACnB,WAAW;AAAA,UACX,WAAW,EAAE,MAAM,UAAU;AAAA,UAC7B,sBAAsB;AAAA,QACxB,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,wBAAG,kBAAkB,YAAY;AAC/B,UAAM,YAAY,iBAAG,GAAG,YAAY,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC,CAAC;AACvE,qBAAG,WAAW,SAAS,SAAS;AAEhC,UAAM,SAAS,IAAI,wBAAa,EAAE,QAAQ,WAAW,QAAQ,2BAA2B,CAAC;AACzF,cAAM,sBAAO,OAAO,WAAW,WAAW,CAAC,EAAE,SAAS,QAAQ,CAAC,CAAC;AAEhE,8BAAO,SAAS,EAAE;AAAA,MAChB;AAAA,MACA,qBAAO,iBAAiB,EAAE,QAAQ,OAAO,CAAC;AAAA,IAC5C;AAAA,EACF,CAAC;AACH,CAAC;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.test.d.ts","sourceRoot":"","sources":["../src/api.test.ts"],"names":[],"mappings":""}
|
package/dist/api.test.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { ProtofaceAPI, ProtofaceException } from "./api.js";
|
|
3
|
+
describe("ProtofaceAPI", () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
vi.unstubAllEnvs();
|
|
6
|
+
vi.unstubAllGlobals();
|
|
7
|
+
});
|
|
8
|
+
it("requires an API key", () => {
|
|
9
|
+
vi.stubEnv("PROTOFACE_API_KEY", "");
|
|
10
|
+
expect(() => new ProtofaceAPI()).toThrow(ProtofaceException);
|
|
11
|
+
});
|
|
12
|
+
it("uses env configuration and sends the expected start session request", async () => {
|
|
13
|
+
vi.stubEnv("PROTOFACE_API_KEY", "sk_test");
|
|
14
|
+
vi.stubEnv("PROTOFACE_API_URL", "https://api.example.test/");
|
|
15
|
+
const fetchMock = vi.fn(async () => new Response('{"id":"sess_test"}', { status: 200 }));
|
|
16
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
17
|
+
const client = new ProtofaceAPI();
|
|
18
|
+
const session = await client.startSession({
|
|
19
|
+
avatarId: "av_test",
|
|
20
|
+
transport: { type: "livekit" },
|
|
21
|
+
maxDurationMs: 12e4
|
|
22
|
+
});
|
|
23
|
+
expect(session).toEqual({ id: "sess_test" });
|
|
24
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
25
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
26
|
+
"https://api.example.test/v1/sessions",
|
|
27
|
+
expect.objectContaining({
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: expect.objectContaining({
|
|
30
|
+
accept: "application/json",
|
|
31
|
+
authorization: "Bearer sk_test",
|
|
32
|
+
"content-type": "application/json",
|
|
33
|
+
"user-agent": "@livekit/agents-plugin-protoface/0.0.0-test"
|
|
34
|
+
}),
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
avatar_id: "av_test",
|
|
37
|
+
transport: { type: "livekit" },
|
|
38
|
+
max_duration_seconds: 120
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
it("ends a session", async () => {
|
|
44
|
+
const fetchMock = vi.fn(async () => new Response("{}", { status: 200 }));
|
|
45
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
46
|
+
const client = new ProtofaceAPI({ apiKey: "sk_test", apiUrl: "https://api.example.test" });
|
|
47
|
+
await expect(client.endSession("sess_test")).resolves.toEqual({});
|
|
48
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
49
|
+
"https://api.example.test/v1/sessions/sess_test/end",
|
|
50
|
+
expect.objectContaining({ method: "POST" })
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=api.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { afterEach, describe, expect, it, vi } from 'vitest';\nimport { ProtofaceAPI, ProtofaceException } from './api.js';\n\ndescribe('ProtofaceAPI', () => {\n afterEach(() => {\n vi.unstubAllEnvs();\n vi.unstubAllGlobals();\n });\n\n it('requires an API key', () => {\n vi.stubEnv('PROTOFACE_API_KEY', '');\n\n expect(() => new ProtofaceAPI()).toThrow(ProtofaceException);\n });\n\n it('uses env configuration and sends the expected start session request', async () => {\n vi.stubEnv('PROTOFACE_API_KEY', 'sk_test');\n vi.stubEnv('PROTOFACE_API_URL', 'https://api.example.test/');\n const fetchMock = vi.fn(async () => new Response('{\"id\":\"sess_test\"}', { status: 200 }));\n vi.stubGlobal('fetch', fetchMock);\n\n const client = new ProtofaceAPI();\n const session = await client.startSession({\n avatarId: 'av_test',\n transport: { type: 'livekit' },\n maxDurationMs: 120_000,\n });\n\n expect(session).toEqual({ id: 'sess_test' });\n expect(fetchMock).toHaveBeenCalledTimes(1);\n expect(fetchMock).toHaveBeenCalledWith(\n 'https://api.example.test/v1/sessions',\n expect.objectContaining({\n method: 'POST',\n headers: expect.objectContaining({\n accept: 'application/json',\n authorization: 'Bearer sk_test',\n 'content-type': 'application/json',\n 'user-agent': '@livekit/agents-plugin-protoface/0.0.0-test',\n }),\n body: JSON.stringify({\n avatar_id: 'av_test',\n transport: { type: 'livekit' },\n max_duration_seconds: 120,\n }),\n }),\n );\n });\n\n it('ends a session', async () => {\n const fetchMock = vi.fn(async () => new Response('{}', { status: 200 }));\n vi.stubGlobal('fetch', fetchMock);\n\n const client = new ProtofaceAPI({ apiKey: 'sk_test', apiUrl: 'https://api.example.test' });\n await expect(client.endSession('sess_test')).resolves.toEqual({});\n\n expect(fetchMock).toHaveBeenCalledWith(\n 'https://api.example.test/v1/sessions/sess_test/end',\n expect.objectContaining({ method: 'POST' }),\n );\n });\n});\n"],"mappings":"AAGA,SAAS,WAAW,UAAU,QAAQ,IAAI,UAAU;AACpD,SAAS,cAAc,0BAA0B;AAEjD,SAAS,gBAAgB,MAAM;AAC7B,YAAU,MAAM;AACd,OAAG,cAAc;AACjB,OAAG,iBAAiB;AAAA,EACtB,CAAC;AAED,KAAG,uBAAuB,MAAM;AAC9B,OAAG,QAAQ,qBAAqB,EAAE;AAElC,WAAO,MAAM,IAAI,aAAa,CAAC,EAAE,QAAQ,kBAAkB;AAAA,EAC7D,CAAC;AAED,KAAG,uEAAuE,YAAY;AACpF,OAAG,QAAQ,qBAAqB,SAAS;AACzC,OAAG,QAAQ,qBAAqB,2BAA2B;AAC3D,UAAM,YAAY,GAAG,GAAG,YAAY,IAAI,SAAS,sBAAsB,EAAE,QAAQ,IAAI,CAAC,CAAC;AACvF,OAAG,WAAW,SAAS,SAAS;AAEhC,UAAM,SAAS,IAAI,aAAa;AAChC,UAAM,UAAU,MAAM,OAAO,aAAa;AAAA,MACxC,UAAU;AAAA,MACV,WAAW,EAAE,MAAM,UAAU;AAAA,MAC7B,eAAe;AAAA,IACjB,CAAC;AAED,WAAO,OAAO,EAAE,QAAQ,EAAE,IAAI,YAAY,CAAC;AAC3C,WAAO,SAAS,EAAE,sBAAsB,CAAC;AACzC,WAAO,SAAS,EAAE;AAAA,MAChB;AAAA,MACA,OAAO,iBAAiB;AAAA,QACtB,QAAQ;AAAA,QACR,SAAS,OAAO,iBAAiB;AAAA,UAC/B,QAAQ;AAAA,UACR,eAAe;AAAA,UACf,gBAAgB;AAAA,UAChB,cAAc;AAAA,QAChB,CAAC;AAAA,QACD,MAAM,KAAK,UAAU;AAAA,UACnB,WAAW;AAAA,UACX,WAAW,EAAE,MAAM,UAAU;AAAA,UAC7B,sBAAsB;AAAA,QACxB,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,KAAG,kBAAkB,YAAY;AAC/B,UAAM,YAAY,GAAG,GAAG,YAAY,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC,CAAC;AACvE,OAAG,WAAW,SAAS,SAAS;AAEhC,UAAM,SAAS,IAAI,aAAa,EAAE,QAAQ,WAAW,QAAQ,2BAA2B,CAAC;AACzF,UAAM,OAAO,OAAO,WAAW,WAAW,CAAC,EAAE,SAAS,QAAQ,CAAC,CAAC;AAEhE,WAAO,SAAS,EAAE;AAAA,MAChB;AAAA,MACA,OAAO,iBAAiB,EAAE,QAAQ,OAAO,CAAC;AAAA,IAC5C;AAAA,EACF,CAAC;AACH,CAAC;","names":[]}
|
package/dist/avatar.cjs
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var avatar_exports = {};
|
|
20
|
+
__export(avatar_exports, {
|
|
21
|
+
AvatarSession: () => AvatarSession,
|
|
22
|
+
DEFAULT_STOCK_AVATAR_ID: () => DEFAULT_STOCK_AVATAR_ID
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(avatar_exports);
|
|
25
|
+
var import_agents = require("@livekit/agents");
|
|
26
|
+
var import_rtc_node = require("@livekit/rtc-node");
|
|
27
|
+
var import_livekit_server_sdk = require("livekit-server-sdk");
|
|
28
|
+
var import_api = require("./api.cjs");
|
|
29
|
+
var import_log = require("./log.cjs");
|
|
30
|
+
const DEFAULT_STOCK_AVATAR_ID = "av_stock_001";
|
|
31
|
+
const ATTRIBUTE_PUBLISH_ON_BEHALF = "lk.publish_on_behalf";
|
|
32
|
+
const SAMPLE_RATE = 16e3;
|
|
33
|
+
const AVATAR_AGENT_IDENTITY = "protoface-avatar-agent";
|
|
34
|
+
const AVATAR_AGENT_NAME = "protoface-avatar-agent";
|
|
35
|
+
class AvatarSession extends import_agents.voice.AvatarSession {
|
|
36
|
+
avatarId;
|
|
37
|
+
maxDurationMs;
|
|
38
|
+
avatarParticipantIdentity;
|
|
39
|
+
avatarParticipantName;
|
|
40
|
+
api;
|
|
41
|
+
sessionIdValue = null;
|
|
42
|
+
#logger = (0, import_log.log)();
|
|
43
|
+
constructor(options = {}) {
|
|
44
|
+
super();
|
|
45
|
+
this.avatarId = options.avatarId ?? DEFAULT_STOCK_AVATAR_ID;
|
|
46
|
+
this.maxDurationMs = options.maxDurationMs;
|
|
47
|
+
this.avatarParticipantIdentity = options.avatarParticipantIdentity || AVATAR_AGENT_IDENTITY;
|
|
48
|
+
this.avatarParticipantName = options.avatarParticipantName || AVATAR_AGENT_NAME;
|
|
49
|
+
this.api = new import_api.ProtofaceAPI({
|
|
50
|
+
apiKey: options.apiKey,
|
|
51
|
+
apiUrl: options.apiUrl,
|
|
52
|
+
connOptions: options.connOptions ?? import_agents.DEFAULT_API_CONNECT_OPTIONS
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
get avatarIdentity() {
|
|
56
|
+
return this.avatarParticipantIdentity;
|
|
57
|
+
}
|
|
58
|
+
get provider() {
|
|
59
|
+
return "protoface";
|
|
60
|
+
}
|
|
61
|
+
/** Protoface session ID after `start()` succeeds, otherwise `null`. */
|
|
62
|
+
get sessionId() {
|
|
63
|
+
return this.sessionIdValue;
|
|
64
|
+
}
|
|
65
|
+
async start(agentSession, room, options = {}) {
|
|
66
|
+
if (this.sessionIdValue !== null) {
|
|
67
|
+
throw new Error("AvatarSession.start() called twice; create a new AvatarSession.");
|
|
68
|
+
}
|
|
69
|
+
await super.start(agentSession, room);
|
|
70
|
+
const livekitUrl = options.livekitUrl ?? process.env.LIVEKIT_URL;
|
|
71
|
+
const livekitApiKey = options.livekitApiKey ?? process.env.LIVEKIT_API_KEY;
|
|
72
|
+
const livekitApiSecret = options.livekitApiSecret ?? process.env.LIVEKIT_API_SECRET;
|
|
73
|
+
if (!livekitUrl || !livekitApiKey || !livekitApiSecret) {
|
|
74
|
+
throw new import_api.ProtofaceException(
|
|
75
|
+
"livekitUrl, livekitApiKey, and livekitApiSecret must be set by arguments or environment variables"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const workerToken = await this.mintWorkerToken({ room, livekitApiKey, livekitApiSecret });
|
|
79
|
+
const session = await this.api.startSession({
|
|
80
|
+
avatarId: this.avatarId,
|
|
81
|
+
transport: {
|
|
82
|
+
type: "livekit",
|
|
83
|
+
url: livekitUrl,
|
|
84
|
+
room_name: room.name,
|
|
85
|
+
worker_token: workerToken,
|
|
86
|
+
worker_identity: this.avatarParticipantIdentity,
|
|
87
|
+
audio_source: "data_stream"
|
|
88
|
+
},
|
|
89
|
+
maxDurationMs: this.maxDurationMs
|
|
90
|
+
});
|
|
91
|
+
if (typeof session.id !== "string") {
|
|
92
|
+
throw new import_api.ProtofaceException("Protoface API response missing session id");
|
|
93
|
+
}
|
|
94
|
+
this.sessionIdValue = session.id;
|
|
95
|
+
this.#logger.debug(
|
|
96
|
+
{ sessionId: this.sessionIdValue, avatarId: this.avatarId },
|
|
97
|
+
"protoface session started"
|
|
98
|
+
);
|
|
99
|
+
agentSession.output.audio = new import_agents.voice.DataStreamAudioOutput({
|
|
100
|
+
room,
|
|
101
|
+
destinationIdentity: this.avatarParticipantIdentity,
|
|
102
|
+
sampleRate: SAMPLE_RATE,
|
|
103
|
+
waitRemoteTrack: import_rtc_node.TrackKind.KIND_VIDEO
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async aclose() {
|
|
107
|
+
const sessionId = this.sessionIdValue;
|
|
108
|
+
this.sessionIdValue = null;
|
|
109
|
+
try {
|
|
110
|
+
if (sessionId !== null) {
|
|
111
|
+
try {
|
|
112
|
+
await this.api.endSession(sessionId);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
this.#logger.warn({ error: String(error), sessionId }, "failed to end protoface session");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
await super.aclose();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async mintWorkerToken({
|
|
122
|
+
room,
|
|
123
|
+
livekitApiKey,
|
|
124
|
+
livekitApiSecret
|
|
125
|
+
}) {
|
|
126
|
+
var _a;
|
|
127
|
+
let localParticipantIdentity = "";
|
|
128
|
+
try {
|
|
129
|
+
const jobCtx = (0, import_agents.getJobContext)();
|
|
130
|
+
localParticipantIdentity = ((_a = jobCtx.agent) == null ? void 0 : _a.identity) || "";
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
if (!localParticipantIdentity && room.isConnected && room.localParticipant) {
|
|
134
|
+
localParticipantIdentity = room.localParticipant.identity;
|
|
135
|
+
}
|
|
136
|
+
if (!localParticipantIdentity) {
|
|
137
|
+
throw new import_api.ProtofaceException("failed to get local participant identity");
|
|
138
|
+
}
|
|
139
|
+
const token = new import_livekit_server_sdk.AccessToken(livekitApiKey, livekitApiSecret, {
|
|
140
|
+
identity: this.avatarParticipantIdentity,
|
|
141
|
+
name: this.avatarParticipantName
|
|
142
|
+
});
|
|
143
|
+
token.kind = "agent";
|
|
144
|
+
token.attributes = { [ATTRIBUTE_PUBLISH_ON_BEHALF]: localParticipantIdentity };
|
|
145
|
+
token.addGrant({
|
|
146
|
+
roomJoin: true,
|
|
147
|
+
room: room.name,
|
|
148
|
+
canPublish: true,
|
|
149
|
+
canSubscribe: true,
|
|
150
|
+
canPublishData: true
|
|
151
|
+
});
|
|
152
|
+
return token.toJwt();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
156
|
+
0 && (module.exports = {
|
|
157
|
+
AvatarSession,
|
|
158
|
+
DEFAULT_STOCK_AVATAR_ID
|
|
159
|
+
});
|
|
160
|
+
//# sourceMappingURL=avatar.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/avatar.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type APIConnectOptions,\n DEFAULT_API_CONNECT_OPTIONS,\n getJobContext,\n voice,\n} from '@livekit/agents';\nimport type { Room } from '@livekit/rtc-node';\nimport { TrackKind } from '@livekit/rtc-node';\nimport type { VideoGrant } from 'livekit-server-sdk';\nimport { AccessToken } from 'livekit-server-sdk';\nimport { ProtofaceAPI, ProtofaceException } from './api.js';\nimport { log } from './log.js';\n\n/** @public */\nexport const DEFAULT_STOCK_AVATAR_ID = 'av_stock_001';\n\nconst ATTRIBUTE_PUBLISH_ON_BEHALF = 'lk.publish_on_behalf';\nconst SAMPLE_RATE = 16000;\nconst AVATAR_AGENT_IDENTITY = 'protoface-avatar-agent';\nconst AVATAR_AGENT_NAME = 'protoface-avatar-agent';\n\n/** @public */\nexport interface AvatarSessionOptions {\n /** Protoface avatar ID to render. Defaults to the stable stock avatar ID `av_stock_001`. */\n avatarId?: string;\n /** Override the Protoface API base URL. */\n apiUrl?: string | null;\n /** Protoface API key. Falls back to the `PROTOFACE_API_KEY` env var. */\n apiKey?: string | null;\n /** Optional maximum session duration in milliseconds. */\n maxDurationMs?: number | null;\n /** Identity for the avatar participant. Defaults to `protoface-avatar-agent`. */\n avatarParticipantIdentity?: string | null;\n /** Display name for the avatar participant. Defaults to `protoface-avatar-agent`. */\n avatarParticipantName?: string | null;\n /** API retry/timeout options. */\n connOptions?: APIConnectOptions;\n}\n\n/**\n * Optional LiveKit credentials for {@link AvatarSession.start}; falls back to env vars.\n *\n * @public\n */\nexport interface StartOptions {\n livekitUrl?: string | null;\n livekitApiKey?: string | null;\n livekitApiSecret?: string | null;\n}\n\n/**\n * A Protoface avatar session for LiveKit Agents.\n *\n * @public\n */\nexport class AvatarSession extends voice.AvatarSession {\n private avatarId: string;\n private maxDurationMs?: number | null;\n private avatarParticipantIdentity: string;\n private avatarParticipantName: string;\n private api: ProtofaceAPI;\n private sessionIdValue: string | null = null;\n\n #logger = log();\n\n constructor(options: AvatarSessionOptions = {}) {\n super();\n this.avatarId = options.avatarId ?? DEFAULT_STOCK_AVATAR_ID;\n this.maxDurationMs = options.maxDurationMs;\n this.avatarParticipantIdentity = options.avatarParticipantIdentity || AVATAR_AGENT_IDENTITY;\n this.avatarParticipantName = options.avatarParticipantName || AVATAR_AGENT_NAME;\n this.api = new ProtofaceAPI({\n apiKey: options.apiKey,\n apiUrl: options.apiUrl,\n connOptions: options.connOptions ?? DEFAULT_API_CONNECT_OPTIONS,\n });\n }\n\n override get avatarIdentity(): string {\n return this.avatarParticipantIdentity;\n }\n\n override get provider(): string {\n return 'protoface';\n }\n\n /** Protoface session ID after `start()` succeeds, otherwise `null`. */\n get sessionId(): string | null {\n return this.sessionIdValue;\n }\n\n async start(\n agentSession: voice.AgentSession,\n room: Room,\n options: StartOptions = {},\n ): Promise<void> {\n if (this.sessionIdValue !== null) {\n throw new Error('AvatarSession.start() called twice; create a new AvatarSession.');\n }\n\n await super.start(agentSession, room);\n\n const livekitUrl = options.livekitUrl ?? process.env.LIVEKIT_URL;\n const livekitApiKey = options.livekitApiKey ?? process.env.LIVEKIT_API_KEY;\n const livekitApiSecret = options.livekitApiSecret ?? process.env.LIVEKIT_API_SECRET;\n if (!livekitUrl || !livekitApiKey || !livekitApiSecret) {\n throw new ProtofaceException(\n 'livekitUrl, livekitApiKey, and livekitApiSecret must be set by arguments or environment variables',\n );\n }\n\n const workerToken = await this.mintWorkerToken({ room, livekitApiKey, livekitApiSecret });\n const session = await this.api.startSession({\n avatarId: this.avatarId,\n transport: {\n type: 'livekit',\n url: livekitUrl,\n room_name: room.name,\n worker_token: workerToken,\n worker_identity: this.avatarParticipantIdentity,\n audio_source: 'data_stream',\n },\n maxDurationMs: this.maxDurationMs,\n });\n\n if (typeof session.id !== 'string') {\n throw new ProtofaceException('Protoface API response missing session id');\n }\n\n this.sessionIdValue = session.id;\n this.#logger.debug(\n { sessionId: this.sessionIdValue, avatarId: this.avatarId },\n 'protoface session started',\n );\n\n agentSession.output.audio = new voice.DataStreamAudioOutput({\n room,\n destinationIdentity: this.avatarParticipantIdentity,\n sampleRate: SAMPLE_RATE,\n waitRemoteTrack: TrackKind.KIND_VIDEO,\n });\n }\n\n async aclose(): Promise<void> {\n const sessionId = this.sessionIdValue;\n this.sessionIdValue = null;\n try {\n if (sessionId !== null) {\n try {\n await this.api.endSession(sessionId);\n } catch (error) {\n this.#logger.warn({ error: String(error), sessionId }, 'failed to end protoface session');\n }\n }\n } finally {\n await super.aclose();\n }\n }\n\n private async mintWorkerToken({\n room,\n livekitApiKey,\n livekitApiSecret,\n }: {\n room: Room;\n livekitApiKey: string;\n livekitApiSecret: string;\n }): Promise<string> {\n let localParticipantIdentity = '';\n try {\n const jobCtx = getJobContext();\n localParticipantIdentity = jobCtx.agent?.identity || '';\n } catch {\n // Fall back to the connected room below when no job context is available.\n }\n\n if (!localParticipantIdentity && room.isConnected && room.localParticipant) {\n localParticipantIdentity = room.localParticipant.identity;\n }\n\n if (!localParticipantIdentity) {\n throw new ProtofaceException('failed to get local participant identity');\n }\n\n const token = new AccessToken(livekitApiKey, livekitApiSecret, {\n identity: this.avatarParticipantIdentity,\n name: this.avatarParticipantName,\n });\n token.kind = 'agent';\n token.attributes = { [ATTRIBUTE_PUBLISH_ON_BEHALF]: localParticipantIdentity };\n token.addGrant({\n roomJoin: true,\n room: room.name,\n canPublish: true,\n canSubscribe: true,\n canPublishData: true,\n } as VideoGrant);\n\n return token.toJwt();\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type APIConnectOptions, voice } from '@livekit/agents';
|
|
2
|
+
import type { Room } from '@livekit/rtc-node';
|
|
3
|
+
/** @public */
|
|
4
|
+
export declare const DEFAULT_STOCK_AVATAR_ID = "av_stock_001";
|
|
5
|
+
/** @public */
|
|
6
|
+
export interface AvatarSessionOptions {
|
|
7
|
+
/** Protoface avatar ID to render. Defaults to the stable stock avatar ID `av_stock_001`. */
|
|
8
|
+
avatarId?: string;
|
|
9
|
+
/** Override the Protoface API base URL. */
|
|
10
|
+
apiUrl?: string | null;
|
|
11
|
+
/** Protoface API key. Falls back to the `PROTOFACE_API_KEY` env var. */
|
|
12
|
+
apiKey?: string | null;
|
|
13
|
+
/** Optional maximum session duration in milliseconds. */
|
|
14
|
+
maxDurationMs?: number | null;
|
|
15
|
+
/** Identity for the avatar participant. Defaults to `protoface-avatar-agent`. */
|
|
16
|
+
avatarParticipantIdentity?: string | null;
|
|
17
|
+
/** Display name for the avatar participant. Defaults to `protoface-avatar-agent`. */
|
|
18
|
+
avatarParticipantName?: string | null;
|
|
19
|
+
/** API retry/timeout options. */
|
|
20
|
+
connOptions?: APIConnectOptions;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Optional LiveKit credentials for {@link AvatarSession.start}; falls back to env vars.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export interface StartOptions {
|
|
28
|
+
livekitUrl?: string | null;
|
|
29
|
+
livekitApiKey?: string | null;
|
|
30
|
+
livekitApiSecret?: string | null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A Protoface avatar session for LiveKit Agents.
|
|
34
|
+
*
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export declare class AvatarSession extends voice.AvatarSession {
|
|
38
|
+
#private;
|
|
39
|
+
private avatarId;
|
|
40
|
+
private maxDurationMs?;
|
|
41
|
+
private avatarParticipantIdentity;
|
|
42
|
+
private avatarParticipantName;
|
|
43
|
+
private api;
|
|
44
|
+
private sessionIdValue;
|
|
45
|
+
constructor(options?: AvatarSessionOptions);
|
|
46
|
+
get avatarIdentity(): string;
|
|
47
|
+
get provider(): string;
|
|
48
|
+
/** Protoface session ID after `start()` succeeds, otherwise `null`. */
|
|
49
|
+
get sessionId(): string | null;
|
|
50
|
+
start(agentSession: voice.AgentSession, room: Room, options?: StartOptions): Promise<void>;
|
|
51
|
+
aclose(): Promise<void>;
|
|
52
|
+
private mintWorkerToken;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=avatar.d.ts.map
|
package/dist/avatar.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type APIConnectOptions, voice } from '@livekit/agents';
|
|
2
|
+
import type { Room } from '@livekit/rtc-node';
|
|
3
|
+
/** @public */
|
|
4
|
+
export declare const DEFAULT_STOCK_AVATAR_ID = "av_stock_001";
|
|
5
|
+
/** @public */
|
|
6
|
+
export interface AvatarSessionOptions {
|
|
7
|
+
/** Protoface avatar ID to render. Defaults to the stable stock avatar ID `av_stock_001`. */
|
|
8
|
+
avatarId?: string;
|
|
9
|
+
/** Override the Protoface API base URL. */
|
|
10
|
+
apiUrl?: string | null;
|
|
11
|
+
/** Protoface API key. Falls back to the `PROTOFACE_API_KEY` env var. */
|
|
12
|
+
apiKey?: string | null;
|
|
13
|
+
/** Optional maximum session duration in milliseconds. */
|
|
14
|
+
maxDurationMs?: number | null;
|
|
15
|
+
/** Identity for the avatar participant. Defaults to `protoface-avatar-agent`. */
|
|
16
|
+
avatarParticipantIdentity?: string | null;
|
|
17
|
+
/** Display name for the avatar participant. Defaults to `protoface-avatar-agent`. */
|
|
18
|
+
avatarParticipantName?: string | null;
|
|
19
|
+
/** API retry/timeout options. */
|
|
20
|
+
connOptions?: APIConnectOptions;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Optional LiveKit credentials for {@link AvatarSession.start}; falls back to env vars.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export interface StartOptions {
|
|
28
|
+
livekitUrl?: string | null;
|
|
29
|
+
livekitApiKey?: string | null;
|
|
30
|
+
livekitApiSecret?: string | null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A Protoface avatar session for LiveKit Agents.
|
|
34
|
+
*
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export declare class AvatarSession extends voice.AvatarSession {
|
|
38
|
+
#private;
|
|
39
|
+
private avatarId;
|
|
40
|
+
private maxDurationMs?;
|
|
41
|
+
private avatarParticipantIdentity;
|
|
42
|
+
private avatarParticipantName;
|
|
43
|
+
private api;
|
|
44
|
+
private sessionIdValue;
|
|
45
|
+
constructor(options?: AvatarSessionOptions);
|
|
46
|
+
get avatarIdentity(): string;
|
|
47
|
+
get provider(): string;
|
|
48
|
+
/** Protoface session ID after `start()` succeeds, otherwise `null`. */
|
|
49
|
+
get sessionId(): string | null;
|
|
50
|
+
start(agentSession: voice.AgentSession, room: Room, options?: StartOptions): Promise<void>;
|
|
51
|
+
aclose(): Promise<void>;
|
|
52
|
+
private mintWorkerToken;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=avatar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../src/avatar.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,EAGtB,KAAK,EACN,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAO9C,cAAc;AACd,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AAOtD,cAAc;AACd,MAAM,WAAW,oBAAoB;IACnC,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iFAAiF;IACjF,yBAAyB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,qFAAqF;IACrF,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,iCAAiC;IACjC,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,KAAK,CAAC,aAAa;;IACpD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,yBAAyB,CAAS;IAC1C,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,GAAG,CAAe;IAC1B,OAAO,CAAC,cAAc,CAAuB;gBAIjC,OAAO,GAAE,oBAAyB;IAa9C,IAAa,cAAc,IAAI,MAAM,CAEpC;IAED,IAAa,QAAQ,IAAI,MAAM,CAE9B;IAED,uEAAuE;IACvE,IAAI,SAAS,IAAI,MAAM,GAAG,IAAI,CAE7B;IAEK,KAAK,CACT,YAAY,EAAE,KAAK,CAAC,YAAY,EAChC,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,IAAI,CAAC;IAgDV,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;YAgBf,eAAe;CAyC9B"}
|
package/dist/avatar.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_API_CONNECT_OPTIONS,
|
|
3
|
+
getJobContext,
|
|
4
|
+
voice
|
|
5
|
+
} from "@livekit/agents";
|
|
6
|
+
import { TrackKind } from "@livekit/rtc-node";
|
|
7
|
+
import { AccessToken } from "livekit-server-sdk";
|
|
8
|
+
import { ProtofaceAPI, ProtofaceException } from "./api.js";
|
|
9
|
+
import { log } from "./log.js";
|
|
10
|
+
const DEFAULT_STOCK_AVATAR_ID = "av_stock_001";
|
|
11
|
+
const ATTRIBUTE_PUBLISH_ON_BEHALF = "lk.publish_on_behalf";
|
|
12
|
+
const SAMPLE_RATE = 16e3;
|
|
13
|
+
const AVATAR_AGENT_IDENTITY = "protoface-avatar-agent";
|
|
14
|
+
const AVATAR_AGENT_NAME = "protoface-avatar-agent";
|
|
15
|
+
class AvatarSession extends voice.AvatarSession {
|
|
16
|
+
avatarId;
|
|
17
|
+
maxDurationMs;
|
|
18
|
+
avatarParticipantIdentity;
|
|
19
|
+
avatarParticipantName;
|
|
20
|
+
api;
|
|
21
|
+
sessionIdValue = null;
|
|
22
|
+
#logger = log();
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
super();
|
|
25
|
+
this.avatarId = options.avatarId ?? DEFAULT_STOCK_AVATAR_ID;
|
|
26
|
+
this.maxDurationMs = options.maxDurationMs;
|
|
27
|
+
this.avatarParticipantIdentity = options.avatarParticipantIdentity || AVATAR_AGENT_IDENTITY;
|
|
28
|
+
this.avatarParticipantName = options.avatarParticipantName || AVATAR_AGENT_NAME;
|
|
29
|
+
this.api = new ProtofaceAPI({
|
|
30
|
+
apiKey: options.apiKey,
|
|
31
|
+
apiUrl: options.apiUrl,
|
|
32
|
+
connOptions: options.connOptions ?? DEFAULT_API_CONNECT_OPTIONS
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
get avatarIdentity() {
|
|
36
|
+
return this.avatarParticipantIdentity;
|
|
37
|
+
}
|
|
38
|
+
get provider() {
|
|
39
|
+
return "protoface";
|
|
40
|
+
}
|
|
41
|
+
/** Protoface session ID after `start()` succeeds, otherwise `null`. */
|
|
42
|
+
get sessionId() {
|
|
43
|
+
return this.sessionIdValue;
|
|
44
|
+
}
|
|
45
|
+
async start(agentSession, room, options = {}) {
|
|
46
|
+
if (this.sessionIdValue !== null) {
|
|
47
|
+
throw new Error("AvatarSession.start() called twice; create a new AvatarSession.");
|
|
48
|
+
}
|
|
49
|
+
await super.start(agentSession, room);
|
|
50
|
+
const livekitUrl = options.livekitUrl ?? process.env.LIVEKIT_URL;
|
|
51
|
+
const livekitApiKey = options.livekitApiKey ?? process.env.LIVEKIT_API_KEY;
|
|
52
|
+
const livekitApiSecret = options.livekitApiSecret ?? process.env.LIVEKIT_API_SECRET;
|
|
53
|
+
if (!livekitUrl || !livekitApiKey || !livekitApiSecret) {
|
|
54
|
+
throw new ProtofaceException(
|
|
55
|
+
"livekitUrl, livekitApiKey, and livekitApiSecret must be set by arguments or environment variables"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const workerToken = await this.mintWorkerToken({ room, livekitApiKey, livekitApiSecret });
|
|
59
|
+
const session = await this.api.startSession({
|
|
60
|
+
avatarId: this.avatarId,
|
|
61
|
+
transport: {
|
|
62
|
+
type: "livekit",
|
|
63
|
+
url: livekitUrl,
|
|
64
|
+
room_name: room.name,
|
|
65
|
+
worker_token: workerToken,
|
|
66
|
+
worker_identity: this.avatarParticipantIdentity,
|
|
67
|
+
audio_source: "data_stream"
|
|
68
|
+
},
|
|
69
|
+
maxDurationMs: this.maxDurationMs
|
|
70
|
+
});
|
|
71
|
+
if (typeof session.id !== "string") {
|
|
72
|
+
throw new ProtofaceException("Protoface API response missing session id");
|
|
73
|
+
}
|
|
74
|
+
this.sessionIdValue = session.id;
|
|
75
|
+
this.#logger.debug(
|
|
76
|
+
{ sessionId: this.sessionIdValue, avatarId: this.avatarId },
|
|
77
|
+
"protoface session started"
|
|
78
|
+
);
|
|
79
|
+
agentSession.output.audio = new voice.DataStreamAudioOutput({
|
|
80
|
+
room,
|
|
81
|
+
destinationIdentity: this.avatarParticipantIdentity,
|
|
82
|
+
sampleRate: SAMPLE_RATE,
|
|
83
|
+
waitRemoteTrack: TrackKind.KIND_VIDEO
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async aclose() {
|
|
87
|
+
const sessionId = this.sessionIdValue;
|
|
88
|
+
this.sessionIdValue = null;
|
|
89
|
+
try {
|
|
90
|
+
if (sessionId !== null) {
|
|
91
|
+
try {
|
|
92
|
+
await this.api.endSession(sessionId);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
this.#logger.warn({ error: String(error), sessionId }, "failed to end protoface session");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} finally {
|
|
98
|
+
await super.aclose();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async mintWorkerToken({
|
|
102
|
+
room,
|
|
103
|
+
livekitApiKey,
|
|
104
|
+
livekitApiSecret
|
|
105
|
+
}) {
|
|
106
|
+
var _a;
|
|
107
|
+
let localParticipantIdentity = "";
|
|
108
|
+
try {
|
|
109
|
+
const jobCtx = getJobContext();
|
|
110
|
+
localParticipantIdentity = ((_a = jobCtx.agent) == null ? void 0 : _a.identity) || "";
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
if (!localParticipantIdentity && room.isConnected && room.localParticipant) {
|
|
114
|
+
localParticipantIdentity = room.localParticipant.identity;
|
|
115
|
+
}
|
|
116
|
+
if (!localParticipantIdentity) {
|
|
117
|
+
throw new ProtofaceException("failed to get local participant identity");
|
|
118
|
+
}
|
|
119
|
+
const token = new AccessToken(livekitApiKey, livekitApiSecret, {
|
|
120
|
+
identity: this.avatarParticipantIdentity,
|
|
121
|
+
name: this.avatarParticipantName
|
|
122
|
+
});
|
|
123
|
+
token.kind = "agent";
|
|
124
|
+
token.attributes = { [ATTRIBUTE_PUBLISH_ON_BEHALF]: localParticipantIdentity };
|
|
125
|
+
token.addGrant({
|
|
126
|
+
roomJoin: true,
|
|
127
|
+
room: room.name,
|
|
128
|
+
canPublish: true,
|
|
129
|
+
canSubscribe: true,
|
|
130
|
+
canPublishData: true
|
|
131
|
+
});
|
|
132
|
+
return token.toJwt();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
AvatarSession,
|
|
137
|
+
DEFAULT_STOCK_AVATAR_ID
|
|
138
|
+
};
|
|
139
|
+
//# sourceMappingURL=avatar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/avatar.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type APIConnectOptions,\n DEFAULT_API_CONNECT_OPTIONS,\n getJobContext,\n voice,\n} from '@livekit/agents';\nimport type { Room } from '@livekit/rtc-node';\nimport { TrackKind } from '@livekit/rtc-node';\nimport type { VideoGrant } from 'livekit-server-sdk';\nimport { AccessToken } from 'livekit-server-sdk';\nimport { ProtofaceAPI, ProtofaceException } from './api.js';\nimport { log } from './log.js';\n\n/** @public */\nexport const DEFAULT_STOCK_AVATAR_ID = 'av_stock_001';\n\nconst ATTRIBUTE_PUBLISH_ON_BEHALF = 'lk.publish_on_behalf';\nconst SAMPLE_RATE = 16000;\nconst AVATAR_AGENT_IDENTITY = 'protoface-avatar-agent';\nconst AVATAR_AGENT_NAME = 'protoface-avatar-agent';\n\n/** @public */\nexport interface AvatarSessionOptions {\n /** Protoface avatar ID to render. Defaults to the stable stock avatar ID `av_stock_001`. */\n avatarId?: string;\n /** Override the Protoface API base URL. */\n apiUrl?: string | null;\n /** Protoface API key. Falls back to the `PROTOFACE_API_KEY` env var. */\n apiKey?: string | null;\n /** Optional maximum session duration in milliseconds. */\n maxDurationMs?: number | null;\n /** Identity for the avatar participant. Defaults to `protoface-avatar-agent`. */\n avatarParticipantIdentity?: string | null;\n /** Display name for the avatar participant. Defaults to `protoface-avatar-agent`. */\n avatarParticipantName?: string | null;\n /** API retry/timeout options. */\n connOptions?: APIConnectOptions;\n}\n\n/**\n * Optional LiveKit credentials for {@link AvatarSession.start}; falls back to env vars.\n *\n * @public\n */\nexport interface StartOptions {\n livekitUrl?: string | null;\n livekitApiKey?: string | null;\n livekitApiSecret?: string | null;\n}\n\n/**\n * A Protoface avatar session for LiveKit Agents.\n *\n * @public\n */\nexport class AvatarSession extends voice.AvatarSession {\n private avatarId: string;\n private maxDurationMs?: number | null;\n private avatarParticipantIdentity: string;\n private avatarParticipantName: string;\n private api: ProtofaceAPI;\n private sessionIdValue: string | null = null;\n\n #logger = log();\n\n constructor(options: AvatarSessionOptions = {}) {\n super();\n this.avatarId = options.avatarId ?? DEFAULT_STOCK_AVATAR_ID;\n this.maxDurationMs = options.maxDurationMs;\n this.avatarParticipantIdentity = options.avatarParticipantIdentity || AVATAR_AGENT_IDENTITY;\n this.avatarParticipantName = options.avatarParticipantName || AVATAR_AGENT_NAME;\n this.api = new ProtofaceAPI({\n apiKey: options.apiKey,\n apiUrl: options.apiUrl,\n connOptions: options.connOptions ?? DEFAULT_API_CONNECT_OPTIONS,\n });\n }\n\n override get avatarIdentity(): string {\n return this.avatarParticipantIdentity;\n }\n\n override get provider(): string {\n return 'protoface';\n }\n\n /** Protoface session ID after `start()` succeeds, otherwise `null`. */\n get sessionId(): string | null {\n return this.sessionIdValue;\n }\n\n async start(\n agentSession: voice.AgentSession,\n room: Room,\n options: StartOptions = {},\n ): Promise<void> {\n if (this.sessionIdValue !== null) {\n throw new Error('AvatarSession.start() called twice; create a new AvatarSession.');\n }\n\n await super.start(agentSession, room);\n\n const livekitUrl = options.livekitUrl ?? process.env.LIVEKIT_URL;\n const livekitApiKey = options.livekitApiKey ?? process.env.LIVEKIT_API_KEY;\n const livekitApiSecret = options.livekitApiSecret ?? process.env.LIVEKIT_API_SECRET;\n if (!livekitUrl || !livekitApiKey || !livekitApiSecret) {\n throw new ProtofaceException(\n 'livekitUrl, livekitApiKey, and livekitApiSecret must be set by arguments or environment variables',\n );\n }\n\n const workerToken = await this.mintWorkerToken({ room, livekitApiKey, livekitApiSecret });\n const session = await this.api.startSession({\n avatarId: this.avatarId,\n transport: {\n type: 'livekit',\n url: livekitUrl,\n room_name: room.name,\n worker_token: workerToken,\n worker_identity: this.avatarParticipantIdentity,\n audio_source: 'data_stream',\n },\n maxDurationMs: this.maxDurationMs,\n });\n\n if (typeof session.id !== 'string') {\n throw new ProtofaceException('Protoface API response missing session id');\n }\n\n this.sessionIdValue = session.id;\n this.#logger.debug(\n { sessionId: this.sessionIdValue, avatarId: this.avatarId },\n 'protoface session started',\n );\n\n agentSession.output.audio = new voice.DataStreamAudioOutput({\n room,\n destinationIdentity: this.avatarParticipantIdentity,\n sampleRate: SAMPLE_RATE,\n waitRemoteTrack: TrackKind.KIND_VIDEO,\n });\n }\n\n async aclose(): Promise<void> {\n const sessionId = this.sessionIdValue;\n this.sessionIdValue = null;\n try {\n if (sessionId !== null) {\n try {\n await this.api.endSession(sessionId);\n } catch (error) {\n this.#logger.warn({ error: String(error), sessionId }, 'failed to end protoface session');\n }\n }\n } finally {\n await super.aclose();\n }\n }\n\n private async mintWorkerToken({\n room,\n livekitApiKey,\n livekitApiSecret,\n }: {\n room: Room;\n livekitApiKey: string;\n livekitApiSecret: string;\n }): Promise<string> {\n let localParticipantIdentity = '';\n try {\n const jobCtx = getJobContext();\n localParticipantIdentity = jobCtx.agent?.identity || '';\n } catch {\n // Fall back to the connected room below when no job context is available.\n }\n\n if (!localParticipantIdentity && room.isConnected && room.localParticipant) {\n localParticipantIdentity = room.localParticipant.identity;\n }\n\n if (!localParticipantIdentity) {\n throw new ProtofaceException('failed to get local participant identity');\n }\n\n const token = new AccessToken(livekitApiKey, livekitApiSecret, {\n identity: this.avatarParticipantIdentity,\n name: this.avatarParticipantName,\n });\n token.kind = 'agent';\n token.attributes = { [ATTRIBUTE_PUBLISH_ON_BEHALF]: localParticipantIdentity };\n token.addGrant({\n roomJoin: true,\n room: room.name,\n canPublish: true,\n canSubscribe: true,\n canPublishData: true,\n } as VideoGrant);\n\n return token.toJwt();\n }\n}\n"],"mappings":"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","names":[]}
|