@memberjunction/server 5.40.2 → 5.41.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/dist/agentSessions/HostInstance.d.ts +19 -0
- package/dist/agentSessions/HostInstance.d.ts.map +1 -0
- package/dist/agentSessions/HostInstance.js +48 -0
- package/dist/agentSessions/HostInstance.js.map +1 -0
- package/dist/agentSessions/SessionJanitor.d.ts +97 -0
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -0
- package/dist/agentSessions/SessionJanitor.js +222 -0
- package/dist/agentSessions/SessionJanitor.js.map +1 -0
- package/dist/agentSessions/SessionManager.d.ts +142 -0
- package/dist/agentSessions/SessionManager.d.ts.map +1 -0
- package/dist/agentSessions/SessionManager.js +308 -0
- package/dist/agentSessions/SessionManager.js.map +1 -0
- package/dist/agentSessions/index.d.ts +4 -0
- package/dist/agentSessions/index.d.ts.map +1 -0
- package/dist/agentSessions/index.js +26 -0
- package/dist/agentSessions/index.js.map +1 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +6 -1
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +41 -7
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +659 -3
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +5459 -1759
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.js +1 -1
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.js +9 -10
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +97 -41
- package/dist/index.js.map +1 -1
- package/dist/logging/StartupLogger.d.ts +121 -0
- package/dist/logging/StartupLogger.d.ts.map +1 -0
- package/dist/logging/StartupLogger.js +245 -0
- package/dist/logging/StartupLogger.js.map +1 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +21 -2
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts +42 -0
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -0
- package/dist/resolvers/AgentSessionResolver.js +152 -0
- package/dist/resolvers/AgentSessionResolver.js.map +1 -0
- package/dist/resolvers/EntityPermissionResolver.d.ts +16 -0
- package/dist/resolvers/EntityPermissionResolver.d.ts.map +1 -0
- package/dist/resolvers/EntityPermissionResolver.js +95 -0
- package/dist/resolvers/EntityPermissionResolver.js.map +1 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +688 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeClientSessionResolver.js +1774 -0
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +359 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.js +896 -0
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -0
- package/dist/services/ScheduledJobsService.d.ts.map +1 -1
- package/dist/services/ScheduledJobsService.js +6 -5
- package/dist/services/ScheduledJobsService.js.map +1 -1
- package/package.json +78 -74
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +2605 -0
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +174 -0
- package/src/__tests__/SessionJanitor.test.ts +234 -0
- package/src/__tests__/SessionManager.test.ts +465 -0
- package/src/__tests__/subscriptionRedaction.test.ts +5 -0
- package/src/agentSessions/HostInstance.ts +53 -0
- package/src/agentSessions/SessionJanitor.ts +267 -0
- package/src/agentSessions/SessionManager.ts +446 -0
- package/src/agentSessions/index.ts +27 -0
- package/src/auth/initializeProviders.ts +6 -1
- package/src/context.ts +42 -7
- package/src/generated/generated.ts +3111 -567
- package/src/generic/ResolverBase.ts +1 -1
- package/src/generic/RunViewResolver.ts +9 -9
- package/src/index.ts +98 -41
- package/src/logging/StartupLogger.ts +317 -0
- package/src/logging/variablesLoggingMiddleware.ts +25 -5
- package/src/resolvers/AgentSessionResolver.ts +138 -0
- package/src/resolvers/EntityPermissionResolver.ts +73 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +2162 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +907 -0
- package/src/services/ScheduledJobsService.ts +6 -5
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview GraphQL resolvers for the CLIENT-DIRECT realtime voice topology (P5b-ii).
|
|
3
|
+
*
|
|
4
|
+
* These resolvers are **thin**: they wire two already-built pieces together —
|
|
5
|
+
* {@link RealtimeClientSessionService} (in `@memberjunction/ai-agents`, the server-side half of the
|
|
6
|
+
* client-direct contract) and {@link SessionManager} (the durable `AIAgentSession` record manager) —
|
|
7
|
+
* over a request's `contextUser` + request-scoped `IMetadataProvider`.
|
|
8
|
+
*
|
|
9
|
+
* In the client-direct topology the browser opens its OWN provider socket (e.g. WebRTC) using a
|
|
10
|
+
* server-minted ephemeral token, but the server retains authority over the system prompt + tool set
|
|
11
|
+
* and **executes** every tool call the browser relays back. The three mutations here cover the MVP:
|
|
12
|
+
*
|
|
13
|
+
* 1. {@link RealtimeClientSessionResolver.StartRealtimeClientSession} — authorize, create the
|
|
14
|
+
* session (storing the target agent id server-side, authoritatively), mint the ephemeral config.
|
|
15
|
+
* 2. {@link RealtimeClientSessionResolver.ExecuteRealtimeSessionTool} — execute a relayed tool call,
|
|
16
|
+
* reading the target agent id from the session (NOT the client) so the browser can't swap targets.
|
|
17
|
+
* 3. {@link RealtimeClientSessionResolver.RelayRealtimeTranscript} — persist a transcript turn as a
|
|
18
|
+
* `Conversation Detail`.
|
|
19
|
+
*
|
|
20
|
+
* Authorization model:
|
|
21
|
+
* - **Start**: the caller must have `CanRun` on the *target* agent (the Realtime Co-Agent is an internal
|
|
22
|
+
* orchestration agent, so the meaningful gate is the agent doing the real work).
|
|
23
|
+
* - **Execute / Relay**: inbound ownership — the session's `UserID` must equal `contextUser.ID`.
|
|
24
|
+
*
|
|
25
|
+
* @module @memberjunction/server
|
|
26
|
+
*/
|
|
27
|
+
import { PubSubEngine } from 'type-graphql';
|
|
28
|
+
import { AppContext } from '../types.js';
|
|
29
|
+
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
30
|
+
/**
|
|
31
|
+
* Result of {@link RealtimeClientSessionResolver.StartRealtimeClientSession} — everything the
|
|
32
|
+
* browser needs to open its own provider socket plus the durable session linkage.
|
|
33
|
+
*/
|
|
34
|
+
export declare class StartRealtimeClientSessionResult {
|
|
35
|
+
/** ID of the newly created `AIAgentSession` record. */
|
|
36
|
+
AgentSessionId: string;
|
|
37
|
+
/** ID of the conversation the session is attached to (supplied or freshly created). */
|
|
38
|
+
ConversationId: string;
|
|
39
|
+
/** The provider that minted the credential (e.g. `'openai'`). */
|
|
40
|
+
Provider: string;
|
|
41
|
+
/** The provider realtime model id the session is scoped to (e.g. `gpt-realtime`). */
|
|
42
|
+
Model: string;
|
|
43
|
+
/** The short-lived client secret the browser presents to the provider to authenticate. */
|
|
44
|
+
EphemeralToken: string;
|
|
45
|
+
/** ISO-8601 timestamp at which {@link StartRealtimeClientSessionResult.EphemeralToken} expires. */
|
|
46
|
+
ExpiresAt: string;
|
|
47
|
+
/** JSON string of the provider-native session config the browser applies verbatim. */
|
|
48
|
+
SessionConfigJson: string;
|
|
49
|
+
/** Display name of the realtime model the session uses (e.g. `GPT Realtime 2`). Null when unknown. */
|
|
50
|
+
ModelName?: string;
|
|
51
|
+
/**
|
|
52
|
+
* DB-driven progress-narration instruction template (contains a `{{ progressMessage }}`
|
|
53
|
+
* placeholder). Null when the narration prompt is not present in this deployment's metadata —
|
|
54
|
+
* the browser falls back to its built-in narration text.
|
|
55
|
+
*/
|
|
56
|
+
NarrationInstructionsTemplate?: string;
|
|
57
|
+
/**
|
|
58
|
+
* JSON object string keyed by channel NAME mapping to that channel's persisted state JSON from
|
|
59
|
+
* the caller's PRIOR session (`lastSessionId`) — e.g. `{"Whiteboard":"{...board scene...}"}`.
|
|
60
|
+
* Null when no `lastSessionId` was supplied, the prior session has no persisted channel state,
|
|
61
|
+
* the prior session is not owned by the caller, or the restore failed for any reason (restore
|
|
62
|
+
* is strictly best-effort — a session start NEVER fails because of it).
|
|
63
|
+
*/
|
|
64
|
+
PriorChannelStatesJson?: string;
|
|
65
|
+
/**
|
|
66
|
+
* The effective narration pace (`realtime.narration.paceMs` from the co-agent's effective
|
|
67
|
+
* configuration) — minimum gap in ms between spoken progress updates. Null when not
|
|
68
|
+
* configured (the browser uses its built-in pacing default). In the client-direct topology
|
|
69
|
+
* narration pacing is enforced CLIENT-side, so the server surfaces the configured value here.
|
|
70
|
+
*/
|
|
71
|
+
NarrationPaceMs?: number;
|
|
72
|
+
/**
|
|
73
|
+
* JSON of the RESOLVED effective realtime configuration for this session (type
|
|
74
|
+
* `DefaultConfiguration` ← agent `TypeConfiguration` ← authorized runtime overrides,
|
|
75
|
+
* deep-merged + normalized server-side). The browser uses it to apply client-side concerns
|
|
76
|
+
* (e.g. per-provider voice settings consumed by client drivers, narration pacing). Null only
|
|
77
|
+
* when the prepare service did not resolve a config (back-compat).
|
|
78
|
+
*/
|
|
79
|
+
EffectiveConfigJson?: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Result of {@link RealtimeClientSessionResolver.SaveSessionChannelArtifact} — a structured
|
|
83
|
+
* success/failure envelope (graceful failures like a missing artifact type must not throw).
|
|
84
|
+
*/
|
|
85
|
+
export declare class SaveSessionChannelArtifactResult {
|
|
86
|
+
/** True when the artifact + first version were both persisted. */
|
|
87
|
+
Success: boolean;
|
|
88
|
+
/** Human-readable failure reason. Null on success. */
|
|
89
|
+
ErrorMessage?: string;
|
|
90
|
+
/** ID of the created `MJ: Artifacts` row. Null when creation failed before the header saved. */
|
|
91
|
+
ArtifactID?: string;
|
|
92
|
+
/** ID of the created `MJ: Artifact Versions` row (version 1). Null on failure. */
|
|
93
|
+
ArtifactVersionID?: string;
|
|
94
|
+
/**
|
|
95
|
+
* True when the version was also linked into conversation history via a
|
|
96
|
+
* `MJ: Conversation Detail Artifacts` junction row. False when the session has no
|
|
97
|
+
* conversation, no conversation detail was stamped with this session id yet, or the
|
|
98
|
+
* (best-effort) junction save failed — none of which fail the overall save.
|
|
99
|
+
*/
|
|
100
|
+
ConversationDetailLinked: boolean;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Result of {@link RealtimeClientSessionResolver.CancelRealtimeSessionTool} — a structured
|
|
104
|
+
* success/failure envelope, mirroring {@link SaveSessionChannelArtifactResult}: tolerated
|
|
105
|
+
* problems (an unexpected registry error) come back as `Success: false`, never a throw, while
|
|
106
|
+
* authn/ownership violations still throw like the sibling mutations.
|
|
107
|
+
*/
|
|
108
|
+
export declare class CancelRealtimeSessionToolResult {
|
|
109
|
+
/**
|
|
110
|
+
* How many in-flight delegations were aborted. `0` is a legitimate SUCCESS outcome — the
|
|
111
|
+
* work the user wanted dead may simply have finished (or never started) already.
|
|
112
|
+
*/
|
|
113
|
+
AbortedCount: number;
|
|
114
|
+
/** True when the cancel request was processed (even when nothing was in flight). */
|
|
115
|
+
Success: boolean;
|
|
116
|
+
/** Human-readable failure reason. Null on success. */
|
|
117
|
+
ErrorMessage?: string;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolver for the client-direct realtime voice topology. A single {@link SessionManager} and a
|
|
121
|
+
* single {@link RealtimeClientSessionService} are shared across requests — neither holds per-user or
|
|
122
|
+
* per-provider state; every method is passed the request `contextUser` + provider explicitly.
|
|
123
|
+
*/
|
|
124
|
+
export declare class RealtimeClientSessionResolver extends ResolverBase {
|
|
125
|
+
private readonly sessionManager;
|
|
126
|
+
private readonly clientSessionService;
|
|
127
|
+
/**
|
|
128
|
+
* Start a client-direct realtime voice session targeting `targetAgentId`.
|
|
129
|
+
*
|
|
130
|
+
* Flow:
|
|
131
|
+
* 1. Authorize — the caller must have `CanRun` on the **target** agent; denial throws and no
|
|
132
|
+
* session is created.
|
|
133
|
+
* 2. Resolve the co-agent id via the metadata-driven resolution chain (runtime `coAgentId` →
|
|
134
|
+
* agent's `DefaultCoAgentID` → type-level `AIAgentCoAgent` default row → global Realtime Co-Agent) —
|
|
135
|
+
* see {@link RealtimeClientSessionResolver.resolveCoAgentID} for the full contract.
|
|
136
|
+
* 3. Create the durable `AIAgentSession` (run by the co-agent), storing `targetAgentID` in its
|
|
137
|
+
* config server-side — this is the authoritative target for all later relays.
|
|
138
|
+
* 4. Mint the {@link import('@memberjunction/ai').ClientRealtimeSessionConfig} via the service.
|
|
139
|
+
* On failure the just-created session is closed so no half-open session leaks.
|
|
140
|
+
*
|
|
141
|
+
* **SECURITY NOTE — `clientToolsJson`:** these are CLIENT-EXECUTED UI tools (e.g. the live
|
|
142
|
+
* whiteboard's `Whiteboard.*` surface). The server only *declares* them to the realtime model
|
|
143
|
+
* so it can call them — the server NEVER executes them, and a relayed call for one of these
|
|
144
|
+
* names falls into the standard "not available" path on the server side. They grant no
|
|
145
|
+
* server-side capability whatsoever; server-executed tools remain exclusively server-declared
|
|
146
|
+
* (`invoke-target-agent` + future action wiring). The declarations are still validated
|
|
147
|
+
* (count cap, size cap, per-tool shape) so a hostile client can't bloat the session config.
|
|
148
|
+
*
|
|
149
|
+
* @param targetAgentId The agent the co-agent voices. OPTIONAL when the resolved co-agent
|
|
150
|
+
* has pairing rows with an `IsDefault` target (`MJ: AI Agent Co Agents`) — the default
|
|
151
|
+
* pairing stands in. Required for a universal co-agent (zero pairing rows). When the
|
|
152
|
+
* co-agent HAS pairing rows, a supplied target must be in that list (clear error otherwise).
|
|
153
|
+
* @param coAgentId Optional EXPLICIT co-agent choice (`MJ: AI Agents.ID` of an Active,
|
|
154
|
+
* Realtime-type agent). When set, the server uses exactly that co-agent and FAILS with a
|
|
155
|
+
* clear reason if it can't (no silent fallback — mirroring `preferredModelId`'s contract).
|
|
156
|
+
* Omit to let metadata drive the choice: the target agent's `DefaultCoAgentID`, then the
|
|
157
|
+
* type-level `AIAgentCoAgent` default row for its agent type, then the global Realtime Co-Agent.
|
|
158
|
+
* @param configOverridesJson Optional RUNTIME configuration-override layer (the most-specific
|
|
159
|
+
* layer of the effective-config merge: type `DefaultConfiguration` ← agent
|
|
160
|
+
* `TypeConfiguration` ← this). **Authorization-gated**: requires the
|
|
161
|
+
* `Realtime: Advanced Session Controls` authorization — unauthorized callers receive a
|
|
162
|
+
* structured rejection (never a silent ignore). Must be a JSON object.
|
|
163
|
+
*
|
|
164
|
+
* @returns The ephemeral config + session linkage the browser needs to open its socket.
|
|
165
|
+
*/
|
|
166
|
+
StartRealtimeClientSession(targetAgentId: string | undefined, { userPayload, providers }: AppContext, conversationId?: string, lastSessionId?: string, preferredModelId?: string, clientToolsJson?: string, coAgentId?: string, configOverridesJson?: string): Promise<StartRealtimeClientSessionResult>;
|
|
167
|
+
/**
|
|
168
|
+
* Execute a single tool call the browser relayed from its provider socket and return the
|
|
169
|
+
* serialized result for the browser to relay back to the model.
|
|
170
|
+
*
|
|
171
|
+
* Ownership-gated; the target agent id is read from the **session config** (authoritative), never
|
|
172
|
+
* from the client. Heartbeats the session on success.
|
|
173
|
+
*
|
|
174
|
+
* @returns The serialized tool result JSON.
|
|
175
|
+
*/
|
|
176
|
+
ExecuteRealtimeSessionTool(agentSessionId: string, callId: string, toolName: string, argsJson: string, { userPayload, providers }: AppContext, pubSub: PubSubEngine): Promise<string>;
|
|
177
|
+
/**
|
|
178
|
+
* Builds the `OnProgress` callback threaded into the delegated target-agent run. Each significant
|
|
179
|
+
* progress event (see {@link SIGNIFICANT_PROGRESS_STEPS}) is published to
|
|
180
|
+
* {@link PUSH_STATUS_UPDATES_TOPIC} on this user's `sessionId` so the browser can correlate it to
|
|
181
|
+
* THIS voice session (via `agentSessionID` + `callID`) and feed it to the realtime model to
|
|
182
|
+
* narrate — distinct from a normal `RunAIAgentResolver` agent-run stream by `resolver`/`type`.
|
|
183
|
+
*/
|
|
184
|
+
private buildDelegationProgressCallback;
|
|
185
|
+
/**
|
|
186
|
+
* Rolls the session's `pendingFeedbackRunID` forward after a relayed tool call. The id we just
|
|
187
|
+
* consumed (resumed or none) is dropped; a new paused run id is stored only when the run paused
|
|
188
|
+
* again awaiting feedback. When neither the old nor new value is set, this is a no-op (no save).
|
|
189
|
+
* Best-effort: a save failure is logged, not thrown.
|
|
190
|
+
*/
|
|
191
|
+
private updatePendingFeedbackRunID;
|
|
192
|
+
/**
|
|
193
|
+
* Persist a transcript turn (user or assistant) as a `Conversation Detail` on the session's
|
|
194
|
+
* conversation. Ownership-gated; heartbeats the session on success.
|
|
195
|
+
*
|
|
196
|
+
* The co-agent observability `AIAgentRun`/`AIPromptRun` are created at session start (see
|
|
197
|
+
* {@link RealtimeClientSessionResolver.StartRealtimeClientSession}) and finalized on close;
|
|
198
|
+
* incremental usage telemetry lands on the `AIPromptRun` via
|
|
199
|
+
* {@link RealtimeClientSessionResolver.RelayRealtimeUsage}. Each persisted turn is ALSO appended
|
|
200
|
+
* to the co-agent `AIPromptRun.Messages` (best-effort), so the run captures the full conversation
|
|
201
|
+
* — observability parity with every other MJ agent run, not just token totals.
|
|
202
|
+
*
|
|
203
|
+
* @returns `true` when the transcript turn was persisted.
|
|
204
|
+
*/
|
|
205
|
+
RelayRealtimeTranscript(agentSessionId: string, role: string, text: string, { userPayload, providers }: AppContext, replacesPrevious?: boolean): Promise<boolean>;
|
|
206
|
+
/**
|
|
207
|
+
* Maps the relayed transcript role (`'User'`/`'AI'`/`'Assistant'`, case-insensitive) to the
|
|
208
|
+
* standard chat-message role stored in `AIPromptRun.Messages`. Anything that isn't an explicit
|
|
209
|
+
* user turn is treated as the assistant (the co-agent's own speech).
|
|
210
|
+
*/
|
|
211
|
+
private mapTranscriptRoleToChatRole;
|
|
212
|
+
/**
|
|
213
|
+
* Relays a co-agent CHANNEL tool-call (browser_ / Whiteboard_ etc.) onto the session's co-agent
|
|
214
|
+
* AIPromptRun.Messages — run-only observability so the run captures what the co-agent DID, not just
|
|
215
|
+
* what it said. Deliberately NOT a ConversationDetail turn (the chat thread stays speech-only).
|
|
216
|
+
* Ownership-gated; best-effort (a missing prompt run / save failure simply returns false).
|
|
217
|
+
*
|
|
218
|
+
* @returns `true` when the tool turn was recorded on the run.
|
|
219
|
+
*/
|
|
220
|
+
RelayRealtimeToolTurn(agentSessionId: string, toolName: string, { userPayload, providers }: AppContext, argsJson?: string, resultJson?: string): Promise<boolean>;
|
|
221
|
+
/**
|
|
222
|
+
* Formats a co-agent tool call into a compact, human-readable run line: `🔧 <tool> <args> → <result>`,
|
|
223
|
+
* clipping args/result so a verbose payload never bloats the Messages blob.
|
|
224
|
+
*/
|
|
225
|
+
private formatToolTurn;
|
|
226
|
+
/**
|
|
227
|
+
* Cancel in-flight relayed tool delegations for a session — the CLIENT-DIRECT cancel channel.
|
|
228
|
+
*
|
|
229
|
+
* The browser calls this on an EXPLICIT user cancel (the call overlay's per-card ✕ — a
|
|
230
|
+
* deliberate host policy: true barge-in alone never aborts delegations, since the narration
|
|
231
|
+
* design expects the user to keep talking while delegated work runs). The service-side
|
|
232
|
+
* in-flight registry aborts the matching `AbortController`(s), which fires the delegated
|
|
233
|
+
* run's `cancellationToken`; the original `ExecuteRealtimeSessionTool` mutation then resolves
|
|
234
|
+
* with the run's cancelled/failed result through its normal path.
|
|
235
|
+
*
|
|
236
|
+
* Ownership-gated like the sibling mutations. A **Closed** session is accepted — a cancel can
|
|
237
|
+
* legitimately race teardown, and aborting stragglers is exactly what the caller wants then.
|
|
238
|
+
*
|
|
239
|
+
* @param callId When supplied, only the delegation for that provider call id is aborted;
|
|
240
|
+
* omit to abort ALL in-flight delegations for the session.
|
|
241
|
+
* @returns A structured {@link CancelRealtimeSessionToolResult}. Tolerant: an unknown call id
|
|
242
|
+
* or a session with nothing in flight is `Success: true, AbortedCount: 0` (the work may
|
|
243
|
+
* simply have finished already); an unexpected registry error is a structured failure.
|
|
244
|
+
*/
|
|
245
|
+
CancelRealtimeSessionTool(agentSessionId: string, { userPayload, providers }: AppContext, callId?: string): Promise<CancelRealtimeSessionToolResult>;
|
|
246
|
+
/**
|
|
247
|
+
* Relay incremental usage telemetry from the browser's provider socket onto the co-agent's
|
|
248
|
+
* observability `AIPromptRun` (the run created at session start).
|
|
249
|
+
*
|
|
250
|
+
* The browser accumulates the realtime client's `OnUsage` token DELTAS and flushes them here
|
|
251
|
+
* debounced (~10s) plus once at teardown; this mutation ACCUMULATES the deltas onto the
|
|
252
|
+
* prompt run's `TokensPrompt` / `TokensCompletion` (and recomputes `TokensUsed`). Status is
|
|
253
|
+
* deliberately untouched — `FinalizeCoAgentRun` keeps stamping Success/Completed at close,
|
|
254
|
+
* and a post-close flush still lands (accumulation has no Status gate).
|
|
255
|
+
*
|
|
256
|
+
* Ownership-gated like the sibling mutations; a **Closed** session is accepted (the final
|
|
257
|
+
* teardown flush can land after `CloseAgentSession`). Everything PAST the ownership gate is
|
|
258
|
+
* best-effort and tolerant: a missing/malformed session config, an absent `promptRunID`
|
|
259
|
+
* (observability creation was skipped), a failed load, or a failed save all log and return
|
|
260
|
+
* `false` — usage relay must never break a live call.
|
|
261
|
+
*
|
|
262
|
+
* @param inputTokens Input-token DELTA to add (negative/non-finite values are clamped to 0).
|
|
263
|
+
* @param outputTokens Output-token DELTA to add (negative/non-finite values are clamped to 0).
|
|
264
|
+
* @returns `true` when the accumulated usage was persisted; `false` on any tolerated failure.
|
|
265
|
+
*/
|
|
266
|
+
RelayRealtimeUsage(agentSessionId: string, inputTokens: number, outputTokens: number, { userPayload, providers }: AppContext): Promise<boolean>;
|
|
267
|
+
/** Clamps a relayed token delta: negative / non-finite values become 0. */
|
|
268
|
+
private clampTokenDelta;
|
|
269
|
+
/**
|
|
270
|
+
* Reads the co-agent `AIPromptRun` id from the session config WITHOUT the relay path's
|
|
271
|
+
* throw-on-missing-target semantics — usage relay is best-effort, so a missing/malformed
|
|
272
|
+
* config or absent id just logs and returns `null`.
|
|
273
|
+
*/
|
|
274
|
+
private readPromptRunID;
|
|
275
|
+
/**
|
|
276
|
+
* Persist an interactive channel's state of record (e.g. the live whiteboard's serialized
|
|
277
|
+
* scene) onto the session's `MJ: AI Agent Session Channels` row.
|
|
278
|
+
*
|
|
279
|
+
* Flow:
|
|
280
|
+
* 1. Ownership gate — like the sibling mutations, the session's `UserID` must equal the
|
|
281
|
+
* caller's. Unlike Execute/Relay, a **Closed** session is accepted: the client's final
|
|
282
|
+
* on-end flush legitimately lands after `CloseAgentSession` has run.
|
|
283
|
+
* 2. Resolve the channel definition (`MJ: AI Agent Channels`) by `channelName`. When no
|
|
284
|
+
* active definition row exists (the deployment hasn't synced the channel seed yet),
|
|
285
|
+
* return `false` gracefully and log — never throw for a missing definition.
|
|
286
|
+
* 3. Offer the payload to the session's SERVER-SIDE channel plugin (the registry row's
|
|
287
|
+
* `ServerPluginClass`, held per-session by {@link RealtimeChannelServerHost}) for
|
|
288
|
+
* validation/normalization — strictly best-effort: no plugin, a plugin failure, or an
|
|
289
|
+
* oversized replacement all fall back to persisting the original payload.
|
|
290
|
+
* 4. Upsert the session-channel row: create it (Status `Connected`) when missing, store
|
|
291
|
+
* the (possibly normalized) state in its `Config` field, and stamp `LastActiveAt`.
|
|
292
|
+
*
|
|
293
|
+
* @returns `true` when the state was persisted; `false` on any tolerated failure (missing
|
|
294
|
+
* channel definition, oversized state, save failure) — all logged.
|
|
295
|
+
*/
|
|
296
|
+
SaveSessionChannelState(agentSessionId: string, channelName: string, stateJson: string, { userPayload, providers }: AppContext): Promise<boolean>;
|
|
297
|
+
/**
|
|
298
|
+
* Routes a landed channel-state save through the session's server-side channel plugin (when
|
|
299
|
+
* one resolved at session start) so it can validate/normalize the payload PRE-persistence.
|
|
300
|
+
* Strictly best-effort: any host/plugin problem — including a replacement that exceeds the
|
|
301
|
+
* channel-state size cap — falls back to the client's original payload, which already passed
|
|
302
|
+
* the cap. A plugin can transform a save; it can never lose or block one.
|
|
303
|
+
*/
|
|
304
|
+
private applyChannelServerPlugin;
|
|
305
|
+
/**
|
|
306
|
+
* Persist an interactive channel's current state (e.g. the live whiteboard's board JSON) as a
|
|
307
|
+
* durable, user-owned **artifact** — distinct from {@link SaveSessionChannelState}, which only
|
|
308
|
+
* stores the session-scoped state of record. The artifact survives the session and shows up in
|
|
309
|
+
* the user's artifact library (and, when possible, in the conversation's history).
|
|
310
|
+
*
|
|
311
|
+
* Flow:
|
|
312
|
+
* 1. Ownership gate — like the sibling mutations (`UserID === contextUser.ID`). A **Closed**
|
|
313
|
+
* session is accepted: "save my board" legitimately arrives as/after the call ends.
|
|
314
|
+
* 2. Size cap — `contentJson` beyond {@link MAX_CHANNEL_STATE_CHARS} is a structured failure.
|
|
315
|
+
* 3. Resolve the {@link WHITEBOARD_ARTIFACT_TYPE_NAME} artifact type by name; when the seed is
|
|
316
|
+
* absent/disabled in this deployment, return a structured failure (never throw).
|
|
317
|
+
* 4. Create the `MJ: Artifacts` header (user-owned, Visibility `Always`) + its
|
|
318
|
+
* `MJ: Artifact Versions` v1 row carrying `contentJson`.
|
|
319
|
+
* 5. Best-effort extras that never fail the save:
|
|
320
|
+
* - when the session has a conversation AND a `Conversation Detail` stamped with this
|
|
321
|
+
* session id exists (the transcript relay stamps `AgentSessionID`), link the version into
|
|
322
|
+
* history via a `MJ: Conversation Detail Artifacts` junction row (Direction `Output`);
|
|
323
|
+
* - stamp `LastActiveAt` on the session-channel row when one exists.
|
|
324
|
+
*
|
|
325
|
+
* @returns A structured {@link SaveSessionChannelArtifactResult} — graceful failures (missing
|
|
326
|
+
* type seed, oversized content, save failure) come back as `Success: false`, while
|
|
327
|
+
* authn/ownership violations throw like the sibling mutations.
|
|
328
|
+
*/
|
|
329
|
+
SaveSessionChannelArtifact(agentSessionId: string, channelName: string, name: string, contentJson: string, { userPayload, providers }: AppContext): Promise<SaveSessionChannelArtifactResult>;
|
|
330
|
+
/** Resolve the request user + read-write provider, throwing a clear error if unauthenticated. */
|
|
331
|
+
private requireUserAndProvider;
|
|
332
|
+
/**
|
|
333
|
+
* Authorization gate for {@link RealtimeClientSessionResolver.StartRealtimeClientSession}: the
|
|
334
|
+
* caller must be able to run the **target** agent. Denial throws (no session is created).
|
|
335
|
+
*/
|
|
336
|
+
private assertCanRunTarget;
|
|
337
|
+
/**
|
|
338
|
+
* Resolves the AUTHORITATIVE target agent id under the co-agent's PAIRING CONSTRAINTS
|
|
339
|
+
* (`MJ: AI Agent Co Agents`, ordered by `Sequence`):
|
|
340
|
+
*
|
|
341
|
+
* - **Zero pairing rows (universal co-agent)** — today's behavior untouched: the runtime
|
|
342
|
+
* `targetAgentId` is required (clear error when absent) and used as-is. Pairings are NEVER
|
|
343
|
+
* mandated; zero-config deployments keep working with zero metadata.
|
|
344
|
+
* - **Rows + runtime target** — the supplied target must be IN the paired list; a target
|
|
345
|
+
* outside the list is a clear structured error (the UX builds its picker from the same rows).
|
|
346
|
+
* - **Rows + no runtime target** — the `IsDefault` row stands in; when no default row exists,
|
|
347
|
+
* a clear error asks for an explicit target.
|
|
348
|
+
*
|
|
349
|
+
* Pairing rows are a TARGETING constraint layered on top of — never a replacement for — the
|
|
350
|
+
* `CanRun` security gate, which the caller applies to the resolved target id immediately
|
|
351
|
+
* after. A failed/erroring pairing query therefore degrades to the universal behavior
|
|
352
|
+
* (logged), it never breaks session starts.
|
|
353
|
+
*
|
|
354
|
+
* @param coAgentID The resolved co-agent id.
|
|
355
|
+
* @param requestedTargetId The runtime `targetAgentId` argument, when supplied.
|
|
356
|
+
* @returns The effective target agent id (canonical casing from the pairing row when matched).
|
|
357
|
+
*/
|
|
358
|
+
private resolveConstrainedTargetAgentID;
|
|
359
|
+
/**
|
|
360
|
+
* Loads the co-agent's pairing rows from {@link AIEngineBase}'s cached
|
|
361
|
+
* `MJ: AI Agent Co Agents` metadata (provider-scoped engine instance, lazy `Config`),
|
|
362
|
+
* ordered by `Sequence`. Only **Active** rows of relationship **Type `'CoAgent'`** with a
|
|
363
|
+
* SPECIFIC agent target participate — type-level rows (`TargetAgentTypeID`) express the
|
|
364
|
+
* type-default in the resolution chain, not a target restriction, and reserved relationship
|
|
365
|
+
* types are ignored until their features ship. Tolerant — a failed cache load logs and
|
|
366
|
+
* returns `[]` (degrading to the universal behavior; pairing is a targeting constraint,
|
|
367
|
+
* `CanRun` remains the security gate), never throws.
|
|
368
|
+
*/
|
|
369
|
+
private loadPairingRows;
|
|
370
|
+
/**
|
|
371
|
+
* The provider-scoped {@link AIEngineBase} instance for this request's connection, lazily
|
|
372
|
+
* configured (`Config(false, ...)` is a no-op when the cache is already loaded). Pairing rows
|
|
373
|
+
* and channel definitions are small metadata tables the engine caches — reading them here
|
|
374
|
+
* replaces per-call RunViews.
|
|
375
|
+
*/
|
|
376
|
+
private configuredAIEngineBase;
|
|
377
|
+
/**
|
|
378
|
+
* The RUNTIME-OVERRIDE AUTHORIZATION GATE: `configOverridesJson` and a DEVIATING explicit
|
|
379
|
+
* `preferredModelId` both require the `Realtime: Advanced Session Controls` authorization
|
|
380
|
+
* (evaluated hierarchy-aware over the caller's roles); a deviating model is additionally
|
|
381
|
+
* subject to the effective `realtime.allowUserModelOverride` policy (which blocks even
|
|
382
|
+
* authorized callers when `false`). Denial THROWS a structured reason — never a silent
|
|
383
|
+
* ignore. Plain starts (no overrides, no explicit model) pass through untouched; an explicit
|
|
384
|
+
* model EQUAL to the co-agent's metadata-configured preference is not a deviation.
|
|
385
|
+
*
|
|
386
|
+
* Judgment calls baked in (per the approved product rules): co-agent selection (`coAgentId`)
|
|
387
|
+
* and target selection within a pairing list / for a universal co-agent are NORMAL user flow
|
|
388
|
+
* — they stay behind the existing `CanRun` gate only and are not touched here.
|
|
389
|
+
*/
|
|
390
|
+
private assertRuntimeOverridesAuthorized;
|
|
391
|
+
/**
|
|
392
|
+
* The co-agent's BASELINE effective configuration — type `DefaultConfiguration` ← agent
|
|
393
|
+
* `TypeConfiguration`, WITHOUT the runtime layer (this feeds the gate that decides whether
|
|
394
|
+
* the runtime layer is even allowed). Tolerant of an unloaded metadata cache.
|
|
395
|
+
*/
|
|
396
|
+
private resolveBaselineEffectiveConfig;
|
|
397
|
+
/**
|
|
398
|
+
* Resolves the baseline config's `realtime.modelPreference` (Name or ID) to an
|
|
399
|
+
* `MJ: AI Models.ID`, so a `preferredModelId` equal to it is recognized as a NON-deviation.
|
|
400
|
+
* When the preference matches no cached model (or the cache is unavailable), the RAW
|
|
401
|
+
* preference string is returned — an ID-style preference then still compares equal to the
|
|
402
|
+
* matching `preferredModelId` (case-insensitive), while a name-style preference simply won't
|
|
403
|
+
* match an id (and the gate treats the request as a deviation — the fail-safe direction).
|
|
404
|
+
* `null` only when no preference is configured at all.
|
|
405
|
+
*/
|
|
406
|
+
private resolveMetadataPreferredModelID;
|
|
407
|
+
/**
|
|
408
|
+
* Hierarchy-aware check for the `Realtime: Advanced Session Controls` authorization against
|
|
409
|
+
* the request provider's cached Authorizations + the caller's roles. FAIL-CLOSED: an absent
|
|
410
|
+
* authorization row (un-synced seed) or an evaluation error denies — runtime overrides are a
|
|
411
|
+
* privileged path, and unauthorized callers still get a fully working session without them.
|
|
412
|
+
*/
|
|
413
|
+
private userHasAdvancedSessionControls;
|
|
414
|
+
/**
|
|
415
|
+
* Resolves the co-agent (the Realtime-type agent that voices the target agent) for a new
|
|
416
|
+
* client-direct session via the metadata-driven **CO-AGENT RESOLUTION CHAIN** — first match
|
|
417
|
+
* wins, evaluated in precedence order:
|
|
418
|
+
*
|
|
419
|
+
* 1. **Runtime parameter** — the mutation's explicit `coAgentId`. A per-call override; an
|
|
420
|
+
* invalid candidate (unknown, not Active, or not of the Realtime agent type) **throws**
|
|
421
|
+
* (fail loud — the caller asked for something specific, mirroring `preferredModelId`).
|
|
422
|
+
* 2. **Per-agent persona** — the target agent's `AIAgent.DefaultCoAgentID`. An invalid
|
|
423
|
+
* reference logs a warning and **falls through** to the next step (stale metadata should
|
|
424
|
+
* degrade gracefully, never break calls).
|
|
425
|
+
* 3. **Per-type default** — an Active `AIAgentCoAgent` row of Type `'CoAgent'` whose
|
|
426
|
+
* `TargetAgentTypeID` is the target agent's type and `IsDefault = 1` (lowest `Sequence`
|
|
427
|
+
* wins a tie). Same tolerant warn-and-fall-through semantics as step 2.
|
|
428
|
+
* 4. **Global default** — the seeded {@link REALTIME_CO_AGENT_NAME} agent, looked up by name
|
|
429
|
+
* (with a deprecated fallback to {@link LEGACY_REALTIME_CO_AGENT_NAME}). Throws when absent
|
|
430
|
+
* entirely (the realtime feature is unconfigured in this deployment).
|
|
431
|
+
*
|
|
432
|
+
* Every candidate from steps 1–3 is validated by {@link findValidCoAgent}: it must exist in
|
|
433
|
+
* {@link AIEngine}'s cached agents, have Status `Active`, and be of the
|
|
434
|
+
* {@link REALTIME_AGENT_TYPE_NAME} agent type. Step 4 keeps its original name-lookup contract.
|
|
435
|
+
*
|
|
436
|
+
* @param targetAgentId The agent the co-agent will voice on behalf of (drives steps 2–3).
|
|
437
|
+
* @param explicitCoAgentId The runtime `coAgentId` mutation argument, when supplied (step 1).
|
|
438
|
+
* @returns The resolved co-agent's id (canonical casing from the metadata cache).
|
|
439
|
+
*/
|
|
440
|
+
private resolveCoAgentID;
|
|
441
|
+
/**
|
|
442
|
+
* Chain step 3 lookup: the TYPE-LEVEL default co-agent for an agent type — the Active
|
|
443
|
+
* `AIAgentCoAgent` row of Type `'CoAgent'` whose `TargetAgentTypeID` matches, with
|
|
444
|
+
* `IsDefault = 1` preferred and the lowest `Sequence` breaking ties (so a deployment can
|
|
445
|
+
* stage multiple type-level candidates deterministically). Reads {@link AIEngineBase}'s
|
|
446
|
+
* cached rows — no RunView. Tolerant: a failed cache read logs and returns `undefined`
|
|
447
|
+
* (the chain falls through to the global default).
|
|
448
|
+
*/
|
|
449
|
+
private findTypeDefaultCoAgentID;
|
|
450
|
+
/**
|
|
451
|
+
* Validates one metadata-level co-agent default (chain steps 2/3). Returns the resolved
|
|
452
|
+
* co-agent id when the reference is valid; logs a warning and returns `null` (caller falls
|
|
453
|
+
* through to the next chain step) when the reference is set but invalid — metadata drift must
|
|
454
|
+
* degrade, not break live calls. A `null`/absent reference returns `null` silently.
|
|
455
|
+
*/
|
|
456
|
+
private resolveMetadataDefault;
|
|
457
|
+
/**
|
|
458
|
+
* Validates a co-agent candidate against {@link AIEngine}'s cached metadata. A valid co-agent
|
|
459
|
+
* must (a) exist, (b) have Status `Active`, and (c) be of the {@link REALTIME_AGENT_TYPE_NAME}
|
|
460
|
+
* agent type. Returns the cached agent on success, or a human-readable `problem` on failure —
|
|
461
|
+
* the CALLER decides whether that's fatal (explicit runtime param) or tolerated (metadata default).
|
|
462
|
+
*/
|
|
463
|
+
private findValidCoAgent;
|
|
464
|
+
/**
|
|
465
|
+
* Resolves the seeded Realtime Co-Agent's id from {@link AIEngine}'s cached agents — the GLOBAL
|
|
466
|
+
* DEFAULT (step 4) of the co-agent resolution chain. The engine is already configured by
|
|
467
|
+
* {@link resolveCoAgentID}. Looks up the current {@link REALTIME_CO_AGENT_NAME} first, then
|
|
468
|
+
* falls back to the DEPRECATED {@link LEGACY_REALTIME_CO_AGENT_NAME} (pre-rename seed) with a
|
|
469
|
+
* deprecation log so un-resynced deployments keep working. Throws a clear error when neither
|
|
470
|
+
* name is present in metadata.
|
|
471
|
+
*/
|
|
472
|
+
private resolveGlobalCoAgentID;
|
|
473
|
+
/** Case/whitespace-insensitive agent lookup by Name in {@link AIEngine}'s cached agents. */
|
|
474
|
+
private findAgentByName;
|
|
475
|
+
/**
|
|
476
|
+
* Mints the client session config for a just-created session. On failure, closes the session so
|
|
477
|
+
* no half-open record leaks, then throws the service's error message.
|
|
478
|
+
*
|
|
479
|
+
* @param preferredModelId Optional explicit realtime model choice — threaded to the service,
|
|
480
|
+
* which FAILS (no silent fallback) when the chosen model cannot be satisfied.
|
|
481
|
+
* @param priorTranscript Optional capped, role-tagged transcript of the PRIOR session chain
|
|
482
|
+
* (from {@link loadPriorTranscript}) — the service frames it into the system prompt so a
|
|
483
|
+
* resumed session remembers the previous leg(s).
|
|
484
|
+
*/
|
|
485
|
+
private prepareClientSessionOrClose;
|
|
486
|
+
/**
|
|
487
|
+
* Writes the co-agent observability run ids into the session's `Config_` alongside the
|
|
488
|
+
* authoritative `targetAgentID`, then saves the session. These ids are read back on close to
|
|
489
|
+
* finalize the runs (and on relay to nest delegated runs). Best-effort: a save failure is logged,
|
|
490
|
+
* not thrown — the voice session still proceeds, it just won't carry the run ids.
|
|
491
|
+
*/
|
|
492
|
+
private persistObservabilityRunIDs;
|
|
493
|
+
/**
|
|
494
|
+
* Loads a session, enforcing inbound ownership (`UserID === contextUser.ID`) and that it is not
|
|
495
|
+
* `Closed`. Throws on any violation. Returns the loaded session for the caller to read from.
|
|
496
|
+
*/
|
|
497
|
+
private loadOwnedActiveSession;
|
|
498
|
+
/**
|
|
499
|
+
* Loads a session and enforces inbound ownership (`UserID === contextUser.ID`) WITHOUT
|
|
500
|
+
* rejecting `Closed` sessions — {@link SaveSessionChannelState}'s final on-end flush
|
|
501
|
+
* legitimately arrives after the session closed. Throws when not found / not owned.
|
|
502
|
+
*/
|
|
503
|
+
private loadOwnedSession;
|
|
504
|
+
/**
|
|
505
|
+
* Resolves an ACTIVE channel definition (`MJ: AI Agent Channels`) by name from
|
|
506
|
+
* {@link AIEngineBase}'s cached `AgentChannels` (provider-scoped engine instance, lazy
|
|
507
|
+
* `Config` — no per-call RunView; name matching is trim + case-insensitive, parity with the
|
|
508
|
+
* previous SQL-collation lookup). Returns its id, or `null` (logged) when no active
|
|
509
|
+
* definition exists — the channel seed is deployed separately, so its absence is tolerated,
|
|
510
|
+
* never thrown.
|
|
511
|
+
*/
|
|
512
|
+
private resolveChannelID;
|
|
513
|
+
/**
|
|
514
|
+
* Upserts the session-channel row for `(agentSessionID, channelID)`: creates it with Status
|
|
515
|
+
* `Connected` when missing, stores `stateJson` in `Config`, and stamps `LastActiveAt`.
|
|
516
|
+
* Returns the boolean save result (logged on failure).
|
|
517
|
+
*/
|
|
518
|
+
private upsertSessionChannelState;
|
|
519
|
+
/**
|
|
520
|
+
* Loads the PRIOR session's persisted channel states and serializes them as a JSON object
|
|
521
|
+
* string keyed by channel NAME (the session-channel view denormalizes the channel name as the
|
|
522
|
+
* `Channel` virtual field). Strictly best-effort — every failure path logs and returns `null`,
|
|
523
|
+
* a session start NEVER fails because of restore:
|
|
524
|
+
* - no `lastSessionId` → `null` (silently — restore simply wasn't requested);
|
|
525
|
+
* - prior session missing OR owned by a different user → `null` (logged — never leak another
|
|
526
|
+
* user's channel state);
|
|
527
|
+
* - rows with an empty `Config` are skipped;
|
|
528
|
+
* - an individual state larger than {@link MAX_CHANNEL_STATE_CHARS} is dropped (logged), and
|
|
529
|
+
* states that would push the accumulated payload past that same cap are dropped too (logged)
|
|
530
|
+
* — the surviving states still restore;
|
|
531
|
+
* - no surviving states → `null`.
|
|
532
|
+
*/
|
|
533
|
+
private loadPriorChannelStatesJson;
|
|
534
|
+
/**
|
|
535
|
+
* Loads the PRIOR session chain's transcript for model-context hydration: when a new session
|
|
536
|
+
* carries `lastSessionId`, the chain's persisted, session-stamped `Conversation Details` are
|
|
537
|
+
* folded into capped, role-tagged lines (`User: …` / `Assistant: …`) the prepare service
|
|
538
|
+
* frames into the system prompt — so the model REMEMBERS the previous live leg(s).
|
|
539
|
+
*
|
|
540
|
+
* Chain walk + caps:
|
|
541
|
+
* - follows `LastSessionID` BACKWARDS from `lastSessionId`, at most
|
|
542
|
+
* {@link MAX_PRIOR_TRANSCRIPT_LEGS} legs, with a visited-set cycle guard (A→B→A stops);
|
|
543
|
+
* - EVERY leg is ownership-checked like the channel-state restore — the FIRST leg failing
|
|
544
|
+
* the check aborts hydration entirely (first leg) or ends the walk (deeper legs), so
|
|
545
|
+
* another user's transcript can never leak;
|
|
546
|
+
* - one details query covers all collected legs, chronological;
|
|
547
|
+
* - hidden/error/empty rows are skipped, then the NEWEST {@link MAX_PRIOR_TRANSCRIPT_TURNS}
|
|
548
|
+
* turns are kept and the total is capped at {@link MAX_PRIOR_TRANSCRIPT_CHARS} chars
|
|
549
|
+
* (oldest dropped first).
|
|
550
|
+
*
|
|
551
|
+
* Strictly best-effort: every failure path logs and returns `undefined` — hydration NEVER
|
|
552
|
+
* blocks a session start.
|
|
553
|
+
*/
|
|
554
|
+
private loadPriorTranscript;
|
|
555
|
+
/**
|
|
556
|
+
* Walks the prior-session chain backwards from `lastSessionId`, returning the OWNED leg ids
|
|
557
|
+
* (newest first). The first leg must exist and be owned by the caller, else `[]` (logged);
|
|
558
|
+
* deeper-leg problems (missing, unowned, cycle) just end the walk.
|
|
559
|
+
*/
|
|
560
|
+
private collectOwnedPriorLegIDs;
|
|
561
|
+
/**
|
|
562
|
+
* Loads the chain legs' visible transcript turns (session-stamped `Conversation Details`,
|
|
563
|
+
* `Role` User/AI, not hidden, non-empty) in ONE chronological query across all legs.
|
|
564
|
+
*/
|
|
565
|
+
private loadChainTranscriptTurns;
|
|
566
|
+
/**
|
|
567
|
+
* Maps visible turns to role-tagged lines and applies the hydration caps: the NEWEST
|
|
568
|
+
* {@link MAX_PRIOR_TRANSCRIPT_TURNS} turns, then a total budget of
|
|
569
|
+
* {@link MAX_PRIOR_TRANSCRIPT_CHARS} chars — oldest lines dropped first in both passes,
|
|
570
|
+
* so the model always keeps the freshest context.
|
|
571
|
+
*/
|
|
572
|
+
private capTranscriptLines;
|
|
573
|
+
/**
|
|
574
|
+
* Folds prior session-channel rows into a `{ channelName: stateJson }` map, skipping empty
|
|
575
|
+
* states and enforcing the {@link MAX_CHANNEL_STATE_CHARS} cap both per-state and on the
|
|
576
|
+
* accumulated total (oversized states are dropped with a log note; the rest survive).
|
|
577
|
+
*/
|
|
578
|
+
private collectChannelStates;
|
|
579
|
+
/**
|
|
580
|
+
* Resolves the ENABLED {@link WHITEBOARD_ARTIFACT_TYPE_NAME} artifact type's id. Returns `null`
|
|
581
|
+
* (logged) when the type seed is absent or disabled — the artifact-type seed is deployed
|
|
582
|
+
* separately, so its absence is a graceful structured failure, never a throw.
|
|
583
|
+
*/
|
|
584
|
+
private resolveWhiteboardArtifactTypeID;
|
|
585
|
+
/**
|
|
586
|
+
* Creates the user-owned artifact header + its v1 version for a channel-state save, then runs
|
|
587
|
+
* the best-effort extras (conversation-history junction + session-channel `LastActiveAt`
|
|
588
|
+
* stamp). Header/version save failures come back as structured failures; the extras NEVER
|
|
589
|
+
* fail the save.
|
|
590
|
+
*/
|
|
591
|
+
private createChannelArtifact;
|
|
592
|
+
/**
|
|
593
|
+
* Best-effort: links an artifact version into conversation history the way chat does — via a
|
|
594
|
+
* `MJ: Conversation Detail Artifacts` junction row (Direction `Output`) against the LATEST
|
|
595
|
+
* `Conversation Detail` stamped with this session's id (the transcript relay stamps
|
|
596
|
+
* `AgentSessionID` on every persisted turn). Skips silently (returns `false`) when the session
|
|
597
|
+
* has no conversation or no stamped detail exists yet; logs (and returns `false`) when the
|
|
598
|
+
* lookup or junction save fails. Never throws.
|
|
599
|
+
*/
|
|
600
|
+
private linkVersionToLatestSessionDetail;
|
|
601
|
+
/**
|
|
602
|
+
* Best-effort: junction-links the artifacts a DELEGATED target-agent run produced into the
|
|
603
|
+
* session's conversation history — closing the gap where voice-path artifacts were created
|
|
604
|
+
* (`RealtimeClientSessionService.createDelegatedRunArtifacts`) but never reached
|
|
605
|
+
* `MJ: Conversation Detail Artifacts`, leaving chat, session review, and resume carryover
|
|
606
|
+
* blind to them.
|
|
607
|
+
*
|
|
608
|
+
* ANCHOR CHOICE: the junction needs a `Conversation Detail`. We anchor to the MOST RECENT
|
|
609
|
+
* detail stamped with this session's id (the transcript relay stamps `AgentSessionID` on every
|
|
610
|
+
* persisted turn) — the turn closest to the tool call that produced the artifact. When NO
|
|
611
|
+
* stamped detail exists yet (the delegated run finished before the browser relayed any
|
|
612
|
+
* transcript — the relay is asynchronous), we create a minimal HIDDEN anchor detail
|
|
613
|
+
* (`Role: 'AI'`, `HiddenToUser: true`, stamped with the session id + conversation + user)
|
|
614
|
+
* rather than dropping the link: `HiddenToUser` keeps it out of the visible chat thread and
|
|
615
|
+
* out of review-mode captions (both filter hidden rows), while conversation-level artifact
|
|
616
|
+
* queries (which scan junctions across ALL details) still surface the artifact. This is the
|
|
617
|
+
* least-invasive correct anchor — no fake visible message, no orphaned artifact.
|
|
618
|
+
*
|
|
619
|
+
* Strictly best-effort: every failure path logs and returns; a relayed tool call NEVER fails
|
|
620
|
+
* because history linking did.
|
|
621
|
+
*/
|
|
622
|
+
private linkDelegatedArtifactsToConversation;
|
|
623
|
+
/**
|
|
624
|
+
* Finds the LATEST `Conversation Detail` stamped with this session's id (the transcript relay
|
|
625
|
+
* stamps `AgentSessionID` on every persisted turn). Returns its id, or `null` when none exists.
|
|
626
|
+
*/
|
|
627
|
+
private findLatestSessionDetailID;
|
|
628
|
+
/**
|
|
629
|
+
* Creates the minimal HIDDEN anchor `Conversation Detail` for artifact junction rows when no
|
|
630
|
+
* session-stamped transcript turn exists yet (see the anchor-choice rationale on
|
|
631
|
+
* {@link linkDelegatedArtifactsToConversation}). Returns the new detail's id, or `null`
|
|
632
|
+
* (logged) when the save fails.
|
|
633
|
+
*/
|
|
634
|
+
private createHiddenSessionAnchorDetail;
|
|
635
|
+
/**
|
|
636
|
+
* Saves one `MJ: Conversation Detail Artifacts` junction row (Direction `Output`) linking an
|
|
637
|
+
* artifact version to a conversation detail. Returns the boolean save result (logged on failure).
|
|
638
|
+
*/
|
|
639
|
+
private saveDetailArtifactJunction;
|
|
640
|
+
/**
|
|
641
|
+
* Best-effort: stamps `LastActiveAt` on the session-channel row for `(session, channelName)`
|
|
642
|
+
* when one exists — saving an artifact IS channel activity. Missing channel definition or
|
|
643
|
+
* session-channel row is a silent no-op; failures log and never throw.
|
|
644
|
+
*/
|
|
645
|
+
private stampSessionChannelLastActive;
|
|
646
|
+
/**
|
|
647
|
+
* Tolerantly parses + validates client-declared UI tool definitions (see the SECURITY NOTE on
|
|
648
|
+
* {@link StartRealtimeClientSession}). Never throws — any rejection logs and returns
|
|
649
|
+
* `undefined` (the session simply minted without client tools):
|
|
650
|
+
* - non-JSON / non-array payloads are rejected wholesale;
|
|
651
|
+
* - payloads larger than {@link MAX_CLIENT_TOOLS_JSON_CHARS} are rejected wholesale;
|
|
652
|
+
* - more than {@link MAX_CLIENT_TOOLS} declarations are rejected wholesale (a legitimate
|
|
653
|
+
* client never sends that many — a flood is a misbehaving caller, not a trim candidate);
|
|
654
|
+
* - individual entries failing the shape check (`Name`/`Description` non-empty strings,
|
|
655
|
+
* `ParametersSchema` a plain object) are skipped, the rest survive.
|
|
656
|
+
*/
|
|
657
|
+
private parseClientTools;
|
|
658
|
+
/** Shape check for one client tool declaration: Name/Description non-empty strings, ParametersSchema a plain object. */
|
|
659
|
+
private isValidClientTool;
|
|
660
|
+
/**
|
|
661
|
+
* Parses the session's persisted config, returning the authoritative `targetAgentID` plus any
|
|
662
|
+
* observability run ids (`coAgentRunID`/`promptRunID`). Throws when the config is missing/malformed
|
|
663
|
+
* or carries no target — a relay cannot proceed without a target.
|
|
664
|
+
*/
|
|
665
|
+
private readSessionConfig;
|
|
666
|
+
/**
|
|
667
|
+
* Persists a single transcript turn as a `Conversation Detail` stamped with the session's
|
|
668
|
+
* conversation, the mapped role, the turn text, the session id, and the owning user.
|
|
669
|
+
*
|
|
670
|
+
* @returns The boolean save result (logs `CompleteMessage` on failure).
|
|
671
|
+
*/
|
|
672
|
+
private persistTranscriptTurn;
|
|
673
|
+
/**
|
|
674
|
+
* CORRECTION persistence (the client transcript's `ReplacesPrevious` marker — e.g.
|
|
675
|
+
* ElevenLabs' post-barge-in `agent_response_correction`): UPDATES the session's most
|
|
676
|
+
* recently persisted turn of the same role IN PLACE with the corrected text, instead of
|
|
677
|
+
* appending a near-duplicate. Falls back to a plain insert when no prior turn exists
|
|
678
|
+
* (the superseded turn may have failed to persist) — a correction is never dropped.
|
|
679
|
+
*/
|
|
680
|
+
private replacePreviousTranscriptTurn;
|
|
681
|
+
/**
|
|
682
|
+
* Maps a transport-level transcript role (`'user'` / `'assistant'`) onto the
|
|
683
|
+
* `Conversation Detail` entity's `Role` union (`'User'` / `'AI'`). Anything not the user is
|
|
684
|
+
* treated as the assistant/AI side.
|
|
685
|
+
*/
|
|
686
|
+
private mapTranscriptRole;
|
|
687
|
+
}
|
|
688
|
+
//# sourceMappingURL=RealtimeClientSessionResolver.d.ts.map
|