@memberjunction/realtime-runtime 6.2.0-edge.3 → 6.2.0-edge.5
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/README.md +75 -0
- package/dist/channels/base-realtime-channel-client.d.ts +535 -28
- package/dist/channels/base-realtime-channel-client.d.ts.map +1 -1
- package/dist/channels/base-realtime-channel-client.js +864 -10
- package/dist/channels/base-realtime-channel-client.js.map +1 -1
- package/dist/channels/channel-action-dispatcher.d.ts +70 -0
- package/dist/channels/channel-action-dispatcher.d.ts.map +1 -0
- package/dist/channels/channel-action-dispatcher.js +136 -0
- package/dist/channels/channel-action-dispatcher.js.map +1 -0
- package/dist/channels/channel-catalog-note.d.ts +48 -0
- package/dist/channels/channel-catalog-note.d.ts.map +1 -0
- package/dist/channels/channel-catalog-note.js +119 -0
- package/dist/channels/channel-catalog-note.js.map +1 -0
- package/dist/channels/channel-contract-types.d.ts +107 -0
- package/dist/channels/channel-contract-types.d.ts.map +1 -0
- package/dist/channels/channel-contract-types.js +12 -0
- package/dist/channels/channel-contract-types.js.map +1 -0
- package/dist/channels/channel-descriptor-synthesis.d.ts +56 -0
- package/dist/channels/channel-descriptor-synthesis.d.ts.map +1 -0
- package/dist/channels/channel-descriptor-synthesis.js +90 -0
- package/dist/channels/channel-descriptor-synthesis.js.map +1 -0
- package/dist/channels/channel-perception.d.ts +82 -0
- package/dist/channels/channel-perception.d.ts.map +1 -0
- package/dist/channels/channel-perception.js +120 -0
- package/dist/channels/channel-perception.js.map +1 -0
- package/dist/channels/channel-schema-format.d.ts +17 -0
- package/dist/channels/channel-schema-format.d.ts.map +1 -0
- package/dist/channels/channel-schema-format.js +45 -0
- package/dist/channels/channel-schema-format.js.map +1 -0
- package/dist/channels/channel-state-delta.d.ts +59 -0
- package/dist/channels/channel-state-delta.d.ts.map +1 -0
- package/dist/channels/channel-state-delta.js +116 -0
- package/dist/channels/channel-state-delta.js.map +1 -0
- package/dist/channels/channel-surface-placement.d.ts +25 -0
- package/dist/channels/channel-surface-placement.d.ts.map +1 -0
- package/dist/channels/channel-surface-placement.js +29 -0
- package/dist/channels/channel-surface-placement.js.map +1 -0
- package/dist/channels/channel-visual-pump.d.ts +126 -0
- package/dist/channels/channel-visual-pump.d.ts.map +1 -0
- package/dist/channels/channel-visual-pump.js +174 -0
- package/dist/channels/channel-visual-pump.js.map +1 -0
- package/dist/hosts/IRealtimeMediaHost.d.ts +86 -34
- package/dist/hosts/IRealtimeMediaHost.d.ts.map +1 -1
- package/dist/hosts/IRealtimeMediaHost.js +2 -1
- package/dist/hosts/IRealtimeMediaHost.js.map +1 -1
- package/dist/index.d.ts +26 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -1
- package/dist/session/RealtimeSessionRuntime.d.ts +984 -111
- package/dist/session/RealtimeSessionRuntime.d.ts.map +1 -1
- package/dist/session/RealtimeSessionRuntime.js +2396 -381
- package/dist/session/RealtimeSessionRuntime.js.map +1 -1
- package/dist/session/RecordingShardDrain.d.ts +59 -0
- package/dist/session/RecordingShardDrain.d.ts.map +1 -0
- package/dist/session/RecordingShardDrain.js +135 -0
- package/dist/session/RecordingShardDrain.js.map +1 -0
- package/dist/session/RecordingShardQueue.d.ts +94 -0
- package/dist/session/RecordingShardQueue.d.ts.map +1 -0
- package/dist/session/RecordingShardQueue.js +176 -0
- package/dist/session/RecordingShardQueue.js.map +1 -0
- package/dist/session/RecordingShardStore.d.ts +57 -0
- package/dist/session/RecordingShardStore.d.ts.map +1 -0
- package/dist/session/RecordingShardStore.js +184 -0
- package/dist/session/RecordingShardStore.js.map +1 -0
- package/dist/session/RecordingShardWindow.d.ts +42 -0
- package/dist/session/RecordingShardWindow.d.ts.map +1 -0
- package/dist/session/RecordingShardWindow.js +88 -0
- package/dist/session/RecordingShardWindow.js.map +1 -0
- package/dist/session/RecordingTransport.d.ts +66 -0
- package/dist/session/RecordingTransport.d.ts.map +1 -0
- package/dist/session/RecordingTransport.js +61 -0
- package/dist/session/RecordingTransport.js.map +1 -0
- package/dist/session/app-client-tool-registry.d.ts +76 -0
- package/dist/session/app-client-tool-registry.d.ts.map +1 -0
- package/dist/session/app-client-tool-registry.js +100 -0
- package/dist/session/app-client-tool-registry.js.map +1 -0
- package/dist/session/avatar-notice.d.ts +43 -0
- package/dist/session/avatar-notice.d.ts.map +1 -0
- package/dist/session/avatar-notice.js +60 -0
- package/dist/session/avatar-notice.js.map +1 -0
- package/dist/session/call-end.d.ts +21 -0
- package/dist/session/call-end.d.ts.map +1 -0
- package/dist/session/call-end.js +19 -0
- package/dist/session/call-end.js.map +1 -0
- package/dist/session/channel-exposure-preferences.d.ts +70 -0
- package/dist/session/channel-exposure-preferences.d.ts.map +1 -0
- package/dist/session/channel-exposure-preferences.js +138 -0
- package/dist/session/channel-exposure-preferences.js.map +1 -0
- package/dist/session/channel-session-scope.d.ts +142 -0
- package/dist/session/channel-session-scope.d.ts.map +1 -0
- package/dist/session/channel-session-scope.js +128 -0
- package/dist/session/channel-session-scope.js.map +1 -0
- package/dist/session/client-session-deadline.d.ts +28 -0
- package/dist/session/client-session-deadline.d.ts.map +1 -0
- package/dist/session/client-session-deadline.js +50 -0
- package/dist/session/client-session-deadline.js.map +1 -0
- package/dist/session/realtime-captures.d.ts +236 -0
- package/dist/session/realtime-captures.d.ts.map +1 -0
- package/dist/session/realtime-captures.js +383 -0
- package/dist/session/realtime-captures.js.map +1 -0
- package/dist/session/realtime-microphone.d.ts +50 -0
- package/dist/session/realtime-microphone.d.ts.map +1 -0
- package/dist/session/realtime-microphone.js +51 -0
- package/dist/session/realtime-microphone.js.map +1 -0
- package/dist/session/session-event-hub.d.ts +72 -0
- package/dist/session/session-event-hub.d.ts.map +1 -0
- package/dist/session/session-event-hub.js +151 -0
- package/dist/session/session-event-hub.js.map +1 -0
- package/dist/session/session-launcher.d.ts +128 -0
- package/dist/session/session-launcher.d.ts.map +1 -0
- package/dist/session/session-launcher.js +160 -0
- package/dist/session/session-launcher.js.map +1 -0
- package/package.json +10 -9
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Cuts a call's continuous Opus packet stream into indexed `MJOPUS1` shards.
|
|
3
|
+
*
|
|
4
|
+
* Shard boundaries are decided here, once, and never revisited: an index is only ever sent with
|
|
5
|
+
* one set of bytes (the server treats a differing resend as a conflict). That is why a tail that is
|
|
6
|
+
* sent early (page hidden) is *sealed* — later packets start a new shard at the next index.
|
|
7
|
+
*
|
|
8
|
+
* @module @memberjunction/realtime-runtime
|
|
9
|
+
*/
|
|
10
|
+
import { EncodeRecordingShard, RECORDING_FRAME_SAMPLES, RECORDING_PACKET_LENGTH_PREFIX_BYTES, RECORDING_SHARD_HEADER_BYTES, RECORDING_SHARD_MAX_PACKETS, RECORDING_TERMINAL_MAX_BYTES, } from '@memberjunction/ai-core-plus';
|
|
11
|
+
/**
|
|
12
|
+
* Accumulates Opus packets and seals them into shards. Pure: no timers, no I/O.
|
|
13
|
+
*
|
|
14
|
+
* A window seals when it reaches {@link RECORDING_SHARD_MAX_PACKETS} packets (two 5 s windows), or
|
|
15
|
+
* just BEFORE a packet that would push the encoded shard past {@link RECORDING_TERMINAL_MAX_BYTES}.
|
|
16
|
+
* The runtime's 5 s tick seals whatever is pending, so a live call still cuts one ~5 s shard per tick;
|
|
17
|
+
* the packet cap only splits a backlog (a throttled tab's batch). The byte cap guarantees that
|
|
18
|
+
* whatever tail is pending always fits the terminal request body, even after a VBR spike.
|
|
19
|
+
*/
|
|
20
|
+
export class RecordingShardWindow {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.pending = [];
|
|
23
|
+
this.pendingBytes = RECORDING_SHARD_HEADER_BYTES;
|
|
24
|
+
this.nextIndex = 0;
|
|
25
|
+
this.sealedSamples = 0;
|
|
26
|
+
}
|
|
27
|
+
/** Index of the last sealed shard; -1 before any seal. */
|
|
28
|
+
get LastSealedIndex() {
|
|
29
|
+
return this.nextIndex - 1;
|
|
30
|
+
}
|
|
31
|
+
/** End sample of the last sealed shard. */
|
|
32
|
+
get SealedSamples() {
|
|
33
|
+
return this.sealedSamples;
|
|
34
|
+
}
|
|
35
|
+
/** End sample of everything appended so far, sealed or pending. */
|
|
36
|
+
get PendingSamples() {
|
|
37
|
+
return this.sealedSamples + this.pending.length * RECORDING_FRAME_SAMPLES;
|
|
38
|
+
}
|
|
39
|
+
/** Adds packets in capture order and returns every shard that sealed as a result. */
|
|
40
|
+
Append(packets) {
|
|
41
|
+
const sealed = [];
|
|
42
|
+
for (const packet of packets) {
|
|
43
|
+
const cost = RECORDING_PACKET_LENGTH_PREFIX_BYTES + packet.length;
|
|
44
|
+
if (this.pending.length > 0 && this.pendingBytes + cost > RECORDING_TERMINAL_MAX_BYTES) {
|
|
45
|
+
sealed.push(this.seal(this.pending.length * RECORDING_FRAME_SAMPLES));
|
|
46
|
+
}
|
|
47
|
+
this.pending.push(packet);
|
|
48
|
+
this.pendingBytes += cost;
|
|
49
|
+
if (this.pending.length >= RECORDING_SHARD_MAX_PACKETS) {
|
|
50
|
+
sealed.push(this.seal(this.pending.length * RECORDING_FRAME_SAMPLES));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return sealed;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Seals the partial window now. `sampleCount` (default packets*960) is clamped to
|
|
57
|
+
* [0, packets*960]; the caller passes less when the encoder's last frame was padded. Returns null
|
|
58
|
+
* when no packets are pending, so a second call is a no-op.
|
|
59
|
+
*/
|
|
60
|
+
SealTail(sampleCount) {
|
|
61
|
+
if (this.pending.length === 0) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const max = this.pending.length * RECORDING_FRAME_SAMPLES;
|
|
65
|
+
const wanted = sampleCount ?? max;
|
|
66
|
+
return this.seal(Math.min(Math.max(Math.floor(wanted), 0), max));
|
|
67
|
+
}
|
|
68
|
+
seal(sampleCount) {
|
|
69
|
+
const shard = {
|
|
70
|
+
Index: this.nextIndex,
|
|
71
|
+
StartSample: this.sealedSamples,
|
|
72
|
+
SampleCount: sampleCount,
|
|
73
|
+
Bytes: EncodeRecordingShard({
|
|
74
|
+
ShardIndex: this.nextIndex,
|
|
75
|
+
StartSample: this.sealedSamples,
|
|
76
|
+
SampleCount: sampleCount,
|
|
77
|
+
PacketCount: this.pending.length,
|
|
78
|
+
Packets: this.pending,
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
this.nextIndex++;
|
|
82
|
+
this.sealedSamples += sampleCount;
|
|
83
|
+
this.pending = [];
|
|
84
|
+
this.pendingBytes = RECORDING_SHARD_HEADER_BYTES;
|
|
85
|
+
return shard;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=RecordingShardWindow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecordingShardWindow.js","sourceRoot":"","sources":["../../src/session/RecordingShardWindow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `fetch` transport for recording shards and the terminal request.
|
|
3
|
+
*
|
|
4
|
+
* Shards travel as raw bytes (no base64, no GraphQL envelope) to MJAPI's REST recording routes. The
|
|
5
|
+
* terminal request sets `keepalive: true` so the browser completes it even while the page is
|
|
6
|
+
* unloading; that flag caps the body at 64 KiB, which is why the window seals tails under 48 KiB.
|
|
7
|
+
*
|
|
8
|
+
* @module @memberjunction/realtime-runtime
|
|
9
|
+
*/
|
|
10
|
+
import type { RecordingShard } from './RecordingShardWindow.js';
|
|
11
|
+
import type { PendingRecordingShard } from './RecordingShardStore.js';
|
|
12
|
+
export interface RecordingTerminalInput {
|
|
13
|
+
/** The final partial shard's bytes, or null when every shard was already sent. */
|
|
14
|
+
Tail: Uint8Array | null;
|
|
15
|
+
LastShardIndex: number;
|
|
16
|
+
CapturedSamples: number;
|
|
17
|
+
/**
|
|
18
|
+
* False when the call may go on (the tab was only hidden): the server stamps the counts but assembles
|
|
19
|
+
* after its grace period instead of now. Absent or true: the call ended.
|
|
20
|
+
*/
|
|
21
|
+
Final?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Per-upload options for {@link IRecordingTransport.UploadShard}. */
|
|
24
|
+
export interface RecordingUploadOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Send with `keepalive` so the request survives page unload. Only for shards sealed inside a
|
|
27
|
+
* `pagehide`/`visibilitychange` handler: the browser caps in-flight keepalive bodies (64 KiB per
|
|
28
|
+
* page), and a request over that budget is refused — which resolves false like any failure.
|
|
29
|
+
*/
|
|
30
|
+
Keepalive?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* What became of one shard upload. `Acked`: the server has it. `Refused`: the server answered that it
|
|
34
|
+
* will never take this shard (bad shard, not this caller's session, conflicting bytes, too late, too
|
|
35
|
+
* big), so re-sending it is pointless. `Retry`: anything that may succeed later (no network, an expired
|
|
36
|
+
* token, a busy or failing server).
|
|
37
|
+
*/
|
|
38
|
+
export type RecordingUploadOutcome = 'Acked' | 'Refused' | 'Retry';
|
|
39
|
+
/** Sends recording bytes to the server. */
|
|
40
|
+
export interface IRecordingTransport {
|
|
41
|
+
UploadShard(sessionID: string, shard: RecordingShard | PendingRecordingShard, options?: RecordingUploadOptions): Promise<RecordingUploadOutcome>;
|
|
42
|
+
/** Sent with `keepalive` so it survives page unload. Resolves true only on a server ack. */
|
|
43
|
+
SendTerminal(sessionID: string, input: RecordingTerminalInput): Promise<boolean>;
|
|
44
|
+
}
|
|
45
|
+
export interface FetchRecordingTransportConfig {
|
|
46
|
+
/** MJAPI base URL without a trailing slash (see {@link RecordingBaseURLFromGraphQLURL}). */
|
|
47
|
+
BaseURL: string;
|
|
48
|
+
/** Called per request so a refreshed token is picked up. */
|
|
49
|
+
Headers: () => Record<string, string>;
|
|
50
|
+
/** Defaults to `globalThis.fetch`, resolved at call time so tests can inject one. */
|
|
51
|
+
Fetch?: typeof fetch;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Derives the REST base from the GraphQL endpoint URL by dropping trailing slashes and `/graphql`.
|
|
55
|
+
* A scan, not a regex: `/\/+$/` backtracks quadratically on a long run of slashes that does not end
|
|
56
|
+
* the string, and the URL comes from host configuration.
|
|
57
|
+
*/
|
|
58
|
+
export declare function RecordingBaseURLFromGraphQLURL(url: string): string;
|
|
59
|
+
export declare class FetchRecordingTransport implements IRecordingTransport {
|
|
60
|
+
private readonly config;
|
|
61
|
+
constructor(config: FetchRecordingTransportConfig);
|
|
62
|
+
UploadShard(sessionID: string, shard: RecordingShard | PendingRecordingShard, options?: RecordingUploadOptions): Promise<RecordingUploadOutcome>;
|
|
63
|
+
SendTerminal(sessionID: string, input: RecordingTerminalInput): Promise<boolean>;
|
|
64
|
+
private post;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=RecordingTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecordingTransport.d.ts","sourceRoot":"","sources":["../../src/session/RecordingTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEnE,MAAM,WAAW,sBAAsB;IACnC,kFAAkF;IAClF,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,sEAAsE;AACtE,MAAM,WAAW,sBAAsB;IACnC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AASnE,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IAChC,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,qBAAqB,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACjJ,4FAA4F;IAC5F,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;AAED,MAAM,WAAW,6BAA6B;IAC1C,4FAA4F;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qFAAqF;IACrF,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAQD;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOlE;AAED,qBAAa,uBAAwB,YAAW,mBAAmB;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,6BAA6B;IAE3D,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,qBAAqB,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAK1I,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,OAAO,CAAC;YAO/E,IAAI;CAqBrB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Statuses that mean the server will never accept this shard, whoever sends it and however often.
|
|
3
|
+
* 401 is not here (a refreshed token can still send it), nor 404: an MJAPI older than the recording
|
|
4
|
+
* routes answers 404 to every shard, and a browser upgraded first must keep its audio for the server.
|
|
5
|
+
*/
|
|
6
|
+
const PERMANENT_REFUSAL_STATUSES = new Set([400, 403, 409, 410, 413]);
|
|
7
|
+
function describeFailure(failure) {
|
|
8
|
+
return failure instanceof Error ? failure.message : String(failure);
|
|
9
|
+
}
|
|
10
|
+
const GRAPHQL_SUFFIX = '/graphql';
|
|
11
|
+
/**
|
|
12
|
+
* Derives the REST base from the GraphQL endpoint URL by dropping trailing slashes and `/graphql`.
|
|
13
|
+
* A scan, not a regex: `/\/+$/` backtracks quadratically on a long run of slashes that does not end
|
|
14
|
+
* the string, and the URL comes from host configuration.
|
|
15
|
+
*/
|
|
16
|
+
export function RecordingBaseURLFromGraphQLURL(url) {
|
|
17
|
+
let end = url.length;
|
|
18
|
+
while (end > 0 && url.charCodeAt(end - 1) === 47 /* '/' */) {
|
|
19
|
+
end--;
|
|
20
|
+
}
|
|
21
|
+
const trimmed = url.slice(0, end);
|
|
22
|
+
return trimmed.endsWith(GRAPHQL_SUFFIX) ? trimmed.slice(0, -GRAPHQL_SUFFIX.length) : trimmed;
|
|
23
|
+
}
|
|
24
|
+
export class FetchRecordingTransport {
|
|
25
|
+
constructor(config) {
|
|
26
|
+
this.config = config;
|
|
27
|
+
}
|
|
28
|
+
UploadShard(sessionID, shard, options) {
|
|
29
|
+
const url = `${this.config.BaseURL}/realtime/recording/${encodeURIComponent(sessionID)}/shard/${shard.Index}`;
|
|
30
|
+
return this.post(url, shard.Bytes, options?.Keepalive === true, `shard ${sessionID}/${shard.Index}`);
|
|
31
|
+
}
|
|
32
|
+
async SendTerminal(sessionID, input) {
|
|
33
|
+
const final = input.Final === false ? '&final=false' : '';
|
|
34
|
+
const query = `lastShardIndex=${input.LastShardIndex}&capturedSamples=${input.CapturedSamples}${final}`;
|
|
35
|
+
const url = `${this.config.BaseURL}/realtime/recording/${encodeURIComponent(sessionID)}/end?${query}`;
|
|
36
|
+
return (await this.post(url, input.Tail ?? new Uint8Array(0), true, `terminal ${sessionID}/${input.LastShardIndex}`)) === 'Acked';
|
|
37
|
+
}
|
|
38
|
+
async post(url, body, keepalive, context) {
|
|
39
|
+
const doFetch = this.config.Fetch ?? globalThis.fetch;
|
|
40
|
+
try {
|
|
41
|
+
const response = await doFetch(url, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: { 'Content-Type': 'application/octet-stream', ...this.config.Headers() },
|
|
44
|
+
// TS types a bare Uint8Array as possibly SharedArrayBuffer-backed; shard bytes never are.
|
|
45
|
+
body: body,
|
|
46
|
+
...(keepalive ? { keepalive: true } : {}),
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const outcome = PERMANENT_REFUSAL_STATUSES.has(response.status) ? 'Refused' : 'Retry';
|
|
50
|
+
console.warn(`[RealtimeSession] Recording ${context} rejected by server with status ${response.status} (${outcome === 'Refused' ? 'will not be re-sent' : 'will be retried'})`);
|
|
51
|
+
return outcome;
|
|
52
|
+
}
|
|
53
|
+
return 'Acked';
|
|
54
|
+
}
|
|
55
|
+
catch (failure) {
|
|
56
|
+
console.warn(`[RealtimeSession] Recording ${context} request failed: ${describeFailure(failure)}`, failure);
|
|
57
|
+
return 'Retry';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=RecordingTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecordingTransport.js","sourceRoot":"","sources":["../../src/session/RecordingTransport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The **owner-keyed registry of app client tools** a host registers with a realtime
|
|
3
|
+
* session (the tools the `ContextTool` proxy can run in the browser).
|
|
4
|
+
*
|
|
5
|
+
* The first version of this registry had one operation: *replace everything*. That is a trap the
|
|
6
|
+
* moment a host has two sources of tools. Explorer has two — a set of always-available globals
|
|
7
|
+
* (navigate, open a record, set the theme) and the tools of whichever surface the user is looking
|
|
8
|
+
* at — and registering the surface's tools replaced the globals unless the caller remembered to
|
|
9
|
+
* merge them back, and left the *previous* surface's tools registered if it forgot to clear them
|
|
10
|
+
* (the stale-tools-after-app-switch bug).
|
|
11
|
+
*
|
|
12
|
+
* The fix is ownership. Each source registers under an **owner key** and a registration replaces
|
|
13
|
+
* **only that owner's set**:
|
|
14
|
+
*
|
|
15
|
+
* ```
|
|
16
|
+
* Register('explorer.global', [NavigateToApp, NavigateToRecord, …]) // once
|
|
17
|
+
* Register('explorer.surface', [SetFilter, …]) // on every surface change
|
|
18
|
+
* Register('explorer.surface', []) // surface gone → ONLY its tools go
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* When two owners register a tool with the same name, the **later-registered owner wins** (an
|
|
22
|
+
* owner keeps its slot when it re-registers, so precedence does not flip every time a surface
|
|
23
|
+
* refreshes) — the more specific source is registered after the more general one.
|
|
24
|
+
*
|
|
25
|
+
* @module @memberjunction/realtime-runtime
|
|
26
|
+
*/
|
|
27
|
+
import type { ClientToolMetadata } from '@memberjunction/ai-core-plus';
|
|
28
|
+
/** Runs a client tool in the browser and returns its result (may be sync or async). */
|
|
29
|
+
export type RealtimeAppClientToolHandler = (params: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
30
|
+
/**
|
|
31
|
+
* One tool a host registers. Only `Name` and `Handler` are required; the optional metadata makes the
|
|
32
|
+
* tool describable (in "available tools" error messages and the resolved capability set) without the
|
|
33
|
+
* host publishing a separate manifest.
|
|
34
|
+
*/
|
|
35
|
+
export interface AppClientToolRegistration {
|
|
36
|
+
/** The tool name the model calls (matched case-insensitively). */
|
|
37
|
+
Name: string;
|
|
38
|
+
/** Executes the tool. */
|
|
39
|
+
Handler: RealtimeAppClientToolHandler;
|
|
40
|
+
/** What the tool does, for the model. */
|
|
41
|
+
Description?: string;
|
|
42
|
+
/** JSON Schema of the tool's parameters. */
|
|
43
|
+
InputSchema?: Record<string, unknown>;
|
|
44
|
+
/** Grouping label. */
|
|
45
|
+
Category?: string;
|
|
46
|
+
}
|
|
47
|
+
/** The owner key used by the single-argument `RegisterAppClientTools(tools)` form. */
|
|
48
|
+
export declare const DEFAULT_APP_TOOL_OWNER = "default";
|
|
49
|
+
/** Owner-keyed registry; see the module documentation. */
|
|
50
|
+
export declare class AppClientToolRegistry {
|
|
51
|
+
/** Owner → (lower-cased tool name → registration). Map insertion order = owner precedence (later wins). */
|
|
52
|
+
private readonly owners;
|
|
53
|
+
/**
|
|
54
|
+
* Replaces the tool set of ONE owner; every other owner's tools are untouched. Invalid entries
|
|
55
|
+
* (no name, no handler) are skipped. An empty list clears the owner's tools (it keeps its place in the
|
|
56
|
+
* precedence order; {@link Unregister} gives the place up too).
|
|
57
|
+
*
|
|
58
|
+
* @param owner A stable key naming the source (`'explorer.surface'`). Blank → {@link DEFAULT_APP_TOOL_OWNER}.
|
|
59
|
+
* @param tools The owner's complete current set.
|
|
60
|
+
*/
|
|
61
|
+
Register(owner: string, tools: ReadonlyArray<AppClientToolRegistration>): void;
|
|
62
|
+
/** Removes one owner's tools (no-op when it has none). */
|
|
63
|
+
Unregister(owner: string): void;
|
|
64
|
+
/** Removes every owner's tools. */
|
|
65
|
+
Clear(): void;
|
|
66
|
+
/** Finds a tool by name (case-insensitive); on a collision the later-registered owner wins. */
|
|
67
|
+
Find(name: string): AppClientToolRegistration | undefined;
|
|
68
|
+
/** The distinct registered tool names, in registration form. */
|
|
69
|
+
Names(): string[];
|
|
70
|
+
/**
|
|
71
|
+
* The registered tools as {@link ClientToolMetadata} — the *session tier* of `ResolveClientTools`.
|
|
72
|
+
* Ordered later-owner-first, so the resolver's first-match-wins agrees with {@link Find}.
|
|
73
|
+
*/
|
|
74
|
+
ToMetadata(): ClientToolMetadata[];
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=app-client-tool-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-client-tool-registry.d.ts","sourceRoot":"","sources":["../../src/session/app-client-tool-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,uFAAuF;AACvF,MAAM,MAAM,4BAA4B,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAE3G;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACtC,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,OAAO,EAAE,4BAA4B,CAAC;IACtC,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,sBAAsB;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,sFAAsF;AACtF,eAAO,MAAM,sBAAsB,YAAY,CAAC;AAEhD,0DAA0D;AAC1D,qBAAa,qBAAqB;IAC9B,2GAA2G;IAC3G,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6D;IAEpF;;;;;;;OAOG;IACI,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,yBAAyB,CAAC,GAAG,IAAI;IAarF,0DAA0D;IACnD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAItC,mCAAmC;IAC5B,KAAK,IAAI,IAAI;IAIpB,+FAA+F;IACxF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS;IAShE,gEAAgE;IACzD,KAAK,IAAI,MAAM,EAAE;IAUxB;;;OAGG;IACI,UAAU,IAAI,kBAAkB,EAAE;CAW5C"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The **owner-keyed registry of app client tools** a host registers with a realtime
|
|
3
|
+
* session (the tools the `ContextTool` proxy can run in the browser).
|
|
4
|
+
*
|
|
5
|
+
* The first version of this registry had one operation: *replace everything*. That is a trap the
|
|
6
|
+
* moment a host has two sources of tools. Explorer has two — a set of always-available globals
|
|
7
|
+
* (navigate, open a record, set the theme) and the tools of whichever surface the user is looking
|
|
8
|
+
* at — and registering the surface's tools replaced the globals unless the caller remembered to
|
|
9
|
+
* merge them back, and left the *previous* surface's tools registered if it forgot to clear them
|
|
10
|
+
* (the stale-tools-after-app-switch bug).
|
|
11
|
+
*
|
|
12
|
+
* The fix is ownership. Each source registers under an **owner key** and a registration replaces
|
|
13
|
+
* **only that owner's set**:
|
|
14
|
+
*
|
|
15
|
+
* ```
|
|
16
|
+
* Register('explorer.global', [NavigateToApp, NavigateToRecord, …]) // once
|
|
17
|
+
* Register('explorer.surface', [SetFilter, …]) // on every surface change
|
|
18
|
+
* Register('explorer.surface', []) // surface gone → ONLY its tools go
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* When two owners register a tool with the same name, the **later-registered owner wins** (an
|
|
22
|
+
* owner keeps its slot when it re-registers, so precedence does not flip every time a surface
|
|
23
|
+
* refreshes) — the more specific source is registered after the more general one.
|
|
24
|
+
*
|
|
25
|
+
* @module @memberjunction/realtime-runtime
|
|
26
|
+
*/
|
|
27
|
+
/** The owner key used by the single-argument `RegisterAppClientTools(tools)` form. */
|
|
28
|
+
export const DEFAULT_APP_TOOL_OWNER = 'default';
|
|
29
|
+
/** Owner-keyed registry; see the module documentation. */
|
|
30
|
+
export class AppClientToolRegistry {
|
|
31
|
+
constructor() {
|
|
32
|
+
/** Owner → (lower-cased tool name → registration). Map insertion order = owner precedence (later wins). */
|
|
33
|
+
this.owners = new Map();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Replaces the tool set of ONE owner; every other owner's tools are untouched. Invalid entries
|
|
37
|
+
* (no name, no handler) are skipped. An empty list clears the owner's tools (it keeps its place in the
|
|
38
|
+
* precedence order; {@link Unregister} gives the place up too).
|
|
39
|
+
*
|
|
40
|
+
* @param owner A stable key naming the source (`'explorer.surface'`). Blank → {@link DEFAULT_APP_TOOL_OWNER}.
|
|
41
|
+
* @param tools The owner's complete current set.
|
|
42
|
+
*/
|
|
43
|
+
Register(owner, tools) {
|
|
44
|
+
const key = normalizeOwner(owner);
|
|
45
|
+
const set = new Map();
|
|
46
|
+
for (const tool of tools) {
|
|
47
|
+
if (tool?.Name?.trim() && typeof tool.Handler === 'function') {
|
|
48
|
+
set.set(tool.Name.trim().toLowerCase(), tool);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Map#set on an existing key keeps its insertion slot, so an owner that re-registers (even with
|
|
52
|
+
// an empty set) keeps its place in the precedence order; only Unregister() gives the slot up.
|
|
53
|
+
this.owners.set(key, set);
|
|
54
|
+
}
|
|
55
|
+
/** Removes one owner's tools (no-op when it has none). */
|
|
56
|
+
Unregister(owner) {
|
|
57
|
+
this.owners.delete(normalizeOwner(owner));
|
|
58
|
+
}
|
|
59
|
+
/** Removes every owner's tools. */
|
|
60
|
+
Clear() {
|
|
61
|
+
this.owners.clear();
|
|
62
|
+
}
|
|
63
|
+
/** Finds a tool by name (case-insensitive); on a collision the later-registered owner wins. */
|
|
64
|
+
Find(name) {
|
|
65
|
+
const wanted = (name ?? '').trim().toLowerCase();
|
|
66
|
+
let found;
|
|
67
|
+
for (const set of this.owners.values()) {
|
|
68
|
+
found = set.get(wanted) ?? found;
|
|
69
|
+
}
|
|
70
|
+
return found;
|
|
71
|
+
}
|
|
72
|
+
/** The distinct registered tool names, in registration form. */
|
|
73
|
+
Names() {
|
|
74
|
+
const names = new Map();
|
|
75
|
+
for (const set of this.owners.values()) {
|
|
76
|
+
for (const [lower, tool] of set) {
|
|
77
|
+
names.set(lower, tool.Name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return [...names.values()];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The registered tools as {@link ClientToolMetadata} — the *session tier* of `ResolveClientTools`.
|
|
84
|
+
* Ordered later-owner-first, so the resolver's first-match-wins agrees with {@link Find}.
|
|
85
|
+
*/
|
|
86
|
+
ToMetadata() {
|
|
87
|
+
const ordered = [...this.owners.values()].reverse();
|
|
88
|
+
return ordered.flatMap((set) => [...set.values()].map((tool) => ({
|
|
89
|
+
Name: tool.Name,
|
|
90
|
+
Description: tool.Description ?? '',
|
|
91
|
+
InputSchema: tool.InputSchema ?? {},
|
|
92
|
+
Category: tool.Category,
|
|
93
|
+
})));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Normalizes an owner key; blank falls back to the default owner. */
|
|
97
|
+
function normalizeOwner(owner) {
|
|
98
|
+
return owner?.trim() ? owner.trim() : DEFAULT_APP_TOOL_OWNER;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=app-client-tool-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-client-tool-registry.js","sourceRoot":"","sources":["../../src/session/app-client-tool-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Whether a connected call tells the user it shows no avatar, and why.
|
|
3
|
+
*
|
|
4
|
+
* The server decides the reasons it can see at mint (the model renders no avatar, the persona has no face for it, a
|
|
5
|
+
* custom avatar, an app that said at mint it shows no agent video: `host`) and returns them as the session's avatar
|
|
6
|
+
* status. Two more show only in the browser, once the call is connected: the app asked for no agent video (`host`), or
|
|
7
|
+
* it asked and the browser could not play it (`browser`). {@link ResolveAvatarNotice} puts the three facts together; the
|
|
8
|
+
* runtime publishes the result once per call.
|
|
9
|
+
*
|
|
10
|
+
* @module @memberjunction/realtime-runtime
|
|
11
|
+
*/
|
|
12
|
+
import type { ClientRealtimeSessionConfig, ParsedRealtimeAvatarStatus, RealtimeAvatarUnavailableReason } from '@memberjunction/ai';
|
|
13
|
+
/** Why a call shows no avatar, told once after it connects. A host turns the reason into words. */
|
|
14
|
+
export interface RealtimeAvatarNotice {
|
|
15
|
+
/**
|
|
16
|
+
* Why the call is audio only. Absent when the mint gave a reason this version doesn't know, such as a newer server's:
|
|
17
|
+
* the avatar was asked for and isn't shown, and a host says so without a reason.
|
|
18
|
+
*/
|
|
19
|
+
readonly Reason?: RealtimeAvatarUnavailableReason;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Whether a connected call should say it shows no avatar, and why.
|
|
23
|
+
*
|
|
24
|
+
* - No status, or the session asked for no avatar: nothing to say.
|
|
25
|
+
* - Asked for and not granted: the status's reason; a notice without one when the reason is one this version doesn't
|
|
26
|
+
* know (`ReasonUnknown`); nothing when the status gives no reason.
|
|
27
|
+
* - Granted, but the app asked for no agent video: `host`.
|
|
28
|
+
* - Granted and asked for, but the agent's video track is not live: `browser`.
|
|
29
|
+
* - Granted, asked for and live: nothing to say; the avatar shows.
|
|
30
|
+
*
|
|
31
|
+
* @param status The mint's avatar status as `ParseRealtimeAvatarStatus` reads it, or `null` when the mint reported none.
|
|
32
|
+
* @param requested Whether the session asked for the agent's video (an outbound video track).
|
|
33
|
+
* @param established Whether the agent's video track is live once connected.
|
|
34
|
+
* @returns The notice, or `null` when there is nothing to say.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ResolveAvatarNotice(status: ParsedRealtimeAvatarStatus | null, requested: boolean, established: boolean): RealtimeAvatarNotice | null;
|
|
37
|
+
/**
|
|
38
|
+
* Whether a client config asks for the agent's video: an outbound video track among the tracks the session requests.
|
|
39
|
+
*
|
|
40
|
+
* @param config The config the session connected with.
|
|
41
|
+
*/
|
|
42
|
+
export declare function RequestsAgentVideo(config: ClientRealtimeSessionConfig): boolean;
|
|
43
|
+
//# sourceMappingURL=avatar-notice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"avatar-notice.d.ts","sourceRoot":"","sources":["../../src/session/avatar-notice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,2BAA2B,EAAa,0BAA0B,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAC;AAG9I,mGAAmG;AACnG,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,+BAA+B,CAAC;CACnD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,0BAA0B,GAAG,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,GAAG,oBAAoB,GAAG,IAAI,CAcpJ;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,2BAA2B,GAAG,OAAO,CAG/E"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Whether a connected call tells the user it shows no avatar, and why.
|
|
3
|
+
*
|
|
4
|
+
* The server decides the reasons it can see at mint (the model renders no avatar, the persona has no face for it, a
|
|
5
|
+
* custom avatar, an app that said at mint it shows no agent video: `host`) and returns them as the session's avatar
|
|
6
|
+
* status. Two more show only in the browser, once the call is connected: the app asked for no agent video (`host`), or
|
|
7
|
+
* it asked and the browser could not play it (`browser`). {@link ResolveAvatarNotice} puts the three facts together; the
|
|
8
|
+
* runtime publishes the result once per call.
|
|
9
|
+
*
|
|
10
|
+
* @module @memberjunction/realtime-runtime
|
|
11
|
+
*/
|
|
12
|
+
import { REQUESTED_TRACKS_SESSION_KEY } from '@memberjunction/ai-realtime-client';
|
|
13
|
+
/**
|
|
14
|
+
* Whether a connected call should say it shows no avatar, and why.
|
|
15
|
+
*
|
|
16
|
+
* - No status, or the session asked for no avatar: nothing to say.
|
|
17
|
+
* - Asked for and not granted: the status's reason; a notice without one when the reason is one this version doesn't
|
|
18
|
+
* know (`ReasonUnknown`); nothing when the status gives no reason.
|
|
19
|
+
* - Granted, but the app asked for no agent video: `host`.
|
|
20
|
+
* - Granted and asked for, but the agent's video track is not live: `browser`.
|
|
21
|
+
* - Granted, asked for and live: nothing to say; the avatar shows.
|
|
22
|
+
*
|
|
23
|
+
* @param status The mint's avatar status as `ParseRealtimeAvatarStatus` reads it, or `null` when the mint reported none.
|
|
24
|
+
* @param requested Whether the session asked for the agent's video (an outbound video track).
|
|
25
|
+
* @param established Whether the agent's video track is live once connected.
|
|
26
|
+
* @returns The notice, or `null` when there is nothing to say.
|
|
27
|
+
*/
|
|
28
|
+
export function ResolveAvatarNotice(status, requested, established) {
|
|
29
|
+
if (!status?.Requested) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (!status.Granted) {
|
|
33
|
+
if (status.Reason) {
|
|
34
|
+
return { Reason: status.Reason };
|
|
35
|
+
}
|
|
36
|
+
return status.ReasonUnknown ? {} : null;
|
|
37
|
+
}
|
|
38
|
+
if (!requested) {
|
|
39
|
+
return { Reason: 'host' };
|
|
40
|
+
}
|
|
41
|
+
return established ? null : { Reason: 'browser' };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether a client config asks for the agent's video: an outbound video track among the tracks the session requests.
|
|
45
|
+
*
|
|
46
|
+
* @param config The config the session connected with.
|
|
47
|
+
*/
|
|
48
|
+
export function RequestsAgentVideo(config) {
|
|
49
|
+
const tracks = config.SessionConfig[REQUESTED_TRACKS_SESSION_KEY];
|
|
50
|
+
return Array.isArray(tracks) && tracks.some(isAgentVideoTrack);
|
|
51
|
+
}
|
|
52
|
+
/** Whether a requested-track entry is the agent's video: outbound, video. */
|
|
53
|
+
function isAgentVideoTrack(raw) {
|
|
54
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const modality = raw['Modality'];
|
|
58
|
+
return raw['Direction'] === 'outbound' && typeof modality === 'string' && modality.trim().toLowerCase() === 'video';
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=avatar-notice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"avatar-notice.js","sourceRoot":"","sources":["../../src/session/avatar-notice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,4BAA4B,EAAE,MAAM,oCAAoC,CAAC;AAWlF;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAyC,EAAE,SAAkB,EAAE,WAAoB;IACrH,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QACnC,CAAC;QACD,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1C,CAAC;IACD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC5B,CAAC;IACD,OAAO,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACpD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAmC;IACpE,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,4BAA4B,CAAC,CAAC;IAClE,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;AACjE,CAAC;AAED,6EAA6E;AAC7E,SAAS,iBAAiB,CAAC,GAAc;IACvC,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAClE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;IACjC,OAAO,GAAG,CAAC,WAAW,CAAC,KAAK,UAAU,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC;AACtH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type RealtimeClientError } from '@memberjunction/ai-realtime-client';
|
|
2
|
+
/** How a host words why a call ended: a short status label, and a sentence for a screen or a notice. */
|
|
3
|
+
export interface RealtimeCallEndDescription {
|
|
4
|
+
/** A short status label, such as a call bar's state chip shows. */
|
|
5
|
+
Label: string;
|
|
6
|
+
/** A sentence saying what happened and what to do next. */
|
|
7
|
+
Message: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* What a call says when it ended because its session reached its time limit (MJAPI's relay ends a relayed call at
|
|
11
|
+
* 30 minutes, or sooner when the session has a shorter cap), in place of a connection error.
|
|
12
|
+
*/
|
|
13
|
+
export declare const REALTIME_CALL_TIME_LIMIT_END: Readonly<RealtimeCallEndDescription>;
|
|
14
|
+
/**
|
|
15
|
+
* Why a call ended, in words a host can show, when the call's client said why (`RealtimeSessionRuntime.LastSessionError`);
|
|
16
|
+
* `null` when it ended for a reason with no words of its own, such as a dropped connection, which a host words itself.
|
|
17
|
+
*
|
|
18
|
+
* @param error The call's fatal client error, or `null` when it reported none.
|
|
19
|
+
*/
|
|
20
|
+
export declare function DescribeCallEnd(error: RealtimeClientError | null): Readonly<RealtimeCallEndDescription> | null;
|
|
21
|
+
//# sourceMappingURL=call-end.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-end.d.ts","sourceRoot":"","sources":["../../src/session/call-end.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyC,KAAK,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAErH,wGAAwG;AACxG,MAAM,WAAW,0BAA0B;IACvC,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,QAAQ,CAAC,0BAA0B,CAG5E,CAAC;AAEH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI,GAAG,QAAQ,CAAC,0BAA0B,CAAC,GAAG,IAAI,CAE9G"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { REALTIME_CLIENT_TIME_LIMIT_ERROR_CODE } from '@memberjunction/ai-realtime-client';
|
|
2
|
+
/**
|
|
3
|
+
* What a call says when it ended because its session reached its time limit (MJAPI's relay ends a relayed call at
|
|
4
|
+
* 30 minutes, or sooner when the session has a shorter cap), in place of a connection error.
|
|
5
|
+
*/
|
|
6
|
+
export const REALTIME_CALL_TIME_LIMIT_END = Object.freeze({
|
|
7
|
+
Label: 'Time limit reached',
|
|
8
|
+
Message: 'This call reached its time limit. Start a new call to keep talking.',
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Why a call ended, in words a host can show, when the call's client said why (`RealtimeSessionRuntime.LastSessionError`);
|
|
12
|
+
* `null` when it ended for a reason with no words of its own, such as a dropped connection, which a host words itself.
|
|
13
|
+
*
|
|
14
|
+
* @param error The call's fatal client error, or `null` when it reported none.
|
|
15
|
+
*/
|
|
16
|
+
export function DescribeCallEnd(error) {
|
|
17
|
+
return error?.Code === REALTIME_CLIENT_TIME_LIMIT_ERROR_CODE ? REALTIME_CALL_TIME_LIMIT_END : null;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=call-end.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-end.js","sourceRoot":"","sources":["../../src/session/call-end.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qCAAqC,EAA4B,MAAM,oCAAoC,CAAC;AAUrH;;;GAGG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAyC,MAAM,CAAC,MAAM,CAAC;IAC5F,KAAK,EAAE,oBAAoB;IAC3B,OAAO,EAAE,qEAAqE;CACjF,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAiC;IAC7D,OAAO,KAAK,EAAE,IAAI,KAAK,qCAAqC,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,IAAI,CAAC;AACvG,CAAC"}
|