@memberjunction/ai-agents 6.1.0-edge.6 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SkillImportExportService.d.ts +17 -1
- package/dist/SkillImportExportService.d.ts.map +1 -1
- package/dist/SkillImportExportService.js +57 -5
- package/dist/SkillImportExportService.js.map +1 -1
- package/dist/SkillMarkdownConverter.d.ts +17 -1
- package/dist/SkillMarkdownConverter.d.ts.map +1 -1
- package/dist/SkillMarkdownConverter.js +40 -5
- package/dist/SkillMarkdownConverter.js.map +1 -1
- package/dist/agent-types/base-agent-type.d.ts +26 -1
- package/dist/agent-types/base-agent-type.d.ts.map +1 -1
- package/dist/agent-types/base-agent-type.js +17 -0
- package/dist/agent-types/base-agent-type.js.map +1 -1
- package/dist/agent-types/loop-agent-response-type.d.ts +16 -0
- package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-response-type.js +19 -1
- package/dist/agent-types/loop-agent-response-type.js.map +1 -1
- package/dist/agent-types/loop-agent-type.d.ts +32 -1
- package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-type.js +190 -3
- package/dist/agent-types/loop-agent-type.js.map +1 -1
- package/dist/base-agent.d.ts +186 -3
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +564 -53
- package/dist/base-agent.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/native-tools/action-tool-builder.d.ts +92 -0
- package/dist/native-tools/action-tool-builder.d.ts.map +1 -0
- package/dist/native-tools/action-tool-builder.js +226 -0
- package/dist/native-tools/action-tool-builder.js.map +1 -0
- package/dist/native-tools/control-tools.d.ts +52 -0
- package/dist/native-tools/control-tools.d.ts.map +1 -0
- package/dist/native-tools/control-tools.js +113 -0
- package/dist/native-tools/control-tools.js.map +1 -0
- package/dist/native-tools/dual-channel.d.ts +16 -0
- package/dist/native-tools/dual-channel.d.ts.map +1 -0
- package/dist/native-tools/dual-channel.js +44 -0
- package/dist/native-tools/dual-channel.js.map +1 -0
- package/dist/native-tools/tool-result-turns.d.ts +32 -0
- package/dist/native-tools/tool-result-turns.d.ts.map +1 -0
- package/dist/native-tools/tool-result-turns.js +29 -0
- package/dist/native-tools/tool-result-turns.js.map +1 -0
- package/dist/realtime/bridge-realtime-session-factory.d.ts.map +1 -1
- package/dist/realtime/bridge-realtime-session-factory.js +22 -2
- package/dist/realtime/bridge-realtime-session-factory.js.map +1 -1
- package/dist/realtime/realtime-client-session-service.d.ts +104 -17
- package/dist/realtime/realtime-client-session-service.d.ts.map +1 -1
- package/dist/realtime/realtime-client-session-service.js +325 -28
- package/dist/realtime/realtime-client-session-service.js.map +1 -1
- package/dist/realtime/realtime-coagent-config.d.ts +61 -1
- package/dist/realtime/realtime-coagent-config.d.ts.map +1 -1
- package/dist/realtime/realtime-coagent-config.js +93 -1
- package/dist/realtime/realtime-coagent-config.js.map +1 -1
- package/dist/realtime/realtime-session-runner.d.ts +10 -8
- package/dist/realtime/realtime-session-runner.d.ts.map +1 -1
- package/dist/realtime/realtime-session-runner.js +11 -9
- package/dist/realtime/realtime-session-runner.js.map +1 -1
- package/dist/realtime/realtime-tool-broker.d.ts +11 -5
- package/dist/realtime/realtime-tool-broker.d.ts.map +1 -1
- package/dist/realtime/realtime-tool-broker.js +19 -7
- package/dist/realtime/realtime-tool-broker.js.map +1 -1
- package/dist/scoped-prompt-config-resolver.d.ts +6 -1
- package/dist/scoped-prompt-config-resolver.d.ts.map +1 -1
- package/dist/scoped-prompt-config-resolver.js.map +1 -1
- package/package.json +18 -18
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
*
|
|
10
10
|
* 1. {@link RealtimeClientSessionService.PrepareClientSession} — resolves the Realtime model,
|
|
11
11
|
* assembles the companion system prompt (co-agent prompt + target identity + history + memory),
|
|
12
|
-
* builds the
|
|
12
|
+
* builds the realtime tool set (always including `invoke-target-agent`, plus allowed direct actions on dynamic-toolset drivers), and
|
|
13
13
|
* asks the model to mint a {@link ClientRealtimeSessionConfig} (ephemeral token + provider
|
|
14
14
|
* session config) the browser applies verbatim.
|
|
15
15
|
* 2. {@link RealtimeClientSessionService.ExecuteRelayedTool} — executes a single tool call the
|
|
16
16
|
* browser relayed, routing it through the shared {@link RealtimeToolBroker} so the result is
|
|
17
17
|
* byte-for-byte identical to the server-bridged path. `invoke-target-agent` delegates to the
|
|
18
|
-
* target agent via {@link AgentRunner.RunAgent};
|
|
19
|
-
*
|
|
18
|
+
* target agent via {@link AgentRunner.RunAgent}; allowed direct actions execute via
|
|
19
|
+
* {@link ActionEngineServer.Instance.RunAction}; other tools return a structured "not available" result.
|
|
20
20
|
*
|
|
21
21
|
* **Why this duplicates BaseAgent.** The private helpers in `BaseAgent.executeRealtimeSession`
|
|
22
22
|
* (model resolution, companion-prompt assembly, target-agent resolution, delegation) are the
|
|
@@ -31,14 +31,16 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import { LogError, LogStatus, RunView } from '@memberjunction/core';
|
|
33
33
|
import { MJGlobal, MJLruCache, UUIDsEqual, EscapeSQLString } from '@memberjunction/global';
|
|
34
|
+
import { ActionEngineServer } from '@memberjunction/actions';
|
|
35
|
+
import { RunActionParams } from '@memberjunction/actions-base';
|
|
34
36
|
import { BaseRealtimeModel, GetAIAPIKey } from '@memberjunction/ai';
|
|
35
37
|
import { FormatAppContextNote } from '@memberjunction/ai-core-plus';
|
|
36
38
|
import { AIEngine } from '@memberjunction/aiengine';
|
|
37
39
|
import { AgentMemoryContextBuilder } from '../agent-memory-context-builder.js';
|
|
38
40
|
import { AgentRunner } from '../AgentRunner.js';
|
|
39
|
-
import { RealtimeToolBroker, INVOKE_TARGET_AGENT_TOOL_NAME, BuildRealtimeAgentFraming } from './realtime-tool-broker.js';
|
|
41
|
+
import { RealtimeToolBroker, INVOKE_TARGET_AGENT_TOOL_NAME, INVOKE_TARGET_AGENT_DESCRIPTION, BuildRealtimeAgentFraming } from './realtime-tool-broker.js';
|
|
40
42
|
import { NARRATION_PROMPT_NAME, LEGACY_NARRATION_PROMPT_NAME, ResolveNarrationInstructionsTemplate } from './realtime-narration.js';
|
|
41
|
-
import { BuildVoiceMannerSection, BuildAppRealtimeOverridesJson, DeepMergeConfigs, GetModelCatalogSessionSettings, GetDisclosureForTarget, GetNarrationPaceMs, GetProviderVoiceSettings, GetSessionTuningSettings, ResolveEffectiveRealtimeConfig, MatchProviderVoiceSettings } from './realtime-coagent-config.js';
|
|
43
|
+
import { BuildVoiceMannerSection, BuildAppRealtimeOverridesJson, DeepMergeConfigs, GetModelCatalogSessionSettings, GetDisclosureForTarget, GetNarrationPaceMs, GetProviderVoiceSettings, GetSessionTuningSettings, ResolveEffectiveRealtimeConfig, MatchProviderVoiceSettings, GetDirectActionsConfig, IsActionAllowedForDirectInvocation } from './realtime-coagent-config.js';
|
|
42
44
|
import { SelectRealtimeVendorForModel } from './realtime-vendor-resolution.js';
|
|
43
45
|
/**
|
|
44
46
|
* Logs when a configuration authored per-provider voice settings but NONE matched the vendor that
|
|
@@ -77,6 +79,32 @@ export function WarnOnUnmatchedProviderVoice(effectiveConfig, driverClass, surfa
|
|
|
77
79
|
`driver '${driverClass ?? 'unknown'}' — none matched, so those settings were dropped. Author ` +
|
|
78
80
|
'realtime.voice.default.voice to carry a voice to whichever vendor runs.');
|
|
79
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Sanitizes an action or tool name to conform to provider wire constraints
|
|
84
|
+
* (e.g. OpenAI function naming: ^[a-zA-Z0-9_-]{1,64}$).
|
|
85
|
+
*
|
|
86
|
+
* Replaces non-alphanumeric/hyphen/underscore characters with underscores,
|
|
87
|
+
* collapses consecutive runs of underscores into a single underscore, and
|
|
88
|
+
* truncates to 64 characters.
|
|
89
|
+
*
|
|
90
|
+
* @param name The original action or tool name.
|
|
91
|
+
* @returns The sanitized wire-safe name.
|
|
92
|
+
*/
|
|
93
|
+
export function SanitizeWireToolName(name) {
|
|
94
|
+
return name
|
|
95
|
+
.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
96
|
+
.replace(/_+/g, '_')
|
|
97
|
+
.slice(0, 64);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Sentinel error thrown when action tool arguments are present but cannot be parsed or are not a JSON object.
|
|
101
|
+
*/
|
|
102
|
+
export class ToolArgumentsError extends Error {
|
|
103
|
+
constructor(message) {
|
|
104
|
+
super(message);
|
|
105
|
+
this.name = 'ToolArgumentsError';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
80
108
|
/**
|
|
81
109
|
* Server-agnostic service that prepares a client-direct realtime session and executes the tool
|
|
82
110
|
* calls the browser relays back. Constructed per-request (a normal injectable service — NOT a
|
|
@@ -87,6 +115,12 @@ export function WarnOnUnmatchedProviderVoice(effectiveConfig, driverClass, surfa
|
|
|
87
115
|
*/
|
|
88
116
|
export class RealtimeClientSessionService {
|
|
89
117
|
constructor() {
|
|
118
|
+
/** Maps session id -> (wireName -> MJActionEntityExtended) built during tool projection. */
|
|
119
|
+
this.sessionWireActionMaps = new Map();
|
|
120
|
+
/** Maps targetAgentID -> (wireName -> MJActionEntityExtended) fallback built during tool projection. */
|
|
121
|
+
this.targetWireActionMaps = new Map();
|
|
122
|
+
/** Maps session id -> direct actions config resolved during session prep. */
|
|
123
|
+
this.sessionDirectConfigs = new Map();
|
|
90
124
|
/**
|
|
91
125
|
* IN-FLIGHT DELEGATION REGISTRY — the server half of the client-direct CANCEL channel.
|
|
92
126
|
*
|
|
@@ -125,6 +159,33 @@ export class RealtimeClientSessionService {
|
|
|
125
159
|
ttlMs: 4 * 60 * 60 * 1000, // 4h backstop — generous vs. any realistic session duration
|
|
126
160
|
});
|
|
127
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Builds a wire-name to action map from candidate actions, sanitizing each
|
|
164
|
+
* action's name and deduplicating collisions (first action wins).
|
|
165
|
+
*
|
|
166
|
+
* @param actions The action entities to index by wire name.
|
|
167
|
+
* @returns Map of wire-name to action entity.
|
|
168
|
+
*/
|
|
169
|
+
buildWireActionMap(actions) {
|
|
170
|
+
const map = new Map();
|
|
171
|
+
const seen = new Map();
|
|
172
|
+
for (const action of actions) {
|
|
173
|
+
if (!action.Name) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const wireName = SanitizeWireToolName(action.Name);
|
|
177
|
+
const lower = wireName.toLowerCase();
|
|
178
|
+
const existing = seen.get(lower);
|
|
179
|
+
if (existing) {
|
|
180
|
+
// Colliding action name after sanitization — first action wins, log collision
|
|
181
|
+
LogError(`RealtimeClientSessionService.buildWireActionMap: wire name collision for '${wireName}' between action '${existing.Name}' and action '${action.Name}'. Keeping '${existing.Name}' (first wins).`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
seen.set(lower, action);
|
|
185
|
+
map.set(wireName, action);
|
|
186
|
+
}
|
|
187
|
+
return map;
|
|
188
|
+
}
|
|
128
189
|
/**
|
|
129
190
|
* The seeded name of the `MJ: AI Prompts` row whose `TemplateText` carries the first-person
|
|
130
191
|
* progress-narration instructions (with a `{{ progressMessage }}` placeholder). Resolved at
|
|
@@ -253,6 +314,7 @@ export class RealtimeClientSessionService {
|
|
|
253
314
|
ParentRunID: obs?.CoAgentRunID,
|
|
254
315
|
TargetAgentID: input.TargetAgentID,
|
|
255
316
|
AllowedAgents: prep.EffectiveConfig?.realtime?.allowedAgents,
|
|
317
|
+
DirectActions: prep.EffectiveConfig?.realtime?.directActions,
|
|
256
318
|
Call: call,
|
|
257
319
|
}, contextUser, provider);
|
|
258
320
|
await session.SendToolResult(call.CallID, result.ResultJson);
|
|
@@ -920,6 +982,7 @@ export class RealtimeClientSessionService {
|
|
|
920
982
|
*/
|
|
921
983
|
async configureEngine(contextUser, provider) {
|
|
922
984
|
await AIEngine.Instance.Config(false, contextUser, provider);
|
|
985
|
+
await ActionEngineServer.Instance.Config(false, contextUser);
|
|
923
986
|
}
|
|
924
987
|
/**
|
|
925
988
|
* Resolves the co-agent from either the supplied entity or its id (from cached metadata).
|
|
@@ -1178,7 +1241,7 @@ export class RealtimeClientSessionService {
|
|
|
1178
1241
|
}
|
|
1179
1242
|
/**
|
|
1180
1243
|
* Builds the {@link RealtimeSessionParams} for the client-direct session: the companion system
|
|
1181
|
-
* prompt plus the
|
|
1244
|
+
* prompt plus the registered realtime tool set.
|
|
1182
1245
|
*
|
|
1183
1246
|
* @param input The prepare-session input.
|
|
1184
1247
|
* @param coAgent The resolved co-agent.
|
|
@@ -1192,9 +1255,13 @@ export class RealtimeClientSessionService {
|
|
|
1192
1255
|
* @returns The assembled session params.
|
|
1193
1256
|
*/
|
|
1194
1257
|
async buildSessionParams(input, coAgent, modelApiName, contextUser, provider, effectiveConfig, driverClass, modelID, modelVendorID) {
|
|
1195
|
-
const
|
|
1258
|
+
const directTools = this.buildDirectActionTools(input.TargetAgentID, effectiveConfig, driverClass, input.AgentSessionID);
|
|
1259
|
+
const systemPrompt = await this.buildCompanionSystemPrompt(input, coAgent, contextUser, provider, effectiveConfig, directTools.length > 0);
|
|
1196
1260
|
const memoryContext = await this.assembleMemoryContext(input, coAgent, contextUser, provider);
|
|
1197
|
-
const
|
|
1261
|
+
const combinedExtra = directTools.length > 0
|
|
1262
|
+
? [...(input.ExtraTools ?? []), ...directTools]
|
|
1263
|
+
: input.ExtraTools;
|
|
1264
|
+
const tools = this.buildStableToolSet(combinedExtra);
|
|
1198
1265
|
// Hoisted (rather than built inline at the return) so the mint log below can report the voice
|
|
1199
1266
|
// that ACTUALLY reached the driver — see the `voice=` field. Same bag, built once.
|
|
1200
1267
|
const configBag = this.buildSessionConfigBag(input, effectiveConfig, driverClass, modelID, modelVendorID);
|
|
@@ -1218,6 +1285,7 @@ export class RealtimeClientSessionService {
|
|
|
1218
1285
|
// bound the provider session/token apply min(default, this); the janitor enforces it
|
|
1219
1286
|
// regardless of driver support via the session deadline stamped by the transport layer.
|
|
1220
1287
|
MaxSessionSeconds: input.MaxSessionSeconds,
|
|
1288
|
+
UserID: contextUser?.ID,
|
|
1221
1289
|
};
|
|
1222
1290
|
}
|
|
1223
1291
|
/**
|
|
@@ -1277,7 +1345,7 @@ export class RealtimeClientSessionService {
|
|
|
1277
1345
|
* @param effectiveConfig The resolved effective configuration (voice persona source).
|
|
1278
1346
|
* @returns The concatenated system prompt (never empty — the framing is always present).
|
|
1279
1347
|
*/
|
|
1280
|
-
async buildCompanionSystemPrompt(input, coAgent, contextUser, provider, effectiveConfig) {
|
|
1348
|
+
async buildCompanionSystemPrompt(input, coAgent, contextUser, provider, effectiveConfig, hasDirectTools = false) {
|
|
1281
1349
|
const target = this.resolveTargetAgent(input.TargetAgentID);
|
|
1282
1350
|
const targetName = target?.Name ?? 'the configured target agent';
|
|
1283
1351
|
// Identity framing comes from the ONE shared producer (see BuildRealtimeAgentFraming) so the agent
|
|
@@ -1285,7 +1353,7 @@ export class RealtimeClientSessionService {
|
|
|
1285
1353
|
// browser/whiteboard); bridges pass none. Colleagues come from the effective allowed-agent union
|
|
1286
1354
|
// (Move 4) so the lead knows who it can delegate to and how to narrate each handoff.
|
|
1287
1355
|
const colleagues = this.buildColleaguesFromConfig(effectiveConfig, input.TargetAgentID);
|
|
1288
|
-
const framing = BuildRealtimeAgentFraming(targetName, this.buildInteractiveSurfaceFraming(input.ExtraTools), colleagues);
|
|
1356
|
+
const framing = BuildRealtimeAgentFraming(targetName, this.buildInteractiveSurfaceFraming(input.ExtraTools), colleagues, hasDirectTools);
|
|
1289
1357
|
const meetingFraming = this.buildMeetingFraming(input);
|
|
1290
1358
|
const coAgentPrompt = this.getCoAgentSystemPromptText(coAgent);
|
|
1291
1359
|
const voiceManner = BuildVoiceMannerSection(effectiveConfig);
|
|
@@ -1580,19 +1648,17 @@ export class RealtimeClientSessionService {
|
|
|
1580
1648
|
}
|
|
1581
1649
|
}
|
|
1582
1650
|
/**
|
|
1583
|
-
* Builds the
|
|
1584
|
-
*
|
|
1585
|
-
* argument *inside* the call,
|
|
1586
|
-
* identical across targets.
|
|
1651
|
+
* Builds the tool set every voice session exposes: the core `invoke-target-agent` tool plus
|
|
1652
|
+
* any caller-supplied extra tools or projected direct actions. The target is a runtime
|
|
1653
|
+
* argument *inside* the delegation call, keeping the provider delegation contract identical.
|
|
1587
1654
|
*
|
|
1588
|
-
* @param extraTools Optional additional
|
|
1655
|
+
* @param extraTools Optional additional tools to register.
|
|
1589
1656
|
* @returns The tools to register at session start.
|
|
1590
1657
|
*/
|
|
1591
1658
|
buildStableToolSet(extraTools) {
|
|
1592
1659
|
const invokeTarget = {
|
|
1593
1660
|
Name: INVOKE_TARGET_AGENT_TOOL_NAME,
|
|
1594
|
-
Description:
|
|
1595
|
-
'real work (data lookup, analysis, actions) is required, then narrate progress while it runs.',
|
|
1661
|
+
Description: INVOKE_TARGET_AGENT_DESCRIPTION,
|
|
1596
1662
|
ParametersSchema: {
|
|
1597
1663
|
type: 'object',
|
|
1598
1664
|
properties: {
|
|
@@ -1609,11 +1675,22 @@ export class RealtimeClientSessionService {
|
|
|
1609
1675
|
required: ['request']
|
|
1610
1676
|
}
|
|
1611
1677
|
};
|
|
1612
|
-
|
|
1678
|
+
const result = [invokeTarget];
|
|
1679
|
+
const seenNames = new Set([INVOKE_TARGET_AGENT_TOOL_NAME.toLowerCase()]);
|
|
1680
|
+
if (extraTools && extraTools.length > 0) {
|
|
1681
|
+
for (const tool of extraTools) {
|
|
1682
|
+
const key = tool.Name.toLowerCase();
|
|
1683
|
+
if (!seenNames.has(key)) {
|
|
1684
|
+
seenNames.add(key);
|
|
1685
|
+
result.push(tool);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
return result;
|
|
1613
1690
|
}
|
|
1614
1691
|
/**
|
|
1615
1692
|
* Builds the {@link RealtimeToolBroker} for a relayed tool call, wiring `DelegateToTarget` to a
|
|
1616
|
-
* target-agent run and `ExecuteTool` to a structured "not available" placeholder.
|
|
1693
|
+
* target-agent run and `ExecuteTool` to direct-action execution or a structured "not available" placeholder.
|
|
1617
1694
|
*
|
|
1618
1695
|
* @param input The relayed tool input.
|
|
1619
1696
|
* @param contextUser The calling user.
|
|
@@ -1623,7 +1700,7 @@ export class RealtimeClientSessionService {
|
|
|
1623
1700
|
buildToolBroker(input, contextUser, provider) {
|
|
1624
1701
|
const deps = {
|
|
1625
1702
|
DelegateToTarget: (request) => this.delegateToTarget(input, request, contextUser, provider),
|
|
1626
|
-
ExecuteTool: (call) => this.executeNonTargetTool(call)
|
|
1703
|
+
ExecuteTool: (call) => this.executeNonTargetTool(call, input, contextUser)
|
|
1627
1704
|
};
|
|
1628
1705
|
return new RealtimeToolBroker(deps);
|
|
1629
1706
|
}
|
|
@@ -1885,19 +1962,239 @@ export class RealtimeClientSessionService {
|
|
|
1885
1962
|
return (await run.Load(parentRunID)) ? run : null;
|
|
1886
1963
|
}
|
|
1887
1964
|
/**
|
|
1888
|
-
*
|
|
1889
|
-
*
|
|
1965
|
+
* Inspects whether the resolved realtime driver class supports dynamic tool registration.
|
|
1966
|
+
* Dynamic tool sets (e.g. per-agent direct action projection) are registered on the provider
|
|
1967
|
+
* socket only when the driver explicitly declares `SupportsDynamicToolSet === true`.
|
|
1890
1968
|
*
|
|
1891
|
-
* @param
|
|
1892
|
-
* @returns
|
|
1969
|
+
* @param driverClass The vendor's DriverClass (the ClassFactory key).
|
|
1970
|
+
* @returns True if the driver supports dynamic tool sets, false otherwise.
|
|
1893
1971
|
*/
|
|
1894
|
-
|
|
1972
|
+
driverSupportsDynamicToolSet(driverClass) {
|
|
1973
|
+
if (!driverClass) {
|
|
1974
|
+
return false;
|
|
1975
|
+
}
|
|
1976
|
+
const reg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseRealtimeModel, driverClass);
|
|
1977
|
+
const subClass = reg?.SubClass;
|
|
1978
|
+
return subClass?.SupportsDynamicToolSet === true;
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
* Resolves the active database actions assigned to the target agent.
|
|
1982
|
+
* **Overridable seam** for tests to provide mock action sets without an engine cache.
|
|
1983
|
+
*
|
|
1984
|
+
* @param targetAgentID The target agent ID.
|
|
1985
|
+
* @returns The active action entities assigned to the agent.
|
|
1986
|
+
*/
|
|
1987
|
+
getTargetAgentActions(targetAgentID) {
|
|
1988
|
+
const agentActions = (AIEngine.Instance.AgentActions ?? []).filter(aa => UUIDsEqual(aa.AgentID, targetAgentID) && aa.Status === 'Active');
|
|
1989
|
+
return (ActionEngineServer.Instance.Actions ?? []).filter(a => agentActions.some(aa => UUIDsEqual(aa.ActionID, a.ID)) && a.Status === 'Active');
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Builds the projected direct action tool definitions for a target agent if supported by the driver.
|
|
1993
|
+
* Gated by driver capability (`SupportsDynamicToolSet`) and explicit configuration opt-in
|
|
1994
|
+
* (`Configuration.realtime.directActions.enabled = true`).
|
|
1995
|
+
*
|
|
1996
|
+
* @param targetAgentID The target agent ID being voiced.
|
|
1997
|
+
* @param effectiveConfig The resolved effective configuration.
|
|
1998
|
+
* @param driverClass The resolved vendor's DriverClass.
|
|
1999
|
+
* @returns The array of projected direct action tools (empty if unsupported or disabled).
|
|
2000
|
+
*/
|
|
2001
|
+
buildDirectActionTools(targetAgentID, effectiveConfig, driverClass, agentSessionID) {
|
|
2002
|
+
if (!targetAgentID || !this.driverSupportsDynamicToolSet(driverClass)) {
|
|
2003
|
+
return [];
|
|
2004
|
+
}
|
|
2005
|
+
const directConfig = GetDirectActionsConfig(effectiveConfig);
|
|
2006
|
+
if (!directConfig || !directConfig.enabled) {
|
|
2007
|
+
return [];
|
|
2008
|
+
}
|
|
2009
|
+
const candidateActions = this.getTargetAgentActions(targetAgentID);
|
|
2010
|
+
const allowedActions = candidateActions.filter(action => IsActionAllowedForDirectInvocation(action.Name, directConfig));
|
|
2011
|
+
const wireMap = this.buildWireActionMap(allowedActions);
|
|
2012
|
+
if (agentSessionID) {
|
|
2013
|
+
this.sessionWireActionMaps.set(agentSessionID, wireMap);
|
|
2014
|
+
if (directConfig) {
|
|
2015
|
+
this.sessionDirectConfigs.set(agentSessionID, directConfig);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
this.targetWireActionMaps.set(targetAgentID, wireMap);
|
|
2019
|
+
const tools = [];
|
|
2020
|
+
for (const [wireName, action] of wireMap.entries()) {
|
|
2021
|
+
tools.push(this.mapActionToToolDefinition(action, wireName));
|
|
2022
|
+
}
|
|
2023
|
+
return tools;
|
|
2024
|
+
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Maps an action entity and its metadata parameters to a RealtimeToolDefinition.
|
|
2027
|
+
*
|
|
2028
|
+
* @param action The action entity to map.
|
|
2029
|
+
* @param wireName Optional pre-sanitized wire name. If omitted, derives via {@link SanitizeWireToolName}.
|
|
2030
|
+
* @returns The constructed tool definition.
|
|
2031
|
+
*/
|
|
2032
|
+
mapActionToToolDefinition(action, wireName) {
|
|
2033
|
+
const rawParams = (action.Params?.Items && action.Params.Items.length > 0)
|
|
2034
|
+
? action.Params.Items
|
|
2035
|
+
: (ActionEngineServer.Instance.ActionParams ?? []).filter(p => UUIDsEqual(p.ActionID, action.ID));
|
|
2036
|
+
const inputParams = rawParams.filter(p => {
|
|
2037
|
+
const dir = (p.Type ?? 'Input').trim().toLowerCase();
|
|
2038
|
+
return dir === 'input' || dir === 'both';
|
|
2039
|
+
});
|
|
2040
|
+
const properties = {};
|
|
2041
|
+
const required = [];
|
|
2042
|
+
for (const p of inputParams) {
|
|
2043
|
+
let schemaType = 'string';
|
|
2044
|
+
if (p.IsArray) {
|
|
2045
|
+
schemaType = 'array';
|
|
2046
|
+
}
|
|
2047
|
+
else if (p.ValueType === 'Simple Object' || p.ValueType === 'BaseEntity Sub-Class') {
|
|
2048
|
+
schemaType = 'object';
|
|
2049
|
+
}
|
|
2050
|
+
const propSchema = {
|
|
2051
|
+
type: schemaType,
|
|
2052
|
+
description: p.Description || p.Name
|
|
2053
|
+
};
|
|
2054
|
+
properties[p.Name] = propSchema;
|
|
2055
|
+
if (p.IsRequired) {
|
|
2056
|
+
required.push(p.Name);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
const parametersSchema = {
|
|
2060
|
+
type: 'object',
|
|
2061
|
+
properties: properties,
|
|
2062
|
+
...(required.length > 0 ? { required } : {})
|
|
2063
|
+
};
|
|
1895
2064
|
return {
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
2065
|
+
Name: wireName ?? SanitizeWireToolName(action.Name),
|
|
2066
|
+
Description: action.Description || `Execute the ${action.Name} action.`,
|
|
2067
|
+
ParametersSchema: parametersSchema
|
|
1899
2068
|
};
|
|
1900
2069
|
}
|
|
2070
|
+
/**
|
|
2071
|
+
* Routes a non-target tool call by resolving the action through the wire map, checking the
|
|
2072
|
+
* direct-actions allowlist, and executing via `ActionEngineServer.Instance.RunAction` under a
|
|
2073
|
+
* configured timeout. If the tool is unrecognized, disallowed, or target resolution fails,
|
|
2074
|
+
* returns a structured "not available" result.
|
|
2075
|
+
*
|
|
2076
|
+
* @param call The non-target tool call.
|
|
2077
|
+
* @param input The optional relayed tool input context.
|
|
2078
|
+
* @param contextUser The calling user context.
|
|
2079
|
+
* @returns A {@link ToolExecutionResult} for the model's tool_response.
|
|
2080
|
+
*/
|
|
2081
|
+
async executeNonTargetTool(call, input, contextUser) {
|
|
2082
|
+
if (!input?.TargetAgentID) {
|
|
2083
|
+
return {
|
|
2084
|
+
CallID: call.CallID,
|
|
2085
|
+
Success: false,
|
|
2086
|
+
Output: `Tool '${call.ToolName}' is not available in this voice session.`
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
const target = this.resolveTargetAgent(input.TargetAgentID);
|
|
2090
|
+
if (!target) {
|
|
2091
|
+
return {
|
|
2092
|
+
CallID: call.CallID,
|
|
2093
|
+
Success: false,
|
|
2094
|
+
Output: `Target agent is not available to execute '${call.ToolName}'.`
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
const candidateActions = this.getTargetAgentActions(target.ID);
|
|
2098
|
+
const candidateWireMap = this.buildWireActionMap(candidateActions);
|
|
2099
|
+
const action = (input?.AgentSessionID ? this.sessionWireActionMaps.get(input.AgentSessionID)?.get(call.ToolName) : undefined)
|
|
2100
|
+
?? candidateWireMap.get(call.ToolName)
|
|
2101
|
+
?? Array.from(candidateWireMap.entries()).find(([w]) => w.toLowerCase() === call.ToolName.trim().toLowerCase())?.[1];
|
|
2102
|
+
if (!action) {
|
|
2103
|
+
return {
|
|
2104
|
+
CallID: call.CallID,
|
|
2105
|
+
Success: false,
|
|
2106
|
+
Output: `Tool '${call.ToolName}' is not available to this agent.`
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
const directConfig = input?.DirectActions
|
|
2110
|
+
?? (input?.AgentSessionID ? this.sessionDirectConfigs.get(input.AgentSessionID) : undefined)
|
|
2111
|
+
?? GetDirectActionsConfig(this.resolveEffectiveConfig(target, undefined, target));
|
|
2112
|
+
if (!IsActionAllowedForDirectInvocation(action.Name, directConfig)) {
|
|
2113
|
+
return {
|
|
2114
|
+
CallID: call.CallID,
|
|
2115
|
+
Success: false,
|
|
2116
|
+
Output: `Action '${action.Name}' is not enabled for direct voice invocation.`
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
if (!contextUser) {
|
|
2120
|
+
return {
|
|
2121
|
+
CallID: call.CallID,
|
|
2122
|
+
Success: false,
|
|
2123
|
+
Output: `Execution of action '${action.Name}' refused: authenticated user context is required.`
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
const timeoutMs = directConfig?.timeoutMs ?? 10_000;
|
|
2127
|
+
const executingUser = contextUser;
|
|
2128
|
+
try {
|
|
2129
|
+
const rawParams = this.parseActionParams(call.Arguments);
|
|
2130
|
+
const actionParams = Object.entries(rawParams).map(([name, value]) => ({
|
|
2131
|
+
Name: name,
|
|
2132
|
+
Value: value,
|
|
2133
|
+
Type: 'Input'
|
|
2134
|
+
}));
|
|
2135
|
+
const runParams = new RunActionParams();
|
|
2136
|
+
runParams.Action = action;
|
|
2137
|
+
runParams.ContextUser = executingUser;
|
|
2138
|
+
runParams.Params = actionParams;
|
|
2139
|
+
runParams.Filters = [];
|
|
2140
|
+
let timerHandle;
|
|
2141
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2142
|
+
timerHandle = setTimeout(() => {
|
|
2143
|
+
reject(new Error(`Action '${action.Name}' execution timed out after ${timeoutMs}ms`));
|
|
2144
|
+
}, timeoutMs);
|
|
2145
|
+
if (typeof timerHandle.unref === 'function') {
|
|
2146
|
+
timerHandle.unref();
|
|
2147
|
+
}
|
|
2148
|
+
});
|
|
2149
|
+
const result = await Promise.race([
|
|
2150
|
+
ActionEngineServer.Instance.RunAction(runParams),
|
|
2151
|
+
timeoutPromise
|
|
2152
|
+
]).finally(() => {
|
|
2153
|
+
if (timerHandle) {
|
|
2154
|
+
clearTimeout(timerHandle);
|
|
2155
|
+
}
|
|
2156
|
+
});
|
|
2157
|
+
const outputText = result.Message || (result.Success ? 'Action completed successfully.' : 'Action failed.');
|
|
2158
|
+
return {
|
|
2159
|
+
CallID: call.CallID,
|
|
2160
|
+
Success: result.Success,
|
|
2161
|
+
Output: outputText
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
catch (error) {
|
|
2165
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2166
|
+
return {
|
|
2167
|
+
CallID: call.CallID,
|
|
2168
|
+
Success: false,
|
|
2169
|
+
Output: `Action '${action.Name}' failed: ${message}`
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
/**
|
|
2174
|
+
* Parses JSON arguments string into a key-value record of parameters.
|
|
2175
|
+
* Distinguishes "no arguments" (valid for zero-param action) from "unparseable JSON".
|
|
2176
|
+
*
|
|
2177
|
+
* @param argumentsJson The raw arguments string emitted by the model.
|
|
2178
|
+
* @returns The parsed arguments record.
|
|
2179
|
+
* @throws ToolArgumentsError with actionable message when arguments are present but unparseable.
|
|
2180
|
+
*/
|
|
2181
|
+
parseActionParams(argumentsJson) {
|
|
2182
|
+
if (!argumentsJson || argumentsJson.trim() === '') {
|
|
2183
|
+
return {};
|
|
2184
|
+
}
|
|
2185
|
+
let parsed;
|
|
2186
|
+
try {
|
|
2187
|
+
parsed = JSON.parse(argumentsJson);
|
|
2188
|
+
}
|
|
2189
|
+
catch (err) {
|
|
2190
|
+
const syntaxMsg = err instanceof Error ? err.message : String(err);
|
|
2191
|
+
throw new ToolArgumentsError(`Unparseable JSON arguments: ${syntaxMsg}. Please provide valid JSON formatted arguments.`);
|
|
2192
|
+
}
|
|
2193
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
2194
|
+
return parsed;
|
|
2195
|
+
}
|
|
2196
|
+
throw new ToolArgumentsError(`Tool arguments must be a JSON object, but received ${Array.isArray(parsed) ? 'an array' : typeof parsed}. Please format arguments as a JSON object.`);
|
|
2197
|
+
}
|
|
1901
2198
|
/**
|
|
1902
2199
|
* Parses the natural-language request text out of an `invoke-target-agent` call's arguments.
|
|
1903
2200
|
* Falls back to the raw argument string when it is not the expected `{ request: string }` JSON.
|