@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,107 @@
1
+ "use strict";
2
+ var import_agents = require("@livekit/agents");
3
+ var import_rtc_node = require("@livekit/rtc-node");
4
+ var import_node_events = require("node:events");
5
+ var import_vitest = require("vitest");
6
+ var import_api = require("./api.cjs");
7
+ var import_avatar = require("./avatar.cjs");
8
+ function fakeRoom() {
9
+ const emitter = new import_node_events.EventEmitter();
10
+ const remoteParticipant = {
11
+ identity: "protoface-avatar-agent",
12
+ trackPublications: /* @__PURE__ */ new Map([["video", { kind: import_rtc_node.TrackKind.KIND_VIDEO }]])
13
+ };
14
+ return {
15
+ name: "test-room",
16
+ isConnected: true,
17
+ localParticipant: {
18
+ identity: "local-agent",
19
+ registerRpcMethod: import_vitest.vi.fn()
20
+ },
21
+ remoteParticipants: /* @__PURE__ */ new Map([[remoteParticipant.identity, remoteParticipant]]),
22
+ on: import_vitest.vi.fn((event, listener) => {
23
+ emitter.on(event, listener);
24
+ return emitter;
25
+ }),
26
+ off: import_vitest.vi.fn((event, listener) => {
27
+ emitter.off(event, listener);
28
+ return emitter;
29
+ })
30
+ };
31
+ }
32
+ function fakeAgentSession() {
33
+ const emitter = new import_node_events.EventEmitter();
34
+ return {
35
+ _started: true,
36
+ output: { audio: null },
37
+ on: import_vitest.vi.fn((event, listener) => {
38
+ emitter.on(event, listener);
39
+ return emitter;
40
+ }),
41
+ off: import_vitest.vi.fn((event, listener) => {
42
+ emitter.off(event, listener);
43
+ return emitter;
44
+ })
45
+ };
46
+ }
47
+ (0, import_vitest.describe)("Protoface AvatarSession", () => {
48
+ (0, import_vitest.afterEach)(() => {
49
+ import_vitest.vi.restoreAllMocks();
50
+ import_agents.voice.DataStreamAudioOutput._playbackFinishedRpcRegistered = false;
51
+ import_agents.voice.DataStreamAudioOutput._playbackFinishedHandlers = {};
52
+ import_agents.voice.DataStreamAudioOutput._playbackStartedRpcRegistered = false;
53
+ import_agents.voice.DataStreamAudioOutput._playbackStartedHandlers = {};
54
+ });
55
+ (0, import_vitest.it)("calls base AvatarSession.start first", async () => {
56
+ const sentinel = new Error("super-start-called");
57
+ const superStartSpy = import_vitest.vi.spyOn(import_agents.voice.AvatarSession.prototype, "start").mockRejectedValue(sentinel);
58
+ const avatar = new import_avatar.AvatarSession({ apiKey: "protoface-key" });
59
+ await (0, import_vitest.expect)(avatar.start(fakeAgentSession(), fakeRoom())).rejects.toThrow(
60
+ "super-start-called"
61
+ );
62
+ (0, import_vitest.expect)(superStartSpy).toHaveBeenCalledTimes(1);
63
+ });
64
+ (0, import_vitest.it)("starts a Protoface session and routes agent audio to the avatar", async () => {
65
+ import_vitest.vi.spyOn(import_agents.voice.AvatarSession.prototype, "start").mockResolvedValue(void 0);
66
+ import_vitest.vi.spyOn(import_agents.voice.AvatarSession.prototype, "aclose").mockResolvedValue(void 0);
67
+ const startSessionSpy = import_vitest.vi.spyOn(import_api.ProtofaceAPI.prototype, "startSession").mockResolvedValue({ id: "protoface-session-1" });
68
+ const endSessionSpy = import_vitest.vi.spyOn(import_api.ProtofaceAPI.prototype, "endSession").mockResolvedValue({});
69
+ const avatar = new import_avatar.AvatarSession({
70
+ apiKey: "protoface-key",
71
+ avatarId: "avatar-1",
72
+ maxDurationMs: 12e4
73
+ });
74
+ const agentSession = fakeAgentSession();
75
+ await avatar.start(agentSession, fakeRoom(), {
76
+ livekitUrl: "wss://livekit.example.com",
77
+ livekitApiKey: "livekit-api-key",
78
+ livekitApiSecret: "livekit-api-secret"
79
+ });
80
+ (0, import_vitest.expect)(avatar.sessionId).toBe("protoface-session-1");
81
+ (0, import_vitest.expect)(startSessionSpy).toHaveBeenCalledWith({
82
+ avatarId: "avatar-1",
83
+ transport: import_vitest.expect.objectContaining({
84
+ type: "livekit",
85
+ url: "wss://livekit.example.com",
86
+ room_name: "test-room",
87
+ worker_identity: "protoface-avatar-agent",
88
+ audio_source: "data_stream",
89
+ worker_token: import_vitest.expect.any(String)
90
+ }),
91
+ maxDurationMs: 12e4
92
+ });
93
+ const audioOutput = agentSession.output.audio;
94
+ (0, import_vitest.expect)(audioOutput).toBeInstanceOf(import_agents.voice.DataStreamAudioOutput);
95
+ (0, import_vitest.expect)(audioOutput.destinationIdentity).toBe(
96
+ "protoface-avatar-agent"
97
+ );
98
+ (0, import_vitest.expect)(audioOutput.sampleRate).toBe(16e3);
99
+ (0, import_vitest.expect)(audioOutput.waitRemoteTrack).toBe(
100
+ import_rtc_node.TrackKind.KIND_VIDEO
101
+ );
102
+ await avatar.aclose();
103
+ (0, import_vitest.expect)(endSessionSpy).toHaveBeenCalledWith("protoface-session-1");
104
+ (0, import_vitest.expect)(avatar.sessionId).toBeNull();
105
+ });
106
+ });
107
+ //# sourceMappingURL=avatar.test.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/avatar.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { voice } from '@livekit/agents';\nimport type { Room } from '@livekit/rtc-node';\nimport { TrackKind } from '@livekit/rtc-node';\nimport { EventEmitter } from 'node:events';\nimport { afterEach, describe, expect, it, vi } from 'vitest';\nimport { ProtofaceAPI } from './api.js';\nimport { AvatarSession } from './avatar.js';\n\ntype DataStreamAudioOutputInternals = {\n destinationIdentity: string;\n sampleRate: number;\n waitRemoteTrack?: TrackKind;\n};\n\nfunction fakeRoom(): Room {\n const emitter = new EventEmitter();\n const remoteParticipant = {\n identity: 'protoface-avatar-agent',\n trackPublications: new Map([['video', { kind: TrackKind.KIND_VIDEO }]]),\n };\n\n return {\n name: 'test-room',\n isConnected: true,\n localParticipant: {\n identity: 'local-agent',\n registerRpcMethod: vi.fn(),\n },\n remoteParticipants: new Map([[remoteParticipant.identity, remoteParticipant]]),\n on: vi.fn((event: string | symbol, listener: (...args: unknown[]) => void) => {\n emitter.on(event, listener);\n return emitter;\n }),\n off: vi.fn((event: string | symbol, listener: (...args: unknown[]) => void) => {\n emitter.off(event, listener);\n return emitter;\n }),\n } as unknown as Room;\n}\n\nfunction fakeAgentSession(): voice.AgentSession {\n const emitter = new EventEmitter();\n return {\n _started: true,\n output: { audio: null as voice.AudioOutput | null },\n on: vi.fn((event: string | symbol, listener: (...args: unknown[]) => void) => {\n emitter.on(event, listener);\n return emitter;\n }),\n off: vi.fn((event: string | symbol, listener: (...args: unknown[]) => void) => {\n emitter.off(event, listener);\n return emitter;\n }),\n } as unknown as voice.AgentSession;\n}\n\ndescribe('Protoface AvatarSession', () => {\n afterEach(() => {\n vi.restoreAllMocks();\n voice.DataStreamAudioOutput._playbackFinishedRpcRegistered = false;\n voice.DataStreamAudioOutput._playbackFinishedHandlers = {};\n voice.DataStreamAudioOutput._playbackStartedRpcRegistered = false;\n voice.DataStreamAudioOutput._playbackStartedHandlers = {};\n });\n\n it('calls base AvatarSession.start first', async () => {\n const sentinel = new Error('super-start-called');\n const superStartSpy = vi\n .spyOn(voice.AvatarSession.prototype, 'start')\n .mockRejectedValue(sentinel);\n\n const avatar = new AvatarSession({ apiKey: 'protoface-key' });\n\n await expect(avatar.start(fakeAgentSession(), fakeRoom())).rejects.toThrow(\n 'super-start-called',\n );\n expect(superStartSpy).toHaveBeenCalledTimes(1);\n });\n\n it('starts a Protoface session and routes agent audio to the avatar', async () => {\n vi.spyOn(voice.AvatarSession.prototype, 'start').mockResolvedValue(undefined);\n vi.spyOn(voice.AvatarSession.prototype, 'aclose').mockResolvedValue(undefined);\n const startSessionSpy = vi\n .spyOn(ProtofaceAPI.prototype, 'startSession')\n .mockResolvedValue({ id: 'protoface-session-1' });\n const endSessionSpy = vi.spyOn(ProtofaceAPI.prototype, 'endSession').mockResolvedValue({});\n\n const avatar = new AvatarSession({\n apiKey: 'protoface-key',\n avatarId: 'avatar-1',\n maxDurationMs: 120_000,\n });\n const agentSession = fakeAgentSession();\n\n await avatar.start(agentSession, fakeRoom(), {\n livekitUrl: 'wss://livekit.example.com',\n livekitApiKey: 'livekit-api-key',\n livekitApiSecret: 'livekit-api-secret',\n });\n\n expect(avatar.sessionId).toBe('protoface-session-1');\n expect(startSessionSpy).toHaveBeenCalledWith({\n avatarId: 'avatar-1',\n transport: expect.objectContaining({\n type: 'livekit',\n url: 'wss://livekit.example.com',\n room_name: 'test-room',\n worker_identity: 'protoface-avatar-agent',\n audio_source: 'data_stream',\n worker_token: expect.any(String),\n }),\n maxDurationMs: 120_000,\n });\n\n const audioOutput = agentSession.output.audio;\n expect(audioOutput).toBeInstanceOf(voice.DataStreamAudioOutput);\n expect((audioOutput as unknown as DataStreamAudioOutputInternals).destinationIdentity).toBe(\n 'protoface-avatar-agent',\n );\n expect((audioOutput as unknown as DataStreamAudioOutputInternals).sampleRate).toBe(16000);\n expect((audioOutput as unknown as DataStreamAudioOutputInternals).waitRemoteTrack).toBe(\n TrackKind.KIND_VIDEO,\n );\n\n await avatar.aclose();\n expect(endSessionSpy).toHaveBeenCalledWith('protoface-session-1');\n expect(avatar.sessionId).toBeNull();\n });\n});\n"],"mappings":"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","names":[]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=avatar.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=avatar.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"avatar.test.d.ts","sourceRoot":"","sources":["../src/avatar.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,106 @@
1
+ import { voice } from "@livekit/agents";
2
+ import { TrackKind } from "@livekit/rtc-node";
3
+ import { EventEmitter } from "node:events";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import { ProtofaceAPI } from "./api.js";
6
+ import { AvatarSession } from "./avatar.js";
7
+ function fakeRoom() {
8
+ const emitter = new EventEmitter();
9
+ const remoteParticipant = {
10
+ identity: "protoface-avatar-agent",
11
+ trackPublications: /* @__PURE__ */ new Map([["video", { kind: TrackKind.KIND_VIDEO }]])
12
+ };
13
+ return {
14
+ name: "test-room",
15
+ isConnected: true,
16
+ localParticipant: {
17
+ identity: "local-agent",
18
+ registerRpcMethod: vi.fn()
19
+ },
20
+ remoteParticipants: /* @__PURE__ */ new Map([[remoteParticipant.identity, remoteParticipant]]),
21
+ on: vi.fn((event, listener) => {
22
+ emitter.on(event, listener);
23
+ return emitter;
24
+ }),
25
+ off: vi.fn((event, listener) => {
26
+ emitter.off(event, listener);
27
+ return emitter;
28
+ })
29
+ };
30
+ }
31
+ function fakeAgentSession() {
32
+ const emitter = new EventEmitter();
33
+ return {
34
+ _started: true,
35
+ output: { audio: null },
36
+ on: vi.fn((event, listener) => {
37
+ emitter.on(event, listener);
38
+ return emitter;
39
+ }),
40
+ off: vi.fn((event, listener) => {
41
+ emitter.off(event, listener);
42
+ return emitter;
43
+ })
44
+ };
45
+ }
46
+ describe("Protoface AvatarSession", () => {
47
+ afterEach(() => {
48
+ vi.restoreAllMocks();
49
+ voice.DataStreamAudioOutput._playbackFinishedRpcRegistered = false;
50
+ voice.DataStreamAudioOutput._playbackFinishedHandlers = {};
51
+ voice.DataStreamAudioOutput._playbackStartedRpcRegistered = false;
52
+ voice.DataStreamAudioOutput._playbackStartedHandlers = {};
53
+ });
54
+ it("calls base AvatarSession.start first", async () => {
55
+ const sentinel = new Error("super-start-called");
56
+ const superStartSpy = vi.spyOn(voice.AvatarSession.prototype, "start").mockRejectedValue(sentinel);
57
+ const avatar = new AvatarSession({ apiKey: "protoface-key" });
58
+ await expect(avatar.start(fakeAgentSession(), fakeRoom())).rejects.toThrow(
59
+ "super-start-called"
60
+ );
61
+ expect(superStartSpy).toHaveBeenCalledTimes(1);
62
+ });
63
+ it("starts a Protoface session and routes agent audio to the avatar", async () => {
64
+ vi.spyOn(voice.AvatarSession.prototype, "start").mockResolvedValue(void 0);
65
+ vi.spyOn(voice.AvatarSession.prototype, "aclose").mockResolvedValue(void 0);
66
+ const startSessionSpy = vi.spyOn(ProtofaceAPI.prototype, "startSession").mockResolvedValue({ id: "protoface-session-1" });
67
+ const endSessionSpy = vi.spyOn(ProtofaceAPI.prototype, "endSession").mockResolvedValue({});
68
+ const avatar = new AvatarSession({
69
+ apiKey: "protoface-key",
70
+ avatarId: "avatar-1",
71
+ maxDurationMs: 12e4
72
+ });
73
+ const agentSession = fakeAgentSession();
74
+ await avatar.start(agentSession, fakeRoom(), {
75
+ livekitUrl: "wss://livekit.example.com",
76
+ livekitApiKey: "livekit-api-key",
77
+ livekitApiSecret: "livekit-api-secret"
78
+ });
79
+ expect(avatar.sessionId).toBe("protoface-session-1");
80
+ expect(startSessionSpy).toHaveBeenCalledWith({
81
+ avatarId: "avatar-1",
82
+ transport: expect.objectContaining({
83
+ type: "livekit",
84
+ url: "wss://livekit.example.com",
85
+ room_name: "test-room",
86
+ worker_identity: "protoface-avatar-agent",
87
+ audio_source: "data_stream",
88
+ worker_token: expect.any(String)
89
+ }),
90
+ maxDurationMs: 12e4
91
+ });
92
+ const audioOutput = agentSession.output.audio;
93
+ expect(audioOutput).toBeInstanceOf(voice.DataStreamAudioOutput);
94
+ expect(audioOutput.destinationIdentity).toBe(
95
+ "protoface-avatar-agent"
96
+ );
97
+ expect(audioOutput.sampleRate).toBe(16e3);
98
+ expect(audioOutput.waitRemoteTrack).toBe(
99
+ TrackKind.KIND_VIDEO
100
+ );
101
+ await avatar.aclose();
102
+ expect(endSessionSpy).toHaveBeenCalledWith("protoface-session-1");
103
+ expect(avatar.sessionId).toBeNull();
104
+ });
105
+ });
106
+ //# sourceMappingURL=avatar.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/avatar.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { voice } from '@livekit/agents';\nimport type { Room } from '@livekit/rtc-node';\nimport { TrackKind } from '@livekit/rtc-node';\nimport { EventEmitter } from 'node:events';\nimport { afterEach, describe, expect, it, vi } from 'vitest';\nimport { ProtofaceAPI } from './api.js';\nimport { AvatarSession } from './avatar.js';\n\ntype DataStreamAudioOutputInternals = {\n destinationIdentity: string;\n sampleRate: number;\n waitRemoteTrack?: TrackKind;\n};\n\nfunction fakeRoom(): Room {\n const emitter = new EventEmitter();\n const remoteParticipant = {\n identity: 'protoface-avatar-agent',\n trackPublications: new Map([['video', { kind: TrackKind.KIND_VIDEO }]]),\n };\n\n return {\n name: 'test-room',\n isConnected: true,\n localParticipant: {\n identity: 'local-agent',\n registerRpcMethod: vi.fn(),\n },\n remoteParticipants: new Map([[remoteParticipant.identity, remoteParticipant]]),\n on: vi.fn((event: string | symbol, listener: (...args: unknown[]) => void) => {\n emitter.on(event, listener);\n return emitter;\n }),\n off: vi.fn((event: string | symbol, listener: (...args: unknown[]) => void) => {\n emitter.off(event, listener);\n return emitter;\n }),\n } as unknown as Room;\n}\n\nfunction fakeAgentSession(): voice.AgentSession {\n const emitter = new EventEmitter();\n return {\n _started: true,\n output: { audio: null as voice.AudioOutput | null },\n on: vi.fn((event: string | symbol, listener: (...args: unknown[]) => void) => {\n emitter.on(event, listener);\n return emitter;\n }),\n off: vi.fn((event: string | symbol, listener: (...args: unknown[]) => void) => {\n emitter.off(event, listener);\n return emitter;\n }),\n } as unknown as voice.AgentSession;\n}\n\ndescribe('Protoface AvatarSession', () => {\n afterEach(() => {\n vi.restoreAllMocks();\n voice.DataStreamAudioOutput._playbackFinishedRpcRegistered = false;\n voice.DataStreamAudioOutput._playbackFinishedHandlers = {};\n voice.DataStreamAudioOutput._playbackStartedRpcRegistered = false;\n voice.DataStreamAudioOutput._playbackStartedHandlers = {};\n });\n\n it('calls base AvatarSession.start first', async () => {\n const sentinel = new Error('super-start-called');\n const superStartSpy = vi\n .spyOn(voice.AvatarSession.prototype, 'start')\n .mockRejectedValue(sentinel);\n\n const avatar = new AvatarSession({ apiKey: 'protoface-key' });\n\n await expect(avatar.start(fakeAgentSession(), fakeRoom())).rejects.toThrow(\n 'super-start-called',\n );\n expect(superStartSpy).toHaveBeenCalledTimes(1);\n });\n\n it('starts a Protoface session and routes agent audio to the avatar', async () => {\n vi.spyOn(voice.AvatarSession.prototype, 'start').mockResolvedValue(undefined);\n vi.spyOn(voice.AvatarSession.prototype, 'aclose').mockResolvedValue(undefined);\n const startSessionSpy = vi\n .spyOn(ProtofaceAPI.prototype, 'startSession')\n .mockResolvedValue({ id: 'protoface-session-1' });\n const endSessionSpy = vi.spyOn(ProtofaceAPI.prototype, 'endSession').mockResolvedValue({});\n\n const avatar = new AvatarSession({\n apiKey: 'protoface-key',\n avatarId: 'avatar-1',\n maxDurationMs: 120_000,\n });\n const agentSession = fakeAgentSession();\n\n await avatar.start(agentSession, fakeRoom(), {\n livekitUrl: 'wss://livekit.example.com',\n livekitApiKey: 'livekit-api-key',\n livekitApiSecret: 'livekit-api-secret',\n });\n\n expect(avatar.sessionId).toBe('protoface-session-1');\n expect(startSessionSpy).toHaveBeenCalledWith({\n avatarId: 'avatar-1',\n transport: expect.objectContaining({\n type: 'livekit',\n url: 'wss://livekit.example.com',\n room_name: 'test-room',\n worker_identity: 'protoface-avatar-agent',\n audio_source: 'data_stream',\n worker_token: expect.any(String),\n }),\n maxDurationMs: 120_000,\n });\n\n const audioOutput = agentSession.output.audio;\n expect(audioOutput).toBeInstanceOf(voice.DataStreamAudioOutput);\n expect((audioOutput as unknown as DataStreamAudioOutputInternals).destinationIdentity).toBe(\n 'protoface-avatar-agent',\n );\n expect((audioOutput as unknown as DataStreamAudioOutputInternals).sampleRate).toBe(16000);\n expect((audioOutput as unknown as DataStreamAudioOutputInternals).waitRemoteTrack).toBe(\n TrackKind.KIND_VIDEO,\n );\n\n await avatar.aclose();\n expect(endSessionSpy).toHaveBeenCalledWith('protoface-session-1');\n expect(avatar.sessionId).toBeNull();\n });\n});\n"],"mappings":"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","names":[]}
package/dist/index.cjs ADDED
@@ -0,0 +1,36 @@
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 __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
15
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
16
+ var index_exports = {};
17
+ module.exports = __toCommonJS(index_exports);
18
+ var import_agents = require("@livekit/agents");
19
+ __reExport(index_exports, require("./api.cjs"), module.exports);
20
+ __reExport(index_exports, require("./avatar.cjs"), module.exports);
21
+ class ProtofacePlugin extends import_agents.Plugin {
22
+ constructor() {
23
+ super({
24
+ title: "protoface",
25
+ version: "1.5.3",
26
+ package: "@livekit/agents-plugin-protoface"
27
+ });
28
+ }
29
+ }
30
+ import_agents.Plugin.registerPlugin(new ProtofacePlugin());
31
+ // Annotate the CommonJS export names for ESM import in node:
32
+ 0 && (module.exports = {
33
+ ...require("./api.cjs"),
34
+ ...require("./avatar.cjs")
35
+ });
36
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Plugin } from '@livekit/agents';\n\nexport * from './api.js';\nexport * from './avatar.js';\n\nclass ProtofacePlugin extends Plugin {\n constructor() {\n super({\n title: 'protoface',\n version: __PACKAGE_VERSION__,\n package: __PACKAGE_NAME__,\n });\n }\n}\n\nPlugin.registerPlugin(new ProtofacePlugin());\n"],"mappings":";;;;;;;;;;;;;;;AAAA;AAAA;AAGA,oBAAuB;AAEvB,0BAAc,qBALd;AAMA,0BAAc,wBANd;AAQA,MAAM,wBAAwB,qBAAO;AAAA,EACnC,cAAc;AACZ,UAAM;AAAA,MACJ,OAAO;AAAA,MACP,SAAS;AAAA,MACT,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF;AAEA,qBAAO,eAAe,IAAI,gBAAgB,CAAC;","names":[]}
@@ -0,0 +1,3 @@
1
+ export * from './api.js';
2
+ export * from './avatar.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export * from './api.js';
2
+ export * from './avatar.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ import { Plugin } from "@livekit/agents";
2
+ export * from "./api.js";
3
+ export * from "./avatar.js";
4
+ class ProtofacePlugin extends Plugin {
5
+ constructor() {
6
+ super({
7
+ title: "protoface",
8
+ version: "1.5.3",
9
+ package: "@livekit/agents-plugin-protoface"
10
+ });
11
+ }
12
+ }
13
+ Plugin.registerPlugin(new ProtofacePlugin());
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Plugin } from '@livekit/agents';\n\nexport * from './api.js';\nexport * from './avatar.js';\n\nclass ProtofacePlugin extends Plugin {\n constructor() {\n super({\n title: 'protoface',\n version: __PACKAGE_VERSION__,\n package: __PACKAGE_NAME__,\n });\n }\n}\n\nPlugin.registerPlugin(new ProtofacePlugin());\n"],"mappings":"AAGA,SAAS,cAAc;AAEvB,cAAc;AACd,cAAc;AAEd,MAAM,wBAAwB,OAAO;AAAA,EACnC,cAAc;AACZ,UAAM;AAAA,MACJ,OAAO;AAAA,MACP,SAAS;AAAA,MACT,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF;AAEA,OAAO,eAAe,IAAI,gBAAgB,CAAC;","names":[]}
package/dist/log.cjs ADDED
@@ -0,0 +1,30 @@
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 log_exports = {};
20
+ __export(log_exports, {
21
+ log: () => log
22
+ });
23
+ module.exports = __toCommonJS(log_exports);
24
+ var import_agents = require("@livekit/agents");
25
+ const log = () => (0, import_agents.log)().child({ plugin: "protoface" });
26
+ // Annotate the CommonJS export names for ESM import in node:
27
+ 0 && (module.exports = {
28
+ log
29
+ });
30
+ //# sourceMappingURL=log.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/log.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { log as agentsLog } from '@livekit/agents';\nimport type { Logger } from 'pino';\n\nexport const log = (): Logger => agentsLog().child({ plugin: 'protoface' });\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAAiC;AAG1B,MAAM,MAAM,UAAc,cAAAA,KAAU,EAAE,MAAM,EAAE,QAAQ,YAAY,CAAC;","names":["agentsLog"]}
package/dist/log.d.cts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Logger } from 'pino';
2
+ export declare const log: () => Logger;
3
+ //# sourceMappingURL=log.d.ts.map
package/dist/log.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Logger } from 'pino';
2
+ export declare const log: () => Logger;
3
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,eAAO,MAAM,GAAG,QAAO,MAAoD,CAAC"}
package/dist/log.js ADDED
@@ -0,0 +1,6 @@
1
+ import { log as agentsLog } from "@livekit/agents";
2
+ const log = () => agentsLog().child({ plugin: "protoface" });
3
+ export {
4
+ log
5
+ };
6
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/log.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { log as agentsLog } from '@livekit/agents';\nimport type { Logger } from 'pino';\n\nexport const log = (): Logger => agentsLog().child({ plugin: 'protoface' });\n"],"mappings":"AAGA,SAAS,OAAO,iBAAiB;AAG1B,MAAM,MAAM,MAAc,UAAU,EAAE,MAAM,EAAE,QAAQ,YAAY,CAAC;","names":[]}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@livekit/agents-plugin-protoface",
3
+ "version": "1.5.3",
4
+ "description": "Protoface avatar plugin for LiveKit Node Agents",
5
+ "main": "dist/index.js",
6
+ "require": "dist/index.cjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ "import": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "require": {
14
+ "types": "./dist/index.d.cts",
15
+ "default": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "author": "LiveKit",
19
+ "type": "module",
20
+ "repository": "git@github.com:livekit/agents-js.git",
21
+ "license": "Apache-2.0",
22
+ "files": [
23
+ "dist",
24
+ "src",
25
+ "README.md"
26
+ ],
27
+ "devDependencies": {
28
+ "@livekit/rtc-node": "^0.13.31",
29
+ "@microsoft/api-extractor": "^7.35.0",
30
+ "pino": "^8.19.0",
31
+ "tsup": "^8.3.5",
32
+ "typescript": "^5.0.0",
33
+ "@livekit/agents": "1.5.4"
34
+ },
35
+ "dependencies": {
36
+ "livekit-server-sdk": "^2.13.3"
37
+ },
38
+ "peerDependencies": {
39
+ "@livekit/rtc-node": "^0.13.31",
40
+ "@livekit/agents": "1.5.4"
41
+ },
42
+ "scripts": {
43
+ "build": "tsup --onSuccess \"pnpm build:types\"",
44
+ "build:types": "tsc --declaration --emitDeclarationOnly && node ../../scripts/copyDeclarationOutput.js",
45
+ "clean": "rm -rf dist",
46
+ "clean:build": "pnpm clean && pnpm build",
47
+ "lint": "eslint -f unix \"src/**/*.{ts,js}\"",
48
+ "api:check": "api-extractor run --typescript-compiler-folder ../../node_modules/typescript",
49
+ "api:update": "api-extractor run --local --typescript-compiler-folder ../../node_modules/typescript --verbose"
50
+ }
51
+ }
@@ -0,0 +1,65 @@
1
+ // SPDX-FileCopyrightText: 2026 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+ import { ProtofaceAPI, ProtofaceException } from './api.js';
6
+
7
+ describe('ProtofaceAPI', () => {
8
+ afterEach(() => {
9
+ vi.unstubAllEnvs();
10
+ vi.unstubAllGlobals();
11
+ });
12
+
13
+ it('requires an API key', () => {
14
+ vi.stubEnv('PROTOFACE_API_KEY', '');
15
+
16
+ expect(() => new ProtofaceAPI()).toThrow(ProtofaceException);
17
+ });
18
+
19
+ it('uses env configuration and sends the expected start session request', async () => {
20
+ vi.stubEnv('PROTOFACE_API_KEY', 'sk_test');
21
+ vi.stubEnv('PROTOFACE_API_URL', 'https://api.example.test/');
22
+ const fetchMock = vi.fn(async () => new Response('{"id":"sess_test"}', { status: 200 }));
23
+ vi.stubGlobal('fetch', fetchMock);
24
+
25
+ const client = new ProtofaceAPI();
26
+ const session = await client.startSession({
27
+ avatarId: 'av_test',
28
+ transport: { type: 'livekit' },
29
+ maxDurationMs: 120_000,
30
+ });
31
+
32
+ expect(session).toEqual({ id: 'sess_test' });
33
+ expect(fetchMock).toHaveBeenCalledTimes(1);
34
+ expect(fetchMock).toHaveBeenCalledWith(
35
+ 'https://api.example.test/v1/sessions',
36
+ expect.objectContaining({
37
+ method: 'POST',
38
+ headers: expect.objectContaining({
39
+ accept: 'application/json',
40
+ authorization: 'Bearer sk_test',
41
+ 'content-type': 'application/json',
42
+ 'user-agent': '@livekit/agents-plugin-protoface/0.0.0-test',
43
+ }),
44
+ body: JSON.stringify({
45
+ avatar_id: 'av_test',
46
+ transport: { type: 'livekit' },
47
+ max_duration_seconds: 120,
48
+ }),
49
+ }),
50
+ );
51
+ });
52
+
53
+ it('ends a session', async () => {
54
+ const fetchMock = vi.fn(async () => new Response('{}', { status: 200 }));
55
+ vi.stubGlobal('fetch', fetchMock);
56
+
57
+ const client = new ProtofaceAPI({ apiKey: 'sk_test', apiUrl: 'https://api.example.test' });
58
+ await expect(client.endSession('sess_test')).resolves.toEqual({});
59
+
60
+ expect(fetchMock).toHaveBeenCalledWith(
61
+ 'https://api.example.test/v1/sessions/sess_test/end',
62
+ expect.objectContaining({ method: 'POST' }),
63
+ );
64
+ });
65
+ });