Media and local execution types
Complete public signatures of the Monky bot SDK, generated from the TypeScript entry point of @monky/bot-sdk. Optional fields use ?; readonly fields must not be mutated. The source links include validation rules and implementation details.
Bot SDK overview and events · Voice · Miniapps · Storage · Local execution
BotDeviceStorageStatus
ts
export type BotDeviceStorageStatus = "granted" | "unavailable" | "denied";BotScreen
ts
export type BotScreen = {
title: string;
id: string;
channelId: string;
botId: string;
createdAt: number;
revision: number;
state: BotScreenJson;
html: string;
instanceId: string;
creatorUserId?: string | undefined;
};BotScreenActionEvent
ts
export type BotScreenActionEvent = {
channelId: string;
revision: number;
userId: string;
userNickname: string;
instanceId: string;
action: string;
payload: BotScreenJson;
actionId: string;
screenId: string;
};BotScreenBeforeCloseEvent
ts
export interface BotScreenBeforeCloseEvent {
readonly reason: BotScreenBeforeCloseReason;
}BotScreenBeforeCloseReason
ts
export type BotScreenBeforeCloseReason = "leave" | "end";BotScreenBridge
ts
export interface BotScreenBridge {
readonly viewer: BotScreenViewer;
/** Delivers the current state at once and every update; returns an unsubscribe function. */
onState(callback: (state: BotScreenJson, revision: number) => void): () => void;
/** Whether the bridge accepted the intent, not whether the bot accepted the action. */
sendAction(action: string, payload: BotScreenJson): boolean;
/**
* Runs, in registration order, before a close this person asked for; the view stays visible
* and may ask anything. Resolving `false` keeps it open. Revocations never wait for it.
*/
onBeforeClose(handler: (event: BotScreenBeforeCloseEvent) => boolean | void | Promise<boolean | void>): () => void;
/**
* Runs on every close, while the view is already hidden, muted and cut from state and actions.
* The document is kept for at most 5 seconds so the handlers can finish, e.g. saving; `storage`
* keeps working meanwhile.
*/
onClose(handler: (event: BotScreenCloseEvent) => void | Promise<void>): () => void;
/**
* Runs at once with whether the person can see the view, then on every change. The view stays
* open but hidden while they browse elsewhere in Monky, scroll it off the stage or minimize the
* window. Not called once the view is closing.
*/
onVisibilityChange(handler: (visible: boolean) => void): () => void;
/**
* Data kept on this computer, when the bot has `device_storage` and the person allows it.
* Missing in clients released before bot storage.
*/
readonly storage?: BotScreenStorage;
}BotScreenClient
ts
export class BotScreenClient {
constructor(adapter: BotScreenAdapter);
createScreen(serverId: string, input: BotScreenCreate): Promise<BotScreen>;
updateScreen(serverId: string, ref: BotScreenRef, patch: BotScreenPatch): Promise<BotScreen>;
closeScreen(serverId: string, ref: BotScreenRef): Promise<void>;
listScreens(serverId: string, channelId: string): Promise<BotScreen[]>;
handle(serverId: string, type: string, payload: unknown): boolean;
}BotScreenCloseEvent
ts
export interface BotScreenCloseEvent {
readonly reason: BotScreenCloseReason;
}BotScreenCloseReason
ts
export type BotScreenCloseReason = "ended" | BotScreenBeforeCloseReason | "revoked";BotScreenCreate
ts
export type BotScreenCreate = {
title: string;
channelId: string;
state: BotScreenJson;
html: string;
id?: string | undefined;
invocationId?: string | undefined;
};BotScreenJson
ts
export type BotScreenJson = string | number | boolean | BotScreenJson[] | {
[key: string]: BotScreenJson;
} | null;BotScreenPatch
ts
export type BotScreenPatch = {
expectedRevision: number;
state: BotScreenJson;
};BotScreenRef
ts
export type BotScreenRef = {
id: string;
instanceId: string;
};BotScreenRemoved
ts
export type BotScreenRemoved = {
id: string;
channelId: string;
reason: "ended";
instanceId: string;
endedByUserId: string;
} | {
id: string;
channelId: string;
reason: "closed";
instanceId: string;
} | {
id: string;
channelId: string;
reason: "access_revoked";
instanceId: string;
} | {
id: string;
channelId: string;
reason: "bot_disconnected";
instanceId: string;
} | {
id: string;
channelId: string;
reason: "view_revoked";
instanceId: string;
};BotScreenStorage
ts
export interface BotScreenStorage {
/** `unavailable` until Monky connects the document, and whenever nothing may be stored. */
readonly status: BotDeviceStorageStatus;
/** Calls back at once with the current status and on every change; returns an unsubscribe function. */
onStatus(callback: (status: BotDeviceStorageStatus) => void): () => void;
get(key: string): Promise<string | ArrayBuffer | null>;
/** With `{ autosave: true }`, a save that cannot happen resolves `false` instead of rejecting. */
set(key: string, value: string | ArrayBuffer | ArrayBufferView | Blob, options?: {
autosave?: boolean;
}): Promise<boolean>;
/** Resolves whether the key existed. */
delete(key: string): Promise<boolean>;
/** In key order. */
list(options?: {
prefix?: string;
}): Promise<BotScreenStorageEntry[]>;
usage(): Promise<BotScreenStorageUsage | null>;
}BotScreenStorageEntry
ts
export interface BotScreenStorageEntry {
readonly key: string;
readonly size: number;
/** Milliseconds since the epoch. */
readonly updatedAt: number;
}BotScreenStorageUsage
ts
export interface BotScreenStorageUsage {
readonly usedBytes: number;
readonly maxBytes: number;
readonly level: BotStorageLevel;
}BotScreenViewer
ts
export interface BotScreenViewer {
readonly id: string;
readonly nickname: string;
readonly locale: BotLocale;
}BotVoiceAudioReceiver
ts
export interface BotVoiceAudioReceiver {
readonly droppedPackets: number;
return: () => Promise<IteratorResult<BotVoicePacket, undefined>>;
throw: (error: unknown) => Promise<IteratorResult<BotVoicePacket, undefined>>;
[Symbol.asyncIterator]: () => AsyncIterableIterator<BotVoicePacket, undefined, undefined>;
next: (...[value]: [
] | [
undefined
]) => Promise<IteratorResult<BotVoicePacket, undefined>>;
}BotVoiceConnection
ts
export class BotVoiceConnection {
constructor(channelId: string, auth: BotVoiceAuth, callbacks: VoiceCallbacks, publishAudio?: boolean);
readonly channelId: string;
get humanParticipantCount(): number;
get isClosed(): boolean;
get isReceivingAudio(): boolean;
get receivesAudio(): boolean;
join(options?: BotVoiceJoinOptions): Promise<void>;
receiveAudio(argument0?: {
signal?: AbortSignal;
}): BotVoiceAudioReceiver;
setMuted(muted: boolean): Promise<void>;
setDeafened(deafened: boolean): Promise<void>;
handle(message: VoiceMessage): boolean;
writeOpus(frame: Uint8Array): Promise<void>;
stopSpeaking(): void;
close(): Promise<void>;
disconnect(reason: string): Promise<void>;
}BotVoiceJoinOptions
ts
export type BotVoiceJoinOptions = {
invocationId?: string | undefined;
receiveAudio?: boolean | undefined;
};BotVoicePacket
ts
export interface BotVoicePacket {
channelId: string;
userId: string;
sessionId: string;
codec: 'opus';
clockRate: 48000;
channels: 2;
/** Raw Opus, not PCM or an Ogg container. Owned by this packet's caller. */
opus: Uint8Array;
sequenceNumber: number;
timestamp: number;
ssrc: number;
/** Monotonic milliseconds in this process, not wall time or RTP time. */
receivedAt: number;
}LocalCapabilityId
ts
export type LocalCapabilityId = "youtube-audio";LocalExecutionClient
ts
export interface LocalExecutionClient {
/**
* Whether the connected server negotiated this operation (false while disconnected).
* The invoker's client is checked per task and rejects with FEATURE_REQUIRES_UPDATE.
*/
supports(operation: LocalOperation): boolean;
/** Interaction contexts capture their original lifetime; source tasks acquire a fresh bot connection. */
executor(context: LocalRequestContext): LocalExecutor;
/** Retained metadata outlives this invocation/signal, but never authorizes a replacement human socket. */
retainSource(invocationId: string, url: string, options?: LocalExecutionTaskOptions): Promise<LocalSourceContext>;
releaseSource(sourceContextId: string): Promise<void>;
/**
* Check the original physical executor's current voice/access without starting
* client work. Rejects with LocalExecutionRpcError when unavailable; a later
* stream must still pass fresh admission and consent.
*/
checkSourceAvailability(sourceContextId: string, voiceChannelId: string, options?: LocalExecutionTaskOptions): Promise<void>;
}LocalExecutionError
ts
export class LocalExecutionError extends Error {
readonly event: Readonly<LocalExecutionTerminalEvent>;
constructor(event: LocalExecutionTerminalEvent);
}LocalExecutionProvider
ts
export interface LocalExecutionProvider {
localExecution(serverId: string): LocalExecutionClient;
}LocalExecutionRpcError
ts
export class LocalExecutionRpcError extends Error {
readonly reason?: LocalTaskFailureReason;
readonly cancellationCause?: LocalTaskCancellationCause;
constructor(code: ProtocolErrorCode, message: string);
readonly code: ProtocolErrorCode;
}LocalExecutionTaskOptions
ts
export interface LocalExecutionTaskOptions {
readonly signal?: AbortSignal;
}LocalExecutionTerminalEvent
ts
export type LocalExecutionTerminalEvent = {
taskId: string;
state: "failed";
reason: "timeout" | "busy" | "invalid_request" | "permission_denied" | "executor_unavailable" | "unsupported_platform" | "tools_missing" | "tool_install_failed" | "integrity_failed" | "storage_failed" | "provider_unavailable" | "transport_failed" | "worker_failed";
sourceFailure?: {
code: "input" | "unsupported" | "tools" | "runtime" | "unavailable" | "timeout" | "busy";
} | {
code: "recovery_failed";
attempts: number;
} | undefined;
} | {
taskId: string;
state: "cancelled";
cause: "requested" | "requester_left_voice" | "requester_disconnected" | "bot_left_voice" | "bot_disconnected" | "permission_revoked" | "source_released" | "voice_mode_changed" | "expired" | "server_shutdown";
};LocalExecutor
ts
export interface LocalExecutor {
execute(spec: Extract<LocalTaskSpec, {
operation: 'youtube.search';
}>, options?: LocalExecutionTaskOptions): Promise<Extract<LocalWireTaskResult, {
operation: 'youtube.search';
}>>;
execute(spec: Extract<LocalTaskSpec, {
operation: 'youtube.resolve';
}>, options?: LocalExecutionTaskOptions): Promise<Extract<LocalWireTaskResult, {
operation: 'youtube.resolve';
}>>;
execute(spec: Extract<LocalTaskSpec, {
operation: 'youtube.preview';
}>, options?: LocalExecutionTaskOptions): Promise<LocalWirePreviewResult>;
/**
* Bounded flat metadata (`spec.limit` ≤ 500); each track still needs its own resolve/stream.
* Rejects with FEATURE_REQUIRES_UPDATE: reason `invalid_request` when the server lacks the
* operation, `executor_unavailable` when the invoker's client does.
*/
execute(spec: Extract<LocalTaskSpec, {
operation: 'youtube.playlist';
}>, options?: LocalExecutionTaskOptions): Promise<Extract<LocalWireTaskResult, {
operation: 'youtube.playlist';
}>>;
execute(spec: LocalMetadataTaskSpec, options?: LocalExecutionTaskOptions): Promise<LocalMetadataTaskResult>;
/** Resolve only after the private peer and reliable data channel are ready. */
stream(spec: Extract<LocalTaskSpec, {
operation: 'youtube.stream';
}>, options: LocalStreamOptions): Promise<LocalOpusStream>;
}LocalMediaTrack
ts
export type LocalMediaTrack = {
title: string;
url: string;
id: string;
duration: number;
};LocalMetadataTaskResult
ts
export type LocalMetadataTaskResult = {
title: string | null;
operation: "youtube.playlist";
total: number | null;
tracks: {
title: string;
url: string;
id: string;
duration: number;
}[];
skipped: number;
} | {
operation: "youtube.search";
tracks: {
title: string;
url: string;
id: string;
duration: number;
}[];
} | {
operation: "youtube.resolve";
track: {
title: string;
url: string;
id: string;
duration: number;
};
} | {
operation: "youtube.preview";
localPreviewId: string;
taskId: string;
requestId: string;
executorSessionId: string;
};LocalMetadataTaskSpec
ts
export type LocalMetadataTaskSpec = {
operation: "youtube.search";
query: string;
} | {
url: string;
operation: "youtube.resolve";
} | {
url: string;
operation: "youtube.preview";
} | {
url: string;
operation: "youtube.playlist";
limit: number;
};LocalOperation
ts
export type LocalOperation = "youtube.search" | "youtube.resolve" | "youtube.preview" | "youtube.stream" | "youtube.playlist";LocalOpusStream
ts
export interface LocalOpusStream {
/** Server wire task ID, never the executor's Main-local task UUID. */
readonly taskId: string;
readonly track: LocalMediaTrack;
readonly frames: AsyncIterable<Uint8Array>;
/** Aborted with LocalExecutionError on failure/cancellation, not normal EOF/drain. */
readonly signal: AbortSignal;
/** Rejects immediately on failure/cancellation; resolves after playback drain and server completion. */
readonly closed: Promise<void>;
/** Advance once after playback consumes a frame, never when buffering it. */
markFrameAdvanced(): void;
/** Await the matching revision; opposite requests supersede/reject older ones. An 8s timeout fails the task. */
setPaused(paused: boolean): Promise<void>;
/**
* Cancel active work and await teardown, or await server completion after playback
* has drained. Cancellation remains observable through signal/closed.
* Does not release a retained source.
*/
close(): Promise<void>;
}LocalPlaylistResult
ts
export type LocalPlaylistResult = {
title: string | null;
operation: "youtube.playlist";
total: number | null;
tracks: {
title: string;
url: string;
id: string;
duration: number;
}[];
skipped: number;
};LocalPreviewReference
ts
export type LocalPreviewReference = {
localPreviewId: string;
taskId: string;
requestId: string;
executorSessionId: string;
};LocalRequestContext
ts
export type LocalRequestContext = {
kind: "invocation";
invocationId: string;
} | {
kind: "autocomplete";
requestId: string;
} | {
kind: "audio-preview";
requestId: string;
} | {
kind: "source";
sourceContextId: string;
};LocalSourceContext
ts
export type LocalSourceContext = {
url: string;
expiresAt: number;
botId: string;
sourceContextId: string;
botPublicKey: string;
invokerId: string;
invokerSessionId: string;
originChannelId: string;
capability: "youtube-audio";
provider: "youtube-local";
};LocalSourceFailure
ts
export type LocalSourceFailure = {
code: "input" | "unsupported" | "tools" | "runtime" | "unavailable" | "timeout" | "busy";
} | {
code: "recovery_failed";
attempts: number;
};LocalStreamOptions
ts
export interface LocalStreamOptions {
readonly voiceChannelId: string;
readonly signal?: AbortSignal | undefined;
}LocalTaskCancellationCause
ts
export type LocalTaskCancellationCause = "requested" | "requester_left_voice" | "requester_disconnected" | "bot_left_voice" | "bot_disconnected" | "permission_revoked" | "source_released" | "voice_mode_changed" | "expired" | "server_shutdown";LocalTaskEvent
ts
export type LocalTaskEvent = {
taskId: string;
state: "accepted";
result: {
title: string | null;
operation: "youtube.playlist";
total: number | null;
tracks: {
title: string;
url: string;
id: string;
duration: number;
}[];
skipped: number;
} | {
operation: "youtube.search";
tracks: {
title: string;
url: string;
id: string;
duration: number;
}[];
} | {
operation: "youtube.resolve";
track: {
title: string;
url: string;
id: string;
duration: number;
};
} | {
operation: "youtube.preview";
localPreviewId: string;
taskId: string;
requestId: string;
executorSessionId: string;
} | {
operation: "youtube.stream";
track: {
title: string;
url: string;
id: string;
duration: number;
};
};
media?: {
protocol: "opus-datachannel-v1";
generation: number;
iceServers: {
urls: string[];
username?: string | undefined;
credential?: string | undefined;
}[];
} | undefined;
} | {
taskId: string;
state: "ready";
mediaGeneration: number;
} | {
revision: number;
taskId: string;
state: "paused";
} | {
revision: number;
taskId: string;
state: "resumed";
} | {
taskId: string;
state: "completed";
mediaGeneration?: number | undefined;
playedFrames?: number | undefined;
} | {
taskId: string;
state: "failed";
reason: "timeout" | "busy" | "invalid_request" | "permission_denied" | "executor_unavailable" | "unsupported_platform" | "tools_missing" | "tool_install_failed" | "integrity_failed" | "storage_failed" | "provider_unavailable" | "transport_failed" | "worker_failed";
sourceFailure?: {
code: "input" | "unsupported" | "tools" | "runtime" | "unavailable" | "timeout" | "busy";
} | {
code: "recovery_failed";
attempts: number;
} | undefined;
} | {
taskId: string;
state: "cancelled";
cause: "requested" | "requester_left_voice" | "requester_disconnected" | "bot_left_voice" | "bot_disconnected" | "permission_revoked" | "source_released" | "voice_mode_changed" | "expired" | "server_shutdown";
};LocalTaskFailureReason
ts
export type LocalTaskFailureReason = "timeout" | "busy" | "invalid_request" | "permission_denied" | "executor_unavailable" | "unsupported_platform" | "tools_missing" | "tool_install_failed" | "integrity_failed" | "storage_failed" | "provider_unavailable" | "transport_failed" | "worker_failed";LocalTaskSpec
ts
export type LocalTaskSpec = {
operation: "youtube.search";
query: string;
} | {
url: string;
operation: "youtube.resolve";
} | {
url: string;
operation: "youtube.preview";
} | {
url: string;
operation: "youtube.stream";
} | {
url: string;
operation: "youtube.playlist";
limit: number;
};LocalWirePreviewResult
ts
export type LocalWirePreviewResult = {
operation: "youtube.preview";
localPreviewId: string;
taskId: string;
requestId: string;
executorSessionId: string;
};LocalWireTaskResult
ts
export type LocalWireTaskResult = {
title: string | null;
operation: "youtube.playlist";
total: number | null;
tracks: {
title: string;
url: string;
id: string;
duration: number;
}[];
skipped: number;
} | {
operation: "youtube.search";
tracks: {
title: string;
url: string;
id: string;
duration: number;
}[];
} | {
operation: "youtube.resolve";
track: {
title: string;
url: string;
id: string;
duration: number;
};
} | {
operation: "youtube.preview";
localPreviewId: string;
taskId: string;
requestId: string;
executorSessionId: string;
} | {
operation: "youtube.stream";
track: {
title: string;
url: string;
id: string;
duration: number;
};
};