@memberjunction/realtime-runtime 0.0.0 → 6.2.0-edge.1
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 +183 -0
- package/dist/channels/base-realtime-channel-client.d.ts +402 -0
- package/dist/channels/base-realtime-channel-client.d.ts.map +1 -0
- package/dist/channels/base-realtime-channel-client.js +233 -0
- package/dist/channels/base-realtime-channel-client.js.map +1 -0
- package/dist/hosts/IRealtimeMediaHost.d.ts +136 -0
- package/dist/hosts/IRealtimeMediaHost.d.ts.map +1 -0
- package/dist/hosts/IRealtimeMediaHost.js +37 -0
- package/dist/hosts/IRealtimeMediaHost.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/narration/narration-template.d.ts +42 -0
- package/dist/narration/narration-template.d.ts.map +1 -0
- package/dist/narration/narration-template.js +73 -0
- package/dist/narration/narration-template.js.map +1 -0
- package/dist/session/RealtimeSessionRuntime.d.ts +1233 -0
- package/dist/session/RealtimeSessionRuntime.d.ts.map +1 -0
- package/dist/session/RealtimeSessionRuntime.js +2589 -0
- package/dist/session/RealtimeSessionRuntime.js.map +1 -0
- package/dist/session/delegation-result-parser.d.ts +50 -0
- package/dist/session/delegation-result-parser.d.ts.map +1 -0
- package/dist/session/delegation-result-parser.js +60 -0
- package/dist/session/delegation-result-parser.js.map +1 -0
- package/package.json +35 -8
- package/README.md +0 -45
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for CLIENT-SIDE interactive-channel plugins (per
|
|
3
|
+
* `plans/ai-agent-sessions.md` → "Interactive Channels" / "Pluggable Channel Interfaces").
|
|
4
|
+
*
|
|
5
|
+
* An interactive channel is a bidirectional surface the session's single realtime agent
|
|
6
|
+
* both PERCEIVES and ACTS UPON (whiteboard, shared doc, map, …). A concrete plugin
|
|
7
|
+
* contributes everything the channel needs, so the session service / call overlay carry
|
|
8
|
+
* ZERO channel-specific wiring:
|
|
9
|
+
*
|
|
10
|
+
* 1. a CLIENT-EXECUTED TOOL SET ({@link GetToolDefinitions}, declared to the realtime
|
|
11
|
+
* model at session mint) plus the local executor ({@link ApplyAgentTool}) the host
|
|
12
|
+
* routes `{@link ToolNamePrefix}*` calls to — the ACTION direction;
|
|
13
|
+
* 2. a STATE→CONTEXT SERIALIZER policy — the plugin owns its state engine and pushes
|
|
14
|
+
* coalesced deltas through {@link RealtimeChannelContext.SendContextNote} — the
|
|
15
|
+
* PERCEPTION direction;
|
|
16
|
+
* 3. an OPTIONAL ANGULAR SURFACE ({@link GetSurfaceComponent}) the overlay creates dynamically
|
|
17
|
+
* in a channel tab, handed back through {@link BindSurface} so the plugin wires its own
|
|
18
|
+
* inputs/outputs (the host never knows the component's API). A channel may be **server-only**
|
|
19
|
+
* (no rendered surface) — e.g. a bridge-contributed meeting-controls or native-whiteboard
|
|
20
|
+
* channel whose surface lives on the external platform, not in MJ. Such a channel returns
|
|
21
|
+
* `null` from {@link GetSurfaceComponent} ({@link HasSurface} is `false`) and the overlay
|
|
22
|
+
* simply skips its tab while still wiring its tools + perception;
|
|
23
|
+
* 4. a STATE OF RECORD ({@link SerializeState}, persisted via
|
|
24
|
+
* {@link RealtimeChannelContext.RequestSave} under {@link ChannelName}).
|
|
25
|
+
*
|
|
26
|
+
* ### Registration & resolution (mirrors the realtime model drivers)
|
|
27
|
+
* Concrete plugins are `@RegisterClass(BaseRealtimeChannelClient, '<ClientPluginClass>')`
|
|
28
|
+
* and are resolved at session start from the `MJ: AI Agent Channels` registry: each ACTIVE
|
|
29
|
+
* row's `ClientPluginClass` is the ClassFactory key (exactly how `BaseRealtimeClient`
|
|
30
|
+
* drivers resolve by provider key). Ship a `Load<YourChannel>()` no-op alongside the class
|
|
31
|
+
* and call it from a static code path to defeat tree-shaking.
|
|
32
|
+
*
|
|
33
|
+
* ### Lifecycle — ONE INSTANCE PER SESSION (not a singleton)
|
|
34
|
+
* `ClassFactory.CreateInstance` → {@link Initialize}(ctx) → zero or more
|
|
35
|
+
* {@link BindSurface}/{@link UnbindSurface} cycles (the surface pane is created/destroyed
|
|
36
|
+
* with the overlay's tab panel, e.g. collapse/expand) → {@link Dispose} at teardown.
|
|
37
|
+
* {@link ApplyAgentTool} MUST work with NO surface bound (apply to the state engine
|
|
38
|
+
* directly; skip the UI garnish) — tool calls can arrive while the panel is collapsed.
|
|
39
|
+
*
|
|
40
|
+
* @typeParam TSurface The plugin's Angular surface component type. The host only ever
|
|
41
|
+
* sees the default (`object`) — the typed parameter exists so concrete plugins get a
|
|
42
|
+
* fully typed {@link BindSurface} without casts.
|
|
43
|
+
*/
|
|
44
|
+
export class BaseRealtimeChannelClient {
|
|
45
|
+
constructor() {
|
|
46
|
+
/**
|
|
47
|
+
* The host context, available from {@link Initialize} until {@link Dispose}.
|
|
48
|
+
* `null` outside that window — guard with `?.` in any code that can run early/late.
|
|
49
|
+
*/
|
|
50
|
+
this.Context = null;
|
|
51
|
+
/**
|
|
52
|
+
* Max time {@link ResolveAgentSessionId} waits for the session id to bind before giving up, and the
|
|
53
|
+
* poll interval it re-checks on. Protected so tests can shrink the wait; production keeps the
|
|
54
|
+
* defaults (the real mint race is sub-second, 8s is generous headroom).
|
|
55
|
+
*/
|
|
56
|
+
this.SessionIdWaitTimeoutMs = 8000;
|
|
57
|
+
this.SessionIdWaitIntervalMs = 200;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* OPTIONAL accent color for the channel's tab (a CSS color string, e.g. an `hsl()` /
|
|
61
|
+
* token). When a plugin supplies one, the overlay paints the tab's dot + active underline
|
|
62
|
+
* with it; when omitted (the default `null`), the overlay derives a stable, deterministic
|
|
63
|
+
* color from the {@link ChannelName} so every channel still reads as a distinct, colored
|
|
64
|
+
* surface. A channel only overrides this to enforce a specific brand accent.
|
|
65
|
+
*/
|
|
66
|
+
get TabColor() {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The Angular component the overlay creates dynamically as this channel's tab pane, or `null`
|
|
71
|
+
* for a **server-only** channel that renders no MJ surface (its surface, if any, lives on the
|
|
72
|
+
* external platform — e.g. a bridge-contributed native whiteboard or meeting-controls channel).
|
|
73
|
+
*
|
|
74
|
+
* When this returns `null`, the overlay renders NO tab for the channel and never calls
|
|
75
|
+
* {@link BindSurface}/{@link UnbindSurface} — but the channel's tools ({@link GetToolDefinitions} /
|
|
76
|
+
* {@link ApplyAgentTool}) and perception ({@link RealtimeChannelContext.SendContextNote}) still run.
|
|
77
|
+
* A created surface instance is handed straight back via {@link BindSurface}; the host treats it as
|
|
78
|
+
* opaque.
|
|
79
|
+
*
|
|
80
|
+
* Default: `null` (server-only). A channel with a rendered surface overrides this to return its
|
|
81
|
+
* component type.
|
|
82
|
+
*/
|
|
83
|
+
GetSurfaceComponent() {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Whether this channel has a rendered MJ surface ({@link GetSurfaceComponent} returns non-null).
|
|
88
|
+
* The overlay uses this to decide whether to register a surface tab; server-only channels are
|
|
89
|
+
* `false`. Override only if surface availability must be decided WITHOUT constructing the type
|
|
90
|
+
* (the default calls {@link GetSurfaceComponent} once).
|
|
91
|
+
*/
|
|
92
|
+
HasSurface() {
|
|
93
|
+
return this.GetSurfaceComponent() != null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The channel's FIRST-RUN INTRO content, or `null` when the channel offers no onboarding.
|
|
97
|
+
* The overlay shows this once per channel per user — the first time the user opens this
|
|
98
|
+
* channel's surface tab — and remembers "seen" via the user's settings (NOT localStorage),
|
|
99
|
+
* so it never re-appears on later sessions or other devices.
|
|
100
|
+
*
|
|
101
|
+
* Default: `null` (no intro). The base Voice/text channel has no plugin at all, so it never
|
|
102
|
+
* shows an intro; an interactive channel with a surface worth explaining (whiteboard, remote
|
|
103
|
+
* browser, …) overrides this to return its {@link ChannelOnboardingDetails}. A plugin that
|
|
104
|
+
* doesn't override it simply shows nothing — onboarding is strictly opt-in.
|
|
105
|
+
*/
|
|
106
|
+
GetOnboardingDetails() {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Called by the host when the surface component is being destroyed (tab panel
|
|
111
|
+
* collapsed / overlay torn down). Drop the instance reference and unsubscribe any
|
|
112
|
+
* output subscriptions — after this, {@link ApplyAgentTool} runs in its no-surface
|
|
113
|
+
* mode. Default: no-op.
|
|
114
|
+
*/
|
|
115
|
+
UnbindSurface() {
|
|
116
|
+
// default: nothing to release
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Binds the host context and invokes the {@link OnInitialize} hook. Called exactly once
|
|
120
|
+
* per session, right after ClassFactory instantiation and before any tool call or
|
|
121
|
+
* surface bind.
|
|
122
|
+
*/
|
|
123
|
+
Initialize(ctx) {
|
|
124
|
+
this.Context = ctx;
|
|
125
|
+
this.OnInitialize();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Subclass hook invoked from {@link Initialize} once {@link Context} is bound — wire
|
|
129
|
+
* state-engine subscriptions (e.g. state change → `Context.RequestSave(...)`) here.
|
|
130
|
+
* Default: no-op.
|
|
131
|
+
*/
|
|
132
|
+
OnInitialize() {
|
|
133
|
+
// default: nothing to initialize
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Subclass hook invoked once the realtime session is connected and live (the client driver
|
|
137
|
+
* is created, connected, and media tracks negotiated). Channels that establish media bridges
|
|
138
|
+
* (e.g. video streaming) can start them here when `Context.Client` is available.
|
|
139
|
+
* Default: no-op.
|
|
140
|
+
*/
|
|
141
|
+
OnSessionStarted() {
|
|
142
|
+
// default: nothing to do
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Resolves the live {@link RealtimeChannelContext.AgentSessionID}, briefly WAITING for it when it
|
|
146
|
+
* isn't bound yet rather than giving up instantly. `AgentSessionID` is a live getter over the
|
|
147
|
+
* session service's current id: it reads `null` in the window BEFORE the session mints (the
|
|
148
|
+
* realtime model can fire a tool call the very first beat it connects, before `mintSession`
|
|
149
|
+
* resolves) and again AFTER teardown. Server-backed tool paths (e.g. the Remote Browser channel's
|
|
150
|
+
* `browser_*` tools) call this instead of reading `Context?.AgentSessionID` synchronously, so a tool
|
|
151
|
+
* invoked a beat early WAITS for the session to come live — defense-in-depth against the
|
|
152
|
+
* "session id missing" race — instead of returning a hard failure to the model.
|
|
153
|
+
*
|
|
154
|
+
* Returns the id as soon as it's non-null (the common path resolves immediately, no delay), or
|
|
155
|
+
* `null` if it's still unbound after {@link SessionIdWaitTimeoutMs} — or the channel was
|
|
156
|
+
* {@link Dispose}d in the meantime (`Context` goes null, so we stop waiting on a torn-down session).
|
|
157
|
+
*/
|
|
158
|
+
async ResolveAgentSessionId() {
|
|
159
|
+
const immediate = this.Context?.AgentSessionID ?? null;
|
|
160
|
+
if (immediate) {
|
|
161
|
+
return immediate;
|
|
162
|
+
}
|
|
163
|
+
const intervalMs = Math.max(1, this.SessionIdWaitIntervalMs);
|
|
164
|
+
for (let waited = 0; waited < this.SessionIdWaitTimeoutMs; waited += intervalMs) {
|
|
165
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
166
|
+
// Context goes null on Dispose() — the session is gone, stop waiting.
|
|
167
|
+
if (!this.Context) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const id = this.Context.AgentSessionID;
|
|
171
|
+
if (id) {
|
|
172
|
+
return id;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return this.Context?.AgentSessionID ?? null;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Serializes the channel's current state of record (the payload persisted on the
|
|
179
|
+
* session's channel row), or `null` when the channel keeps no persistent state.
|
|
180
|
+
* Default: `null`.
|
|
181
|
+
*/
|
|
182
|
+
SerializeState() {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Restores a PRIOR session's saved channel state (the payload a previous session
|
|
187
|
+
* persisted via {@link SerializeState} / {@link RealtimeChannelContext.RequestSave}).
|
|
188
|
+
* Invoked by the session host AFTER {@link Initialize} and BEFORE any surface binding,
|
|
189
|
+
* when a prior session's saved state exists for this channel.
|
|
190
|
+
*
|
|
191
|
+
* Returns `true` when the state was applied; `false` when the channel ignored it —
|
|
192
|
+
* either because it keeps no persistent state (this default) or because the payload was
|
|
193
|
+
* malformed/incompatible. Implementations MUST be tolerant: never throw on bad input,
|
|
194
|
+
* just return `false` and start fresh.
|
|
195
|
+
*/
|
|
196
|
+
RestoreState(stateJson) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* The focus pill's "exit" affordance, routed by the overlay to the channel that holds
|
|
201
|
+
* focus. Implementations should leave focus mode through their OWN surface (so surface
|
|
202
|
+
* toggles stay in sync), ultimately emitting `Context.SetFocusMode(false)`. The overlay
|
|
203
|
+
* defensively clears its layout flag as well, so a no-op default is safe.
|
|
204
|
+
*/
|
|
205
|
+
RequestFocusExit() {
|
|
206
|
+
// default: the overlay's defensive clear handles it
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Media tracks this client channel can SOURCE — samples flowing into the model.
|
|
210
|
+
* Default `[]`.
|
|
211
|
+
*/
|
|
212
|
+
GetSourcedTracks() {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Media tracks this client channel can SINK — samples flowing from the model OUT.
|
|
217
|
+
* Default `[]`.
|
|
218
|
+
*/
|
|
219
|
+
GetSunkTracks() {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Tears the plugin down at session end: release the surface binding, unsubscribe
|
|
224
|
+
* state-engine subscriptions, then drop the context. Subclasses overriding this MUST
|
|
225
|
+
* call `super.Dispose()`. Any final state save has already been flushed by the host
|
|
226
|
+
* (the debounced {@link RealtimeChannelContext.RequestSave} pipeline) before disposal.
|
|
227
|
+
*/
|
|
228
|
+
Dispose() {
|
|
229
|
+
this.UnbindSurface();
|
|
230
|
+
this.Context = null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=base-realtime-channel-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-realtime-channel-client.js","sourceRoot":"","sources":["../../src/channels/base-realtime-channel-client.ts"],"names":[],"mappings":"AAkMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,OAAgB,yBAAyB;IAA/C;QACE;;;WAGG;QACO,YAAO,GAAkC,IAAI,CAAC;QA+IxD;;;;WAIG;QACO,2BAAsB,GAAG,IAAI,CAAC;QAC9B,4BAAuB,GAAG,GAAG,CAAC;IAgG1C,CAAC;IA/NC;;;;;;OAMG;IACH,IAAW,QAAQ;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAoBD;;;;;;;;;;;;;OAaG;IACI,mBAAmB;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACI,UAAU;QACf,OAAO,IAAI,CAAC,mBAAmB,EAAE,IAAI,IAAI,CAAC;IAC5C,CAAC;IAED;;;;;;;;;;OAUG;IACI,oBAAoB;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAYD;;;;;OAKG;IACI,aAAa;QAClB,8BAA8B;IAChC,CAAC;IAED;;;;OAIG;IACI,UAAU,CAAC,GAA2B;QAC3C,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC;QACnB,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACO,YAAY;QACpB,iCAAiC;IACnC,CAAC;IAED;;;;;OAKG;IACI,gBAAgB;QACrB,yBAAyB;IAC3B,CAAC;IAUD;;;;;;;;;;;;;OAaG;IACO,KAAK,CAAC,qBAAqB;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC;QACvD,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAC7D,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,sBAAsB,EAAE,MAAM,IAAI,UAAU,EAAE,CAAC;YAChF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;YACtE,sEAAsE;YACtE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC;YACvC,IAAI,EAAE,EAAE,CAAC;gBACP,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC;IAC9C,CAAC;IAED;;;;OAIG;IACI,cAAc;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;;;OAUG;IACI,YAAY,CAAC,SAAiB;QACnC,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;OAKG;IACI,gBAAgB;QACrB,oDAAoD;IACtD,CAAC;IAED;;;OAGG;IACI,gBAAgB;QACrB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED;;;OAGG;IACI,aAAa;QAClB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED;;;;;OAKG;IACI,OAAO;QACZ,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;CACF"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The media seam between the framework-agnostic realtime session runtime and
|
|
3
|
+
* whatever platform is hosting it.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this interface exists
|
|
6
|
+
*
|
|
7
|
+
* The realtime co-agent session orchestration — mint, driver resolution, transcript relay, tool
|
|
8
|
+
* relay, delegation narration, channel lifecycle, usage relay, teardown — is pure TypeScript and
|
|
9
|
+
* runs identically in a browser, in React Native, or in a Node test harness. Exactly **one** line
|
|
10
|
+
* of it was not: microphone acquisition, which was hard-coded to
|
|
11
|
+
* `navigator.mediaDevices.getUserMedia({ audio: true })`.
|
|
12
|
+
*
|
|
13
|
+
* That was never meant to live in the runtime. The Real-Time Co-Agents guide states the contract
|
|
14
|
+
* plainly — *"the host acquires the mic (it owns the permission UX)"* — because microphone
|
|
15
|
+
* permission is a **product** decision that differs per platform: a browser shows the URL-bar
|
|
16
|
+
* prompt, iOS shows a system sheet gated on an `Info.plist` usage string, Android runs the
|
|
17
|
+
* runtime-permission flow. The runtime has no business making that call; it only needs the
|
|
18
|
+
* resulting stream. This interface makes that pre-existing contract explicit instead of implied.
|
|
19
|
+
*
|
|
20
|
+
* ## Implementations
|
|
21
|
+
*
|
|
22
|
+
* - **Browser** — `getUserMedia({ audio: true })` and the global `RTCPeerConnection`.
|
|
23
|
+
* - **React Native** — the `react-native-webrtc` equivalents, which polyfill both APIs natively
|
|
24
|
+
* and additionally give the platform's acoustic echo cancellation, noise suppression and jitter
|
|
25
|
+
* buffering for free.
|
|
26
|
+
* - **Test harness** — a synthetic stream, so a session can be driven end to end with no hardware.
|
|
27
|
+
*
|
|
28
|
+
* ## A note on `MediaStream`
|
|
29
|
+
*
|
|
30
|
+
* `MediaStream` is referenced as a **type only**. It is erased at compile time, it is already in
|
|
31
|
+
* this repo's base `lib` (`tsconfig.server.json`), and `react-native-webrtc` ships a conforming
|
|
32
|
+
* implementation — so naming it here costs nothing and invents no parallel abstraction. The
|
|
33
|
+
* runtime never *constructs* one; it only receives it from the host and hands it to the realtime
|
|
34
|
+
* client driver, whose `Connect()` already takes exactly this type.
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* The platform capabilities the realtime session runtime needs but cannot provide itself.
|
|
38
|
+
*
|
|
39
|
+
* Implement one of these per host. Every member is intentionally coarse — the runtime asks for an
|
|
40
|
+
* outcome ("give me a microphone"), never for a mechanism ("call getUserMedia with these
|
|
41
|
+
* constraints"), so hosts stay free to satisfy it however their platform requires.
|
|
42
|
+
*/
|
|
43
|
+
export interface IRealtimeMediaHost {
|
|
44
|
+
/**
|
|
45
|
+
* Acquires the user's microphone, prompting for permission if the platform requires it.
|
|
46
|
+
*
|
|
47
|
+
* The host owns the entire permission experience, including any pre-permission priming UI and
|
|
48
|
+
* the decision about what to do when permission is permanently denied.
|
|
49
|
+
*
|
|
50
|
+
* @returns the live audio stream to hand to the realtime driver.
|
|
51
|
+
* @throws if permission is denied or no input device is available. The runtime treats a throw
|
|
52
|
+
* as a failed session start and tears down cleanly — hosts do **not** need to
|
|
53
|
+
* pre-check permission to avoid a half-open session.
|
|
54
|
+
*/
|
|
55
|
+
AcquireMicrophone(): Promise<MediaStream>;
|
|
56
|
+
/**
|
|
57
|
+
* OPTIONAL: returns the platform's audio state to what it was before
|
|
58
|
+
* {@link AcquireMicrophone} changed it.
|
|
59
|
+
*
|
|
60
|
+
* Acquiring a microphone is not always a symmetric act. A browser hands back a stream and
|
|
61
|
+
* nothing else changes, so most hosts omit this. iOS is the counter-example: a voice call
|
|
62
|
+
* requires putting the shared audio session into a record-and-play category with the speaker
|
|
63
|
+
* route, and that setting outlives the call — every later sound in the app plays through the
|
|
64
|
+
* call route, at call volume, until something puts it back.
|
|
65
|
+
*
|
|
66
|
+
* Called by the runtime on teardown, after the microphone tracks are stopped, on every exit
|
|
67
|
+
* path including a failed start. Best-effort: a rejection is logged and swallowed, because
|
|
68
|
+
* restoring audio state is never worth failing the end of a call over.
|
|
69
|
+
*/
|
|
70
|
+
ReleaseMicrophone?(): Promise<void> | void;
|
|
71
|
+
/**
|
|
72
|
+
* OPTIONAL: creates a recorder for this session's audio, when the platform can record and the
|
|
73
|
+
* user has consented.
|
|
74
|
+
*
|
|
75
|
+
* Returning `null` — or omitting the member entirely — disables recording for the session.
|
|
76
|
+
* That is a fully supported configuration, not a degraded one: recording is consent-gated and
|
|
77
|
+
* many hosts will never offer it. The runtime's session clock is anchored independently of the
|
|
78
|
+
* recorder precisely so that unrecorded sessions still produce correct per-turn timings.
|
|
79
|
+
*
|
|
80
|
+
* The returned recorder is **unstarted** — the runtime calls {@link IRealtimeSessionRecorder.Start}
|
|
81
|
+
* once it knows whether the agent's remote stream is already available.
|
|
82
|
+
*/
|
|
83
|
+
CreateRecorder?(): IRealtimeSessionRecorder | null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The recording capability a host may optionally provide.
|
|
87
|
+
*
|
|
88
|
+
* Mirrors the browser recorder's existing surface so the runtime's call sites are unchanged by the
|
|
89
|
+
* extraction. Every method is best-effort by contract: a recorder that fails mid-session must
|
|
90
|
+
* degrade to "no recording" rather than failing the call, because audio capture is never worth
|
|
91
|
+
* dropping a live conversation over.
|
|
92
|
+
*/
|
|
93
|
+
export interface IRealtimeSessionRecorder {
|
|
94
|
+
/** `false` when capture could not start (unsupported platform, no permission). */
|
|
95
|
+
readonly IsRecording: boolean;
|
|
96
|
+
/** Capture sample rate, used to label the header-less PCM16 crash-recovery shards. */
|
|
97
|
+
readonly SampleRate: number;
|
|
98
|
+
/**
|
|
99
|
+
* Container MIME type of the FINAL consolidated recording.
|
|
100
|
+
*
|
|
101
|
+
* Read it BEFORE {@link StopAndEncode} — implementations are permitted to clear it on stop,
|
|
102
|
+
* which is why the runtime captures it first.
|
|
103
|
+
*/
|
|
104
|
+
readonly MimeType: string;
|
|
105
|
+
/**
|
|
106
|
+
* Begins capture of the microphone, mixing in the agent's audio when it is already available.
|
|
107
|
+
*
|
|
108
|
+
* `remoteStream` is usually `null` here: on WebRTC the agent's track commonly lands slightly
|
|
109
|
+
* after the connection resolves, so the runtime also wires {@link AttachRemoteStream}.
|
|
110
|
+
*/
|
|
111
|
+
Start(micStream: MediaStream, remoteStream: MediaStream | null): void;
|
|
112
|
+
/** Mixes the agent's audio in once its track arrives, so the recording carries both sides. */
|
|
113
|
+
AttachRemoteStream(stream: MediaStream): void;
|
|
114
|
+
/** Milliseconds into the recording, used to stamp per-turn cue offsets into a seekable file. */
|
|
115
|
+
NowOffsetMs(): number;
|
|
116
|
+
/** Waveform peaks computed during capture; survives {@link StopAndEncode}. */
|
|
117
|
+
GetPeaks(): number[];
|
|
118
|
+
/**
|
|
119
|
+
* Returns audio captured since the previous snapshot, **already base64-encoded**, or `null`
|
|
120
|
+
* when nothing new is pending.
|
|
121
|
+
*
|
|
122
|
+
* Encoding lives behind this seam deliberately: browsers reach for `Blob` + `FileReader`,
|
|
123
|
+
* React Native reads a file off disk, and a test harness may hold bytes in memory. The runtime
|
|
124
|
+
* only forwards the string to the server, so it should never learn which of those it is.
|
|
125
|
+
*
|
|
126
|
+
* Shards are header-less raw little-endian PCM16 — recovery is concatenate-in-order then
|
|
127
|
+
* WAV-wrap. The canonical seekable file is the consolidated {@link StopAndEncode} upload.
|
|
128
|
+
*/
|
|
129
|
+
SnapshotNewSegmentBase64(): Promise<string | null>;
|
|
130
|
+
/**
|
|
131
|
+
* Stops capture and returns the consolidated recording **already base64-encoded**, or `null`
|
|
132
|
+
* when nothing was captured. Idempotent.
|
|
133
|
+
*/
|
|
134
|
+
StopAndEncode(): Promise<string | null>;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=IRealtimeMediaHost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IRealtimeMediaHost.d.ts","sourceRoot":"","sources":["../../src/hosts/IRealtimeMediaHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IAC/B;;;;;;;;;;OAUG;IACH,iBAAiB,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAE1C;;;;;;;;;;;;;OAaG;IACH,iBAAiB,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAE3C;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,IAAI,wBAAwB,GAAG,IAAI,CAAC;CACtD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACrC,kFAAkF;IAClF,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAE9B,sFAAsF;IACtF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B;;;;;OAKG;IACH,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC;IAEtE,8FAA8F;IAC9F,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAE9C,gGAAgG;IAChG,WAAW,IAAI,MAAM,CAAC;IAEtB,8EAA8E;IAC9E,QAAQ,IAAI,MAAM,EAAE,CAAC;IAErB;;;;;;;;;;OAUG;IACH,wBAAwB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAEnD;;;OAGG;IACH,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC3C"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The media seam between the framework-agnostic realtime session runtime and
|
|
3
|
+
* whatever platform is hosting it.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this interface exists
|
|
6
|
+
*
|
|
7
|
+
* The realtime co-agent session orchestration — mint, driver resolution, transcript relay, tool
|
|
8
|
+
* relay, delegation narration, channel lifecycle, usage relay, teardown — is pure TypeScript and
|
|
9
|
+
* runs identically in a browser, in React Native, or in a Node test harness. Exactly **one** line
|
|
10
|
+
* of it was not: microphone acquisition, which was hard-coded to
|
|
11
|
+
* `navigator.mediaDevices.getUserMedia({ audio: true })`.
|
|
12
|
+
*
|
|
13
|
+
* That was never meant to live in the runtime. The Real-Time Co-Agents guide states the contract
|
|
14
|
+
* plainly — *"the host acquires the mic (it owns the permission UX)"* — because microphone
|
|
15
|
+
* permission is a **product** decision that differs per platform: a browser shows the URL-bar
|
|
16
|
+
* prompt, iOS shows a system sheet gated on an `Info.plist` usage string, Android runs the
|
|
17
|
+
* runtime-permission flow. The runtime has no business making that call; it only needs the
|
|
18
|
+
* resulting stream. This interface makes that pre-existing contract explicit instead of implied.
|
|
19
|
+
*
|
|
20
|
+
* ## Implementations
|
|
21
|
+
*
|
|
22
|
+
* - **Browser** — `getUserMedia({ audio: true })` and the global `RTCPeerConnection`.
|
|
23
|
+
* - **React Native** — the `react-native-webrtc` equivalents, which polyfill both APIs natively
|
|
24
|
+
* and additionally give the platform's acoustic echo cancellation, noise suppression and jitter
|
|
25
|
+
* buffering for free.
|
|
26
|
+
* - **Test harness** — a synthetic stream, so a session can be driven end to end with no hardware.
|
|
27
|
+
*
|
|
28
|
+
* ## A note on `MediaStream`
|
|
29
|
+
*
|
|
30
|
+
* `MediaStream` is referenced as a **type only**. It is erased at compile time, it is already in
|
|
31
|
+
* this repo's base `lib` (`tsconfig.server.json`), and `react-native-webrtc` ships a conforming
|
|
32
|
+
* implementation — so naming it here costs nothing and invents no parallel abstraction. The
|
|
33
|
+
* runtime never *constructs* one; it only receives it from the host and hands it to the realtime
|
|
34
|
+
* client driver, whose `Connect()` already takes exactly this type.
|
|
35
|
+
*/
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=IRealtimeMediaHost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IRealtimeMediaHost.js","sourceRoot":"","sources":["../../src/hosts/IRealtimeMediaHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Public entry point for `@memberjunction/realtime-runtime`.
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic orchestration for MemberJunction **client-direct realtime co-agent sessions**:
|
|
5
|
+
* minting a session, resolving the provider's client driver, wiring transcripts and captions,
|
|
6
|
+
* relaying tool calls, pacing delegation progress narration, managing interactive channels, relaying
|
|
7
|
+
* usage, and tearing all of it down cleanly.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this package exists
|
|
10
|
+
*
|
|
11
|
+
* This code shipped inside `@memberjunction/ng-conversations` as an `@Injectable` Angular service.
|
|
12
|
+
* It was never Angular-specific — its own header noted it stays component-free so it "must stay
|
|
13
|
+
* importable in plain-node tests" — but living in an Angular package made it unusable from any
|
|
14
|
+
* other host. A React Native app, a React or Vue surface, or a headless test harness had no way to
|
|
15
|
+
* drive a realtime session except by reimplementing ~2,700 lines that already existed, and then
|
|
16
|
+
* watching the two copies drift apart at the next protocol change.
|
|
17
|
+
*
|
|
18
|
+
* Extracting it follows the precedent set by `@memberjunction/conversations-runtime`, which did the
|
|
19
|
+
* same for chat orchestration. The Angular service remains — as a thin adapter that supplies the
|
|
20
|
+
* browser's media host and the `@Injectable` shell — so Explorer is unchanged.
|
|
21
|
+
*
|
|
22
|
+
* ## What a host must provide
|
|
23
|
+
*
|
|
24
|
+
* Exactly one thing: an {@link IRealtimeMediaHost}. Microphone acquisition and audio recording are
|
|
25
|
+
* platform decisions (permission UX, container format, where bytes live), so they sit behind that
|
|
26
|
+
* seam. Everything else in a realtime session is portable and lives here.
|
|
27
|
+
*
|
|
28
|
+
* @module @memberjunction/realtime-runtime
|
|
29
|
+
*/
|
|
30
|
+
export { RealtimeSessionRuntime } from './session/RealtimeSessionRuntime.js';
|
|
31
|
+
export { REALTIME_RECORDING_CONSENT_KEY, type RealtimeConnectionState, type RealtimeCaption, type RealtimeDelegationProgress, type RealtimeDelegationResult, type RealtimeClientToolHandler, type RealtimeChannelFocusEvent, type RealtimeDelegationNarration, type RealtimeThoughtNarration, type StartRealtimeClientSessionResult, type RealtimeSessionRunOptions, } from './session/RealtimeSessionRuntime.js';
|
|
32
|
+
export { type IRealtimeMediaHost, type IRealtimeSessionRecorder, } from './hosts/IRealtimeMediaHost.js';
|
|
33
|
+
export { BaseRealtimeChannelClient, type RealtimeChannelContext, type RealtimeSurfaceComponentType, type ChannelOnboardingDetails, } from './channels/base-realtime-channel-client.js';
|
|
34
|
+
export { ParseDelegationResultJson, type ParsedDelegationArtifact, type ParsedDelegationResult, FormatToolName, } from './session/delegation-result-parser.js';
|
|
35
|
+
export { BuildNarrationInstructions, DefaultNarrationInstructions, type NarrationBuildOptions, } from './narration/narration-template.js';
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAG1E,OAAO,EACH,8BAA8B,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,gCAAgC,EACrC,KAAK,yBAAyB,GACjC,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EACH,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,GAChC,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EACH,yBAAyB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,4BAA4B,EACjC,KAAK,wBAAwB,GAChC,MAAM,yCAAyC,CAAC;AAGjD,OAAO,EACH,yBAAyB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,cAAc,GACjB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACH,0BAA0B,EAC1B,4BAA4B,EAC5B,KAAK,qBAAqB,GAC7B,MAAM,gCAAgC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Public entry point for `@memberjunction/realtime-runtime`.
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic orchestration for MemberJunction **client-direct realtime co-agent sessions**:
|
|
5
|
+
* minting a session, resolving the provider's client driver, wiring transcripts and captions,
|
|
6
|
+
* relaying tool calls, pacing delegation progress narration, managing interactive channels, relaying
|
|
7
|
+
* usage, and tearing all of it down cleanly.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this package exists
|
|
10
|
+
*
|
|
11
|
+
* This code shipped inside `@memberjunction/ng-conversations` as an `@Injectable` Angular service.
|
|
12
|
+
* It was never Angular-specific — its own header noted it stays component-free so it "must stay
|
|
13
|
+
* importable in plain-node tests" — but living in an Angular package made it unusable from any
|
|
14
|
+
* other host. A React Native app, a React or Vue surface, or a headless test harness had no way to
|
|
15
|
+
* drive a realtime session except by reimplementing ~2,700 lines that already existed, and then
|
|
16
|
+
* watching the two copies drift apart at the next protocol change.
|
|
17
|
+
*
|
|
18
|
+
* Extracting it follows the precedent set by `@memberjunction/conversations-runtime`, which did the
|
|
19
|
+
* same for chat orchestration. The Angular service remains — as a thin adapter that supplies the
|
|
20
|
+
* browser's media host and the `@Injectable` shell — so Explorer is unchanged.
|
|
21
|
+
*
|
|
22
|
+
* ## What a host must provide
|
|
23
|
+
*
|
|
24
|
+
* Exactly one thing: an {@link IRealtimeMediaHost}. Microphone acquisition and audio recording are
|
|
25
|
+
* platform decisions (permission UX, container format, where bytes live), so they sit behind that
|
|
26
|
+
* seam. Everything else in a realtime session is portable and lives here.
|
|
27
|
+
*
|
|
28
|
+
* @module @memberjunction/realtime-runtime
|
|
29
|
+
*/
|
|
30
|
+
// The session runtime itself — the orchestration a host subclasses or composes.
|
|
31
|
+
export { RealtimeSessionRuntime } from './session/RealtimeSessionRuntime.js';
|
|
32
|
+
// Session-facing types a host or UI binds to.
|
|
33
|
+
export { REALTIME_RECORDING_CONSENT_KEY, } from './session/RealtimeSessionRuntime.js';
|
|
34
|
+
// Interactive channel plugin contract — the client half of the channel registry.
|
|
35
|
+
export { BaseRealtimeChannelClient, } from './channels/base-realtime-channel-client.js';
|
|
36
|
+
// Delegation result parsing + narration instruction assembly.
|
|
37
|
+
export { ParseDelegationResultJson, FormatToolName, } from './session/delegation-result-parser.js';
|
|
38
|
+
export { BuildNarrationInstructions, DefaultNarrationInstructions, } from './narration/narration-template.js';
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,gFAAgF;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAE1E,8CAA8C;AAC9C,OAAO,EACH,8BAA8B,GAWjC,MAAM,kCAAkC,CAAC;AAQ1C,iFAAiF;AACjF,OAAO,EACH,yBAAyB,GAI5B,MAAM,yCAAyC,CAAC;AAEjD,8DAA8D;AAC9D,OAAO,EACH,yBAAyB,EAGzB,cAAc,GACjB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACH,0BAA0B,EAC1B,4BAA4B,GAE/B,MAAM,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure helpers for building the one-off spoken progress-narration instructions a
|
|
3
|
+
* realtime voice session sends to the model while delegated work runs.
|
|
4
|
+
*
|
|
5
|
+
* The instruction text is DB-driven: the server resolves the `Realtime Co-Agent - Progress Narration`
|
|
6
|
+
* prompt's `TemplateText` at session start and threads it to the browser. The template may use:
|
|
7
|
+
* - `{{ progressMessage }}` — the aggregated progress digest (one or more updates, oldest first)
|
|
8
|
+
* - `{{ priorNarrations }}` — what the model has ALREADY said aloud for this task (so it can
|
|
9
|
+
* continue the story instead of repeating itself)
|
|
10
|
+
* - `{{ updateNumber }}` — 1-based count of spoken updates for this task
|
|
11
|
+
* When a deployment hasn't synced that prompt yet, the built-in
|
|
12
|
+
* {@link DefaultNarrationInstructions} fallback keeps narration working with the same semantics.
|
|
13
|
+
*/
|
|
14
|
+
/** Options accompanying the progress digest when building narration instructions. */
|
|
15
|
+
export interface NarrationBuildOptions {
|
|
16
|
+
/** The narration utterances the model has already spoken for this task, oldest first. */
|
|
17
|
+
PriorNarrations?: string[];
|
|
18
|
+
/** 1-based number of this spoken update within the current task. */
|
|
19
|
+
UpdateNumber?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Builds the built-in (fallback) narration instructions — same semantics as the DB template:
|
|
23
|
+
* first person, varied phrasing that continues from what was already said, no repeats.
|
|
24
|
+
*
|
|
25
|
+
* @param digest The aggregated progress digest (one or more updates, oldest first).
|
|
26
|
+
* @param options Prior narrations + update number for phrasing variation/chaining.
|
|
27
|
+
* @returns The complete spoken-update instruction text.
|
|
28
|
+
*/
|
|
29
|
+
export declare function DefaultNarrationInstructions(digest: string, options?: NarrationBuildOptions): string;
|
|
30
|
+
/**
|
|
31
|
+
* Builds the narration instructions from the server-provided template by substituting
|
|
32
|
+
* `{{ progressMessage }}`, `{{ priorNarrations }}`, and `{{ updateNumber }}` (space and no-space
|
|
33
|
+
* variants). Falls back to {@link DefaultNarrationInstructions} when the template is absent or
|
|
34
|
+
* blank (deployments that haven't synced the narration prompt).
|
|
35
|
+
*
|
|
36
|
+
* @param template The DB-driven instruction template, or `null`/`undefined` when unavailable.
|
|
37
|
+
* @param digest The aggregated progress digest (one or more updates, oldest first).
|
|
38
|
+
* @param options Prior narrations + update number for phrasing variation/chaining.
|
|
39
|
+
* @returns The complete spoken-update instruction text.
|
|
40
|
+
*/
|
|
41
|
+
export declare function BuildNarrationInstructions(template: string | null | undefined, digest: string, options?: NarrationBuildOptions): string;
|
|
42
|
+
//# sourceMappingURL=narration-template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narration-template.d.ts","sourceRoot":"","sources":["../../src/narration/narration-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,qFAAqF;AACrF,MAAM,WAAW,qBAAqB;IACpC,yFAAyF;IACzF,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAuBD;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAcpG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,qBAAqB,GAC9B,MAAM,CAQR"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure helpers for building the one-off spoken progress-narration instructions a
|
|
3
|
+
* realtime voice session sends to the model while delegated work runs.
|
|
4
|
+
*
|
|
5
|
+
* The instruction text is DB-driven: the server resolves the `Realtime Co-Agent - Progress Narration`
|
|
6
|
+
* prompt's `TemplateText` at session start and threads it to the browser. The template may use:
|
|
7
|
+
* - `{{ progressMessage }}` — the aggregated progress digest (one or more updates, oldest first)
|
|
8
|
+
* - `{{ priorNarrations }}` — what the model has ALREADY said aloud for this task (so it can
|
|
9
|
+
* continue the story instead of repeating itself)
|
|
10
|
+
* - `{{ updateNumber }}` — 1-based count of spoken updates for this task
|
|
11
|
+
* When a deployment hasn't synced that prompt yet, the built-in
|
|
12
|
+
* {@link DefaultNarrationInstructions} fallback keeps narration working with the same semantics.
|
|
13
|
+
*/
|
|
14
|
+
const PROGRESS_TOKENS = ['{{ progressMessage }}', '{{progressMessage}}'];
|
|
15
|
+
const PRIOR_TOKENS = ['{{ priorNarrations }}', '{{priorNarrations}}'];
|
|
16
|
+
const NUMBER_TOKENS = ['{{ updateNumber }}', '{{updateNumber}}'];
|
|
17
|
+
/** Replaces every occurrence of each token with the value. */
|
|
18
|
+
function replaceTokens(text, tokens, value) {
|
|
19
|
+
let out = text;
|
|
20
|
+
for (const t of tokens) {
|
|
21
|
+
out = out.split(t).join(value);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
/** Formats the prior spoken narrations for injection ("none yet" when this is the first update). */
|
|
26
|
+
function formatPriorNarrations(prior) {
|
|
27
|
+
if (!prior || prior.length === 0) {
|
|
28
|
+
return 'Nothing yet — this is your first spoken update for this task.';
|
|
29
|
+
}
|
|
30
|
+
return prior.map((p) => `- "${p}"`).join('\n');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Builds the built-in (fallback) narration instructions — same semantics as the DB template:
|
|
34
|
+
* first person, varied phrasing that continues from what was already said, no repeats.
|
|
35
|
+
*
|
|
36
|
+
* @param digest The aggregated progress digest (one or more updates, oldest first).
|
|
37
|
+
* @param options Prior narrations + update number for phrasing variation/chaining.
|
|
38
|
+
* @returns The complete spoken-update instruction text.
|
|
39
|
+
*/
|
|
40
|
+
export function DefaultNarrationInstructions(digest, options) {
|
|
41
|
+
const updateNumber = options?.UpdateNumber ?? 1;
|
|
42
|
+
return (`Live progress on the work YOU are doing for the user (oldest first): ${digest}. ` +
|
|
43
|
+
`This is spoken update #${updateNumber} for this task. You have already told the user:\n` +
|
|
44
|
+
`${formatPriorNarrations(options?.PriorNarrations)}\n` +
|
|
45
|
+
`Say ONE short, natural sentence in the FIRST PERSON continuing the story of what you are doing. ` +
|
|
46
|
+
`For the first update something like "I'm pulling that up now" is fine; for later updates VARY the ` +
|
|
47
|
+
`phrasing and build on what you last said ("Got the first part — grabbing the rest", "Almost there, ` +
|
|
48
|
+
`just double-checking the numbers") instead of repeating an "I'm now…" pattern. Never repeat ` +
|
|
49
|
+
`information you've already conveyed — only add what's new. Strictly first person: the words "it" ` +
|
|
50
|
+
`and the agent's name must not be the subject of your sentence, and never say generic filler like ` +
|
|
51
|
+
`"it's still running in the background".`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Builds the narration instructions from the server-provided template by substituting
|
|
55
|
+
* `{{ progressMessage }}`, `{{ priorNarrations }}`, and `{{ updateNumber }}` (space and no-space
|
|
56
|
+
* variants). Falls back to {@link DefaultNarrationInstructions} when the template is absent or
|
|
57
|
+
* blank (deployments that haven't synced the narration prompt).
|
|
58
|
+
*
|
|
59
|
+
* @param template The DB-driven instruction template, or `null`/`undefined` when unavailable.
|
|
60
|
+
* @param digest The aggregated progress digest (one or more updates, oldest first).
|
|
61
|
+
* @param options Prior narrations + update number for phrasing variation/chaining.
|
|
62
|
+
* @returns The complete spoken-update instruction text.
|
|
63
|
+
*/
|
|
64
|
+
export function BuildNarrationInstructions(template, digest, options) {
|
|
65
|
+
if (!template || template.trim().length === 0) {
|
|
66
|
+
return DefaultNarrationInstructions(digest, options);
|
|
67
|
+
}
|
|
68
|
+
let out = replaceTokens(template, PROGRESS_TOKENS, digest);
|
|
69
|
+
out = replaceTokens(out, PRIOR_TOKENS, formatPriorNarrations(options?.PriorNarrations));
|
|
70
|
+
out = replaceTokens(out, NUMBER_TOKENS, String(options?.UpdateNumber ?? 1));
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=narration-template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narration-template.js","sourceRoot":"","sources":["../../src/narration/narration-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAUH,MAAM,eAAe,GAAG,CAAC,uBAAuB,EAAE,qBAAqB,CAAC,CAAC;AACzE,MAAM,YAAY,GAAG,CAAC,uBAAuB,EAAE,qBAAqB,CAAC,CAAC;AACtE,MAAM,aAAa,GAAG,CAAC,oBAAoB,EAAE,kBAAkB,CAAC,CAAC;AAEjE,8DAA8D;AAC9D,SAAS,aAAa,CAAC,IAAY,EAAE,MAAgB,EAAE,KAAa;IAClE,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,oGAAoG;AACpG,SAAS,qBAAqB,CAAC,KAA2B;IACxD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,+DAA+D,CAAC;IACzE,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAAC,MAAc,EAAE,OAA+B;IAC1F,MAAM,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,CAAC,CAAC;IAChD,OAAO,CACL,wEAAwE,MAAM,IAAI;QAClF,0BAA0B,YAAY,mDAAmD;QACzF,GAAG,qBAAqB,CAAC,OAAO,EAAE,eAAe,CAAC,IAAI;QACtD,kGAAkG;QAClG,oGAAoG;QACpG,qGAAqG;QACrG,8FAA8F;QAC9F,mGAAmG;QACnG,mGAAmG;QACnG,yCAAyC,CAC1C,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,0BAA0B,CACxC,QAAmC,EACnC,MAAc,EACd,OAA+B;IAE/B,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9C,OAAO,4BAA4B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,GAAG,GAAG,aAAa,CAAC,QAAQ,EAAE,eAAe,EAAE,MAAM,CAAC,CAAC;IAC3D,GAAG,GAAG,aAAa,CAAC,GAAG,EAAE,YAAY,EAAE,qBAAqB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC;IACxF,GAAG,GAAG,aAAa,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5E,OAAO,GAAG,CAAC;AACb,CAAC"}
|