@komaa/standin-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/ambient.d.ts +80 -0
- package/dist/ambient.d.ts.map +1 -0
- package/dist/ambient.js +281 -0
- package/dist/ambient.js.map +1 -0
- package/dist/attachments.d.ts +149 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +451 -0
- package/dist/attachments.js.map +1 -0
- package/dist/audio.d.ts +80 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +170 -0
- package/dist/audio.js.map +1 -0
- package/dist/avatar.d.ts +111 -0
- package/dist/avatar.d.ts.map +1 -0
- package/dist/avatar.js +201 -0
- package/dist/avatar.js.map +1 -0
- package/dist/callServer.d.ts +151 -0
- package/dist/callServer.d.ts.map +1 -0
- package/dist/callServer.js +1097 -0
- package/dist/callServer.js.map +1 -0
- package/dist/callTools.d.ts +154 -0
- package/dist/callTools.d.ts.map +1 -0
- package/dist/callTools.js +255 -0
- package/dist/callTools.js.map +1 -0
- package/dist/chat.d.ts +226 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +574 -0
- package/dist/chat.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +86 -0
- package/dist/config.js.map +1 -0
- package/dist/consult.d.ts +147 -0
- package/dist/consult.d.ts.map +1 -0
- package/dist/consult.js +388 -0
- package/dist/consult.js.map +1 -0
- package/dist/delivery.d.ts +81 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +187 -0
- package/dist/delivery.js.map +1 -0
- package/dist/echoGuard.d.ts +56 -0
- package/dist/echoGuard.d.ts.map +1 -0
- package/dist/echoGuard.js +57 -0
- package/dist/echoGuard.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +10 -0
- package/dist/errors.js.map +1 -0
- package/dist/fetch.d.ts +40 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +240 -0
- package/dist/fetch.js.map +1 -0
- package/dist/gate.d.ts +80 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +249 -0
- package/dist/gate.js.map +1 -0
- package/dist/handler.d.ts +283 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +4 -0
- package/dist/handler.js.map +1 -0
- package/dist/hmac.d.ts +32 -0
- package/dist/hmac.d.ts.map +1 -0
- package/dist/hmac.js +108 -0
- package/dist/hmac.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/lane.d.ts +92 -0
- package/dist/lane.d.ts.map +1 -0
- package/dist/lane.js +269 -0
- package/dist/lane.js.map +1 -0
- package/dist/lipsync.d.ts +136 -0
- package/dist/lipsync.d.ts.map +1 -0
- package/dist/lipsync.js +310 -0
- package/dist/lipsync.js.map +1 -0
- package/dist/log.d.ts +18 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +20 -0
- package/dist/log.js.map +1 -0
- package/dist/media.d.ts +43 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +190 -0
- package/dist/media.js.map +1 -0
- package/dist/minutes.d.ts +398 -0
- package/dist/minutes.d.ts.map +1 -0
- package/dist/minutes.js +805 -0
- package/dist/minutes.js.map +1 -0
- package/dist/outbound.d.ts +388 -0
- package/dist/outbound.d.ts.map +1 -0
- package/dist/outbound.js +933 -0
- package/dist/outbound.js.map +1 -0
- package/dist/plugins/cartesia/agent.d.ts +58 -0
- package/dist/plugins/cartesia/agent.d.ts.map +1 -0
- package/dist/plugins/cartesia/agent.js +254 -0
- package/dist/plugins/cartesia/agent.js.map +1 -0
- package/dist/plugins/cartesia/cli.d.ts +3 -0
- package/dist/plugins/cartesia/cli.d.ts.map +1 -0
- package/dist/plugins/cartesia/cli.js +10 -0
- package/dist/plugins/cartesia/cli.js.map +1 -0
- package/dist/plugins/cartesia/config.d.ts +30 -0
- package/dist/plugins/cartesia/config.d.ts.map +1 -0
- package/dist/plugins/cartesia/config.js +29 -0
- package/dist/plugins/cartesia/config.js.map +1 -0
- package/dist/plugins/cartesia/handler.d.ts +40 -0
- package/dist/plugins/cartesia/handler.d.ts.map +1 -0
- package/dist/plugins/cartesia/handler.js +142 -0
- package/dist/plugins/cartesia/handler.js.map +1 -0
- package/dist/plugins/cartesia/index.d.ts +6 -0
- package/dist/plugins/cartesia/index.d.ts.map +1 -0
- package/dist/plugins/cartesia/index.js +54 -0
- package/dist/plugins/cartesia/index.js.map +1 -0
- package/dist/plugins/deepgram/agent.d.ts +60 -0
- package/dist/plugins/deepgram/agent.d.ts.map +1 -0
- package/dist/plugins/deepgram/agent.js +302 -0
- package/dist/plugins/deepgram/agent.js.map +1 -0
- package/dist/plugins/deepgram/cli.d.ts +3 -0
- package/dist/plugins/deepgram/cli.d.ts.map +1 -0
- package/dist/plugins/deepgram/cli.js +10 -0
- package/dist/plugins/deepgram/cli.js.map +1 -0
- package/dist/plugins/deepgram/config.d.ts +39 -0
- package/dist/plugins/deepgram/config.d.ts.map +1 -0
- package/dist/plugins/deepgram/config.js +66 -0
- package/dist/plugins/deepgram/config.js.map +1 -0
- package/dist/plugins/deepgram/handler.d.ts +87 -0
- package/dist/plugins/deepgram/handler.d.ts.map +1 -0
- package/dist/plugins/deepgram/handler.js +268 -0
- package/dist/plugins/deepgram/handler.js.map +1 -0
- package/dist/plugins/deepgram/index.d.ts +11 -0
- package/dist/plugins/deepgram/index.d.ts.map +1 -0
- package/dist/plugins/deepgram/index.js +76 -0
- package/dist/plugins/deepgram/index.js.map +1 -0
- package/dist/plugins/echo/cli.d.ts +3 -0
- package/dist/plugins/echo/cli.d.ts.map +1 -0
- package/dist/plugins/echo/cli.js +10 -0
- package/dist/plugins/echo/cli.js.map +1 -0
- package/dist/plugins/echo/index.d.ts +37 -0
- package/dist/plugins/echo/index.d.ts.map +1 -0
- package/dist/plugins/echo/index.js +61 -0
- package/dist/plugins/echo/index.js.map +1 -0
- package/dist/plugins/elevenlabs/agent.d.ts +72 -0
- package/dist/plugins/elevenlabs/agent.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/agent.js +305 -0
- package/dist/plugins/elevenlabs/agent.js.map +1 -0
- package/dist/plugins/elevenlabs/cli.d.ts +3 -0
- package/dist/plugins/elevenlabs/cli.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/cli.js +10 -0
- package/dist/plugins/elevenlabs/cli.js.map +1 -0
- package/dist/plugins/elevenlabs/config.d.ts +27 -0
- package/dist/plugins/elevenlabs/config.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/config.js +42 -0
- package/dist/plugins/elevenlabs/config.js.map +1 -0
- package/dist/plugins/elevenlabs/handler.d.ts +67 -0
- package/dist/plugins/elevenlabs/handler.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/handler.js +419 -0
- package/dist/plugins/elevenlabs/handler.js.map +1 -0
- package/dist/plugins/elevenlabs/index.d.ts +11 -0
- package/dist/plugins/elevenlabs/index.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/index.js +72 -0
- package/dist/plugins/elevenlabs/index.js.map +1 -0
- package/dist/plugins/livekit/cli.d.ts +3 -0
- package/dist/plugins/livekit/cli.d.ts.map +1 -0
- package/dist/plugins/livekit/cli.js +10 -0
- package/dist/plugins/livekit/cli.js.map +1 -0
- package/dist/plugins/livekit/config.d.ts +47 -0
- package/dist/plugins/livekit/config.d.ts.map +1 -0
- package/dist/plugins/livekit/config.js +46 -0
- package/dist/plugins/livekit/config.js.map +1 -0
- package/dist/plugins/livekit/handler.d.ts +59 -0
- package/dist/plugins/livekit/handler.d.ts.map +1 -0
- package/dist/plugins/livekit/handler.js +211 -0
- package/dist/plugins/livekit/handler.js.map +1 -0
- package/dist/plugins/livekit/index.d.ts +6 -0
- package/dist/plugins/livekit/index.d.ts.map +1 -0
- package/dist/plugins/livekit/index.js +68 -0
- package/dist/plugins/livekit/index.js.map +1 -0
- package/dist/plugins/livekit/room.d.ts +117 -0
- package/dist/plugins/livekit/room.d.ts.map +1 -0
- package/dist/plugins/livekit/room.js +449 -0
- package/dist/plugins/livekit/room.js.map +1 -0
- package/dist/plugins/openai/agent.d.ts +74 -0
- package/dist/plugins/openai/agent.d.ts.map +1 -0
- package/dist/plugins/openai/agent.js +264 -0
- package/dist/plugins/openai/agent.js.map +1 -0
- package/dist/plugins/openai/cli.d.ts +3 -0
- package/dist/plugins/openai/cli.d.ts.map +1 -0
- package/dist/plugins/openai/cli.js +10 -0
- package/dist/plugins/openai/cli.js.map +1 -0
- package/dist/plugins/openai/config.d.ts +29 -0
- package/dist/plugins/openai/config.d.ts.map +1 -0
- package/dist/plugins/openai/config.js +37 -0
- package/dist/plugins/openai/config.js.map +1 -0
- package/dist/plugins/openai/handler.d.ts +65 -0
- package/dist/plugins/openai/handler.d.ts.map +1 -0
- package/dist/plugins/openai/handler.js +297 -0
- package/dist/plugins/openai/handler.js.map +1 -0
- package/dist/plugins/openai/index.d.ts +6 -0
- package/dist/plugins/openai/index.d.ts.map +1 -0
- package/dist/plugins/openai/index.js +72 -0
- package/dist/plugins/openai/index.js.map +1 -0
- package/dist/plugins/openclaw/allowlist.d.ts +9 -0
- package/dist/plugins/openclaw/allowlist.d.ts.map +1 -0
- package/dist/plugins/openclaw/allowlist.js +11 -0
- package/dist/plugins/openclaw/allowlist.js.map +1 -0
- package/dist/plugins/openclaw/config.d.ts +50 -0
- package/dist/plugins/openclaw/config.d.ts.map +1 -0
- package/dist/plugins/openclaw/config.js +53 -0
- package/dist/plugins/openclaw/config.js.map +1 -0
- package/dist/plugins/openclaw/handler.d.ts +90 -0
- package/dist/plugins/openclaw/handler.d.ts.map +1 -0
- package/dist/plugins/openclaw/handler.js +146 -0
- package/dist/plugins/openclaw/handler.js.map +1 -0
- package/dist/plugins/openclaw/index.d.ts +55 -0
- package/dist/plugins/openclaw/index.d.ts.map +1 -0
- package/dist/plugins/openclaw/index.js +89 -0
- package/dist/plugins/openclaw/index.js.map +1 -0
- package/dist/plugins/openclaw/openclaw.plugin.json +74 -0
- package/dist/plugins/openclaw/package.json +14 -0
- package/dist/plugins/openclaw/realtime.d.ts +82 -0
- package/dist/plugins/openclaw/realtime.d.ts.map +1 -0
- package/dist/plugins/openclaw/realtime.js +238 -0
- package/dist/plugins/openclaw/realtime.js.map +1 -0
- package/dist/plugins/openclaw/runtime.d.ts +19 -0
- package/dist/plugins/openclaw/runtime.d.ts.map +1 -0
- package/dist/plugins/openclaw/runtime.js +160 -0
- package/dist/plugins/openclaw/runtime.js.map +1 -0
- package/dist/policy.d.ts +38 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +95 -0
- package/dist/policy.js.map +1 -0
- package/dist/protocol.d.ts +44 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +77 -0
- package/dist/protocol.js.map +1 -0
- package/dist/protocolRuntime.d.ts +19 -0
- package/dist/protocolRuntime.d.ts.map +1 -0
- package/dist/protocolRuntime.js +78 -0
- package/dist/protocolRuntime.js.map +1 -0
- package/dist/smoke.d.ts +65 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +293 -0
- package/dist/smoke.js.map +1 -0
- package/dist/startup.d.ts +74 -0
- package/dist/startup.d.ts.map +1 -0
- package/dist/startup.js +112 -0
- package/dist/startup.js.map +1 -0
- package/dist/tile.d.ts +101 -0
- package/dist/tile.d.ts.map +1 -0
- package/dist/tile.js +195 -0
- package/dist/tile.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/dist/vision.d.ts +160 -0
- package/dist/vision.d.ts.map +1 -0
- package/dist/vision.js +301 -0
- package/dist/vision.js.map +1 -0
- package/dist/visionTools.d.ts +237 -0
- package/dist/visionTools.d.ts.map +1 -0
- package/dist/visionTools.js +578 -0
- package/dist/visionTools.js.map +1 -0
- package/dist/voice.d.ts +143 -0
- package/dist/voice.d.ts.map +1 -0
- package/dist/voice.js +403 -0
- package/dist/voice.js.map +1 -0
- package/package.json +129 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { ExpressionCue } from "../../avatar.js";
|
|
4
|
+
import { CallTools } from "../../callTools.js";
|
|
5
|
+
import { fetchPublicImage } from "../../fetch.js";
|
|
6
|
+
import { TurnLipSync } from "../../lipsync.js";
|
|
7
|
+
import { logger } from "../../log.js";
|
|
8
|
+
import { StartupBuffer } from "../../startup.js";
|
|
9
|
+
import { FrameDescriber } from "../../vision.js";
|
|
10
|
+
import { VisionTools } from "../../visionTools.js";
|
|
11
|
+
import { AgentSocket, BUILT_IN_TOOL_NAMES, buildInstructions, buildSessionUpdate, } from "./agent.js";
|
|
12
|
+
import { openAIConfigFromEnv } from "./config.js";
|
|
13
|
+
const MAX_EMOTION_CHARS = 40;
|
|
14
|
+
const MAX_CAPTION_CHARS = 200;
|
|
15
|
+
const IMAGE_FETCH_TIMEOUT_MS = 10_000;
|
|
16
|
+
const MAX_PENDING_AUDIO = 200;
|
|
17
|
+
const MAX_PENDING_CONTEXT = 20;
|
|
18
|
+
/** One Microsoft Teams call answered by one OpenAI Realtime session. */
|
|
19
|
+
export class OpenAIHandler {
|
|
20
|
+
#config;
|
|
21
|
+
#tools;
|
|
22
|
+
#mcpTools;
|
|
23
|
+
#describer;
|
|
24
|
+
#connect;
|
|
25
|
+
#call;
|
|
26
|
+
#vision;
|
|
27
|
+
#callTools;
|
|
28
|
+
#agent;
|
|
29
|
+
#closed = false;
|
|
30
|
+
/** The mouth and the face. Both cosmetic, both cheap, both per call. */
|
|
31
|
+
#lip = new TurnLipSync();
|
|
32
|
+
#cue = new ExpressionCue();
|
|
33
|
+
#replyText = "";
|
|
34
|
+
// Holds BOTH the caller's first words and the first context, so neither
|
|
35
|
+
// is lost while the provider is still connecting.
|
|
36
|
+
#pending = new StartupBuffer();
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
this.#config = options.config ?? openAIConfigFromEnv();
|
|
39
|
+
this.#tools = new Map((options.tools ?? []).map((tool) => [tool.name, tool]));
|
|
40
|
+
this.#mcpTools = options.mcpTools ?? [];
|
|
41
|
+
this.#describer = options.describer ?? FrameDescriber.fromEnv();
|
|
42
|
+
this.#connect = options.connect ?? AgentSocket.connect.bind(AgentSocket);
|
|
43
|
+
const collisions = [...this.#tools.keys()].filter((name) => BUILT_IN_TOOL_NAMES.has(name));
|
|
44
|
+
if (collisions.length > 0) {
|
|
45
|
+
throw new Error(`custom tools may not shadow built-in ones: ${collisions.sort().join(", ")}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Whether the call is being recorded, straight off the session.
|
|
50
|
+
*
|
|
51
|
+
* The server keeps this current from `session.start` and every later
|
|
52
|
+
* `recording.status`, so there is nothing to re-derive here.
|
|
53
|
+
*/
|
|
54
|
+
#recording() {
|
|
55
|
+
return this.#call?.recordingActive ?? false;
|
|
56
|
+
}
|
|
57
|
+
// ---- the SDK seam -------------------------------------------------------
|
|
58
|
+
async onStart(session) {
|
|
59
|
+
this.#call = session;
|
|
60
|
+
this.#vision = new VisionTools(session, { describer: this.#describer });
|
|
61
|
+
this.#callTools = new CallTools(session, { vision: this.#vision });
|
|
62
|
+
const caller = session.start.caller;
|
|
63
|
+
const context = {
|
|
64
|
+
callerName: caller.displayName ?? "the caller",
|
|
65
|
+
tenantId: caller.tenantId ?? "unknown-tenant",
|
|
66
|
+
direction: session.start.direction,
|
|
67
|
+
};
|
|
68
|
+
let agent;
|
|
69
|
+
try {
|
|
70
|
+
agent = await this.#connect(this.#config, (message) => this.#onAgentMessage(message), (pcm) => this.#onAgentAudio(pcm), (code, reason) => this.#onAgentClose(code, reason));
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
logger.error(`standin: could not open the OpenAI Realtime session: ${String(err)}`);
|
|
74
|
+
await session.end("agent-unavailable");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// The call can end during the connect above.
|
|
78
|
+
if (this.#closed) {
|
|
79
|
+
await agent.aclose();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.#agent = agent;
|
|
83
|
+
const extraTools = [
|
|
84
|
+
...[...this.#tools.values()].map((tool) => ({
|
|
85
|
+
type: "function",
|
|
86
|
+
name: tool.name,
|
|
87
|
+
description: tool.description,
|
|
88
|
+
parameters: tool.parameters ?? {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {},
|
|
91
|
+
required: [],
|
|
92
|
+
},
|
|
93
|
+
})),
|
|
94
|
+
...this.#mcpTools,
|
|
95
|
+
];
|
|
96
|
+
agent.sendSessionUpdate(buildSessionUpdate(this.#config, buildInstructions(this.#config.instructions, context), extraTools));
|
|
97
|
+
await this.#pending.release((pcm) => agent.sendAudio(pcm), (text) => agent.sendContext(text));
|
|
98
|
+
if (this.#pending.dropped.audio || this.#pending.dropped.context) {
|
|
99
|
+
logger.info("standin: the caller outran the agent starting up; some early input was dropped");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async onCallerAudio(pcm) {
|
|
103
|
+
const agent = this.#agent;
|
|
104
|
+
if (agent === undefined || !agent.isOpen) {
|
|
105
|
+
this.#pending.audio(pcm);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
agent.sendAudio(pcm);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Context arrives as a user item with no response requested, so the model
|
|
112
|
+
* reads it on its next turn instead of interrupting to acknowledge it.
|
|
113
|
+
*/
|
|
114
|
+
/**
|
|
115
|
+
* Keep a short history, so a question about a slide already gone can still be
|
|
116
|
+
* answered. Only kept while the call is recorded.
|
|
117
|
+
*/
|
|
118
|
+
async onVideoFrame(frame) {
|
|
119
|
+
this.#vision?.keyframes.offer(frame, this.#call?.recordingActive ?? false);
|
|
120
|
+
}
|
|
121
|
+
async onContext(text) {
|
|
122
|
+
const agent = this.#agent;
|
|
123
|
+
if (agent === undefined || !agent.isOpen) {
|
|
124
|
+
this.#pending.context(text);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
agent.sendContext(text);
|
|
128
|
+
}
|
|
129
|
+
/** Cancel whatever is being said, then ask for the goodbye. */
|
|
130
|
+
async onGoodbye(text) {
|
|
131
|
+
const agent = this.#agent;
|
|
132
|
+
if (agent === undefined || !agent.isOpen)
|
|
133
|
+
return;
|
|
134
|
+
agent.cancelResponse();
|
|
135
|
+
agent.sendUserTurn(`[system: the call is ending. Say a brief goodbye now: "${text}"]`);
|
|
136
|
+
}
|
|
137
|
+
async aclose() {
|
|
138
|
+
this.#closed = true;
|
|
139
|
+
const agent = this.#agent;
|
|
140
|
+
this.#agent = undefined;
|
|
141
|
+
if (agent !== undefined) {
|
|
142
|
+
try {
|
|
143
|
+
await agent.aclose();
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Teardown must not throw: the slot is freed either way.
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The shared vision tools for this call.
|
|
152
|
+
*
|
|
153
|
+
* They carry the budget, the keyframes and the graceful refusals, so this
|
|
154
|
+
* plugin no longer keeps its own copy of any of them.
|
|
155
|
+
*/
|
|
156
|
+
#visionTools() {
|
|
157
|
+
if (this.#vision === undefined)
|
|
158
|
+
throw new Error("the call has not started yet");
|
|
159
|
+
return this.#vision;
|
|
160
|
+
}
|
|
161
|
+
// ---- what OpenAI sends us -----------------------------------------------
|
|
162
|
+
/** Agent audio, already resampled to the wire rate by the socket. */
|
|
163
|
+
async #onAgentAudio(pcm) {
|
|
164
|
+
if (pcm.length === 0)
|
|
165
|
+
return;
|
|
166
|
+
await this.#call?.sendAudio(pcm);
|
|
167
|
+
// The only duration this worker genuinely knows. A realtime model hands
|
|
168
|
+
// back no timings, and a guess from text length drifts further out of step
|
|
169
|
+
// with the voice the longer the turn runs.
|
|
170
|
+
this.#lip.audioSent(pcm);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* One viseme timeline per turn, spread over the audio that turn actually sent.
|
|
174
|
+
*
|
|
175
|
+
* Cosmetic, so everything here is swallowed: the worst acceptable outcome is
|
|
176
|
+
* a still mouth over correct audio, and the unacceptable one is a dropped
|
|
177
|
+
* turn because a lip shape threw.
|
|
178
|
+
*/
|
|
179
|
+
async #sendVisemes() {
|
|
180
|
+
const text = this.#replyText;
|
|
181
|
+
this.#replyText = "";
|
|
182
|
+
try {
|
|
183
|
+
const marks = this.#lip.finish(text);
|
|
184
|
+
if (marks.length > 0)
|
|
185
|
+
await this.#call?.sendSpeechMarks(marks);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
logger.debug(`standin: the viseme timeline was skipped: ${String(err)}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* A piece of what the model is saying, as it is being said.
|
|
193
|
+
*
|
|
194
|
+
* Held for the viseme timeline, and read for the face. Re-read on every piece
|
|
195
|
+
* rather than at the end, because waiting for the final transcript leaves the
|
|
196
|
+
* face wrong for the whole time the reply is being spoken.
|
|
197
|
+
*/
|
|
198
|
+
async #onReplyText(text) {
|
|
199
|
+
if (text === "")
|
|
200
|
+
return;
|
|
201
|
+
this.#replyText += text;
|
|
202
|
+
try {
|
|
203
|
+
const emotion = this.#cue.cue(this.#replyText);
|
|
204
|
+
if (emotion !== null)
|
|
205
|
+
await this.#call?.express(emotion);
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
logger.debug(`standin: the expression cue was skipped: ${String(err)}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async #onAgentMessage(message) {
|
|
212
|
+
switch (message.type) {
|
|
213
|
+
case "input_audio_buffer.speech_started":
|
|
214
|
+
// The model has already cancelled its own response server-side. This is
|
|
215
|
+
// the other half: flushing what StandIn has buffered, which is what
|
|
216
|
+
// actually stops the bot mid-word.
|
|
217
|
+
await this.#call?.cancelPlayback();
|
|
218
|
+
// The service drops audio the caller never heard. Keeping the count
|
|
219
|
+
// would spread the NEXT turn's words over its own audio plus the audio
|
|
220
|
+
// that was thrown away, and the mouth would run long for the rest of it.
|
|
221
|
+
this.#lip.cancel();
|
|
222
|
+
this.#replyText = "";
|
|
223
|
+
break;
|
|
224
|
+
case "response.output_audio_transcript.delta":
|
|
225
|
+
await this.#onReplyText(typeof message.delta === "string" ? message.delta : "");
|
|
226
|
+
break;
|
|
227
|
+
case "response.done":
|
|
228
|
+
await this.#sendVisemes();
|
|
229
|
+
break;
|
|
230
|
+
case "response.function_call_arguments.done":
|
|
231
|
+
void this.#runTool(message);
|
|
232
|
+
break;
|
|
233
|
+
case "conversation.item.input_audio_transcription.completed":
|
|
234
|
+
case "response.output_audio_transcript.done":
|
|
235
|
+
if (this.#config.logTranscripts && this.#recording()) {
|
|
236
|
+
logger.info(`standin: openai ${message.type}`);
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
case "error":
|
|
240
|
+
logger.warn(`standin: OpenAI error: ${JSON.stringify(message.error ?? message)}`);
|
|
241
|
+
break;
|
|
242
|
+
default:
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async #onAgentClose(code, reason) {
|
|
247
|
+
logger.info(`standin: the OpenAI Realtime session closed (${code} ${reason})`);
|
|
248
|
+
if (!this.#closed)
|
|
249
|
+
await this.#call?.end("agent-disconnected");
|
|
250
|
+
}
|
|
251
|
+
// ---- the model's tools --------------------------------------------------
|
|
252
|
+
async #runTool(message) {
|
|
253
|
+
const name = message.name;
|
|
254
|
+
const callId = message.call_id;
|
|
255
|
+
if (typeof name !== "string" || typeof callId !== "string")
|
|
256
|
+
return;
|
|
257
|
+
let params = {};
|
|
258
|
+
if (typeof message.arguments === "string") {
|
|
259
|
+
try {
|
|
260
|
+
const parsed = JSON.parse(message.arguments || "{}");
|
|
261
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
262
|
+
params = parsed;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
params = {};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
let output;
|
|
270
|
+
try {
|
|
271
|
+
output = await this.dispatch(name, params);
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
// Tell the model the truth: a tool that silently "succeeded" leaves it
|
|
275
|
+
// talking about something that never happened.
|
|
276
|
+
output = `${name} failed: ${String(err)}`;
|
|
277
|
+
}
|
|
278
|
+
this.#agent?.sendToolResult(callId, output);
|
|
279
|
+
}
|
|
280
|
+
/** Run one tool and return what the model is told. Exposed for tests. */
|
|
281
|
+
async dispatch(name, params) {
|
|
282
|
+
const call = this.#call;
|
|
283
|
+
if (call === undefined)
|
|
284
|
+
return "the call is no longer active";
|
|
285
|
+
const callTools = this.#callTools;
|
|
286
|
+
if (callTools !== undefined && BUILT_IN_TOOL_NAMES.has(name)) {
|
|
287
|
+
// The SDK owns these, and its dispatch never throws: what comes back is
|
|
288
|
+
// the sentence the model should say.
|
|
289
|
+
return callTools.dispatch(name, params);
|
|
290
|
+
}
|
|
291
|
+
const tool = this.#tools.get(name);
|
|
292
|
+
if (tool === undefined)
|
|
293
|
+
return `"${name}" is not a tool this plugin answers`;
|
|
294
|
+
return String(await tool.handler(params, { call, recording: this.#recording() }));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../../src/plugins/openai/handler.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAkB/B,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAmB,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,GAInB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAqB,MAAM,aAAa,CAAC;AAErE,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,MAAM,sBAAsB,GAAG,MAAM,CAAC;AACtC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAsC/B,wEAAwE;AACxE,MAAM,OAAO,aAAa;IACxB,OAAO,CAAe;IACtB,MAAM,CAA0B;IAChC,SAAS,CAAiC;IAC1C,UAAU,CAA6B;IACvC,QAAQ,CAAU;IAClB,KAAK,CAA0B;IAC/B,OAAO,CAA0B;IACjC,UAAU,CAAwB;IAClC,MAAM,CAAwB;IAC9B,OAAO,GAAG,KAAK,CAAC;IAChB,wEAAwE;IAC/D,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;IACzB,IAAI,GAAG,IAAI,aAAa,EAAE,CAAC;IACpC,UAAU,GAAG,EAAE,CAAC;IAChB,wEAAwE;IACxE,kDAAkD;IACzC,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAC;IAExC,YAAY,UAAgC,EAAE;QAC5C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,mBAAmB,EAAE,CAAC;QACvD,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,CACnB,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CACvD,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,SAAS,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;QAChE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,IAAI,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAEzE,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACzD,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,CAC9B,CAAC;QACF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,8CAA8C,UAAU,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC7E,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,KAAK,EAAE,eAAe,IAAI,KAAK,CAAC;IAC9C,CAAC;IAED,4EAA4E;IAE5E,KAAK,CAAC,OAAO,CAAC,OAAoB;QAChC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QACxE,IAAI,CAAC,UAAU,GAAG,IAAI,SAAS,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;QACpC,MAAM,OAAO,GAAkB;YAC7B,UAAU,EAAE,MAAM,CAAC,WAAW,IAAI,YAAY;YAC9C,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,gBAAgB;YAC7C,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,SAAS;SACnC,CAAC;QAEF,IAAI,KAAgB,CAAC;QACrB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CACzB,IAAI,CAAC,OAAO,EACZ,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,EAC1C,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAChC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CACnD,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CACV,wDAAwD,MAAM,CAAC,GAAG,CAAC,EAAE,CACtE,CAAC;YACF,MAAM,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;YACvC,OAAO;QACT,CAAC;QAED,6CAA6C;QAC7C,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QAEpB,MAAM,UAAU,GAAG;YACjB,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC1C,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI;oBAC7B,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE,EAAE;oBACd,QAAQ,EAAE,EAAE;iBACb;aACF,CAAC,CAAC;YACH,GAAG,IAAI,CAAC,SAAS;SAClB,CAAC;QACF,KAAK,CAAC,iBAAiB,CACrB,kBAAkB,CAChB,IAAI,CAAC,OAAO,EACZ,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,EACrD,UAAU,CACX,CACF,CAAC;QACF,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CACzB,CAAC,GAAW,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EACrC,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAC1C,CAAC;QACF,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACjE,MAAM,CAAC,IAAI,CACT,gFAAgF,CACjF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,GAAW;QAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YACzC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACzB,OAAO;QACT,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IAED;;;OAGG;IACH;;;OAGG;IACH,KAAK,CAAC,YAAY,CAAC,KAAiB;QAClC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,eAAe,IAAI,KAAK,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAY;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YACzC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,SAAS,CAAC,IAAY;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,OAAO;QACjD,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,KAAK,CAAC,YAAY,CAChB,0DAA0D,IAAI,IAAI,CACnE,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;YACvB,CAAC;YAAC,MAAM,CAAC;gBACP,yDAAyD;YAC3D,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,YAAY;QACV,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAC5B,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,4EAA4E;IAE5E,qEAAqE;IACrE,KAAK,CAAC,aAAa,CAAC,GAAW;QAC7B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC7B,MAAM,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;QACjC,wEAAwE;QACxE,2EAA2E;QAC3E,2CAA2C;QAC3C,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;QACrB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,MAAM,IAAI,CAAC,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,6CAA6C,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,IAAY;QAC7B,IAAI,IAAI,KAAK,EAAE;YAAE,OAAO;QACxB,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/C,IAAI,OAAO,KAAK,IAAI;gBAAE,MAAM,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,4CAA4C,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,OAAqB;QACzC,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;YACrB,KAAK,mCAAmC;gBACtC,wEAAwE;gBACxE,oEAAoE;gBACpE,mCAAmC;gBACnC,MAAM,IAAI,CAAC,KAAK,EAAE,cAAc,EAAE,CAAC;gBACnC,oEAAoE;gBACpE,uEAAuE;gBACvE,yEAAyE;gBACzE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACnB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;gBACrB,MAAM;YACR,KAAK,wCAAwC;gBAC3C,MAAM,IAAI,CAAC,YAAY,CACrB,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CACvD,CAAC;gBACF,MAAM;YACR,KAAK,eAAe;gBAClB,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;gBAC1B,MAAM;YACR,KAAK,uCAAuC;gBAC1C,KAAK,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAC5B,MAAM;YACR,KAAK,uDAAuD,CAAC;YAC7D,KAAK,uCAAuC;gBAC1C,IAAI,IAAI,CAAC,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;oBACrD,MAAM,CAAC,IAAI,CAAC,mBAAmB,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;gBACjD,CAAC;gBACD,MAAM;YACR,KAAK,OAAO;gBACV,MAAM,CAAC,IAAI,CACT,0BAA0B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,EAAE,CACrE,CAAC;gBACF,MAAM;YACR;gBACE,MAAM;QACV,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,IAAY,EAAE,MAAc;QAC9C,MAAM,CAAC,IAAI,CACT,gDAAgD,IAAI,IAAI,MAAM,GAAG,CAClE,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACjE,CAAC;IAED,4EAA4E;IAE5E,KAAK,CAAC,QAAQ,CAAC,OAAqB;QAClC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO;QAEnE,IAAI,MAAM,GAA4B,EAAE,CAAC;QACzC,IAAI,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;YAC1C,IAAI,CAAC;gBACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;gBAC9D,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;oBAClD,MAAM,GAAG,MAAiC,CAAC;gBAC7C,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,GAAG,EAAE,CAAC;YACd,CAAC;QACH,CAAC;QAED,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,uEAAuE;YACvE,+CAA+C;YAC/C,MAAM,GAAG,GAAG,IAAI,YAAY,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5C,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,QAAQ,CACZ,IAAY,EACZ,MAA+B;QAE/B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,8BAA8B,CAAC;QAE9D,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,SAAS,KAAK,SAAS,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7D,wEAAwE;YACxE,qCAAqC;YACrC,OAAO,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1C,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,IAAI,KAAK,SAAS;YACpB,OAAO,IAAI,IAAI,qCAAqC,CAAC;QACvD,OAAO,MAAM,CACX,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CACnE,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { AgentSocket, BUILT_IN_TOOLS, buildInstructions, buildSessionUpdate, mcpTool, type AgentMessage, type AgentPort, type CallerContext, } from "./agent.js";
|
|
2
|
+
export { DEFAULT_INSTRUCTIONS, openAIConfigFromEnv, type OpenAIConfig, } from "./config.js";
|
|
3
|
+
export { OpenAIHandler, type Connect, type CustomTool, type OpenAIHandlerOptions, type ToolContext, } from "./handler.js";
|
|
4
|
+
/** Answer Microsoft Teams calls with OpenAI until the process is interrupted. */
|
|
5
|
+
export declare function serve(): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/openai/index.ts"],"names":[],"mappings":"AAuDA,OAAO,EACL,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,OAAO,EACP,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,aAAa,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,EACb,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,KAAK,oBAAoB,EACzB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AAEtB,iFAAiF;AACjF,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAgB3C"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* Answer Microsoft Teams calls with an OpenAI Realtime model.
|
|
5
|
+
*
|
|
6
|
+
* Speech to speech: the model hears the caller's voice rather than a transcript
|
|
7
|
+
* of it, so there is no recognition step adding latency in the middle.
|
|
8
|
+
*
|
|
9
|
+
* Nothing to install beyond the SDK: the Realtime API is reached over an
|
|
10
|
+
* ordinary WebSocket, so this plugin adds no dependency.
|
|
11
|
+
*
|
|
12
|
+
* ```bash
|
|
13
|
+
* npm install @komaa/standin-sdk
|
|
14
|
+
*
|
|
15
|
+
* export STANDIN_SECRET=... # your StandIn connection secret
|
|
16
|
+
* export OPENAI_API_KEY=...
|
|
17
|
+
* npx standin-openai
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* The Realtime API speaks PCM at 24 kHz and a Microsoft Teams call speaks 16
|
|
21
|
+
* kHz. This plugin owns that conversion in both directions, using the same
|
|
22
|
+
* resampler the Python SDK uses, driven by the same shared conformance vectors.
|
|
23
|
+
*
|
|
24
|
+
* The model gets four call capabilities without any configuration: `end_call`,
|
|
25
|
+
* `express`, `show_image` and `look`. Looking needs a vision model, because the
|
|
26
|
+
* Realtime model hears but does not see - set `STANDIN_VISION_API_URL` and
|
|
27
|
+
* `STANDIN_VISION_MODEL` to any OpenAI-compatible endpoint that takes images.
|
|
28
|
+
*
|
|
29
|
+
* Add tools of your own, or point the session at a remote MCP server that
|
|
30
|
+
* OpenAI dials itself:
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { CallServer } from "@komaa/standin-sdk";
|
|
34
|
+
* import { OpenAIHandler, mcpTool } from "@komaa/standin-sdk/openai";
|
|
35
|
+
*
|
|
36
|
+
* const server = new CallServer({
|
|
37
|
+
* handlerFactory: () => new OpenAIHandler({
|
|
38
|
+
* tools: [{
|
|
39
|
+
* name: "open_ticket",
|
|
40
|
+
* description: "Open a support ticket for the caller.",
|
|
41
|
+
* parameters: { type: "object", properties: { summary: { type: "string" } }, required: ["summary"] },
|
|
42
|
+
* handler: async (params) => `opened ticket for ${String(params.summary)}`,
|
|
43
|
+
* }],
|
|
44
|
+
* mcpTools: [mcpTool({ server_label: "docs", server_url: "https://mcp.example.com" })],
|
|
45
|
+
* }),
|
|
46
|
+
* });
|
|
47
|
+
* await server.start();
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
import { CallServer } from "../../index.js";
|
|
51
|
+
import { OpenAIHandler } from "./handler.js";
|
|
52
|
+
import { openAIConfigFromEnv } from "./config.js";
|
|
53
|
+
export { AgentSocket, BUILT_IN_TOOLS, buildInstructions, buildSessionUpdate, mcpTool, } from "./agent.js";
|
|
54
|
+
export { DEFAULT_INSTRUCTIONS, openAIConfigFromEnv, } from "./config.js";
|
|
55
|
+
export { OpenAIHandler, } from "./handler.js";
|
|
56
|
+
/** Answer Microsoft Teams calls with OpenAI until the process is interrupted. */
|
|
57
|
+
export async function serve() {
|
|
58
|
+
const config = openAIConfigFromEnv();
|
|
59
|
+
const server = new CallServer({
|
|
60
|
+
handlerFactory: () => new OpenAIHandler({ config }),
|
|
61
|
+
});
|
|
62
|
+
await server.start();
|
|
63
|
+
await new Promise((resolve) => {
|
|
64
|
+
const stop = () => {
|
|
65
|
+
resolve();
|
|
66
|
+
};
|
|
67
|
+
process.once("SIGINT", stop);
|
|
68
|
+
process.once("SIGTERM", stop);
|
|
69
|
+
});
|
|
70
|
+
await server.aclose();
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/plugins/openai/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EACL,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,OAAO,GAIR,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,GAEpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,GAKd,MAAM,cAAc,CAAC;AAEtB,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,MAAM,MAAM,GAAG,mBAAmB,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC;QAC5B,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;KACpD,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IAErB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAClC,MAAM,IAAI,GAAG,GAAS,EAAE;YACtB,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kept so this plugin's historical import path still resolves.
|
|
3
|
+
*
|
|
4
|
+
* The caller policy moved into the SDK core as `policy.ts`. It was never
|
|
5
|
+
* OpenClaw-specific: it already said so itself, and every plugin that answers a
|
|
6
|
+
* call has to decide whether this caller may be answered.
|
|
7
|
+
*/
|
|
8
|
+
export { describeInboundRejection, isAllowlistedCaller, isInboundCallAllowed, normalizePhoneNumber, } from "../../policy.js";
|
|
9
|
+
//# sourceMappingURL=allowlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allowlist.d.ts","sourceRoot":"","sources":["../../../src/plugins/openclaw/allowlist.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AAEH,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* Kept so this plugin's historical import path still resolves.
|
|
5
|
+
*
|
|
6
|
+
* The caller policy moved into the SDK core as `policy.ts`. It was never
|
|
7
|
+
* OpenClaw-specific: it already said so itself, and every plugin that answers a
|
|
8
|
+
* call has to decide whether this caller may be answered.
|
|
9
|
+
*/
|
|
10
|
+
export { describeInboundRejection, isAllowlistedCaller, isInboundCallAllowed, normalizePhoneNumber, } from "../../policy.js";
|
|
11
|
+
//# sourceMappingURL=allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allowlist.js","sourceRoot":"","sources":["../../../src/plugins/openclaw/allowlist.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B;;;;;;GAMG;AAEH,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `api.pluginConfig` in, a typed settings object out.
|
|
3
|
+
*
|
|
4
|
+
* OpenClaw validates the raw config against `openclaw.plugin.json`'s
|
|
5
|
+
* `configSchema` BEFORE this runs and before it resolves any secret reference,
|
|
6
|
+
* so this is a boundary adapter over input that has already been shape-checked
|
|
7
|
+
* but whose secrets may or may not have resolved. Tolerant casts belong here and
|
|
8
|
+
* nowhere else in the plugin.
|
|
9
|
+
*/
|
|
10
|
+
/** Everything the plugin reads, resolved and defaulted. */
|
|
11
|
+
export interface ResolvedPluginConfig {
|
|
12
|
+
/** `enabled: false` turns the plugin off without uninstalling it. */
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
/** The call listener - what {@link CallServer} is constructed with. */
|
|
15
|
+
media: {
|
|
16
|
+
port: number;
|
|
17
|
+
bindAddress: string | undefined;
|
|
18
|
+
path: string;
|
|
19
|
+
/** The StandIn connection secret. Empty means refuse to start. */
|
|
20
|
+
secret: string;
|
|
21
|
+
maxConcurrentCalls: number;
|
|
22
|
+
};
|
|
23
|
+
/** How the agent behaves on a call. */
|
|
24
|
+
voice: {
|
|
25
|
+
inboundPolicy?: "disabled" | "allowlist" | "pairing" | "open";
|
|
26
|
+
allowFrom?: string[];
|
|
27
|
+
/** Spoken on pickup. Omitted means the agent waits for the caller. */
|
|
28
|
+
inboundGreeting?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Hold caller audio back until Microsoft Teams reports recording active.
|
|
31
|
+
*
|
|
32
|
+
* This is a Microsoft Media Access API obligation, not a preference: a bot
|
|
33
|
+
* that processes call media before `updateRecordingStatus` goes active is
|
|
34
|
+
* out of policy. Default OFF because the hosted service can be configured
|
|
35
|
+
* either way, and a gate nobody asked for is a call of silence.
|
|
36
|
+
*/
|
|
37
|
+
requireRecordingStatus: boolean;
|
|
38
|
+
realtime: {
|
|
39
|
+
provider?: string;
|
|
40
|
+
providers?: Record<string, Record<string, unknown>>;
|
|
41
|
+
instructions?: string;
|
|
42
|
+
suppressInputDuringPlayback?: boolean;
|
|
43
|
+
echoSuppressionWindowMs?: number;
|
|
44
|
+
echoBargeInRms?: number;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Resolve the host's raw plugin config. Never throws: an unusable config fails closed. */
|
|
49
|
+
export declare function resolvePluginConfig(rawInput: unknown): ResolvedPluginConfig;
|
|
50
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/plugins/openclaw/config.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;QAChC,IAAI,EAAE,MAAM,CAAC;QACb,kEAAkE;QAClE,MAAM,EAAE,MAAM,CAAC;QACf,kBAAkB,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF,uCAAuC;IACvC,KAAK,EAAE;QACL,aAAa,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;QAC9D,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,sEAAsE;QACtE,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB;;;;;;;WAOG;QACH,sBAAsB,EAAE,OAAO,CAAC;QAChC,QAAQ,EAAE;YACR,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;YACpD,YAAY,CAAC,EAAE,MAAM,CAAC;YACtB,2BAA2B,CAAC,EAAE,OAAO,CAAC;YACtC,uBAAuB,CAAC,EAAE,MAAM,CAAC;YACjC,cAAc,CAAC,EAAE,MAAM,CAAC;SACzB,CAAC;KACH,CAAC;CACH;AAqBD,2FAA2F;AAC3F,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,GAAG,oBAAoB,CAuC3E"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* A config value only counts when it is a non-empty STRING.
|
|
5
|
+
*
|
|
6
|
+
* This is the second line of defence on the secret, and it is deliberate. The
|
|
7
|
+
* manifest types `secret` as a string and OpenClaw rejects anything else before
|
|
8
|
+
* this resolver runs - but if an UNRESOLVED secret reference ever did arrive (an
|
|
9
|
+
* env reference whose variable is unset), `String({})` yields the literal
|
|
10
|
+
* "[object Object]": a non-empty, guessable secret that the fail-closed check in
|
|
11
|
+
* the entry point would happily accept. Coercing a non-string to "" makes it fail
|
|
12
|
+
* CLOSED, with the listener refusing to start, instead.
|
|
13
|
+
*/
|
|
14
|
+
const str = (v) => (typeof v === "string" ? v : "");
|
|
15
|
+
const asObject = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : undefined;
|
|
16
|
+
/** Resolve the host's raw plugin config. Never throws: an unusable config fails closed. */
|
|
17
|
+
export function resolvePluginConfig(rawInput) {
|
|
18
|
+
const c = rawInput ?? {};
|
|
19
|
+
const r = asObject(c.realtime) ?? {};
|
|
20
|
+
return {
|
|
21
|
+
enabled: c.enabled !== false,
|
|
22
|
+
media: {
|
|
23
|
+
port: Number(c.callingPort ?? 9442),
|
|
24
|
+
// undefined, not "": CallServer's own default (0.0.0.0) should win when the
|
|
25
|
+
// operator said nothing, and an empty string is not a bind address.
|
|
26
|
+
bindAddress: str(c.bindAddress) || undefined,
|
|
27
|
+
path: str(c.path) || "/msteams/calling",
|
|
28
|
+
secret: str(c.secret),
|
|
29
|
+
maxConcurrentCalls: Number(c.maxConcurrentCalls ?? 4),
|
|
30
|
+
},
|
|
31
|
+
voice: {
|
|
32
|
+
inboundPolicy: c.inboundPolicy,
|
|
33
|
+
allowFrom: Array.isArray(c.allowFrom)
|
|
34
|
+
? c.allowFrom.map(String)
|
|
35
|
+
: undefined,
|
|
36
|
+
inboundGreeting: str(c.inboundGreeting) || undefined,
|
|
37
|
+
requireRecordingStatus: c.requireRecordingStatus === true,
|
|
38
|
+
realtime: {
|
|
39
|
+
provider: str(r.provider) || undefined,
|
|
40
|
+
providers: asObject(r.providers),
|
|
41
|
+
instructions: str(r.instructions) || undefined,
|
|
42
|
+
suppressInputDuringPlayback: typeof r.suppressInputDuringPlayback === "boolean"
|
|
43
|
+
? r.suppressInputDuringPlayback
|
|
44
|
+
: undefined,
|
|
45
|
+
echoSuppressionWindowMs: r.echoSuppressionWindowMs === undefined
|
|
46
|
+
? undefined
|
|
47
|
+
: Number(r.echoSuppressionWindowMs),
|
|
48
|
+
echoBargeInRms: r.echoBargeInRms === undefined ? undefined : Number(r.echoBargeInRms),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/plugins/openclaw/config.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAsD/B;;;;;;;;;;GAUG;AACH,MAAM,GAAG,GAAG,CAAC,CAAU,EAAU,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAErE,MAAM,QAAQ,GAAG,CAAC,CAAU,EAAmB,EAAE,CAC/C,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAS,CAAC,CAAC,CAAC,SAAS,CAAC;AAE3E,2FAA2F;AAC3F,MAAM,UAAU,mBAAmB,CAAC,QAAiB;IACnD,MAAM,CAAC,GAAS,QAAgB,IAAI,EAAE,CAAC;IACvC,MAAM,CAAC,GAAQ,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC1C,OAAO;QACL,OAAO,EAAE,CAAC,CAAC,OAAO,KAAK,KAAK;QAC5B,KAAK,EAAE;YACL,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,IAAI,IAAI,CAAC;YACnC,4EAA4E;YAC5E,oEAAoE;YACpE,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,SAAS;YAC5C,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,kBAAkB;YACvC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;YACrB,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC,kBAAkB,IAAI,CAAC,CAAC;SACtD;QACD,KAAK,EAAE;YACL,aAAa,EAAE,CAAC,CAAC,aAAa;YAC9B,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;gBACnC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;gBACzB,CAAC,CAAC,SAAS;YACb,eAAe,EAAE,GAAG,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,SAAS;YACpD,sBAAsB,EAAE,CAAC,CAAC,sBAAsB,KAAK,IAAI;YACzD,QAAQ,EAAE;gBACR,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,SAAS;gBACtC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CACsB;gBACrD,YAAY,EAAE,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,SAAS;gBAC9C,2BAA2B,EACzB,OAAO,CAAC,CAAC,2BAA2B,KAAK,SAAS;oBAChD,CAAC,CAAC,CAAC,CAAC,2BAA2B;oBAC/B,CAAC,CAAC,SAAS;gBACf,uBAAuB,EACrB,CAAC,CAAC,uBAAuB,KAAK,SAAS;oBACrC,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,uBAAuB,CAAC;gBACvC,cAAc,EACZ,CAAC,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC;aACxE;SACF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seam adapter: one {@link CallHandler} per Microsoft Teams call.
|
|
3
|
+
*
|
|
4
|
+
* This is the whole surface between StandIn and OpenClaw, and it is deliberately
|
|
5
|
+
* thin. Everything framework-independent - the socket, the HMAC handshake and its
|
|
6
|
+
* replay guard, capacity, the wire protocol, the outbound sequence number and
|
|
7
|
+
* audio timeline, the pre-start and audio-idle watchdogs, idempotent teardown -
|
|
8
|
+
* belongs to the SDK's `CallServer`. Everything model-shaped belongs to
|
|
9
|
+
* {@link createRealtimeCall}. What is left here is the decision of whether to
|
|
10
|
+
* take the call at all, and the wiring between the two.
|
|
11
|
+
*
|
|
12
|
+
* There are four refusal paths, and each is one line, because `session.end()` is
|
|
13
|
+
* safe to call from inside `onStart`: it returns immediately and the close runs
|
|
14
|
+
* once `onStart` unwinds, so refusing a call needs no dance around a teardown
|
|
15
|
+
* deadlock.
|
|
16
|
+
*/
|
|
17
|
+
import { type CallHandler, type CallSession } from "../../index.js";
|
|
18
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
|
19
|
+
import type { RealtimeVoiceProviderConfig, RealtimeVoiceProviderPlugin } from "openclaw/plugin-sdk/realtime-voice";
|
|
20
|
+
import type { ResolvedPluginConfig } from "./config.js";
|
|
21
|
+
import { type CallLogger, type RealtimeCall } from "./realtime.js";
|
|
22
|
+
/** The host realtime provider, resolved once at startup and shared by every call. */
|
|
23
|
+
export interface ResolvedRealtime {
|
|
24
|
+
provider: RealtimeVoiceProviderPlugin;
|
|
25
|
+
providerConfig: RealtimeVoiceProviderConfig;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Concurrency and shutdown, owned by the runtime.
|
|
29
|
+
*
|
|
30
|
+
* The SDK caps connections too, and that cap is the one that protects the worker.
|
|
31
|
+
* This one is the OPERATOR's number: it is what `maxConcurrentCalls` in the
|
|
32
|
+
* plugin config means, it is enforced after the handshake so a refused call gets
|
|
33
|
+
* a spoken-protocol close rather than a dropped socket, and it is the hook the
|
|
34
|
+
* gateway's shutdown uses to end live calls before the listener goes away.
|
|
35
|
+
*/
|
|
36
|
+
export interface CallRegistry {
|
|
37
|
+
/** Reserve a slot. False when the operator's cap is already reached. */
|
|
38
|
+
acquire(callId: string): boolean;
|
|
39
|
+
/** Attach the live call so shutdown can reach it. */
|
|
40
|
+
bind(callId: string, call: RealtimeCall): void;
|
|
41
|
+
/** Free the slot. Idempotent - teardown runs on every path. */
|
|
42
|
+
release(callId: string): void;
|
|
43
|
+
}
|
|
44
|
+
/** Everything one handler needs. Built once by the runtime and closed over. */
|
|
45
|
+
export interface HandlerDeps {
|
|
46
|
+
config: ResolvedPluginConfig;
|
|
47
|
+
/**
|
|
48
|
+
* Undefined when no realtime provider resolved at startup. Not an error at
|
|
49
|
+
* construction time: the gateway may be brought up before its credentials are,
|
|
50
|
+
* and refusing each call with a named reason beats refusing to boot.
|
|
51
|
+
*/
|
|
52
|
+
realtime?: ResolvedRealtime;
|
|
53
|
+
/** The gateway's own config, passed through to the host session. */
|
|
54
|
+
cfg?: OpenClawConfig;
|
|
55
|
+
registry: CallRegistry;
|
|
56
|
+
logger?: CallLogger;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* One Microsoft Teams call.
|
|
60
|
+
*
|
|
61
|
+
* Implements four of the SDK's five methods. `onCallerAudio` is the hot path and
|
|
62
|
+
* does no work of its own - it hands the frame to the realtime bridge, which owns
|
|
63
|
+
* the recording gate, the echo guard and the resampling.
|
|
64
|
+
*/
|
|
65
|
+
export declare class TeamsCallHandler implements CallHandler {
|
|
66
|
+
#private;
|
|
67
|
+
constructor(deps: HandlerDeps);
|
|
68
|
+
onStart(session: CallSession): Promise<void>;
|
|
69
|
+
/** PCM16, 16 kHz, mono, little-endian, already validated by the SDK. */
|
|
70
|
+
onCallerAudio(pcm: Buffer): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Participant counts, DTMF and recording status, as plain sentences.
|
|
73
|
+
*
|
|
74
|
+
* Recording status is the one that is not just text: it opens and closes the
|
|
75
|
+
* media gate, so it is matched rather than forwarded. Matched against the SDK's
|
|
76
|
+
* own {@link contextSentences} rather than a string literal of our own, so the
|
|
77
|
+
* two cannot drift apart the next time the wording is improved.
|
|
78
|
+
*/
|
|
79
|
+
onContext(text: string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* StandIn is ending the call and wants this line spoken first.
|
|
82
|
+
*
|
|
83
|
+
* Interrupt, then say. A goodbye queued behind a long answer is a goodbye the
|
|
84
|
+
* caller never hears, and teardown follows within seconds.
|
|
85
|
+
*/
|
|
86
|
+
onGoodbye(text: string): Promise<void>;
|
|
87
|
+
/** Always called exactly once, on every path, before the slot is freed. */
|
|
88
|
+
aclose(reason: string): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/plugins/openclaw/handler.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,KAAK,EACV,2BAA2B,EAC3B,2BAA2B,EAC5B,MAAM,oCAAoC,CAAC;AAG5C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAEvB,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,2BAA2B,CAAC;IACtC,cAAc,EAAE,2BAA2B,CAAC;CAC7C;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,qDAAqD;IACrD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/C,+DAA+D;IAC/D,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,oBAAoB,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,oEAAoE;IACpE,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED;;;;;;GAMG;AACH,qBAAa,gBAAiB,YAAW,WAAW;;gBAOtC,IAAI,EAAE,WAAW;IAIvB,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAkFlD,wEAAwE;IAClE,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C;;;;;;;OAOG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa5C;;;;;OAKG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5C,2EAA2E;IACrE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAW5C"}
|