@memberjunction/ai-agents 6.2.0-edge.1 → 6.2.0-edge.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/AgentDecisionService.d.ts +90 -0
- package/dist/AgentDecisionService.d.ts.map +1 -0
- package/dist/AgentDecisionService.js +159 -0
- package/dist/AgentDecisionService.js.map +1 -0
- package/dist/AgentRunner.d.ts +9 -0
- package/dist/AgentRunner.d.ts.map +1 -1
- package/dist/AgentRunner.js +23 -7
- package/dist/AgentRunner.js.map +1 -1
- package/dist/ConversationCompactionManager.d.ts +7 -1
- package/dist/ConversationCompactionManager.d.ts.map +1 -1
- package/dist/ConversationCompactionManager.js +4 -1
- package/dist/ConversationCompactionManager.js.map +1 -1
- package/dist/KnowledgeAgent.d.ts.map +1 -1
- package/dist/KnowledgeAgent.js +6 -6
- package/dist/KnowledgeAgent.js.map +1 -1
- package/dist/PayloadFeedbackManager.d.ts +116 -14
- package/dist/PayloadFeedbackManager.d.ts.map +1 -1
- package/dist/PayloadFeedbackManager.js +229 -88
- package/dist/PayloadFeedbackManager.js.map +1 -1
- package/dist/agent-context-injector.d.ts +47 -3
- package/dist/agent-context-injector.d.ts.map +1 -1
- package/dist/agent-context-injector.js +77 -10
- package/dist/agent-context-injector.js.map +1 -1
- package/dist/agent-memory-context-builder.d.ts +13 -0
- package/dist/agent-memory-context-builder.d.ts.map +1 -1
- package/dist/agent-memory-context-builder.js +24 -5
- package/dist/agent-memory-context-builder.js.map +1 -1
- package/dist/agent-types/flow-agent-type.d.ts +97 -0
- package/dist/agent-types/flow-agent-type.d.ts.map +1 -1
- package/dist/agent-types/flow-agent-type.js +281 -24
- package/dist/agent-types/flow-agent-type.js.map +1 -1
- package/dist/agent-types/flow-graph-executor.d.ts +12 -1
- package/dist/agent-types/flow-graph-executor.d.ts.map +1 -1
- package/dist/agent-types/flow-graph-executor.js +25 -1
- package/dist/agent-types/flow-graph-executor.js.map +1 -1
- package/dist/agent-types/loop-agent-prompt-params.d.ts +239 -66
- package/dist/agent-types/loop-agent-prompt-params.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-prompt-params.js +47 -2
- package/dist/agent-types/loop-agent-prompt-params.js.map +1 -1
- package/dist/agent-types/loop-agent-response-type.d.ts +12 -1
- package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-response-type.js.map +1 -1
- package/dist/agent-types/loop-agent-type.d.ts +26 -0
- package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-type.js +115 -5
- package/dist/agent-types/loop-agent-type.js.map +1 -1
- package/dist/base-agent.d.ts +527 -10
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +1779 -110
- package/dist/base-agent.js.map +1 -1
- package/dist/catalog-narrowing.d.ts +136 -0
- package/dist/catalog-narrowing.d.ts.map +1 -0
- package/dist/catalog-narrowing.js +212 -0
- package/dist/catalog-narrowing.js.map +1 -0
- package/dist/decision-discovery.d.ts +401 -0
- package/dist/decision-discovery.d.ts.map +1 -0
- package/dist/decision-discovery.js +511 -0
- package/dist/decision-discovery.js.map +1 -0
- package/dist/finish-if-mode-warnings.d.ts +39 -0
- package/dist/finish-if-mode-warnings.d.ts.map +1 -0
- package/dist/finish-if-mode-warnings.js +78 -0
- package/dist/finish-if-mode-warnings.js.map +1 -0
- package/dist/finish-if-state.d.ts +72 -0
- package/dist/finish-if-state.d.ts.map +1 -0
- package/dist/finish-if-state.js +109 -0
- package/dist/finish-if-state.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/memory-manager-agent.d.ts +108 -2
- package/dist/memory-manager-agent.d.ts.map +1 -1
- package/dist/memory-manager-agent.js +172 -9
- package/dist/memory-manager-agent.js.map +1 -1
- package/dist/memory-note-gate.d.ts +208 -0
- package/dist/memory-note-gate.d.ts.map +1 -0
- package/dist/memory-note-gate.js +294 -0
- package/dist/memory-note-gate.js.map +1 -0
- package/dist/native-tools/action-tool-builder.d.ts +14 -0
- package/dist/native-tools/action-tool-builder.d.ts.map +1 -1
- package/dist/native-tools/action-tool-builder.js +62 -7
- package/dist/native-tools/action-tool-builder.js.map +1 -1
- package/dist/native-tools/control-tools.d.ts +17 -1
- package/dist/native-tools/control-tools.d.ts.map +1 -1
- package/dist/native-tools/control-tools.js +40 -3
- package/dist/native-tools/control-tools.js.map +1 -1
- package/dist/realtime/bridge-host-params.d.ts +22 -0
- package/dist/realtime/bridge-host-params.d.ts.map +1 -0
- package/dist/realtime/bridge-host-params.js +53 -0
- package/dist/realtime/bridge-host-params.js.map +1 -0
- package/dist/realtime/bridge-realtime-session-factory.d.ts +23 -2
- package/dist/realtime/bridge-realtime-session-factory.d.ts.map +1 -1
- package/dist/realtime/bridge-realtime-session-factory.js +22 -6
- package/dist/realtime/bridge-realtime-session-factory.js.map +1 -1
- package/dist/realtime/bridge-room-transcript-sink.d.ts.map +1 -1
- package/dist/realtime/bridge-room-transcript-sink.js +15 -9
- package/dist/realtime/bridge-room-transcript-sink.js.map +1 -1
- package/dist/realtime/bridge-session-transcript-sink.d.ts +37 -0
- package/dist/realtime/bridge-session-transcript-sink.d.ts.map +1 -0
- package/dist/realtime/bridge-session-transcript-sink.js +58 -0
- package/dist/realtime/bridge-session-transcript-sink.js.map +1 -0
- package/dist/realtime/realtime-client-session-service.d.ts +117 -12
- package/dist/realtime/realtime-client-session-service.d.ts.map +1 -1
- package/dist/realtime/realtime-client-session-service.js +162 -39
- package/dist/realtime/realtime-client-session-service.js.map +1 -1
- package/dist/realtime/realtime-coagent-resolution.d.ts +90 -0
- package/dist/realtime/realtime-coagent-resolution.d.ts.map +1 -0
- package/dist/realtime/realtime-coagent-resolution.js +186 -0
- package/dist/realtime/realtime-coagent-resolution.js.map +1 -0
- package/dist/realtime/realtime-delegation-narrator.d.ts +97 -0
- package/dist/realtime/realtime-delegation-narrator.d.ts.map +1 -0
- package/dist/realtime/realtime-delegation-narrator.js +162 -0
- package/dist/realtime/realtime-delegation-narrator.js.map +1 -0
- package/dist/realtime/realtime-session-runner.d.ts +2 -82
- package/dist/realtime/realtime-session-runner.d.ts.map +1 -1
- package/dist/realtime/realtime-session-runner.js +12 -187
- package/dist/realtime/realtime-session-runner.js.map +1 -1
- package/dist/realtime/realtime-vendor-resolution.d.ts +8 -3
- package/dist/realtime/realtime-vendor-resolution.d.ts.map +1 -1
- package/dist/realtime/realtime-vendor-resolution.js +3 -3
- package/dist/realtime/realtime-vendor-resolution.js.map +1 -1
- package/dist/rubric-evaluation-agent-runner.d.ts +2 -0
- package/dist/rubric-evaluation-agent-runner.d.ts.map +1 -0
- package/dist/rubric-evaluation-agent-runner.js +47 -0
- package/dist/rubric-evaluation-agent-runner.js.map +1 -0
- package/dist/self-check.d.ts +101 -0
- package/dist/self-check.d.ts.map +1 -0
- package/dist/self-check.js +74 -0
- package/dist/self-check.js.map +1 -0
- package/package.json +19 -18
|
@@ -33,15 +33,28 @@ import { LogError, LogStatus, RunView } from '@memberjunction/core';
|
|
|
33
33
|
import { MJGlobal, MJLruCache, UUIDsEqual, EscapeSQLString } from '@memberjunction/global';
|
|
34
34
|
import { ActionEngineServer } from '@memberjunction/actions';
|
|
35
35
|
import { RunActionParams } from '@memberjunction/actions-base';
|
|
36
|
-
import { BaseRealtimeModel, GetAIAPIKey } from '@memberjunction/ai';
|
|
36
|
+
import { BaseRealtimeModel, GetAIAPIKey, CredentialScopeAllows } from '@memberjunction/ai';
|
|
37
37
|
import { FormatAppContextNote, ResolvePromptRunUserID } from '@memberjunction/ai-core-plus';
|
|
38
38
|
import { AIEngine } from '@memberjunction/aiengine';
|
|
39
39
|
import { AgentMemoryContextBuilder } from '../agent-memory-context-builder.js';
|
|
40
40
|
import { AgentRunner } from '../AgentRunner.js';
|
|
41
|
+
import { DelegationNarrator } from './realtime-delegation-narrator.js';
|
|
42
|
+
import { FilterAllowedAgentsByCanRun } from './realtime-coagent-resolution.js';
|
|
41
43
|
import { RealtimeToolBroker, INVOKE_TARGET_AGENT_TOOL_NAME, INVOKE_TARGET_AGENT_DESCRIPTION, BuildRealtimeAgentFraming } from './realtime-tool-broker.js';
|
|
42
44
|
import { NARRATION_PROMPT_NAME, LEGACY_NARRATION_PROMPT_NAME, ResolveNarrationInstructionsTemplate } from './realtime-narration.js';
|
|
43
45
|
import { BuildVoiceMannerSection, BuildAppRealtimeOverridesJson, DeepMergeConfigs, GetModelCatalogSessionSettings, GetDisclosureForTarget, GetNarrationPaceMs, GetProviderVoiceSettings, GetSessionTuningSettings, ResolveEffectiveRealtimeConfig, MatchProviderVoiceSettings, GetDirectActionsConfig, IsActionAllowedForDirectInvocation } from './realtime-coagent-config.js';
|
|
44
46
|
import { SelectRealtimeVendorForModel } from './realtime-vendor-resolution.js';
|
|
47
|
+
/** Runtime handles by their realtime session, so the layer that only holds the session can reach its runtime. */
|
|
48
|
+
const bridgeRuntimes = new WeakMap();
|
|
49
|
+
/**
|
|
50
|
+
* Returns the runtime wired onto a bridged realtime session by
|
|
51
|
+
* {@link RealtimeClientSessionService.WireBridgeRealtimeSession}, or `undefined` for a session that was never
|
|
52
|
+
* wired. Lets a host that only holds the {@link IRealtimeSession} (the telephony services) cancel delegations on
|
|
53
|
+
* barge-in and install its local tool handler.
|
|
54
|
+
*/
|
|
55
|
+
export function GetBridgeRealtimeRuntime(session) {
|
|
56
|
+
return bridgeRuntimes.get(session);
|
|
57
|
+
}
|
|
45
58
|
/**
|
|
46
59
|
* Logs when a configuration authored per-provider voice settings but NONE matched the vendor that
|
|
47
60
|
* actually ran — those settings are dropped, and pre-#3530 that happened with no trace at all: the
|
|
@@ -115,12 +128,27 @@ export class ToolArgumentsError extends Error {
|
|
|
115
128
|
*/
|
|
116
129
|
export class RealtimeClientSessionService {
|
|
117
130
|
constructor() {
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Maps session id -> (wireName -> MJActionEntityExtended) built during tool projection.
|
|
133
|
+
* Bounded with `MJLruCache` (rather than a plain `Map`): the resolver holds ONE shared service
|
|
134
|
+
* instance for the process lifetime (see class doc above), and there is no "session ended" hook
|
|
135
|
+
* this service can key eviction off — every voice session ever prepared would otherwise leave an
|
|
136
|
+
* entry here forever. Same TTL/maxSize rationale as {@link promptRunWriteChains} below.
|
|
137
|
+
*/
|
|
138
|
+
this.sessionWireActionMaps = new MJLruCache({
|
|
139
|
+
maxSize: 5_000,
|
|
140
|
+
ttlMs: 4 * 60 * 60 * 1000, // 4h — generous vs. any realistic voice-session duration
|
|
141
|
+
});
|
|
142
|
+
/** Maps targetAgentID -> (wireName -> MJActionEntityExtended) fallback built during tool projection. Bounded for the same reason as {@link sessionWireActionMaps}. */
|
|
143
|
+
this.targetWireActionMaps = new MJLruCache({
|
|
144
|
+
maxSize: 5_000,
|
|
145
|
+
ttlMs: 4 * 60 * 60 * 1000,
|
|
146
|
+
});
|
|
147
|
+
/** Maps session id -> direct actions config resolved during session prep. Bounded for the same reason as {@link sessionWireActionMaps}. */
|
|
148
|
+
this.sessionDirectConfigs = new MJLruCache({
|
|
149
|
+
maxSize: 5_000,
|
|
150
|
+
ttlMs: 4 * 60 * 60 * 1000,
|
|
151
|
+
});
|
|
124
152
|
/**
|
|
125
153
|
* IN-FLIGHT DELEGATION REGISTRY — the server half of the client-direct CANCEL channel.
|
|
126
154
|
*
|
|
@@ -300,6 +328,16 @@ export class RealtimeClientSessionService {
|
|
|
300
328
|
// Prep must have succeeded before wiring; degrade to a tool-error fallback rather than throw.
|
|
301
329
|
return this.wireBridgeFallbackRuntime(session);
|
|
302
330
|
}
|
|
331
|
+
// The delegation set the model may reach, narrowed to what THIS run-as user may run (the browser path
|
|
332
|
+
// applies the same filter; without it a bridged call would reach colleagues the caller cannot).
|
|
333
|
+
const allowedAgents = await FilterAllowedAgentsByCanRun(prep.EffectiveConfig?.realtime?.allowedAgents, contextUser);
|
|
334
|
+
// Spoken progress while delegated work runs — the same pacing/wording the generic session runner uses.
|
|
335
|
+
const narrator = new DelegationNarrator({
|
|
336
|
+
GetSession: () => session,
|
|
337
|
+
NarrationInstructionsTemplate: this.resolveNarrationInstructionsTemplate(),
|
|
338
|
+
NarrationPaceMs: GetNarrationPaceMs(prep.EffectiveConfig) ?? undefined,
|
|
339
|
+
});
|
|
340
|
+
let localToolHandler;
|
|
303
341
|
const promptID = this.resolveCoAgentSystemPrompt(coAgent).PromptID;
|
|
304
342
|
const obs = await this.createCoAgentObservabilityRun(coAgent, promptID, resolution.ModelID, resolution.VendorID, input.UserID || contextUser?.ID, input.AgentSessionID, contextUser, provider, input.ConversationID);
|
|
305
343
|
let finalized = false;
|
|
@@ -313,15 +351,18 @@ export class RealtimeClientSessionService {
|
|
|
313
351
|
// Tool calls → the shared delegation entry point, then hand the serialized result back to the model.
|
|
314
352
|
session.OnToolCall(async (call) => {
|
|
315
353
|
try {
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
354
|
+
const resultJson = localToolHandler?.Handles(call.ToolName)
|
|
355
|
+
? await localToolHandler.Execute(call)
|
|
356
|
+
: (await narrator.Track(() => this.ExecuteRelayedTool({
|
|
357
|
+
AgentSessionID: input.AgentSessionID,
|
|
358
|
+
ParentRunID: obs?.CoAgentRunID,
|
|
359
|
+
TargetAgentID: input.TargetAgentID,
|
|
360
|
+
AllowedAgents: allowedAgents,
|
|
361
|
+
DirectActions: prep.EffectiveConfig?.realtime?.directActions,
|
|
362
|
+
OnProgress: (progress) => narrator.HandleProgress(progress),
|
|
363
|
+
Call: call,
|
|
364
|
+
}, contextUser, provider))).ResultJson;
|
|
365
|
+
await session.SendToolResult(call.CallID, resultJson);
|
|
325
366
|
}
|
|
326
367
|
catch (error) {
|
|
327
368
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -333,11 +374,24 @@ export class RealtimeClientSessionService {
|
|
|
333
374
|
// through the idempotent finalizer, so double-fire is harmless.
|
|
334
375
|
const originalClose = session.Close.bind(session);
|
|
335
376
|
session.Close = async () => {
|
|
377
|
+
narrator.Cancel();
|
|
336
378
|
await finalize(true);
|
|
337
379
|
await originalClose();
|
|
338
380
|
};
|
|
339
381
|
session.OnClose?.(() => { void finalize(true); });
|
|
340
|
-
|
|
382
|
+
const runtime = {
|
|
383
|
+
CoAgentRunID: obs?.CoAgentRunID,
|
|
384
|
+
PromptRunID: obs?.PromptRunID,
|
|
385
|
+
Finalize: finalize,
|
|
386
|
+
CancelInFlightDelegations: () => {
|
|
387
|
+
narrator.Cancel(); // a stale "still working on it" line must not be spoken over the caller
|
|
388
|
+
return this.CancelInFlightDelegations(input.AgentSessionID);
|
|
389
|
+
},
|
|
390
|
+
CancelPendingNarration: () => narrator.Cancel(),
|
|
391
|
+
SetLocalToolHandler: (handler) => { localToolHandler = handler; },
|
|
392
|
+
};
|
|
393
|
+
bridgeRuntimes.set(session, runtime);
|
|
394
|
+
return runtime;
|
|
341
395
|
}
|
|
342
396
|
/**
|
|
343
397
|
* Degenerate {@link BridgeRealtimeRuntime} for the rare case wiring is attempted without a resolved
|
|
@@ -348,7 +402,14 @@ export class RealtimeClientSessionService {
|
|
|
348
402
|
session.OnToolCall((call) => {
|
|
349
403
|
void session.SendToolResult(call.CallID, JSON.stringify({ success: false, error: 'Tool execution is unavailable — the co-agent did not resolve. Let the user know.' }));
|
|
350
404
|
});
|
|
351
|
-
|
|
405
|
+
const runtime = {
|
|
406
|
+
Finalize: async () => { },
|
|
407
|
+
CancelInFlightDelegations: () => 0,
|
|
408
|
+
CancelPendingNarration: () => { },
|
|
409
|
+
SetLocalToolHandler: () => { },
|
|
410
|
+
};
|
|
411
|
+
bridgeRuntimes.set(session, runtime);
|
|
412
|
+
return runtime;
|
|
352
413
|
}
|
|
353
414
|
/**
|
|
354
415
|
* **The single source of truth for realtime session prep.** Builds the {@link RealtimeSessionParams}
|
|
@@ -1024,16 +1085,36 @@ export class RealtimeClientSessionService {
|
|
|
1024
1085
|
* @returns The resolution outcome (resolution or failure reason).
|
|
1025
1086
|
*/
|
|
1026
1087
|
async resolveModelForSession(input, coAgent, effectiveConfig) {
|
|
1088
|
+
// One resolver for every branch below, built here rather than per branch so the requested-model,
|
|
1089
|
+
// configured-preference and default paths cannot disagree about which credentials the session
|
|
1090
|
+
// may use. It answers with the session's RUN keys only: resolveVendorAndInstantiate layers this
|
|
1091
|
+
// service's getAPIKeyForDriver seam under it, giving run key → seam → (the seam's default) env.
|
|
1092
|
+
// A resolver that fell back to the environment itself would answer before the seam, so a
|
|
1093
|
+
// subclass that overrides the seam would lose to AI_VENDOR_API_KEY__<driver>.
|
|
1094
|
+
const resolveRunKey = this.buildRunKeyResolver(input.APIKeys);
|
|
1095
|
+
const scope = input.CredentialScope ?? 'Any';
|
|
1027
1096
|
if (input.PreferredModelID) {
|
|
1028
|
-
return this.resolvePreferredRealtimeModel(input.PreferredModelID);
|
|
1097
|
+
return this.resolvePreferredRealtimeModel(input.PreferredModelID, resolveRunKey, scope);
|
|
1029
1098
|
}
|
|
1030
|
-
const fromConfig = this.resolveConfiguredModelPreference(effectiveConfig);
|
|
1099
|
+
const fromConfig = this.resolveConfiguredModelPreference(effectiveConfig, resolveRunKey, scope);
|
|
1031
1100
|
if (fromConfig) {
|
|
1032
1101
|
return { Resolution: fromConfig };
|
|
1033
1102
|
}
|
|
1034
|
-
const resolution = await this.resolveRealtimeModel(coAgent);
|
|
1103
|
+
const resolution = await this.resolveRealtimeModel(coAgent, resolveRunKey, scope);
|
|
1035
1104
|
return resolution ? { Resolution: resolution } : { ErrorMessage: this.noModelMessage() };
|
|
1036
1105
|
}
|
|
1106
|
+
/**
|
|
1107
|
+
* The session's run-scoped keys as a resolver: the key for a driver class when the run carries
|
|
1108
|
+
* one, else `undefined` — no platform fallback. The fallback belongs to
|
|
1109
|
+
* {@link getAPIKeyForDriver}, which {@link resolveVendorAndInstantiate} consults for any class
|
|
1110
|
+
* this does not key.
|
|
1111
|
+
*
|
|
1112
|
+
* @param apiKeys The session's run keys ({@link PrepareClientSessionInput.APIKeys}), if any.
|
|
1113
|
+
* @returns A resolver over those keys only.
|
|
1114
|
+
*/
|
|
1115
|
+
buildRunKeyResolver(apiKeys) {
|
|
1116
|
+
return (driverClass) => apiKeys?.find(k => k.driverClass === driverClass)?.apiKey || undefined;
|
|
1117
|
+
}
|
|
1037
1118
|
/**
|
|
1038
1119
|
* Resolves the effective config's `realtime.modelPreference` (an `MJ: AI Models` Name OR ID)
|
|
1039
1120
|
* into a usable realtime model. TOLERANT by design — this is a METADATA preference, so any
|
|
@@ -1042,9 +1123,11 @@ export class RealtimeClientSessionService {
|
|
|
1042
1123
|
* explicit runtime choice ({@link resolvePreferredRealtimeModel}), which fails loud.
|
|
1043
1124
|
*
|
|
1044
1125
|
* @param effectiveConfig The resolved effective configuration.
|
|
1126
|
+
* @param resolve The session's run-scoped key resolver (run keys only; see {@link resolveVendorAndInstantiate}).
|
|
1127
|
+
* @param credentialScope The session's credential scope; see {@link resolveVendorAndInstantiate}.
|
|
1045
1128
|
* @returns The resolution, or `null` when no preference is configured or it can't be satisfied.
|
|
1046
1129
|
*/
|
|
1047
|
-
resolveConfiguredModelPreference(effectiveConfig) {
|
|
1130
|
+
resolveConfiguredModelPreference(effectiveConfig, resolve, credentialScope = 'Any') {
|
|
1048
1131
|
const preference = effectiveConfig?.realtime?.modelPreference;
|
|
1049
1132
|
if (!preference) {
|
|
1050
1133
|
return null;
|
|
@@ -1060,7 +1143,7 @@ export class RealtimeClientSessionService {
|
|
|
1060
1143
|
`'${model.Name}' but it is not an Active Realtime model — falling through to default resolution.`);
|
|
1061
1144
|
return null;
|
|
1062
1145
|
}
|
|
1063
|
-
const resolution = this.resolveVendorAndInstantiate(model);
|
|
1146
|
+
const resolution = this.resolveVendorAndInstantiate(model, resolve, credentialScope);
|
|
1064
1147
|
if (!resolution) {
|
|
1065
1148
|
LogError(`RealtimeClientSessionService: configured realtime model preference '${model.Name}' has no usable ` +
|
|
1066
1149
|
'vendor DriverClass/API key — falling through to default resolution.');
|
|
@@ -1092,9 +1175,11 @@ export class RealtimeClientSessionService {
|
|
|
1092
1175
|
* the caller's user explicitly chose this one.
|
|
1093
1176
|
*
|
|
1094
1177
|
* @param preferredModelID The `MJ: AI Models.ID` the user chose.
|
|
1178
|
+
* @param resolve The session's run-scoped key resolver (run keys only; see {@link resolveVendorAndInstantiate}).
|
|
1179
|
+
* @param credentialScope The session's credential scope; see {@link resolveVendorAndInstantiate}.
|
|
1095
1180
|
* @returns The resolution outcome (resolution or a specific failure reason).
|
|
1096
1181
|
*/
|
|
1097
|
-
resolvePreferredRealtimeModel(preferredModelID) {
|
|
1182
|
+
resolvePreferredRealtimeModel(preferredModelID, resolve, credentialScope = 'Any') {
|
|
1098
1183
|
const model = this.findModelByID(preferredModelID);
|
|
1099
1184
|
if (!model) {
|
|
1100
1185
|
return { ErrorMessage: `The requested realtime model (id '${preferredModelID}') was not found in AI model metadata.` };
|
|
@@ -1105,7 +1190,7 @@ export class RealtimeClientSessionService {
|
|
|
1105
1190
|
if (!this.isRealtimeModel(model)) {
|
|
1106
1191
|
return { ErrorMessage: `The requested model '${model.Name}' is not a Realtime model (its type is '${model.AIModelType}').` };
|
|
1107
1192
|
}
|
|
1108
|
-
const resolution = this.resolveVendorAndInstantiate(model);
|
|
1193
|
+
const resolution = this.resolveVendorAndInstantiate(model, resolve, credentialScope);
|
|
1109
1194
|
if (!resolution) {
|
|
1110
1195
|
return {
|
|
1111
1196
|
ErrorMessage: `The requested model '${model.Name}' has no active vendor with a usable DriverClass/API key ` +
|
|
@@ -1137,9 +1222,11 @@ export class RealtimeClientSessionService {
|
|
|
1137
1222
|
* step can't be satisfied.
|
|
1138
1223
|
*
|
|
1139
1224
|
* @param coAgent The co-agent being voiced (reserved for future per-agent model preference).
|
|
1225
|
+
* @param resolve The session's run-scoped key resolver (run keys only; see {@link resolveVendorAndInstantiate}).
|
|
1226
|
+
* @param credentialScope The session's credential scope; see {@link resolveVendorAndInstantiate}.
|
|
1140
1227
|
* @returns The resolved model + identifiers, or `null`.
|
|
1141
1228
|
*/
|
|
1142
|
-
async resolveRealtimeModel(coAgent) {
|
|
1229
|
+
async resolveRealtimeModel(coAgent, resolve, credentialScope = 'Any') {
|
|
1143
1230
|
// Walk candidates in descending PowerRank, returning the FIRST that fully resolves to a usable
|
|
1144
1231
|
// client-direct driver (active vendor + API key + ClassFactory driver + SupportsClientDirect).
|
|
1145
1232
|
// Single-pick dead-ended whenever the highest-power model lacked a key or client-direct support
|
|
@@ -1147,7 +1234,7 @@ export class RealtimeClientSessionService {
|
|
|
1147
1234
|
// surfaced "No usable Realtime model" instead of falling through to a model that works.
|
|
1148
1235
|
const candidates = this.selectRealtimeModelCandidates(coAgent);
|
|
1149
1236
|
for (const model of candidates) {
|
|
1150
|
-
const resolution = this.resolveVendorAndInstantiate(model);
|
|
1237
|
+
const resolution = this.resolveVendorAndInstantiate(model, resolve, credentialScope);
|
|
1151
1238
|
if (resolution && resolution.Model.SupportsClientDirect) {
|
|
1152
1239
|
return resolution;
|
|
1153
1240
|
}
|
|
@@ -1158,15 +1245,31 @@ export class RealtimeClientSessionService {
|
|
|
1158
1245
|
* Shared tail of model resolution: picks the vendor (with a usable API key) for an
|
|
1159
1246
|
* already-chosen model entity and instantiates its realtime driver.
|
|
1160
1247
|
*
|
|
1248
|
+
* Key precedence, per driver class: `resolve` (the session's run keys), then this service's
|
|
1249
|
+
* {@link getAPIKeyForDriver} seam, whose default is the environment key. Vendor selection and the
|
|
1250
|
+
* mint use that one chain, so a session is never routed to a vendor it cannot then be minted on.
|
|
1251
|
+
*
|
|
1161
1252
|
* @param model The chosen model entity.
|
|
1253
|
+
* @param resolve The session's run-scoped key resolver. Expected to answer with run keys only:
|
|
1254
|
+
* one that falls back to the environment itself answers before {@link getAPIKeyForDriver} and
|
|
1255
|
+
* so bypasses an override of it.
|
|
1256
|
+
* @param credentialScope A scope that rules out the `'Environment'` source (`'RuntimeOnly'`) drops the
|
|
1257
|
+
* {@link getAPIKeyForDriver} seam: the run's keys are the whole chain, so a vendor they do not key
|
|
1258
|
+
* is never selected on the platform's key.
|
|
1162
1259
|
* @returns The full resolution, or `null` when no vendor/key/driver can be satisfied.
|
|
1163
1260
|
*/
|
|
1164
|
-
resolveVendorAndInstantiate(model) {
|
|
1165
|
-
|
|
1261
|
+
resolveVendorAndInstantiate(model, resolve, credentialScope = 'Any') {
|
|
1262
|
+
// The run's keys first, then this service's own seam (which subclasses and tests override) —
|
|
1263
|
+
// so a run-scoped credential wins without taking that seam away from anyone who replaced it.
|
|
1264
|
+
// A scope that rules out the environment has no second step: the seam's default is the platform key.
|
|
1265
|
+
const resolveKey = CredentialScopeAllows(credentialScope, 'Environment')
|
|
1266
|
+
? (driverClass) => resolve?.(driverClass) ?? this.getAPIKeyForDriver(driverClass)
|
|
1267
|
+
: (driverClass) => resolve?.(driverClass);
|
|
1268
|
+
const vendor = this.selectRealtimeVendor(model.ID, resolveKey);
|
|
1166
1269
|
if (!vendor) {
|
|
1167
1270
|
return null;
|
|
1168
1271
|
}
|
|
1169
|
-
const apiKey =
|
|
1272
|
+
const apiKey = resolveKey(vendor.DriverClass);
|
|
1170
1273
|
if (!apiKey) {
|
|
1171
1274
|
return null;
|
|
1172
1275
|
}
|
|
@@ -1187,6 +1290,9 @@ export class RealtimeClientSessionService {
|
|
|
1187
1290
|
/**
|
|
1188
1291
|
* Resolves the API key for a vendor driver class. **Overridable seam** (wraps the module-level
|
|
1189
1292
|
* {@link GetAIAPIKey}) so tests can simulate present/absent keys without environment setup.
|
|
1293
|
+
* Consulted for every driver class the session's run keys do not cover, so an override (a vault,
|
|
1294
|
+
* a different key, or `undefined` to disable a vendor) applies whether or not the environment
|
|
1295
|
+
* also holds a key.
|
|
1190
1296
|
*
|
|
1191
1297
|
* @param driverClass The vendor's `DriverClass`.
|
|
1192
1298
|
* @returns The API key, or a falsy value when none is configured.
|
|
@@ -1225,10 +1331,11 @@ export class RealtimeClientSessionService {
|
|
|
1225
1331
|
* THIS service's {@link getAPIKeyForDriver} so subclasses and tests keep their key-resolution seam.
|
|
1226
1332
|
*
|
|
1227
1333
|
* @param modelID The chosen model's id.
|
|
1334
|
+
* @param resolve The complete key chain to select against; defaults to {@link getAPIKeyForDriver}.
|
|
1228
1335
|
* @returns The vendor driver/api identifiers, or `null` when none has a usable key.
|
|
1229
1336
|
*/
|
|
1230
|
-
selectRealtimeVendor(modelID) {
|
|
1231
|
-
return SelectRealtimeVendorForModel(modelID, (driverClass) => this.getAPIKeyForDriver(driverClass));
|
|
1337
|
+
selectRealtimeVendor(modelID, resolve) {
|
|
1338
|
+
return SelectRealtimeVendorForModel(modelID, resolve ?? ((driverClass) => this.getAPIKeyForDriver(driverClass)));
|
|
1232
1339
|
}
|
|
1233
1340
|
/**
|
|
1234
1341
|
* Resolves the DB-driven progress-narration instruction template: the Active `MJ: AI Prompts`
|
|
@@ -1267,7 +1374,7 @@ export class RealtimeClientSessionService {
|
|
|
1267
1374
|
const combinedExtra = directTools.length > 0
|
|
1268
1375
|
? [...(input.ExtraTools ?? []), ...directTools]
|
|
1269
1376
|
: input.ExtraTools;
|
|
1270
|
-
const tools = this.buildStableToolSet(combinedExtra);
|
|
1377
|
+
const tools = this.appendHostTools(this.buildStableToolSet(combinedExtra), input.HostTools);
|
|
1271
1378
|
// Hoisted (rather than built inline at the return) so the mint log below can report the voice
|
|
1272
1379
|
// that ACTUALLY reached the driver — see the `voice=` field. Same bag, built once.
|
|
1273
1380
|
const configBag = this.buildSessionConfigBag(input, effectiveConfig, driverClass, modelID, modelVendorID);
|
|
@@ -1362,6 +1469,7 @@ export class RealtimeClientSessionService {
|
|
|
1362
1469
|
const colleagues = this.buildColleaguesFromConfig(effectiveConfig, input.TargetAgentID);
|
|
1363
1470
|
const framing = BuildRealtimeAgentFraming(targetName, this.buildInteractiveSurfaceFraming(input.ExtraTools), colleagues, hasDirectTools);
|
|
1364
1471
|
const meetingFraming = this.buildMeetingFraming(input);
|
|
1472
|
+
const hostFraming = input.HostFraming?.trim() ?? '';
|
|
1365
1473
|
const coAgentPrompt = this.getCoAgentSystemPromptText(coAgent);
|
|
1366
1474
|
const voiceManner = BuildVoiceMannerSection(effectiveConfig);
|
|
1367
1475
|
const targetIdentity = this.formatTargetIdentity(target);
|
|
@@ -1369,7 +1477,7 @@ export class RealtimeClientSessionService {
|
|
|
1369
1477
|
const priorTranscript = this.formatPriorTranscript(input.PriorTranscript);
|
|
1370
1478
|
const history = this.formatConversationHistory(input.ConversationMessages);
|
|
1371
1479
|
const memoryContext = await this.assembleMemoryContext(input, coAgent, contextUser, provider);
|
|
1372
|
-
return [framing, meetingFraming, coAgentPrompt, voiceManner, targetIdentity, appContextSection, priorTranscript, history, memoryContext]
|
|
1480
|
+
return [framing, meetingFraming, hostFraming, coAgentPrompt, voiceManner, targetIdentity, appContextSection, priorTranscript, history, memoryContext]
|
|
1373
1481
|
.filter(part => part && part.trim().length > 0)
|
|
1374
1482
|
.join('\n\n');
|
|
1375
1483
|
}
|
|
@@ -1695,6 +1803,21 @@ export class RealtimeClientSessionService {
|
|
|
1695
1803
|
}
|
|
1696
1804
|
return result;
|
|
1697
1805
|
}
|
|
1806
|
+
/** Appends the host-declared tools to a stable tool set, dropping any whose name is already taken. */
|
|
1807
|
+
appendHostTools(tools, hostTools) {
|
|
1808
|
+
if (!hostTools || hostTools.length === 0) {
|
|
1809
|
+
return tools;
|
|
1810
|
+
}
|
|
1811
|
+
const taken = new Set(tools.map((t) => t.Name.toLowerCase()));
|
|
1812
|
+
const merged = [...tools];
|
|
1813
|
+
for (const tool of hostTools) {
|
|
1814
|
+
if (!taken.has(tool.Name.toLowerCase())) {
|
|
1815
|
+
taken.add(tool.Name.toLowerCase());
|
|
1816
|
+
merged.push(tool);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
return merged;
|
|
1820
|
+
}
|
|
1698
1821
|
/**
|
|
1699
1822
|
* Builds the {@link RealtimeToolBroker} for a relayed tool call, wiring `DelegateToTarget` to a
|
|
1700
1823
|
* target-agent run and `ExecuteTool` to direct-action execution or a structured "not available" placeholder.
|
|
@@ -2017,12 +2140,12 @@ export class RealtimeClientSessionService {
|
|
|
2017
2140
|
const allowedActions = candidateActions.filter(action => IsActionAllowedForDirectInvocation(action.Name, directConfig));
|
|
2018
2141
|
const wireMap = this.BuildWireActionMap(allowedActions);
|
|
2019
2142
|
if (agentSessionID) {
|
|
2020
|
-
this.sessionWireActionMaps.
|
|
2143
|
+
this.sessionWireActionMaps.Set(agentSessionID, wireMap);
|
|
2021
2144
|
if (directConfig) {
|
|
2022
|
-
this.sessionDirectConfigs.
|
|
2145
|
+
this.sessionDirectConfigs.Set(agentSessionID, directConfig);
|
|
2023
2146
|
}
|
|
2024
2147
|
}
|
|
2025
|
-
this.targetWireActionMaps.
|
|
2148
|
+
this.targetWireActionMaps.Set(targetAgentID, wireMap);
|
|
2026
2149
|
const tools = [];
|
|
2027
2150
|
for (const [wireName, action] of wireMap.entries()) {
|
|
2028
2151
|
tools.push(this.mapActionToToolDefinition(action, wireName));
|
|
@@ -2107,7 +2230,7 @@ export class RealtimeClientSessionService {
|
|
|
2107
2230
|
}
|
|
2108
2231
|
const candidateActions = this.getTargetAgentActions(target.ID);
|
|
2109
2232
|
const candidateWireMap = this.BuildWireActionMap(candidateActions);
|
|
2110
|
-
const action = (input?.AgentSessionID ? this.sessionWireActionMaps.
|
|
2233
|
+
const action = (input?.AgentSessionID ? this.sessionWireActionMaps.Get(input.AgentSessionID)?.get(call.ToolName) : undefined)
|
|
2111
2234
|
?? candidateWireMap.get(call.ToolName)
|
|
2112
2235
|
?? Array.from(candidateWireMap.entries()).find(([w]) => w.toLowerCase() === call.ToolName.trim().toLowerCase())?.[1];
|
|
2113
2236
|
if (!action) {
|
|
@@ -2118,7 +2241,7 @@ export class RealtimeClientSessionService {
|
|
|
2118
2241
|
};
|
|
2119
2242
|
}
|
|
2120
2243
|
const directConfig = input?.DirectActions
|
|
2121
|
-
?? (input?.AgentSessionID ? this.sessionDirectConfigs.
|
|
2244
|
+
?? (input?.AgentSessionID ? this.sessionDirectConfigs.Get(input.AgentSessionID) : undefined)
|
|
2122
2245
|
?? GetDirectActionsConfig(this.resolveEffectiveConfig(target, undefined, target));
|
|
2123
2246
|
if (!IsActionAllowedForDirectInvocation(action.Name, directConfig)) {
|
|
2124
2247
|
return {
|