@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
package/dist/vision.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* The vision lane: what the caller shows you, and what you show back.
|
|
5
|
+
*
|
|
6
|
+
* A Microsoft Teams call carries more than voice. StandIn samples the caller's
|
|
7
|
+
* camera and their screen share and forwards single JPEG frames, and it will
|
|
8
|
+
* draw an image you send onto the bot's own tile. This module is both halves of
|
|
9
|
+
* that: {@link parseVideoFrame} reads what arrives, {@link displayImage} builds
|
|
10
|
+
* what goes back.
|
|
11
|
+
*
|
|
12
|
+
* Frames arrive **sparsely and best-effort**. StandIn drops a frame rather than
|
|
13
|
+
* queueing it when the socket is busy, so this is not a video stream and must
|
|
14
|
+
* not be treated as one. The useful shape is the one every provider plugin
|
|
15
|
+
* ends up with: keep the latest frame per source and send it to a vision model
|
|
16
|
+
* only when something asks to look. {@link CallSession.latestVideoFrame} does
|
|
17
|
+
* that for you, so a plugin that only wants on-demand vision implements
|
|
18
|
+
* no callback at all.
|
|
19
|
+
*
|
|
20
|
+
* {@link FrameDescriber} is the other way round, and the one most voice
|
|
21
|
+
* providers need: a speech-to-speech model that hears but cannot see gets a
|
|
22
|
+
* sentence of text instead of a picture. The frame goes to a vision model of
|
|
23
|
+
* your choosing, transiently, and only the description comes back.
|
|
24
|
+
*
|
|
25
|
+
* Identical in shape to the Python SDK's `standin.vision`, translated to TS
|
|
26
|
+
* naming.
|
|
27
|
+
*/
|
|
28
|
+
import { createHash } from "node:crypto";
|
|
29
|
+
import { encode } from "./protocolRuntime.js";
|
|
30
|
+
import { TYPE_DISPLAY_FRAME, TYPE_DISPLAY_IMAGE } from "./protocol.js";
|
|
31
|
+
/** The two things a caller can show: their camera, or their screen share. */
|
|
32
|
+
export const VIDEO_SOURCES = ["camera", "screenshare"];
|
|
33
|
+
/** What StandIn will draw on the bot tile. JPEG or PNG, nothing else. */
|
|
34
|
+
export const DISPLAY_IMAGE_MIME_TYPES = ["image/jpeg", "image/png"];
|
|
35
|
+
/**
|
|
36
|
+
* One wire message is bounded at 2 MB by both SDKs, and base64 costs a third on
|
|
37
|
+
* top of the raw bytes. Refusing an oversized image here names the real problem,
|
|
38
|
+
* rather than letting the service close the socket mid-call.
|
|
39
|
+
*/
|
|
40
|
+
export const MAX_IMAGE_BYTES = 1_400_000;
|
|
41
|
+
function text(value) {
|
|
42
|
+
if (typeof value !== "string")
|
|
43
|
+
return undefined;
|
|
44
|
+
const trimmed = value.trim();
|
|
45
|
+
return trimmed === "" ? undefined : trimmed;
|
|
46
|
+
}
|
|
47
|
+
function positiveInt(value) {
|
|
48
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0
|
|
49
|
+
? value
|
|
50
|
+
: undefined;
|
|
51
|
+
}
|
|
52
|
+
function decodeStrict(dataBase64) {
|
|
53
|
+
const data = Buffer.from(dataBase64, "base64");
|
|
54
|
+
// Buffer.from never throws on bad input, it silently discards what it cannot
|
|
55
|
+
// read. Re-encoding is the only way to know the bytes about to reach a
|
|
56
|
+
// provider really are the payload that was sent.
|
|
57
|
+
return data.length > 0 && data.toString("base64") === dataBase64
|
|
58
|
+
? data
|
|
59
|
+
: undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Read a `video.frame`, or return `undefined` if it is unusable.
|
|
63
|
+
*
|
|
64
|
+
* Never throws. A frame that fails any check is a frame to drop: the call is
|
|
65
|
+
* healthy, the caller is still talking, and one malformed image is not worth
|
|
66
|
+
* ending a conversation over. That is the same leniency the rest of the wire
|
|
67
|
+
* contract is built on, where a receiver ignores what it cannot use.
|
|
68
|
+
*/
|
|
69
|
+
export function parseVideoFrame(msg) {
|
|
70
|
+
const source = text(msg.source);
|
|
71
|
+
if (source !== "camera" && source !== "screenshare")
|
|
72
|
+
return undefined;
|
|
73
|
+
const dataBase64 = msg.dataBase64;
|
|
74
|
+
if (typeof dataBase64 !== "string" || dataBase64 === "")
|
|
75
|
+
return undefined;
|
|
76
|
+
const data = decodeStrict(dataBase64);
|
|
77
|
+
if (data === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
const width = positiveInt(msg.width);
|
|
80
|
+
const height = positiveInt(msg.height);
|
|
81
|
+
if (width === undefined || height === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
const ts = msg.ts;
|
|
84
|
+
const mime = text(msg.mime) ?? "image/jpeg";
|
|
85
|
+
return {
|
|
86
|
+
source,
|
|
87
|
+
ts: typeof ts === "number" && Number.isInteger(ts) && ts >= 0 ? ts : 0,
|
|
88
|
+
width,
|
|
89
|
+
height,
|
|
90
|
+
mime,
|
|
91
|
+
dataBase64,
|
|
92
|
+
participantId: text(msg.participantId),
|
|
93
|
+
participantName: text(msg.participantName),
|
|
94
|
+
data,
|
|
95
|
+
dataUrl: `data:${mime};base64,${dataBase64}`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function encodeImage(image, mime, label) {
|
|
99
|
+
if (!DISPLAY_IMAGE_MIME_TYPES.includes(mime)) {
|
|
100
|
+
throw new Error(`${label} mime must be one of ${DISPLAY_IMAGE_MIME_TYPES.join(", ")}, got ${mime}`);
|
|
101
|
+
}
|
|
102
|
+
let size;
|
|
103
|
+
let dataBase64;
|
|
104
|
+
if (typeof image === "string") {
|
|
105
|
+
// Already base64: measure the decoded size, because that is what the 2 MB
|
|
106
|
+
// envelope actually bounds.
|
|
107
|
+
const decoded = decodeStrict(image);
|
|
108
|
+
if (decoded === undefined)
|
|
109
|
+
throw new Error(`${label} data is not valid base64`);
|
|
110
|
+
size = decoded.length;
|
|
111
|
+
dataBase64 = image;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
size = image.length;
|
|
115
|
+
dataBase64 = image.toString("base64");
|
|
116
|
+
}
|
|
117
|
+
if (size === 0)
|
|
118
|
+
throw new Error(`${label} carries no image data`);
|
|
119
|
+
if (size > MAX_IMAGE_BYTES) {
|
|
120
|
+
throw new Error(`${label} is ${size} bytes, over the ${MAX_IMAGE_BYTES} limit`);
|
|
121
|
+
}
|
|
122
|
+
return dataBase64;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Build a `display.image`: show the caller a still, then return to the avatar.
|
|
126
|
+
*
|
|
127
|
+
* `image` is a Buffer or an already-base64 string.
|
|
128
|
+
*/
|
|
129
|
+
export function displayImage(image, options = {}) {
|
|
130
|
+
const mime = options.mime ?? "image/jpeg";
|
|
131
|
+
const message = {
|
|
132
|
+
type: TYPE_DISPLAY_IMAGE,
|
|
133
|
+
dataBase64: encodeImage(image, mime, "display.image"),
|
|
134
|
+
mime,
|
|
135
|
+
// The wire reserves a timeline anchor this lane does not use. Both SDKs
|
|
136
|
+
// send 0 rather than one omitting it, so a single conformance vector
|
|
137
|
+
// covers both and neither can drift.
|
|
138
|
+
ts: 0,
|
|
139
|
+
};
|
|
140
|
+
if (options.durationMs !== undefined && options.durationMs > 0) {
|
|
141
|
+
message.durationMs = options.durationMs;
|
|
142
|
+
}
|
|
143
|
+
if (options.mode)
|
|
144
|
+
message.mode = options.mode;
|
|
145
|
+
if (options.caption)
|
|
146
|
+
message.caption = options.caption;
|
|
147
|
+
return encode(message);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Build a `display.frame`: one frame of continuous avatar video.
|
|
151
|
+
*
|
|
152
|
+
* Latest wins. There is no handshake, the first frames start the stream and
|
|
153
|
+
* silence ends it, and a sender under backpressure MUST drop frames rather than
|
|
154
|
+
* buffer them, exactly as it does for hot-path audio.
|
|
155
|
+
*
|
|
156
|
+
* `ts` belongs to the sender's own media timeline, the same one its outbound
|
|
157
|
+
* audio is stamped on, so the two streams share a clock.
|
|
158
|
+
*/
|
|
159
|
+
export function displayFrame(seq, ts, image, options = {}) {
|
|
160
|
+
const mime = options.mime ?? "image/jpeg";
|
|
161
|
+
const message = {
|
|
162
|
+
type: TYPE_DISPLAY_FRAME,
|
|
163
|
+
seq,
|
|
164
|
+
ts,
|
|
165
|
+
mime,
|
|
166
|
+
dataBase64: encodeImage(image, mime, "display.frame"),
|
|
167
|
+
};
|
|
168
|
+
if (options.width !== undefined)
|
|
169
|
+
message.width = options.width;
|
|
170
|
+
if (options.height !== undefined)
|
|
171
|
+
message.height = options.height;
|
|
172
|
+
return encode(message);
|
|
173
|
+
}
|
|
174
|
+
/** Hard bound on the vision round trip. The caller hears this as silence. */
|
|
175
|
+
const DESCRIBE_TIMEOUT_MS = 20_000;
|
|
176
|
+
/** Enough for a sentence or two read aloud. A voice agent cannot relay an essay. */
|
|
177
|
+
const DESCRIBE_MAX_TOKENS = 300;
|
|
178
|
+
/**
|
|
179
|
+
* Turn a frame into a sentence, using a vision model you choose.
|
|
180
|
+
*
|
|
181
|
+
* Most speech-to-speech providers hear but cannot see. This is what lets one
|
|
182
|
+
* answer "what is on my screen?": the frame goes to any OpenAI-compatible
|
|
183
|
+
* chat-completions endpoint that accepts image input (OpenAI, Azure OpenAI,
|
|
184
|
+
* Ollama, vLLM, whatever you run), and what comes back is text the agent can say
|
|
185
|
+
* out loud.
|
|
186
|
+
*
|
|
187
|
+
* The frame is sent for inference and not stored, which is the difference
|
|
188
|
+
* between this and uploading it into a provider's own conversation history.
|
|
189
|
+
*
|
|
190
|
+
* Deliberately NOT put through the guard in `fetch.ts`: this URL is yours, set
|
|
191
|
+
* by you in the environment, and a vision model on localhost is a normal way to
|
|
192
|
+
* run one. That is the opposite of an image URL a model chose.
|
|
193
|
+
*/
|
|
194
|
+
/**
|
|
195
|
+
* A short, stable fingerprint of one frame.
|
|
196
|
+
*
|
|
197
|
+
* For asking "is this the same screen as last time?" without keeping the
|
|
198
|
+
* picture. A hash of the encoded form is enough: two encodes of an unchanged
|
|
199
|
+
* screen are byte-identical.
|
|
200
|
+
*/
|
|
201
|
+
export function frameDigest(dataBase64) {
|
|
202
|
+
return createHash("sha256")
|
|
203
|
+
.update(dataBase64 ?? "", "ascii")
|
|
204
|
+
.digest("hex")
|
|
205
|
+
.slice(0, 32);
|
|
206
|
+
}
|
|
207
|
+
/** Who is showing this, when the wire said. */
|
|
208
|
+
export function frameOwner(frame) {
|
|
209
|
+
const name = (frame.participantName ?? "").trim();
|
|
210
|
+
return name === "" ? undefined : name;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Who to say it is when nobody was named.
|
|
214
|
+
*
|
|
215
|
+
* Attribution that degrades rather than vanishing: "a participant's screen" is
|
|
216
|
+
* worth more to a model than an unlabelled picture.
|
|
217
|
+
*/
|
|
218
|
+
export function fallbackOwner(source) {
|
|
219
|
+
return source === "screenshare" ? "a participant" : "the caller";
|
|
220
|
+
}
|
|
221
|
+
/** The sentence that goes beside a frame, so a model knows whose it is. */
|
|
222
|
+
export function frameCaption(owner) {
|
|
223
|
+
return owner === "a participant"
|
|
224
|
+
? `screen shared by ${owner}`
|
|
225
|
+
: `camera of ${owner}`;
|
|
226
|
+
}
|
|
227
|
+
export class FrameDescriber {
|
|
228
|
+
url;
|
|
229
|
+
model;
|
|
230
|
+
apiKey;
|
|
231
|
+
constructor(options) {
|
|
232
|
+
this.url = options.url;
|
|
233
|
+
this.model = options.model;
|
|
234
|
+
this.apiKey = options.apiKey;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Build one from `STANDIN_VISION_API_URL` and `STANDIN_VISION_MODEL`.
|
|
238
|
+
*
|
|
239
|
+
* Returns `undefined` when they are not set, which is the signal a
|
|
240
|
+
* plugin uses to tell an agent that looking is not available here.
|
|
241
|
+
*/
|
|
242
|
+
static fromEnv() {
|
|
243
|
+
const url = (process.env.STANDIN_VISION_API_URL ?? "").trim();
|
|
244
|
+
const model = (process.env.STANDIN_VISION_MODEL ?? "").trim();
|
|
245
|
+
if (!url || !model)
|
|
246
|
+
return undefined;
|
|
247
|
+
const apiKey = (process.env.STANDIN_VISION_API_KEY ?? "").trim();
|
|
248
|
+
return new FrameDescriber({ url, model, apiKey: apiKey || undefined });
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Ask the model about one frame. Throws on anything that goes wrong, so a
|
|
252
|
+
* caller can hand the reason back to the agent that asked.
|
|
253
|
+
*/
|
|
254
|
+
async describe(frame, question) {
|
|
255
|
+
const who = frameOwner(frame) ?? fallbackOwner(frame.source);
|
|
256
|
+
const seeing = frameCaption(who);
|
|
257
|
+
const headers = {
|
|
258
|
+
"content-type": "application/json",
|
|
259
|
+
};
|
|
260
|
+
if (this.apiKey)
|
|
261
|
+
headers.authorization = `Bearer ${this.apiKey}`;
|
|
262
|
+
const controller = new AbortController();
|
|
263
|
+
const deadline = setTimeout(() => controller.abort(), DESCRIBE_TIMEOUT_MS);
|
|
264
|
+
try {
|
|
265
|
+
const response = await fetch(this.url, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers,
|
|
268
|
+
signal: controller.signal,
|
|
269
|
+
body: JSON.stringify({
|
|
270
|
+
model: this.model,
|
|
271
|
+
max_tokens: DESCRIBE_MAX_TOKENS,
|
|
272
|
+
messages: [
|
|
273
|
+
{
|
|
274
|
+
role: "user",
|
|
275
|
+
content: [
|
|
276
|
+
{
|
|
277
|
+
type: "text",
|
|
278
|
+
text: `This is a live frame from a Microsoft Teams call (${seeing}). ` +
|
|
279
|
+
`Answer concisely, for a voice agent to say out loud. Question: ${question}`,
|
|
280
|
+
},
|
|
281
|
+
{ type: "image_url", image_url: { url: frame.dataUrl } },
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
}),
|
|
286
|
+
});
|
|
287
|
+
if (!response.ok) {
|
|
288
|
+
throw new Error(`the vision model returned HTTP ${response.status}`);
|
|
289
|
+
}
|
|
290
|
+
const data = (await response.json());
|
|
291
|
+
const text = (data.choices?.[0]?.message?.content ?? "").trim();
|
|
292
|
+
if (!text)
|
|
293
|
+
throw new Error("the vision model returned nothing");
|
|
294
|
+
return text;
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
clearTimeout(deadline);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=vision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision.js","sourceRoot":"","sources":["../src/vision.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type { CallSession } from "./handler.js";
|
|
2
|
+
import { type DisplayImageMode, type FrameDescriber, type VideoFrame, type VideoSource } from "./vision.js";
|
|
3
|
+
/** The only two ways a picture can be put on the tile. */
|
|
4
|
+
export declare const DISPLAY_MODES: readonly ["fullscreen", "overlay"];
|
|
5
|
+
/** How long each picture of a slideshow stays up before the next one. */
|
|
6
|
+
export declare const SLIDESHOW_HOLD_MS = 4000;
|
|
7
|
+
/**
|
|
8
|
+
* How much longer than the gap each picture is held for.
|
|
9
|
+
*
|
|
10
|
+
* Without it the tile blanks for the moment between one picture expiring and
|
|
11
|
+
* the next arriving, and the caller sees a flicker rather than a slideshow.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SLIDESHOW_OVERLAP_MS = 500;
|
|
14
|
+
/**
|
|
15
|
+
* How many pictures one slideshow may hold.
|
|
16
|
+
*
|
|
17
|
+
* A model handed a folder will pass the whole folder. Ten at four seconds is
|
|
18
|
+
* already most of a minute of a live call spent looking at pictures.
|
|
19
|
+
*/
|
|
20
|
+
export declare const MAX_SLIDESHOW_IMAGES = 10;
|
|
21
|
+
/** How long a page has to render before the caller is told it did not. */
|
|
22
|
+
export declare const PAGE_RENDER_TIMEOUT_MS = 45000;
|
|
23
|
+
/**
|
|
24
|
+
* How long a rendered page stays on the tile. Longer than a chart: a page is
|
|
25
|
+
* read rather than glanced at.
|
|
26
|
+
*/
|
|
27
|
+
export declare const PAGE_DISPLAY_MS = 15000;
|
|
28
|
+
/**
|
|
29
|
+
* The display mode this value means, or the default.
|
|
30
|
+
*
|
|
31
|
+
* One rule, shared by both SDKs and every plugin, because the value comes from
|
|
32
|
+
* a model: `"pip"`, `"inset"`, `"full"` and nothing at all are all things a
|
|
33
|
+
* model will say, and none of them is a mode.
|
|
34
|
+
*/
|
|
35
|
+
export declare function normalizeDisplayMode(value: unknown, fallback?: DisplayImageMode): DisplayImageMode | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* A filename to show beside a picture.
|
|
38
|
+
*
|
|
39
|
+
* Taken from the source when it looks like a filename and nothing else. The
|
|
40
|
+
* string came from a model steered by whoever is on the call, and it is about
|
|
41
|
+
* to be shown to them.
|
|
42
|
+
*/
|
|
43
|
+
export declare function displayImageName(pathOrUrl: string, mime: string): string;
|
|
44
|
+
/** The last picture the caller actually saw. */
|
|
45
|
+
export declare class ShownImage {
|
|
46
|
+
readonly image: Buffer | string;
|
|
47
|
+
readonly mime: string;
|
|
48
|
+
readonly name: string;
|
|
49
|
+
readonly atMs: number;
|
|
50
|
+
constructor(image: Buffer | string, mime: string, name: string, atMs: number);
|
|
51
|
+
asBase64(): string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Renders one page to bytes plus a mime type. Supplied by a plugin whose host
|
|
55
|
+
* already runs a browser; the core never gains one.
|
|
56
|
+
*/
|
|
57
|
+
export type PageRenderer = (url: string) => Promise<{
|
|
58
|
+
bytes: Buffer | string;
|
|
59
|
+
mime: string;
|
|
60
|
+
}>;
|
|
61
|
+
/** One picture in a slideshow. Bytes, base64, or an https URL. */
|
|
62
|
+
export interface ShowItem {
|
|
63
|
+
image: Buffer | string;
|
|
64
|
+
mime?: string;
|
|
65
|
+
name?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A ceiling on how often one call may spend on vision.
|
|
69
|
+
*
|
|
70
|
+
* A model that can look can look in a loop, and each look is a paid inference
|
|
71
|
+
* over somebody's screen. A sliding window rather than a total, so a long call
|
|
72
|
+
* is not punished for having been long.
|
|
73
|
+
*
|
|
74
|
+
* Spending returns a token and refunding takes that token back. Two tool calls
|
|
75
|
+
* can overlap, and a refund that simply dropped "the most recent charge" would
|
|
76
|
+
* refund the wrong one and let the budget drift upward under exactly the load it
|
|
77
|
+
* exists to bound.
|
|
78
|
+
*/
|
|
79
|
+
export declare class VisionBudget {
|
|
80
|
+
#private;
|
|
81
|
+
readonly maxPerMinute: number;
|
|
82
|
+
constructor(maxPerMinute?: number);
|
|
83
|
+
/** Take one look's worth of budget, or undefined when there is none left. */
|
|
84
|
+
tryConsume(): string | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* How much of the window only an explicit look may spend.
|
|
87
|
+
*
|
|
88
|
+
* Ambient vision spends on every scene change, which is exactly the load that
|
|
89
|
+
* would leave a caller's own "look at this" with nothing left. The reserve is
|
|
90
|
+
* what the ambient lane cannot touch.
|
|
91
|
+
*/
|
|
92
|
+
get reserve(): number;
|
|
93
|
+
/**
|
|
94
|
+
* Take one look's worth, from the ambient lane only.
|
|
95
|
+
*
|
|
96
|
+
* Refused once the window is down to the reserve. Refunded through the same
|
|
97
|
+
* {@link refund}, with the same token, so a failed ambient push and a failed
|
|
98
|
+
* explicit look are given back the same way.
|
|
99
|
+
*/
|
|
100
|
+
tryConsumeAmbient(): string | undefined;
|
|
101
|
+
/** Give back a charge whose look never happened. Idempotent. */
|
|
102
|
+
refund(token: string): void;
|
|
103
|
+
/** Looks charged in the current window. */
|
|
104
|
+
get spent(): number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A short history of what the caller showed.
|
|
108
|
+
*
|
|
109
|
+
* The call session keeps the LATEST frame per source, which answers "what am I
|
|
110
|
+
* looking at now". This answers "what was on that slide a moment ago".
|
|
111
|
+
*
|
|
112
|
+
* Bounded, and **gated on the call being recorded**. Keeping a history of
|
|
113
|
+
* somebody's screen is a materially different promise from glancing at it once,
|
|
114
|
+
* and the recording is the thing that told them their call is being kept.
|
|
115
|
+
*/
|
|
116
|
+
export declare class KeyframeStore {
|
|
117
|
+
#private;
|
|
118
|
+
constructor(capacity?: number);
|
|
119
|
+
/** Keep this frame, if the call is being recorded. Returns whether it was kept. */
|
|
120
|
+
offer(frame: VideoFrame, recording: boolean): boolean;
|
|
121
|
+
/** Frames kept so far, oldest first. */
|
|
122
|
+
recent(source?: VideoSource): VideoFrame[];
|
|
123
|
+
/** Forget everything. Called on teardown. */
|
|
124
|
+
clear(): void;
|
|
125
|
+
get size(): number;
|
|
126
|
+
}
|
|
127
|
+
/** One beat of a walkthrough: something to say, optionally something to show. */
|
|
128
|
+
export interface WalkthroughStep {
|
|
129
|
+
/** The line spoken before the image appears. */
|
|
130
|
+
say: string;
|
|
131
|
+
image?: Buffer | string;
|
|
132
|
+
mime?: string;
|
|
133
|
+
caption?: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Says one line and resolves when the caller has heard it. Supplied by the
|
|
137
|
+
* plugin, because "finished speaking" is a thing only the provider knows.
|
|
138
|
+
*/
|
|
139
|
+
export type Speaker = (text: string) => Promise<void>;
|
|
140
|
+
/** Options for {@link VisionTools}. */
|
|
141
|
+
export interface VisionToolsOptions {
|
|
142
|
+
describer?: FrameDescriber;
|
|
143
|
+
budget?: VisionBudget;
|
|
144
|
+
keyframes?: KeyframeStore;
|
|
145
|
+
/**
|
|
146
|
+
* What to use when the model says nothing. Left out, no mode is sent at all,
|
|
147
|
+
* so the service's own default applies rather than one chosen here.
|
|
148
|
+
*/
|
|
149
|
+
defaultDisplayMode?: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The capabilities, bound to one call.
|
|
153
|
+
*
|
|
154
|
+
* Every method returns a sentence for a model to read out, including when it
|
|
155
|
+
* failed. None of them throw.
|
|
156
|
+
*/
|
|
157
|
+
export declare class VisionTools {
|
|
158
|
+
#private;
|
|
159
|
+
readonly budget: VisionBudget;
|
|
160
|
+
readonly keyframes: KeyframeStore;
|
|
161
|
+
constructor(session: CallSession, options?: VisionToolsOptions);
|
|
162
|
+
/**
|
|
163
|
+
* The picture the caller can see, if any.
|
|
164
|
+
*
|
|
165
|
+
* Recorded only after a send actually returned, so "send me that" attaches
|
|
166
|
+
* what they saw rather than what was attempted. One slot, replaced each time:
|
|
167
|
+
* a list would be a growing copy of everything shown on the call.
|
|
168
|
+
*/
|
|
169
|
+
get lastShown(): ShownImage | undefined;
|
|
170
|
+
/** The slideshow now running, if any. Await it to let one finish. */
|
|
171
|
+
get slideshow(): Promise<void> | undefined;
|
|
172
|
+
/** Forget what was shown and stop any slideshow. Call this on teardown. */
|
|
173
|
+
reset(): Promise<void>;
|
|
174
|
+
/**
|
|
175
|
+
* Answer a question about what the caller is showing.
|
|
176
|
+
*
|
|
177
|
+
* Uses the newest frame, preferring the screen share, because an agent asked
|
|
178
|
+
* to look is nearly always being asked about what is being shown rather than
|
|
179
|
+
* who is showing it.
|
|
180
|
+
*/
|
|
181
|
+
look(question?: string, source?: string): Promise<string>;
|
|
182
|
+
/**
|
|
183
|
+
* Answer about a frame the caller has already moved past.
|
|
184
|
+
*
|
|
185
|
+
* Only possible when the call is being recorded, because that is the only time
|
|
186
|
+
* frames are kept at all.
|
|
187
|
+
*/
|
|
188
|
+
lookBack(question?: string): Promise<string>;
|
|
189
|
+
/** Put an image on the bot's video tile. */
|
|
190
|
+
show(image: Buffer | string, mime?: string, caption?: string, durationMs?: number, display?: string, name?: string): Promise<string>;
|
|
191
|
+
/**
|
|
192
|
+
* Fetch an image the model chose, and show it.
|
|
193
|
+
*
|
|
194
|
+
* The URL comes from a model steered by whoever is on the call, so it goes
|
|
195
|
+
* through the SDK's guard: public hosts only, and the address re-checked at
|
|
196
|
+
* connect time.
|
|
197
|
+
*/
|
|
198
|
+
showUrl(url: string, caption?: string, display?: string): Promise<string>;
|
|
199
|
+
/**
|
|
200
|
+
* Put a web page on the tile, as a picture of it.
|
|
201
|
+
*
|
|
202
|
+
* The core has no browser and must never gain one. `render` is supplied by a
|
|
203
|
+
* plugin whose host already runs one, and it returns bytes rather than a
|
|
204
|
+
* path: reading a file chosen downstream of whoever is on the call is not a
|
|
205
|
+
* primitive this belongs in.
|
|
206
|
+
*
|
|
207
|
+
* The guard runs HERE, before the renderer is reached, and it runs even when
|
|
208
|
+
* that renderer is a browser advertising private-network protection of its
|
|
209
|
+
* own. Such a browser assumes whoever wrote the URL already has a shell on
|
|
210
|
+
* the machine. Here the URL was written by a model being steered by a
|
|
211
|
+
* stranger, which is exactly the case that relaxation lets through.
|
|
212
|
+
*/
|
|
213
|
+
showPage(url: string, caption?: string, render?: PageRenderer, timeoutMs?: number): Promise<string>;
|
|
214
|
+
/**
|
|
215
|
+
* Show several pictures in turn, without waiting for all of them.
|
|
216
|
+
*
|
|
217
|
+
* The FIRST one goes before this resolves, so the model can say "here it is"
|
|
218
|
+
* and be right. The rest are paced from a detached chain: a model that waits
|
|
219
|
+
* out a ten-picture slideshow before speaking leaves the caller in silence for
|
|
220
|
+
* most of a minute.
|
|
221
|
+
*
|
|
222
|
+
* Never throws. The sentence says what is on screen now and what follows.
|
|
223
|
+
*/
|
|
224
|
+
showMany(items: readonly ShowItem[], caption?: string, display?: string, holdMs?: number): Promise<string>;
|
|
225
|
+
/**
|
|
226
|
+
* Say and show several things in order, pausing for each.
|
|
227
|
+
*
|
|
228
|
+
* The pacing is here; the SPEAKING is not. Only the provider knows when a line
|
|
229
|
+
* has finished being said, so `speak` is supplied by the plugin and awaited
|
|
230
|
+
* before the next beat begins. Without that, a walkthrough talks over itself.
|
|
231
|
+
*
|
|
232
|
+
* `interrupted` is checked between beats. A caller who cuts in should stop the
|
|
233
|
+
* tour, and the plugin is the only thing that knows they did.
|
|
234
|
+
*/
|
|
235
|
+
walkthrough(steps: readonly WalkthroughStep[], speak: Speaker, interrupted?: () => boolean, display?: string): Promise<string>;
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=visionTools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visionTools.d.ts","sourceRoot":"","sources":["../src/visionTools.ts"],"names":[],"mappings":"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"}
|