@botonic/plugin-ai-agents 0.48.1 → 0.49.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/lib/cjs/agents/base-agent.d.ts +28 -0
  2. package/lib/cjs/agents/base-agent.js +39 -0
  3. package/lib/cjs/agents/base-agent.js.map +1 -0
  4. package/lib/cjs/agents/index.d.ts +2 -0
  5. package/lib/cjs/agents/index.js +8 -0
  6. package/lib/cjs/agents/index.js.map +1 -0
  7. package/lib/cjs/agents/router-agent.d.ts +25 -0
  8. package/lib/cjs/agents/router-agent.js +33 -0
  9. package/lib/cjs/agents/router-agent.js.map +1 -0
  10. package/lib/cjs/agents/specialist-agent.d.ts +35 -0
  11. package/lib/cjs/{agent-builder.js → agents/specialist-agent.js} +48 -49
  12. package/lib/cjs/agents/specialist-agent.js.map +1 -0
  13. package/lib/cjs/bot-config-tools.js +3 -4
  14. package/lib/cjs/bot-config-tools.js.map +1 -1
  15. package/lib/cjs/debug-logger.d.ts +1 -1
  16. package/lib/cjs/debug-logger.js +4 -1
  17. package/lib/cjs/debug-logger.js.map +1 -1
  18. package/lib/cjs/guardrails/input.d.ts +1 -1
  19. package/lib/cjs/guardrails/input.js +20 -9
  20. package/lib/cjs/guardrails/input.js.map +1 -1
  21. package/lib/cjs/index.d.ts +4 -1
  22. package/lib/cjs/index.js +112 -51
  23. package/lib/cjs/index.js.map +1 -1
  24. package/lib/cjs/llm-config.d.ts +2 -1
  25. package/lib/cjs/llm-config.js +3 -0
  26. package/lib/cjs/llm-config.js.map +1 -1
  27. package/lib/cjs/runners/base-runner.d.ts +26 -0
  28. package/lib/cjs/runners/base-runner.js +114 -0
  29. package/lib/cjs/runners/base-runner.js.map +1 -0
  30. package/lib/cjs/runners/index.d.ts +2 -0
  31. package/lib/cjs/runners/index.js +8 -0
  32. package/lib/cjs/runners/index.js.map +1 -0
  33. package/lib/cjs/runners/router-runner.d.ts +6 -0
  34. package/lib/cjs/runners/router-runner.js +29 -0
  35. package/lib/cjs/runners/router-runner.js.map +1 -0
  36. package/lib/cjs/runners/specialist-runner.d.ts +10 -0
  37. package/lib/cjs/runners/specialist-runner.js +78 -0
  38. package/lib/cjs/runners/specialist-runner.js.map +1 -0
  39. package/lib/cjs/structured-output/bot-executor.d.ts +6 -42
  40. package/lib/cjs/structured-output/bot-executor.js +9 -9
  41. package/lib/cjs/structured-output/bot-executor.js.map +1 -1
  42. package/lib/cjs/structured-output/carousel.d.ts +6 -78
  43. package/lib/cjs/structured-output/carousel.js +2 -1
  44. package/lib/cjs/structured-output/carousel.js.map +1 -1
  45. package/lib/cjs/structured-output/exit.d.ts +4 -8
  46. package/lib/cjs/structured-output/exit.js +4 -4
  47. package/lib/cjs/structured-output/exit.js.map +1 -1
  48. package/lib/cjs/structured-output/index.d.ts +48 -553
  49. package/lib/cjs/structured-output/index.js +15 -0
  50. package/lib/cjs/structured-output/index.js.map +1 -1
  51. package/lib/cjs/structured-output/text-with-buttons.d.ts +10 -49
  52. package/lib/cjs/structured-output/text-with-buttons.js +10 -10
  53. package/lib/cjs/structured-output/text-with-buttons.js.map +1 -1
  54. package/lib/cjs/structured-output/text.d.ts +4 -18
  55. package/lib/cjs/structured-output/text.js +2 -1
  56. package/lib/cjs/structured-output/text.js.map +1 -1
  57. package/lib/cjs/tools/index.d.ts +1 -1
  58. package/lib/cjs/tools/index.js +3 -2
  59. package/lib/cjs/tools/index.js.map +1 -1
  60. package/lib/cjs/tools/retrieve-knowledge.d.ts +3 -6
  61. package/lib/cjs/tools/retrieve-knowledge.js +7 -5
  62. package/lib/cjs/tools/retrieve-knowledge.js.map +1 -1
  63. package/lib/cjs/types.d.ts +2 -3
  64. package/lib/esm/agents/base-agent.d.ts +28 -0
  65. package/lib/esm/agents/base-agent.js +39 -0
  66. package/lib/esm/agents/base-agent.js.map +1 -0
  67. package/lib/esm/agents/index.d.ts +2 -0
  68. package/lib/esm/agents/index.js +8 -0
  69. package/lib/esm/agents/index.js.map +1 -0
  70. package/lib/esm/agents/router-agent.d.ts +25 -0
  71. package/lib/esm/agents/router-agent.js +33 -0
  72. package/lib/esm/agents/router-agent.js.map +1 -0
  73. package/lib/esm/agents/specialist-agent.d.ts +35 -0
  74. package/lib/esm/{agent-builder.js → agents/specialist-agent.js} +48 -49
  75. package/lib/esm/agents/specialist-agent.js.map +1 -0
  76. package/lib/esm/bot-config-tools.js +3 -4
  77. package/lib/esm/bot-config-tools.js.map +1 -1
  78. package/lib/esm/debug-logger.d.ts +1 -1
  79. package/lib/esm/debug-logger.js +4 -1
  80. package/lib/esm/debug-logger.js.map +1 -1
  81. package/lib/esm/guardrails/input.d.ts +1 -1
  82. package/lib/esm/guardrails/input.js +20 -9
  83. package/lib/esm/guardrails/input.js.map +1 -1
  84. package/lib/esm/index.d.ts +4 -1
  85. package/lib/esm/index.js +112 -51
  86. package/lib/esm/index.js.map +1 -1
  87. package/lib/esm/llm-config.d.ts +2 -1
  88. package/lib/esm/llm-config.js +3 -0
  89. package/lib/esm/llm-config.js.map +1 -1
  90. package/lib/esm/runners/base-runner.d.ts +26 -0
  91. package/lib/esm/runners/base-runner.js +114 -0
  92. package/lib/esm/runners/base-runner.js.map +1 -0
  93. package/lib/esm/runners/index.d.ts +2 -0
  94. package/lib/esm/runners/index.js +8 -0
  95. package/lib/esm/runners/index.js.map +1 -0
  96. package/lib/esm/runners/router-runner.d.ts +6 -0
  97. package/lib/esm/runners/router-runner.js +29 -0
  98. package/lib/esm/runners/router-runner.js.map +1 -0
  99. package/lib/esm/runners/specialist-runner.d.ts +10 -0
  100. package/lib/esm/runners/specialist-runner.js +78 -0
  101. package/lib/esm/runners/specialist-runner.js.map +1 -0
  102. package/lib/esm/structured-output/bot-executor.d.ts +6 -42
  103. package/lib/esm/structured-output/bot-executor.js +9 -9
  104. package/lib/esm/structured-output/bot-executor.js.map +1 -1
  105. package/lib/esm/structured-output/carousel.d.ts +6 -78
  106. package/lib/esm/structured-output/carousel.js +2 -1
  107. package/lib/esm/structured-output/carousel.js.map +1 -1
  108. package/lib/esm/structured-output/exit.d.ts +4 -8
  109. package/lib/esm/structured-output/exit.js +4 -4
  110. package/lib/esm/structured-output/exit.js.map +1 -1
  111. package/lib/esm/structured-output/index.d.ts +48 -553
  112. package/lib/esm/structured-output/index.js +15 -0
  113. package/lib/esm/structured-output/index.js.map +1 -1
  114. package/lib/esm/structured-output/text-with-buttons.d.ts +10 -49
  115. package/lib/esm/structured-output/text-with-buttons.js +10 -10
  116. package/lib/esm/structured-output/text-with-buttons.js.map +1 -1
  117. package/lib/esm/structured-output/text.d.ts +4 -18
  118. package/lib/esm/structured-output/text.js +2 -1
  119. package/lib/esm/structured-output/text.js.map +1 -1
  120. package/lib/esm/tools/index.d.ts +1 -1
  121. package/lib/esm/tools/index.js +3 -2
  122. package/lib/esm/tools/index.js.map +1 -1
  123. package/lib/esm/tools/retrieve-knowledge.d.ts +3 -6
  124. package/lib/esm/tools/retrieve-knowledge.js +7 -5
  125. package/lib/esm/tools/retrieve-knowledge.js.map +1 -1
  126. package/lib/esm/types.d.ts +2 -3
  127. package/package.json +5 -6
  128. package/src/agents/base-agent.ts +75 -0
  129. package/src/agents/index.ts +2 -0
  130. package/src/agents/router-agent.ts +71 -0
  131. package/src/{agent-builder.ts → agents/specialist-agent.ts} +77 -77
  132. package/src/bot-config-tools.ts +3 -4
  133. package/src/debug-logger.ts +10 -4
  134. package/src/guardrails/input.ts +26 -9
  135. package/src/index.ts +216 -82
  136. package/src/llm-config.ts +5 -0
  137. package/src/runners/base-runner.ts +190 -0
  138. package/src/runners/index.ts +2 -0
  139. package/src/runners/router-runner.ts +41 -0
  140. package/src/runners/specialist-runner.ts +112 -0
  141. package/src/structured-output/bot-executor.ts +3 -3
  142. package/src/structured-output/carousel.ts +2 -2
  143. package/src/structured-output/exit.ts +3 -3
  144. package/src/structured-output/index.ts +15 -0
  145. package/src/structured-output/text-with-buttons.ts +3 -3
  146. package/src/structured-output/text.ts +2 -2
  147. package/src/tools/index.ts +4 -1
  148. package/src/tools/retrieve-knowledge.ts +32 -29
  149. package/src/types.ts +2 -3
  150. package/lib/cjs/agent-builder.d.ts +0 -37
  151. package/lib/cjs/agent-builder.js.map +0 -1
  152. package/lib/cjs/runner.d.ts +0 -18
  153. package/lib/cjs/runner.js +0 -180
  154. package/lib/cjs/runner.js.map +0 -1
  155. package/lib/esm/agent-builder.d.ts +0 -37
  156. package/lib/esm/agent-builder.js.map +0 -1
  157. package/lib/esm/runner.d.ts +0 -18
  158. package/lib/esm/runner.js +0 -180
  159. package/lib/esm/runner.js.map +0 -1
  160. package/src/runner.ts +0 -283
@@ -1,5 +1,5 @@
1
1
  import type { ToolConfigJSON } from '@botonic/core'
2
- import { zodToJsonSchema } from 'zod-to-json-schema'
2
+ import { z } from 'zod'
3
3
 
4
4
  import type { CustomTool } from './types'
5
5
 
@@ -13,9 +13,8 @@ export function getToolsForBotConfig(
13
13
  return customTools.map(tool => ({
14
14
  name: tool.name,
15
15
  description: tool.description,
16
- // Cast to avoid TS "Type instantiation is excessively deep" with zodToJsonSchema + ZodSchema
17
- schema: zodToJsonSchema(tool.schema as never, {
18
- $refStrategy: 'none',
16
+ schema: z.toJSONSchema(tool.schema, {
17
+ target: 'draft-07',
19
18
  }) as ToolConfigJSON['schema'],
20
19
  }))
21
20
  }
@@ -1,4 +1,8 @@
1
- import type { AiAgentArgs, ToolExecution } from '@botonic/core'
1
+ import {
2
+ type AiAgentArgs,
3
+ AiAgentType,
4
+ type ToolExecution,
5
+ } from '@botonic/core'
2
6
  import type { ModelSettings } from '@openai/agents'
3
7
  import { OPENAI_PROVIDER } from './constants'
4
8
  import type { AgenticInputMessage, MemoryOptions, RunResult } from './types'
@@ -66,9 +70,11 @@ class EnabledDebugLogger implements DebugLogger {
66
70
  console.log(`${PREFIX} === AI Agent Debug Info ===`)
67
71
  console.log(`${PREFIX} Agent Name: ${aiAgentArgs.name}`)
68
72
  console.log(`${PREFIX} Active Tools: ${JSON.stringify(toolNames)}`)
69
- console.log(
70
- `${PREFIX} Source IDs: ${JSON.stringify(aiAgentArgs.sourceIds || [])}`
71
- )
73
+ if (aiAgentArgs.type === AiAgentType.Specialist) {
74
+ console.log(
75
+ `${PREFIX} Source IDs: ${JSON.stringify(aiAgentArgs.sourceIds || [])}`
76
+ )
77
+ }
72
78
  console.log(`${PREFIX} Message History Count: ${messages.length}`)
73
79
  console.log(
74
80
  `${PREFIX} Input Guardrail Rules: ${aiAgentArgs.inputGuardrailRules?.length || 0}`
@@ -19,20 +19,34 @@ export interface GuardrailTrackingContext {
19
19
  inferenceId: string
20
20
  }
21
21
 
22
- export function createInputGuardrail(
22
+ export async function createInputGuardrails(
23
23
  rules: GuardrailRule[],
24
24
  llmConfig: LLMConfig,
25
25
  trackingContext: GuardrailTrackingContext
26
- ): InputGuardrail {
26
+ ): Promise<InputGuardrail[]> {
27
+ if (rules.length === 0) {
28
+ return []
29
+ }
30
+
31
+ return [await buildInputGuardrail(rules, llmConfig, trackingContext)]
32
+ }
33
+
34
+ async function buildInputGuardrail(
35
+ rules: GuardrailRule[],
36
+ llmConfig: LLMConfig,
37
+ trackingContext: GuardrailTrackingContext
38
+ ): Promise<InputGuardrail> {
27
39
  const outputType = z.object(
28
40
  Object.fromEntries(
29
41
  rules.map(rule => [rule.name, z.boolean().describe(rule.description)])
30
42
  )
31
43
  )
44
+ const modelSettings = createInputGuardrailModelSettings(llmConfig)
32
45
 
33
46
  const agent = new Agent({
34
47
  name: 'InputGuardrail',
35
- model: llmConfig.modelName,
48
+ model: await llmConfig.getModel(),
49
+ modelSettings,
36
50
  instructions:
37
51
  'Check if the user triggers some of the following guardrails.',
38
52
  outputType,
@@ -42,11 +56,7 @@ export function createInputGuardrail(
42
56
  name: 'InputGuardrail',
43
57
  execute: async ({ input, context }) => {
44
58
  const lastMessage = input[input.length - 1] as UserMessageItem
45
- const modelProvider = llmConfig.modelProvider
46
- const modelSettings = createInputGuardrailModelSettings(llmConfig)
47
59
  const runner = new Runner({
48
- modelSettings,
49
- modelProvider,
50
60
  tracingDisabled: true,
51
61
  })
52
62
  const startTime = Date.now()
@@ -80,10 +90,17 @@ export function createInputGuardrail(
80
90
  function createInputGuardrailModelSettings(
81
91
  llmConfig: LLMConfig
82
92
  ): ModelSettings {
83
- return {
93
+ const modelSettings: ModelSettings = {
84
94
  ...llmConfig.modelSettings,
85
95
  toolChoice: undefined,
86
96
  }
97
+ if (llmConfig.modelSettings.reasoning) {
98
+ modelSettings.reasoning = { ...llmConfig.modelSettings.reasoning }
99
+ }
100
+ if (llmConfig.modelSettings.text) {
101
+ modelSettings.text = { ...llmConfig.modelSettings.text }
102
+ }
103
+ return modelSettings
87
104
  }
88
105
 
89
106
  async function sendGuardrailLlmRunTracking(
@@ -114,7 +131,7 @@ async function sendGuardrailLlmRunTracking(
114
131
  product_name: TrackProductName.AI_AGENT,
115
132
  deployment_name: llmConfig.modelName,
116
133
  model_name:
117
- (response.providerData?.['model'] as string | undefined) ??
134
+ (response.providerData?.model as string | undefined) ??
118
135
  llmConfig.modelName,
119
136
  feature: TrackFeature.AI_AGENT_GUARDRAIL,
120
137
  api_version: apiVersion,
package/src/index.ts CHANGED
@@ -1,13 +1,17 @@
1
- import type {
2
- AiAgentArgs,
3
- BotContext,
4
- HubtypeAssistantMessage,
5
- Plugin,
6
- ResolvedPlugins,
1
+ import {
2
+ type AIAgentRouterArgs,
3
+ type AiAgentArgs,
4
+ type AiAgentSpecialistArgs,
5
+ AiAgentType,
6
+ type BotContext,
7
+ type HubtypeAssistantMessage,
8
+ type Plugin,
9
+ type ResolvedPlugins,
7
10
  } from '@botonic/core'
8
- import { setTracingDisabled, tool } from '@openai/agents'
11
+ import { handoff, setTracingDisabled, tool } from '@openai/agents'
9
12
  import { v7 as uuidv7 } from 'uuid'
10
- import { AIAgentBuilder } from './agent-builder'
13
+ import type { ZodObject } from 'zod'
14
+ import { RouterAgent, SpecialistAgent } from './agents'
11
15
  import {
12
16
  DEFAULT_MAX_RETRIES,
13
17
  DEFAULT_TIMEOUT_16_SECONDS,
@@ -16,7 +20,7 @@ import {
16
20
  } from './constants'
17
21
  import { createDebugLogger, type DebugLogger } from './debug-logger'
18
22
  import { LLMConfig } from './llm-config'
19
- import { AIAgentRunner } from './runner'
23
+ import { RouterRunner, WorkerRunner } from './runners'
20
24
  import { HubtypeApiClient } from './services/hubtype-api-client'
21
25
  import type {
22
26
  AgenticInputMessage,
@@ -77,84 +81,33 @@ export default class BotonicPluginAiAgents<
77
81
  botContext: BotContext<TPlugins, TExtraData>,
78
82
  aiAgentArgs: AiAgentArgs
79
83
  ): Promise<InferenceResponse> {
80
- try {
81
- const authToken = isProd
82
- ? botContext.session._access_token
83
- : this.authToken
84
- if (!authToken) {
85
- throw new Error('Auth token is required')
86
- }
87
-
88
- const inferenceId = uuidv7()
89
-
90
- // Create client for OpenAI/Azure OpenAI
91
- const llmConfig = new LLMConfig(
92
- this.maxRetries,
93
- this.timeout,
94
- aiAgentArgs.model,
95
- aiAgentArgs.verbosity
96
- )
84
+ const authToken = isProd ? botContext.session._access_token : this.authToken
85
+ if (!authToken) {
86
+ throw new Error('Auth token is required')
87
+ }
97
88
 
98
- // Build tools
99
- const tools = this.buildTools(
100
- aiAgentArgs.activeTools?.map(tool => tool.name) || []
101
- )
89
+ const inferenceId = uuidv7()
102
90
 
103
- // Build agent
104
- const agent = new AIAgentBuilder<TPlugins, TExtraData>({
105
- name: aiAgentArgs.name,
106
- instructions: aiAgentArgs.instructions,
107
- tools: tools,
108
- contactInfo: botContext.session.user.contact_info || [],
109
- inputGuardrailRules: aiAgentArgs.inputGuardrailRules || [],
110
- sourceIds: aiAgentArgs.sourceIds || [],
111
- outputMessagesSchemas: aiAgentArgs.outputMessagesSchemas || [],
112
- campaignsContext: botContext.input.context?.campaigns_v2,
113
- logger: this.logger,
114
- llmConfig,
115
- guardrailTrackingContext: {
116
- botId: botContext.session.bot.id,
117
- isTest: botContext.session.is_test_integration,
91
+ try {
92
+ if (aiAgentArgs.type === AiAgentType.Specialist) {
93
+ return await this.executeWorkerAIAgent(
94
+ botContext,
95
+ aiAgentArgs,
118
96
  authToken,
119
- inferenceId,
120
- },
121
- }).build()
122
-
123
- // Get messages
124
- const messages = await this.getMessages(
125
- botContext,
126
- authToken,
127
- aiAgentArgs.previousHubtypeMessages || []
128
- )
129
-
130
- // Build context
131
- const context: Context<TPlugins, TExtraData> = {
132
- authToken,
133
- sourceIds: aiAgentArgs.sourceIds || [],
134
- knowledgeUsed: {
135
- query: '',
136
- sourceIds: [],
137
- chunksIds: [],
138
- chunkTexts: [],
139
- },
140
- request: botContext,
97
+ inferenceId
98
+ )
141
99
  }
142
100
 
143
- // Log agent debug info
144
- this.logger.logAgentDebugInfo(
145
- aiAgentArgs,
146
- tools.map(t => t.name),
147
- messages
148
- )
101
+ if (aiAgentArgs.type === AiAgentType.Router) {
102
+ return await this.executeRouterAIAgent(
103
+ botContext,
104
+ aiAgentArgs,
105
+ authToken,
106
+ inferenceId
107
+ )
108
+ }
149
109
 
150
- // Run agent
151
- const runner = new AIAgentRunner<TPlugins, TExtraData>(
152
- agent,
153
- llmConfig,
154
- inferenceId,
155
- this.logger
156
- )
157
- return await runner.run(messages, context)
110
+ throw new Error('Invalid agent type')
158
111
  } catch (error) {
159
112
  console.error('error plugin returns undefined', error)
160
113
  return {
@@ -165,10 +118,188 @@ export default class BotonicPluginAiAgents<
165
118
  error: true,
166
119
  inputGuardrailsTriggered: [],
167
120
  outputGuardrailsTriggered: [],
121
+ startingAgentName: '',
122
+ lastAgentName: '',
123
+ availableSpecialists: [],
124
+ isTransferredToSpecialist: false,
168
125
  }
169
126
  }
170
127
  }
171
128
 
129
+ private async executeWorkerAIAgent(
130
+ botContext: BotContext<TPlugins, TExtraData>,
131
+ aiAgentArgs: AiAgentSpecialistArgs,
132
+ authToken: string,
133
+ inferenceId: string
134
+ ) {
135
+ const llmConfig = new LLMConfig(
136
+ this.maxRetries,
137
+ this.timeout,
138
+ aiAgentArgs.model,
139
+ aiAgentArgs.verbosity
140
+ )
141
+
142
+ // Get LLM config, tools and agent
143
+ const { tools, agent } = await this.getAIAgentWorkerAndTools(
144
+ botContext,
145
+ aiAgentArgs,
146
+ aiAgentArgs.outputMessagesSchemas || [],
147
+ authToken,
148
+ inferenceId,
149
+ llmConfig
150
+ )
151
+
152
+ // Get messages
153
+ const messages = await this.getMessages(
154
+ botContext,
155
+ authToken,
156
+ aiAgentArgs.previousHubtypeMessages || []
157
+ )
158
+
159
+ // Build context
160
+ const context: Context<TPlugins, TExtraData> = {
161
+ authToken,
162
+ knowledgeUsed: {
163
+ query: '',
164
+ sourceIds: [],
165
+ chunksIds: [],
166
+ chunkTexts: [],
167
+ },
168
+ request: botContext,
169
+ }
170
+
171
+ // Log agent debug info
172
+ this.logger.logAgentDebugInfo(
173
+ aiAgentArgs,
174
+ tools.map(t => t.name),
175
+ messages
176
+ )
177
+
178
+ // Run agent
179
+ const runner = new WorkerRunner<TPlugins, TExtraData>(
180
+ agent,
181
+ llmConfig,
182
+ inferenceId,
183
+ this.logger
184
+ )
185
+ return await runner.run(messages, context)
186
+ }
187
+
188
+ private async executeRouterAIAgent(
189
+ botContext: BotContext<TPlugins, TExtraData>,
190
+ aiAgentArgs: AIAgentRouterArgs,
191
+ authToken: string,
192
+ inferenceId: string
193
+ ) {
194
+ const { specialists, name, instructions } = aiAgentArgs
195
+
196
+ const llmConfig = new LLMConfig(
197
+ this.maxRetries,
198
+ this.timeout,
199
+ aiAgentArgs.model,
200
+ aiAgentArgs.verbosity
201
+ )
202
+
203
+ const specialistsAgents = await Promise.all(
204
+ specialists.map(async specialistData => {
205
+ const { agent } = await this.getAIAgentWorkerAndTools(
206
+ botContext,
207
+ specialistData,
208
+ aiAgentArgs.outputMessagesSchemas || [],
209
+ authToken,
210
+ inferenceId,
211
+ llmConfig
212
+ )
213
+ return handoff(agent, {
214
+ toolNameOverride: specialistData.name,
215
+ toolDescriptionOverride: specialistData.description,
216
+ })
217
+ })
218
+ )
219
+
220
+ const router = await RouterAgent.create<TPlugins, TExtraData>({
221
+ name,
222
+ instructions,
223
+ llmConfig,
224
+ handoffs: specialistsAgents,
225
+ inputGuardrailRules: aiAgentArgs.inputGuardrailRules || [],
226
+ outputMessagesSchemas: aiAgentArgs.outputMessagesSchemas || [],
227
+ guardrailTrackingContext: {
228
+ botId: botContext.session.bot.id,
229
+ isTest: botContext.session.is_test_integration,
230
+ authToken,
231
+ inferenceId,
232
+ },
233
+ })
234
+
235
+ // Get messages
236
+ const messages = await this.getMessages(
237
+ botContext,
238
+ authToken,
239
+ aiAgentArgs.previousHubtypeMessages || []
240
+ )
241
+
242
+ // Build context
243
+ const context: Context<TPlugins, TExtraData> = {
244
+ authToken,
245
+ knowledgeUsed: {
246
+ query: '',
247
+ sourceIds: [],
248
+ chunksIds: [],
249
+ chunkTexts: [],
250
+ },
251
+ request: botContext,
252
+ }
253
+
254
+ // Run agent
255
+ const routerAgent = router.getAgent()
256
+ const runner = new RouterRunner<TPlugins, TExtraData>(
257
+ routerAgent,
258
+ llmConfig,
259
+ inferenceId,
260
+ this.logger
261
+ )
262
+
263
+ return await runner.run(messages, context)
264
+ }
265
+
266
+ private async getAIAgentWorkerAndTools(
267
+ botContext: BotContext,
268
+ aiAgentArgs: AiAgentArgs,
269
+ outputMessagesSchemas: ZodObject<any>[],
270
+ authToken: string,
271
+ inferenceId: string,
272
+ llmConfig: LLMConfig
273
+ ) {
274
+ // Build tools
275
+ const tools = this.buildTools(aiAgentArgs)
276
+
277
+ // Build agent
278
+ const sourceIds =
279
+ aiAgentArgs.type === AiAgentType.Specialist ? aiAgentArgs.sourceIds : []
280
+ const worker = await SpecialistAgent.create<TPlugins, TExtraData>({
281
+ name: aiAgentArgs.name,
282
+ instructions: aiAgentArgs.instructions,
283
+ tools: tools,
284
+ contactInfo: botContext.session.user.contact_info || [],
285
+ inputGuardrailRules: aiAgentArgs.inputGuardrailRules || [],
286
+ sourceIds,
287
+ outputMessagesSchemas: outputMessagesSchemas || [],
288
+ campaignsContext: botContext.input.context?.campaigns_v2,
289
+ logger: this.logger,
290
+ llmConfig,
291
+ guardrailTrackingContext: {
292
+ botId: botContext.session.bot.id,
293
+ isTest: botContext.session.is_test_integration,
294
+ authToken,
295
+ inferenceId,
296
+ },
297
+ })
298
+ const workerAgent = worker.getAgent()
299
+
300
+ return { agent: workerAgent, tools }
301
+ }
302
+
172
303
  private async getMessages(
173
304
  botContext: BotContext,
174
305
  authToken: string,
@@ -192,7 +323,10 @@ export default class BotonicPluginAiAgents<
192
323
  return result.messages
193
324
  }
194
325
 
195
- private buildTools(activeToolNames: string[]): Tool<TPlugins, TExtraData>[] {
326
+ private buildTools(aiAgentArgs: AiAgentArgs): Tool<TPlugins, TExtraData>[] {
327
+ const activeTools =
328
+ aiAgentArgs.type === AiAgentType.Router ? [] : aiAgentArgs.activeTools
329
+ const activeToolNames = activeTools.map(tool => tool.name)
196
330
  const availableTools = this.toolDefinitions.filter(tool =>
197
331
  activeToolNames.includes(tool.name)
198
332
  )
package/src/llm-config.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { VerbosityLevel } from '@botonic/core'
2
2
  import {
3
+ type Model,
3
4
  type ModelProvider,
4
5
  type ModelSettings,
5
6
  OpenAIProvider,
@@ -35,6 +36,10 @@ export class LLMConfig {
35
36
  this.modelSettings = this.getModelSettings(modelName, verbosity)
36
37
  }
37
38
 
39
+ async getModel(): Promise<Model> {
40
+ return await this.modelProvider.getModel(this.modelName)
41
+ }
42
+
38
43
  private getModelProvider(): ModelProvider {
39
44
  const client = this.getClient()
40
45
  return new OpenAIProvider({
@@ -0,0 +1,190 @@
1
+ import type {
2
+ OutputMessage,
3
+ ResolvedPlugins,
4
+ ToolExecution,
5
+ } from '@botonic/core'
6
+ import { InputGuardrailTripwireTriggered, Runner } from '@openai/agents'
7
+
8
+ import { isProd } from '../constants'
9
+ import type { DebugLogger } from '../debug-logger'
10
+ import { getApiVersion, type LLMConfig } from '../llm-config'
11
+ import { HubtypeApiClient } from '../services/hubtype-api-client'
12
+ import { TrackFeature, TrackProductName } from '../services/types'
13
+ import type {
14
+ AgenticInputMessage,
15
+ AgenticOutputMessage,
16
+ AIAgent,
17
+ Context,
18
+ ResultRawResponse,
19
+ RunResult,
20
+ } from '../types'
21
+
22
+ // Minimal interface matching the properties we actually use from Runner.run() result.
23
+ // This bypasses strict type checking while maintaining type safety for accessed properties.
24
+ export interface RunnerResult {
25
+ finalOutput?: {
26
+ messages?: OutputMessage[]
27
+ }
28
+ rawResponses?: ResultRawResponse[]
29
+ newItems?: unknown[]
30
+ lastAgent?: { name?: string }
31
+ // biome-ignore lint/suspicious/noExplicitAny: state is a complex internal type
32
+ state?: any
33
+ }
34
+
35
+ export abstract class BaseRunner<
36
+ TPlugins extends ResolvedPlugins = ResolvedPlugins,
37
+ TExtraData = unknown,
38
+ > {
39
+ protected agent: AIAgent<TPlugins, TExtraData>
40
+ protected llmConfig: LLMConfig
41
+ protected inferenceId: string
42
+ protected logger: DebugLogger
43
+
44
+ constructor(
45
+ agent: AIAgent<TPlugins, TExtraData>,
46
+ llmConfig: LLMConfig,
47
+ inferenceId: string,
48
+ logger: DebugLogger
49
+ ) {
50
+ this.agent = agent
51
+ this.llmConfig = llmConfig
52
+ this.inferenceId = inferenceId
53
+ this.logger = logger
54
+ }
55
+
56
+ async run(
57
+ messages: AgenticInputMessage[],
58
+ context: Context<TPlugins, TExtraData>
59
+ ): Promise<RunResult> {
60
+ const startTime = Date.now()
61
+
62
+ this.logger.logRunnerStart(
63
+ this.llmConfig.modelName,
64
+ this.llmConfig.modelSettings
65
+ )
66
+
67
+ try {
68
+ const runner = new Runner({
69
+ tracingDisabled: true,
70
+ })
71
+ const result = (await runner.run(this.agent, messages, {
72
+ context,
73
+ })) as RunnerResult
74
+ const endTime = Date.now()
75
+
76
+ await this.sendLlmRunTracking(result, context, startTime, endTime)
77
+
78
+ const runResult = this.buildRunResult(result, context, messages.length)
79
+
80
+ this.logger.logRunResult(runResult, startTime)
81
+
82
+ return runResult
83
+ } catch (error) {
84
+ if (error instanceof InputGuardrailTripwireTriggered) {
85
+ const runResult: RunResult = {
86
+ startingAgentName: '',
87
+ lastAgentName: '',
88
+ availableSpecialists: [],
89
+ isTransferredToSpecialist: false,
90
+ messages: [],
91
+ memoryLength: 0,
92
+ toolsExecuted: [],
93
+ exit: true,
94
+ error: false,
95
+ inputGuardrailsTriggered: error.result.output.outputInfo,
96
+ outputGuardrailsTriggered: [],
97
+ }
98
+
99
+ this.logger.logGuardrailTriggered()
100
+ this.logger.logRunResult(runResult, startTime)
101
+
102
+ return runResult
103
+ }
104
+
105
+ this.logger.logRunnerError(startTime, error)
106
+
107
+ throw error
108
+ }
109
+ }
110
+
111
+ protected getToolsExecuted(
112
+ _result: RunnerResult,
113
+ _context: Context<TPlugins, TExtraData>
114
+ ): ToolExecution[] {
115
+ return []
116
+ }
117
+
118
+ protected buildRunResult(
119
+ result: RunnerResult,
120
+ context: Context<TPlugins, TExtraData>,
121
+ memoryLength: number
122
+ ): RunResult {
123
+ const outputMessages = result.finalOutput?.messages || []
124
+ const hasExit =
125
+ outputMessages.length === 0 ||
126
+ outputMessages.some(message => message.type === 'exit')
127
+
128
+ return {
129
+ startingAgentName: '',
130
+ lastAgentName: '',
131
+ availableSpecialists: [],
132
+ isTransferredToSpecialist: false,
133
+ messages: hasExit
134
+ ? []
135
+ : (outputMessages.filter(
136
+ message => message.type !== 'exit'
137
+ ) as AgenticOutputMessage[]),
138
+ toolsExecuted: this.getToolsExecuted(result, context),
139
+ exit: hasExit,
140
+ memoryLength,
141
+ error: false,
142
+ inputGuardrailsTriggered: [],
143
+ outputGuardrailsTriggered: [],
144
+ }
145
+ }
146
+
147
+ private async sendLlmRunTracking(
148
+ result: RunnerResult,
149
+ context: Context<TPlugins, TExtraData>,
150
+ startTime: number,
151
+ endTime: number
152
+ ): Promise<void> {
153
+ if (!isProd) {
154
+ return
155
+ }
156
+ const rawResponses = result.rawResponses ?? []
157
+ if (rawResponses.length === 0) {
158
+ return
159
+ }
160
+ const botId = context.request.session.bot.id
161
+ const isTest = context.request.session.is_test_integration
162
+ const totalDuration = endTime - startTime
163
+ const durationPerCall = Math.round(totalDuration / rawResponses.length)
164
+ const temperature =
165
+ (this.llmConfig.modelSettings.temperature as number | undefined) ?? 0
166
+ const apiVersion = getApiVersion()
167
+
168
+ const llmRuns = rawResponses.map(response => ({
169
+ inference_id: this.inferenceId,
170
+ is_test: isTest,
171
+ product_name: TrackProductName.AI_AGENT,
172
+ deployment_name: this.llmConfig.modelName,
173
+ model_name:
174
+ (response.providerData?.model as string | undefined) ??
175
+ this.llmConfig.modelName,
176
+ feature: TrackFeature.AI_AGENT_RUN,
177
+ api_version: apiVersion,
178
+ num_prompt_tokens: response.usage.inputTokens,
179
+ num_completion_tokens: response.usage.outputTokens,
180
+ duration_in_milliseconds: durationPerCall,
181
+ temperature,
182
+ error: null,
183
+ }))
184
+
185
+ const client = new HubtypeApiClient(context.authToken)
186
+ await client.trackLlmRuns(botId, {
187
+ llm_runs: llmRuns,
188
+ })
189
+ }
190
+ }
@@ -0,0 +1,2 @@
1
+ export { RouterRunner } from './router-runner'
2
+ export { SpecialistRunner as WorkerRunner } from './specialist-runner'