@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.
Files changed (51) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +15 -0
  3. package/dist/api.cjs +153 -0
  4. package/dist/api.cjs.map +1 -0
  5. package/dist/api.d.cts +42 -0
  6. package/dist/api.d.ts +42 -0
  7. package/dist/api.d.ts.map +1 -0
  8. package/dist/api.js +133 -0
  9. package/dist/api.js.map +1 -0
  10. package/dist/api.test.cjs +55 -0
  11. package/dist/api.test.cjs.map +1 -0
  12. package/dist/api.test.d.cts +2 -0
  13. package/dist/api.test.d.ts +2 -0
  14. package/dist/api.test.d.ts.map +1 -0
  15. package/dist/api.test.js +54 -0
  16. package/dist/api.test.js.map +1 -0
  17. package/dist/avatar.cjs +160 -0
  18. package/dist/avatar.cjs.map +1 -0
  19. package/dist/avatar.d.cts +54 -0
  20. package/dist/avatar.d.ts +54 -0
  21. package/dist/avatar.d.ts.map +1 -0
  22. package/dist/avatar.js +139 -0
  23. package/dist/avatar.js.map +1 -0
  24. package/dist/avatar.test.cjs +107 -0
  25. package/dist/avatar.test.cjs.map +1 -0
  26. package/dist/avatar.test.d.cts +2 -0
  27. package/dist/avatar.test.d.ts +2 -0
  28. package/dist/avatar.test.d.ts.map +1 -0
  29. package/dist/avatar.test.js +106 -0
  30. package/dist/avatar.test.js.map +1 -0
  31. package/dist/index.cjs +36 -0
  32. package/dist/index.cjs.map +1 -0
  33. package/dist/index.d.cts +3 -0
  34. package/dist/index.d.ts +3 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +14 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/log.cjs +30 -0
  39. package/dist/log.cjs.map +1 -0
  40. package/dist/log.d.cts +3 -0
  41. package/dist/log.d.ts +3 -0
  42. package/dist/log.d.ts.map +1 -0
  43. package/dist/log.js +6 -0
  44. package/dist/log.js.map +1 -0
  45. package/package.json +51 -0
  46. package/src/api.test.ts +65 -0
  47. package/src/api.ts +182 -0
  48. package/src/avatar.test.ts +132 -0
  49. package/src/avatar.ts +204 -0
  50. package/src/index.ts +19 -0
  51. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.test.d.ts","sourceRoot":"","sources":["../src/api.test.ts"],"names":[],"mappings":""}
@@ -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":[]}
@@ -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
@@ -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":[]}