@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,81 @@
|
|
|
1
|
+
import { OutboundPolicy, PendingMessages, type OutboundCaller } from "./outbound.js";
|
|
2
|
+
/** Which organisation a placed call belongs to. Operator configuration only. */
|
|
3
|
+
export declare const TENANT_ENV = "STANDIN_TENANT_ID";
|
|
4
|
+
/**
|
|
5
|
+
* Something that can say a line on a call that is already up.
|
|
6
|
+
*
|
|
7
|
+
* Implemented by the plugin, because only it knows how to make its provider
|
|
8
|
+
* speak without stepping on whatever the agent was already saying.
|
|
9
|
+
*/
|
|
10
|
+
export interface LiveSpeaker {
|
|
11
|
+
say(text: string): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Which calls are up, and how to speak into them.
|
|
15
|
+
*
|
|
16
|
+
* A call is filed under its own id and, when it has one, under the Microsoft
|
|
17
|
+
* Teams conversation it belongs to. Both, because a delivery addressed by
|
|
18
|
+
* conversation would otherwise miss a call that is only filed by call id, and
|
|
19
|
+
* the agent would place a second call to somebody already on the line with it.
|
|
20
|
+
*/
|
|
21
|
+
export declare class LiveCalls {
|
|
22
|
+
#private;
|
|
23
|
+
register(speaker: LiveSpeaker, callId: string, threadId?: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Remove this speaker's keys, and only this speaker's.
|
|
26
|
+
*
|
|
27
|
+
* The identity check is the point. A second call on the same thread can start
|
|
28
|
+
* before the first one's teardown runs, and a blind delete would then wipe
|
|
29
|
+
* the live call's entry: every later delivery for that thread would ring a
|
|
30
|
+
* fresh call, and the person would hear a second ring instead of an answer.
|
|
31
|
+
*/
|
|
32
|
+
unregister(speaker: LiveSpeaker, callId: string, threadId?: string): void;
|
|
33
|
+
/** The first live call any of these keys names. Conversation first. */
|
|
34
|
+
find(...keys: string[]): LiveSpeaker | undefined;
|
|
35
|
+
get size(): number;
|
|
36
|
+
}
|
|
37
|
+
/** What happened to one line of text. */
|
|
38
|
+
export interface Delivery {
|
|
39
|
+
readonly ok: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* `"live-call"` when it was spoken into a call that was already up, or
|
|
42
|
+
* `"call-back"` when a call was placed and the line parked for the answer.
|
|
43
|
+
* Both strings are part of this API.
|
|
44
|
+
*/
|
|
45
|
+
readonly mode?: "live-call" | "call-back";
|
|
46
|
+
/** The call that was placed, on the call-back path. */
|
|
47
|
+
readonly callId?: string;
|
|
48
|
+
/** One sentence, safe to read out loud. Absent when ok. */
|
|
49
|
+
readonly error?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Options for {@link VoiceDelivery}. */
|
|
52
|
+
export interface VoiceDeliveryOptions {
|
|
53
|
+
caller?: OutboundCaller;
|
|
54
|
+
policy?: OutboundPolicy;
|
|
55
|
+
pending?: PendingMessages;
|
|
56
|
+
/**
|
|
57
|
+
* The organisation to place calls into.
|
|
58
|
+
*
|
|
59
|
+
* Operator configuration only: this option, falling back to
|
|
60
|
+
* `STANDIN_TENANT_ID`. Never the message, the metadata, the model or the
|
|
61
|
+
* caller, because a model steered by whoever is talking must not be able to
|
|
62
|
+
* choose which organisation gets dialled.
|
|
63
|
+
*/
|
|
64
|
+
tenantId?: string;
|
|
65
|
+
/** Directory id recorded against a parked message, for the audit trail. */
|
|
66
|
+
requestedBy?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Speak into a live call if there is one, otherwise ring back.
|
|
70
|
+
*
|
|
71
|
+
* {@link deliver} never throws. Every refusal comes back as a sentence, because
|
|
72
|
+
* the thing reading the result is either a host that will mark the whole
|
|
73
|
+
* platform failed on an exception, or a model that will say it out loud.
|
|
74
|
+
*/
|
|
75
|
+
export declare class VoiceDelivery {
|
|
76
|
+
#private;
|
|
77
|
+
constructor(live: LiveCalls, options?: VoiceDeliveryOptions);
|
|
78
|
+
/** Say this line to that person, by whichever lane can reach them. */
|
|
79
|
+
deliver(text: string, target?: string, threadId?: string): Promise<Delivery>;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=delivery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery.d.ts","sourceRoot":"","sources":["../src/delivery.ts"],"names":[],"mappings":"AA2BA,OAAO,EAEL,cAAc,EACd,eAAe,EACf,KAAK,cAAc,EAEpB,MAAM,eAAe,CAAC;AAEvB,gFAAgF;AAChF,eAAO,MAAM,UAAU,sBAAsB,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,qBAAa,SAAS;;IAGpB,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAK,GAAG,IAAI;IAKnE;;;;;;;OAOG;IACH,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAK,GAAG,IAAI;IAOrE,uEAAuE;IACvE,IAAI,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,WAAW,GAAG,SAAS;IAQhD,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED,yCAAyC;AACzC,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,WAAW,CAAC;IAC1C,uDAAuD;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,qBAAa,aAAa;;gBAQZ,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,oBAAyB;IAS/D,sEAAsE;IAChE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAK,EAAE,QAAQ,SAAK,GAAG,OAAO,CAAC,QAAQ,CAAC;CAoG3E"}
|
package/dist/delivery.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* Getting a line of text to a person, whether or not they are already on a call.
|
|
5
|
+
*
|
|
6
|
+
* Something outside the call wants to reach somebody: a scheduled job, a chat
|
|
7
|
+
* message, a host handing off a task. There are two ways that can land, and
|
|
8
|
+
* which one is right depends entirely on whether a call to that person is up
|
|
9
|
+
* right now.
|
|
10
|
+
*
|
|
11
|
+
* If one is, the line should be spoken into it. Ringing somebody who is mid
|
|
12
|
+
* sentence with you is the rudest possible way to tell them something.
|
|
13
|
+
*
|
|
14
|
+
* If one is not, the call has to be placed, and the line parked so it is said
|
|
15
|
+
* the moment they answer. `outbound.ts` already has every piece of that: who
|
|
16
|
+
* may be rung, how often, and the durable parking. What it has never had is a
|
|
17
|
+
* way to reach a call that is ALREADY up, so every plugin that wanted this
|
|
18
|
+
* wrote its own registry, and the ones that exist disagree with each other
|
|
19
|
+
* about identity and about what to do when the live path fails.
|
|
20
|
+
*
|
|
21
|
+
* Nothing in this module imports a provider or a host.
|
|
22
|
+
*
|
|
23
|
+
* Identical in shape to the Python SDK's `standin.delivery`.
|
|
24
|
+
*/
|
|
25
|
+
import { logger } from "./log.js";
|
|
26
|
+
import { OutboundError, OutboundPolicy, PendingMessages, } from "./outbound.js";
|
|
27
|
+
/** Which organisation a placed call belongs to. Operator configuration only. */
|
|
28
|
+
export const TENANT_ENV = "STANDIN_TENANT_ID";
|
|
29
|
+
/**
|
|
30
|
+
* Which calls are up, and how to speak into them.
|
|
31
|
+
*
|
|
32
|
+
* A call is filed under its own id and, when it has one, under the Microsoft
|
|
33
|
+
* Teams conversation it belongs to. Both, because a delivery addressed by
|
|
34
|
+
* conversation would otherwise miss a call that is only filed by call id, and
|
|
35
|
+
* the agent would place a second call to somebody already on the line with it.
|
|
36
|
+
*/
|
|
37
|
+
export class LiveCalls {
|
|
38
|
+
#speakers = new Map();
|
|
39
|
+
register(speaker, callId, threadId = "") {
|
|
40
|
+
if (callId.trim())
|
|
41
|
+
this.#speakers.set(callId.trim(), speaker);
|
|
42
|
+
if (threadId.trim())
|
|
43
|
+
this.#speakers.set(threadId.trim(), speaker);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Remove this speaker's keys, and only this speaker's.
|
|
47
|
+
*
|
|
48
|
+
* The identity check is the point. A second call on the same thread can start
|
|
49
|
+
* before the first one's teardown runs, and a blind delete would then wipe
|
|
50
|
+
* the live call's entry: every later delivery for that thread would ring a
|
|
51
|
+
* fresh call, and the person would hear a second ring instead of an answer.
|
|
52
|
+
*/
|
|
53
|
+
unregister(speaker, callId, threadId = "") {
|
|
54
|
+
for (const key of [callId.trim(), threadId.trim()]) {
|
|
55
|
+
if (key && this.#speakers.get(key) === speaker)
|
|
56
|
+
this.#speakers.delete(key);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** The first live call any of these keys names. Conversation first. */
|
|
60
|
+
find(...keys) {
|
|
61
|
+
for (const key of keys) {
|
|
62
|
+
const found = key ? this.#speakers.get(key.trim()) : undefined;
|
|
63
|
+
if (found !== undefined)
|
|
64
|
+
return found;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
get size() {
|
|
69
|
+
return this.#speakers.size;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Speak into a live call if there is one, otherwise ring back.
|
|
74
|
+
*
|
|
75
|
+
* {@link deliver} never throws. Every refusal comes back as a sentence, because
|
|
76
|
+
* the thing reading the result is either a host that will mark the whole
|
|
77
|
+
* platform failed on an exception, or a model that will say it out loud.
|
|
78
|
+
*/
|
|
79
|
+
export class VoiceDelivery {
|
|
80
|
+
#live;
|
|
81
|
+
#caller;
|
|
82
|
+
#policy;
|
|
83
|
+
#pending;
|
|
84
|
+
#tenantId;
|
|
85
|
+
#requestedBy;
|
|
86
|
+
constructor(live, options = {}) {
|
|
87
|
+
this.#live = live;
|
|
88
|
+
this.#caller = options.caller;
|
|
89
|
+
this.#policy = options.policy ?? new OutboundPolicy({});
|
|
90
|
+
this.#pending = options.pending;
|
|
91
|
+
this.#tenantId = (options.tenantId ?? process.env[TENANT_ENV] ?? "").trim();
|
|
92
|
+
this.#requestedBy = options.requestedBy ?? "";
|
|
93
|
+
}
|
|
94
|
+
/** Say this line to that person, by whichever lane can reach them. */
|
|
95
|
+
async deliver(text, target = "", threadId = "") {
|
|
96
|
+
const line = (text ?? "").trim();
|
|
97
|
+
if (line === "") {
|
|
98
|
+
// Checked before the registry is even consulted: an empty message must
|
|
99
|
+
// never place a real phone call to a real person.
|
|
100
|
+
return { ok: false, error: "there was nothing to say" };
|
|
101
|
+
}
|
|
102
|
+
const speaker = this.#live.find(threadId, target);
|
|
103
|
+
if (speaker !== undefined) {
|
|
104
|
+
try {
|
|
105
|
+
await speaker.say(line);
|
|
106
|
+
return { ok: true, mode: "live-call" };
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
// Falls through to the call-back rather than stopping here. A wedged
|
|
110
|
+
// provider socket would otherwise swallow the message with nobody
|
|
111
|
+
// told. The trade is deliberate: a half-spoken line can be repeated by
|
|
112
|
+
// the call-back, and a repeat beats silence.
|
|
113
|
+
logger.warn(`standin: could not speak into the live call, ringing back instead: ${String(err)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return await this.#callBack(line, target, threadId);
|
|
117
|
+
}
|
|
118
|
+
async #callBack(line, target, threadId) {
|
|
119
|
+
if (this.#caller === undefined) {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
error: "no outbound caller is configured on this deployment",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const who = (target ?? "").trim();
|
|
126
|
+
try {
|
|
127
|
+
this.#policy.check(who);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
if (err instanceof OutboundError)
|
|
131
|
+
return { ok: false, error: err.message };
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
if (this.#tenantId === "") {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
error: `no tenant is configured: set ${TENANT_ENV} to place calls`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
let callId = "";
|
|
141
|
+
try {
|
|
142
|
+
const placed = await this.#caller.placeCall({
|
|
143
|
+
userObjectId: who,
|
|
144
|
+
tenantId: this.#tenantId,
|
|
145
|
+
});
|
|
146
|
+
callId = (placed.callId ?? "").trim();
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
return { ok: false, error: `could not place the call: ${String(err)}` };
|
|
150
|
+
}
|
|
151
|
+
if (callId === "")
|
|
152
|
+
return { ok: false, error: "the call was not placed" };
|
|
153
|
+
// Recorded only now. Counting the attempt instead would let a broken worker
|
|
154
|
+
// burn the hourly budget on calls that never rang anybody, and the next
|
|
155
|
+
// real delivery would be refused for an hour because of it.
|
|
156
|
+
this.#policy.record();
|
|
157
|
+
// Parked before this returns. The answering leg is a different call and can
|
|
158
|
+
// be answered before a later park would have run, and then the person picks
|
|
159
|
+
// up to silence.
|
|
160
|
+
const parked = {
|
|
161
|
+
callId,
|
|
162
|
+
text: line,
|
|
163
|
+
threadId: threadId.trim(),
|
|
164
|
+
requestedBy: this.#requestedBy,
|
|
165
|
+
tenantId: this.#tenantId,
|
|
166
|
+
target: who,
|
|
167
|
+
};
|
|
168
|
+
try {
|
|
169
|
+
// Guarded because parking writes to disk, and a full or read-only one
|
|
170
|
+
// would otherwise throw out of a method whose whole contract is that it
|
|
171
|
+
// does not. The call HAS been placed by now, so the honest answer names
|
|
172
|
+
// what was lost rather than pretending nothing rang.
|
|
173
|
+
this.#pending?.park(parked);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
logger.warn(`standin: the call was placed but the line was not parked: ${String(err)}`);
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
mode: "call-back",
|
|
180
|
+
callId,
|
|
181
|
+
error: "the call was placed, but what to say could not be saved",
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return { ok: true, mode: "call-back", callId };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=delivery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery.js","sourceRoot":"","sources":["../src/delivery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The half-duplex echo guard.
|
|
3
|
+
*
|
|
4
|
+
* A Microsoft Teams call is one acoustic loop. When the caller is on a speakerphone, the
|
|
5
|
+
* agent's own voice comes back up the caller leg loudly enough for a realtime
|
|
6
|
+
* model's server-side VAD to hear it as a turn - so the model answers itself,
|
|
7
|
+
* interrupts itself, and re-greets, with the caller silent the whole time. That
|
|
8
|
+
* is not a hypothetical: it is the failure this guard was written to stop.
|
|
9
|
+
*
|
|
10
|
+
* The fix is a time window rather than acoustic cancellation: while our own audio
|
|
11
|
+
* is still PLAYING OUT on the call, caller input below a loudness threshold is
|
|
12
|
+
* treated as our own echo and dropped, and anything above it is treated as a real
|
|
13
|
+
* barge-in and passed through.
|
|
14
|
+
*
|
|
15
|
+
* It lives in the plugin rather than the SDK core: the SDK owns the wire,
|
|
16
|
+
* not the room's acoustics.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* How long after our audio finishes playing the guard stays armed. Playout is not
|
|
20
|
+
* instantaneous and neither is the echo path.
|
|
21
|
+
*/
|
|
22
|
+
export declare const ECHO_SUPPRESSION_WINDOW_MS = 600;
|
|
23
|
+
/** Normalized RMS above which in-window caller input is a real barge-in, not echo. */
|
|
24
|
+
export declare const ECHO_BARGE_IN_RMS = 0.04;
|
|
25
|
+
import { pcm16Rms } from "./audio.js";
|
|
26
|
+
export { pcm16Rms };
|
|
27
|
+
/** Tunables, straight off the plugin's `realtime` config block. */
|
|
28
|
+
export interface EchoGuardOptions {
|
|
29
|
+
/** Set false to disable the guard entirely. */
|
|
30
|
+
suppressInputDuringPlayback?: boolean;
|
|
31
|
+
/** Window in ms; default {@link ECHO_SUPPRESSION_WINDOW_MS}. */
|
|
32
|
+
echoSuppressionWindowMs?: number;
|
|
33
|
+
/** Barge-in RMS gate 0..1; default {@link ECHO_BARGE_IN_RMS}. */
|
|
34
|
+
echoBargeInRms?: number;
|
|
35
|
+
/**
|
|
36
|
+
* False drops the loudness exception and suppresses ALL in-window input.
|
|
37
|
+
*
|
|
38
|
+
* Used until the caller's first real turn. On a speakerphone the opening
|
|
39
|
+
* greeting echoes back loud enough to clear the barge-in RMS, so allowing
|
|
40
|
+
* barge-in from the first frame is exactly what starts the echo loop. Once the
|
|
41
|
+
* caller has genuinely spoken once, normal RMS barge-in resumes for the rest of
|
|
42
|
+
* the call.
|
|
43
|
+
*/
|
|
44
|
+
allowBargeIn?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* True when this frame of caller audio should be withheld from the model.
|
|
48
|
+
*
|
|
49
|
+
* `playbackActiveUntil` is the estimated epoch-ms at which the audio we have
|
|
50
|
+
* already sent finishes PLAYING - not the time we last sent a chunk. A realtime
|
|
51
|
+
* model generates faster than realtime, so send-time and play-time diverge by
|
|
52
|
+
* seconds on a long answer, and a guard keyed on send-time disarms in the middle
|
|
53
|
+
* of the sentence it is supposed to cover.
|
|
54
|
+
*/
|
|
55
|
+
export declare function shouldSuppressEcho(pcm16k: Buffer, playbackActiveUntil: number, opts?: EchoGuardOptions): boolean;
|
|
56
|
+
//# sourceMappingURL=echoGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"echoGuard.d.ts","sourceRoot":"","sources":["../src/echoGuard.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;GAGG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAKtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,CAAC;AAEpB,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,gEAAgE;IAChE,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,mBAAmB,EAAE,MAAM,EAC3B,IAAI,CAAC,EAAE,gBAAgB,GACtB,OAAO,CAgBT"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* The half-duplex echo guard.
|
|
5
|
+
*
|
|
6
|
+
* A Microsoft Teams call is one acoustic loop. When the caller is on a speakerphone, the
|
|
7
|
+
* agent's own voice comes back up the caller leg loudly enough for a realtime
|
|
8
|
+
* model's server-side VAD to hear it as a turn - so the model answers itself,
|
|
9
|
+
* interrupts itself, and re-greets, with the caller silent the whole time. That
|
|
10
|
+
* is not a hypothetical: it is the failure this guard was written to stop.
|
|
11
|
+
*
|
|
12
|
+
* The fix is a time window rather than acoustic cancellation: while our own audio
|
|
13
|
+
* is still PLAYING OUT on the call, caller input below a loudness threshold is
|
|
14
|
+
* treated as our own echo and dropped, and anything above it is treated as a real
|
|
15
|
+
* barge-in and passed through.
|
|
16
|
+
*
|
|
17
|
+
* It lives in the plugin rather than the SDK core: the SDK owns the wire,
|
|
18
|
+
* not the room's acoustics.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* How long after our audio finishes playing the guard stays armed. Playout is not
|
|
22
|
+
* instantaneous and neither is the echo path.
|
|
23
|
+
*/
|
|
24
|
+
export const ECHO_SUPPRESSION_WINDOW_MS = 600;
|
|
25
|
+
/** Normalized RMS above which in-window caller input is a real barge-in, not echo. */
|
|
26
|
+
export const ECHO_BARGE_IN_RMS = 0.04;
|
|
27
|
+
// The one definition lives in the SDK's core audio module, because an echo
|
|
28
|
+
// guard, a barge-in check and a voice segmenter all want it. Imported for use
|
|
29
|
+
// below and re-exported so this module keeps its historical surface.
|
|
30
|
+
import { pcm16Rms } from "./audio.js";
|
|
31
|
+
export { pcm16Rms };
|
|
32
|
+
/**
|
|
33
|
+
* True when this frame of caller audio should be withheld from the model.
|
|
34
|
+
*
|
|
35
|
+
* `playbackActiveUntil` is the estimated epoch-ms at which the audio we have
|
|
36
|
+
* already sent finishes PLAYING - not the time we last sent a chunk. A realtime
|
|
37
|
+
* model generates faster than realtime, so send-time and play-time diverge by
|
|
38
|
+
* seconds on a long answer, and a guard keyed on send-time disarms in the middle
|
|
39
|
+
* of the sentence it is supposed to cover.
|
|
40
|
+
*/
|
|
41
|
+
export function shouldSuppressEcho(pcm16k, playbackActiveUntil, opts) {
|
|
42
|
+
if (opts?.suppressInputDuringPlayback === false) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const inPlaybackWindow = Date.now() <
|
|
46
|
+
playbackActiveUntil +
|
|
47
|
+
(opts?.echoSuppressionWindowMs ?? ECHO_SUPPRESSION_WINDOW_MS);
|
|
48
|
+
if (!inPlaybackWindow) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
// Before the caller's first real turn, every in-window frame is echo.
|
|
52
|
+
if (opts?.allowBargeIn === false) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return pcm16Rms(pcm16k) < (opts?.echoBargeInRms ?? ECHO_BARGE_IN_RMS);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=echoGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"echoGuard.js","sourceRoot":"","sources":["../src/echoGuard.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B;;;;;;;;;;;;;;;;GAgBG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAE9C,sFAAsF;AACtF,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAEtC,2EAA2E;AAC3E,8EAA8E;AAC9E,qEAAqE;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,CAAC;AAsBpB;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAc,EACd,mBAA2B,EAC3B,IAAuB;IAEvB,IAAI,IAAI,EAAE,2BAA2B,KAAK,KAAK,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,gBAAgB,GACpB,IAAI,CAAC,GAAG,EAAE;QACV,mBAAmB;YACjB,CAAC,IAAI,EAAE,uBAAuB,IAAI,0BAA0B,CAAC,CAAC;IAClE,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,sEAAsE;IACtE,IAAI,IAAI,EAAE,YAAY,KAAK,KAAK,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,cAAc,IAAI,iBAAiB,CAAC,CAAC;AACxE,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA,4EAA4E;AAC5E,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/** Every error this SDK raises. Mirrors the Python SDK's `StandInError`. */
|
|
4
|
+
export class StandInError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "StandInError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B,4EAA4E;AAC5E,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF"}
|
package/dist/fetch.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Resolves a hostname to addresses. Replaceable so tests can drive a rebind. */
|
|
2
|
+
export type LookupFn = (hostname: string, opts: {
|
|
3
|
+
all: true;
|
|
4
|
+
verbatim: true;
|
|
5
|
+
}) => Promise<Array<{
|
|
6
|
+
address: string;
|
|
7
|
+
family: number;
|
|
8
|
+
}>>;
|
|
9
|
+
/** Decides whether an address may be fetched. Replaceable for tests. */
|
|
10
|
+
export type ForbiddenIpFn = (ip: string) => boolean;
|
|
11
|
+
/** True for any IPv4 address that must never be fetched server-side. */
|
|
12
|
+
export declare function isForbiddenIpv4(ip: string): boolean;
|
|
13
|
+
/** True for any IPv6 address that must never be fetched server-side. */
|
|
14
|
+
export declare function isForbiddenIpv6(ip: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* True for any address that must never be fetched server-side.
|
|
17
|
+
*
|
|
18
|
+
* Unparseable input is forbidden too: something that is not an address cannot be
|
|
19
|
+
* proven safe, and failing closed is the only correct direction here.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isForbiddenIp(ip: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Check a URL is safe to fetch, or throw saying why.
|
|
24
|
+
*
|
|
25
|
+
* http and https only, no embedded credentials, and every address the host
|
|
26
|
+
* resolves to must be public.
|
|
27
|
+
*/
|
|
28
|
+
export declare function assertPublicHttpUrl(raw: string, lookupFn?: LookupFn, isForbiddenFn?: ForbiddenIpFn): Promise<URL>;
|
|
29
|
+
/**
|
|
30
|
+
* Fetch an image from an untrusted URL.
|
|
31
|
+
*
|
|
32
|
+
* Bounded in every direction that matters: total time, declared length, streamed
|
|
33
|
+
* length, and one redirect hop. Throws for anything that fails, so a caller can
|
|
34
|
+
* hand the reason straight back to the agent that supplied the URL.
|
|
35
|
+
*/
|
|
36
|
+
export declare function fetchPublicImage(rawUrl: string, maxBytes: number, timeoutMs?: number, lookupFn?: LookupFn, isForbiddenFn?: ForbiddenIpFn, redirectsLeft?: number): Promise<{
|
|
37
|
+
bytes: Buffer;
|
|
38
|
+
mime: string;
|
|
39
|
+
}>;
|
|
40
|
+
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAiCA,iFAAiF;AACjF,MAAM,MAAM,QAAQ,GAAG,CACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,GAAG,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,KAChC,OAAO,CAAC,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC;AAEzD,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;AAepD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAcnD;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAsBnD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAKjD;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,QAA6B,EACvC,aAAa,GAAE,aAA6B,GAC3C,OAAO,CAAC,GAAG,CAAC,CAoCd;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAS,EAClB,QAAQ,GAAE,QAA6B,EACvC,aAAa,GAAE,aAA6B,EAC5C,aAAa,SAAI,GAChB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA6I1C"}
|