@vybestack/llxprt-code-agents 0.10.0-nightly.260613.1adad3b34

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 (176) hide show
  1. package/dist/.last_build +0 -0
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.js +11 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/agents/executor-prompt-builder.d.ts +19 -0
  6. package/dist/src/agents/executor-prompt-builder.js +44 -0
  7. package/dist/src/agents/executor-prompt-builder.js.map +1 -0
  8. package/dist/src/agents/executor-termination.d.ts +22 -0
  9. package/dist/src/agents/executor-termination.js +44 -0
  10. package/dist/src/agents/executor-termination.js.map +1 -0
  11. package/dist/src/agents/executor-validation.d.ts +17 -0
  12. package/dist/src/agents/executor-validation.js +35 -0
  13. package/dist/src/agents/executor-validation.js.map +1 -0
  14. package/dist/src/agents/executor.d.ts +136 -0
  15. package/dist/src/agents/executor.js +874 -0
  16. package/dist/src/agents/executor.js.map +1 -0
  17. package/dist/src/agents/invocation.d.ts +45 -0
  18. package/dist/src/agents/invocation.js +118 -0
  19. package/dist/src/agents/invocation.js.map +1 -0
  20. package/dist/src/agents/recovery.d.ts +75 -0
  21. package/dist/src/agents/recovery.js +107 -0
  22. package/dist/src/agents/recovery.js.map +1 -0
  23. package/dist/src/agents/types.d.ts +149 -0
  24. package/dist/src/agents/types.js +18 -0
  25. package/dist/src/agents/types.js.map +1 -0
  26. package/dist/src/agents/utils.d.ts +15 -0
  27. package/dist/src/agents/utils.js +28 -0
  28. package/dist/src/agents/utils.js.map +1 -0
  29. package/dist/src/compression/CompressionHandler.d.ts +178 -0
  30. package/dist/src/compression/CompressionHandler.js +630 -0
  31. package/dist/src/compression/CompressionHandler.js.map +1 -0
  32. package/dist/src/compression/HighDensityStrategy.d.ts +113 -0
  33. package/dist/src/compression/HighDensityStrategy.js +769 -0
  34. package/dist/src/compression/HighDensityStrategy.js.map +1 -0
  35. package/dist/src/compression/MiddleOutStrategy.d.ts +27 -0
  36. package/dist/src/compression/MiddleOutStrategy.js +347 -0
  37. package/dist/src/compression/MiddleOutStrategy.js.map +1 -0
  38. package/dist/src/compression/OneShotStrategy.d.ts +22 -0
  39. package/dist/src/compression/OneShotStrategy.js +235 -0
  40. package/dist/src/compression/OneShotStrategy.js.map +1 -0
  41. package/dist/src/compression/TopDownTruncationStrategy.d.ts +13 -0
  42. package/dist/src/compression/TopDownTruncationStrategy.js +74 -0
  43. package/dist/src/compression/TopDownTruncationStrategy.js.map +1 -0
  44. package/dist/src/compression/compressionBudgeting.d.ts +34 -0
  45. package/dist/src/compression/compressionBudgeting.js +139 -0
  46. package/dist/src/compression/compressionBudgeting.js.map +1 -0
  47. package/dist/src/compression/compressionStrategyFactory.d.ts +23 -0
  48. package/dist/src/compression/compressionStrategyFactory.js +40 -0
  49. package/dist/src/compression/compressionStrategyFactory.js.map +1 -0
  50. package/dist/src/compression/index.d.ts +16 -0
  51. package/dist/src/compression/index.js +17 -0
  52. package/dist/src/compression/index.js.map +1 -0
  53. package/dist/src/compression/reasoningUtils.d.ts +18 -0
  54. package/dist/src/compression/reasoningUtils.js +22 -0
  55. package/dist/src/compression/reasoningUtils.js.map +1 -0
  56. package/dist/src/compression/utils.d.ts +114 -0
  57. package/dist/src/compression/utils.js +316 -0
  58. package/dist/src/compression/utils.js.map +1 -0
  59. package/dist/src/core/AgentHookManager.d.ts +41 -0
  60. package/dist/src/core/AgentHookManager.js +65 -0
  61. package/dist/src/core/AgentHookManager.js.map +1 -0
  62. package/dist/src/core/ChatSessionFactory.d.ts +50 -0
  63. package/dist/src/core/ChatSessionFactory.js +216 -0
  64. package/dist/src/core/ChatSessionFactory.js.map +1 -0
  65. package/dist/src/core/ConversationManager.d.ts +113 -0
  66. package/dist/src/core/ConversationManager.js +311 -0
  67. package/dist/src/core/ConversationManager.js.map +1 -0
  68. package/dist/src/core/DirectMessageProcessor.d.ts +79 -0
  69. package/dist/src/core/DirectMessageProcessor.js +478 -0
  70. package/dist/src/core/DirectMessageProcessor.js.map +1 -0
  71. package/dist/src/core/IdeContextTracker.d.ts +56 -0
  72. package/dist/src/core/IdeContextTracker.js +207 -0
  73. package/dist/src/core/IdeContextTracker.js.map +1 -0
  74. package/dist/src/core/MessageConverter.d.ts +78 -0
  75. package/dist/src/core/MessageConverter.js +492 -0
  76. package/dist/src/core/MessageConverter.js.map +1 -0
  77. package/dist/src/core/MessageStreamOrchestrator.d.ts +85 -0
  78. package/dist/src/core/MessageStreamOrchestrator.js +448 -0
  79. package/dist/src/core/MessageStreamOrchestrator.js.map +1 -0
  80. package/dist/src/core/MessageStreamTerminalHandler.d.ts +16 -0
  81. package/dist/src/core/MessageStreamTerminalHandler.js +170 -0
  82. package/dist/src/core/MessageStreamTerminalHandler.js.map +1 -0
  83. package/dist/src/core/StreamProcessor.d.ts +105 -0
  84. package/dist/src/core/StreamProcessor.js +660 -0
  85. package/dist/src/core/StreamProcessor.js.map +1 -0
  86. package/dist/src/core/TodoContinuationService.d.ts +74 -0
  87. package/dist/src/core/TodoContinuationService.js +312 -0
  88. package/dist/src/core/TodoContinuationService.js.map +1 -0
  89. package/dist/src/core/TurnProcessor.d.ts +91 -0
  90. package/dist/src/core/TurnProcessor.js +540 -0
  91. package/dist/src/core/TurnProcessor.js.map +1 -0
  92. package/dist/src/core/baseLlmClient.d.ts +115 -0
  93. package/dist/src/core/baseLlmClient.js +231 -0
  94. package/dist/src/core/baseLlmClient.js.map +1 -0
  95. package/dist/src/core/bucketFailoverIntegration.d.ts +62 -0
  96. package/dist/src/core/bucketFailoverIntegration.js +186 -0
  97. package/dist/src/core/bucketFailoverIntegration.js.map +1 -0
  98. package/dist/src/core/chatSession.d.ts +121 -0
  99. package/dist/src/core/chatSession.js +720 -0
  100. package/dist/src/core/chatSession.js.map +1 -0
  101. package/dist/src/core/client.d.ts +132 -0
  102. package/dist/src/core/client.js +524 -0
  103. package/dist/src/core/client.js.map +1 -0
  104. package/dist/src/core/clientHelpers.d.ts +15 -0
  105. package/dist/src/core/clientHelpers.js +122 -0
  106. package/dist/src/core/clientHelpers.js.map +1 -0
  107. package/dist/src/core/clientLlmUtilities.d.ts +30 -0
  108. package/dist/src/core/clientLlmUtilities.js +125 -0
  109. package/dist/src/core/clientLlmUtilities.js.map +1 -0
  110. package/dist/src/core/clientToolGovernance.d.ts +37 -0
  111. package/dist/src/core/clientToolGovernance.js +104 -0
  112. package/dist/src/core/clientToolGovernance.js.map +1 -0
  113. package/dist/src/core/compression-config.d.ts +12 -0
  114. package/dist/src/core/compression-config.js +21 -0
  115. package/dist/src/core/compression-config.js.map +1 -0
  116. package/dist/src/core/coreToolScheduler.d.ts +105 -0
  117. package/dist/src/core/coreToolScheduler.js +453 -0
  118. package/dist/src/core/coreToolScheduler.js.map +1 -0
  119. package/dist/src/core/hookToolRestrictions.d.ts +18 -0
  120. package/dist/src/core/hookToolRestrictions.js +187 -0
  121. package/dist/src/core/hookToolRestrictions.js.map +1 -0
  122. package/dist/src/core/nonInteractiveToolExecutor.d.ts +17 -0
  123. package/dist/src/core/nonInteractiveToolExecutor.js +102 -0
  124. package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -0
  125. package/dist/src/core/subagent.d.ts +105 -0
  126. package/dist/src/core/subagent.js +650 -0
  127. package/dist/src/core/subagent.js.map +1 -0
  128. package/dist/src/core/subagentExecution.d.ts +104 -0
  129. package/dist/src/core/subagentExecution.js +275 -0
  130. package/dist/src/core/subagentExecution.js.map +1 -0
  131. package/dist/src/core/subagentOrchestrator.d.ts +84 -0
  132. package/dist/src/core/subagentOrchestrator.js +503 -0
  133. package/dist/src/core/subagentOrchestrator.js.map +1 -0
  134. package/dist/src/core/subagentRuntimeSetup.d.ts +75 -0
  135. package/dist/src/core/subagentRuntimeSetup.js +387 -0
  136. package/dist/src/core/subagentRuntimeSetup.js.map +1 -0
  137. package/dist/src/core/subagentScheduler.d.ts +17 -0
  138. package/dist/src/core/subagentScheduler.js +7 -0
  139. package/dist/src/core/subagentScheduler.js.map +1 -0
  140. package/dist/src/core/subagentToolProcessing.d.ts +51 -0
  141. package/dist/src/core/subagentToolProcessing.js +359 -0
  142. package/dist/src/core/subagentToolProcessing.js.map +1 -0
  143. package/dist/src/core/toolGovernance.d.ts +18 -0
  144. package/dist/src/core/toolGovernance.js +50 -0
  145. package/dist/src/core/toolGovernance.js.map +1 -0
  146. package/dist/src/core/turn.d.ts +47 -0
  147. package/dist/src/core/turn.js +455 -0
  148. package/dist/src/core/turn.js.map +1 -0
  149. package/dist/src/core/turnLogging.d.ts +35 -0
  150. package/dist/src/core/turnLogging.js +80 -0
  151. package/dist/src/core/turnLogging.js.map +1 -0
  152. package/dist/src/index.d.ts +32 -0
  153. package/dist/src/index.js +44 -0
  154. package/dist/src/index.js.map +1 -0
  155. package/dist/src/scheduler/confirmation-coordinator.d.ts +139 -0
  156. package/dist/src/scheduler/confirmation-coordinator.js +443 -0
  157. package/dist/src/scheduler/confirmation-coordinator.js.map +1 -0
  158. package/dist/src/scheduler/result-aggregator.d.ts +127 -0
  159. package/dist/src/scheduler/result-aggregator.js +302 -0
  160. package/dist/src/scheduler/result-aggregator.js.map +1 -0
  161. package/dist/src/scheduler/status-transitions.d.ts +39 -0
  162. package/dist/src/scheduler/status-transitions.js +184 -0
  163. package/dist/src/scheduler/status-transitions.js.map +1 -0
  164. package/dist/src/scheduler/tool-dispatcher.d.ts +40 -0
  165. package/dist/src/scheduler/tool-dispatcher.js +120 -0
  166. package/dist/src/scheduler/tool-dispatcher.js.map +1 -0
  167. package/dist/src/scheduler/tool-executor.d.ts +44 -0
  168. package/dist/src/scheduler/tool-executor.js +117 -0
  169. package/dist/src/scheduler/tool-executor.js.map +1 -0
  170. package/dist/src/scheduler/utils.d.ts +11 -0
  171. package/dist/src/scheduler/utils.js +19 -0
  172. package/dist/src/scheduler/utils.js.map +1 -0
  173. package/dist/src/tools/task.d.ts +128 -0
  174. package/dist/src/tools/task.js +932 -0
  175. package/dist/src/tools/task.js.map +1 -0
  176. package/package.json +54 -0
@@ -0,0 +1,874 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { reportError } from '@vybestack/llxprt-code-core/utils/errorReporting.js';
7
+ import { ChatSession, StreamEventType, } from '../core/chatSession.js';
8
+ import { Type } from '@google/genai';
9
+ import { loadAgentRuntime } from '@vybestack/llxprt-code-core/runtime/AgentRuntimeLoader.js';
10
+ import { createSettingsProviderRuntimeContext } from '@vybestack/llxprt-code-core/runtime/settingsRuntimeAdapter.js';
11
+ import { createAgentRuntimeStateFromConfig } from '@vybestack/llxprt-code-core/runtime/runtimeStateFactory.js';
12
+ import { executeToolCall } from '../core/nonInteractiveToolExecutor.js';
13
+ import { filterHookRestrictedFunctionCalls, filterHookRestrictedParts, getHookRestrictedAllowedTools, getHookRestrictedAllowedToolsForFunctionCall, getHookRestrictedFunctionCallsFromParts, hasFilteredHookRestrictedToolCalls, isHookRestrictedToolCall, mergeHookRestrictedFunctionCalls, } from '../core/hookToolRestrictions.js';
14
+ import { ToolRegistry } from '@vybestack/llxprt-code-tools';
15
+ import { CoreMessageBusAdapter } from '@vybestack/llxprt-code-core/tools-adapters/CoreMessageBusAdapter.js';
16
+ import { CoreToolRegistryHostAdapter } from '@vybestack/llxprt-code-core/tools-adapters/CoreToolRegistryHostAdapter.js';
17
+ import { AgentTerminateMode } from './types.js';
18
+ import { validateToolsForNonInteractiveUse } from './executor-validation.js';
19
+ import { buildAgentSystemPrompt, applyTemplateToInitialMessages, } from './executor-prompt-builder.js';
20
+ import { checkAgentTermination } from './executor-termination.js';
21
+ import { resolveGracePeriodSeconds, isRecoverableTermination, enterRecovery, recoveryFailureResult, markRecoveryResponseUsed, } from './recovery.js';
22
+ import { templateString } from './utils.js';
23
+ import { parseThought } from '@vybestack/llxprt-code-core/utils/thoughtUtils.js';
24
+ import { zodToJsonSchema } from 'zod-to-json-schema';
25
+ import { debugLogger } from '@vybestack/llxprt-code-core/utils/debugLogger.js';
26
+ import { createAbortError } from '@vybestack/llxprt-code-core/utils/delay.js';
27
+ import { nextStreamEventWithIdleTimeout, resolveStreamIdleTimeoutMs, } from '@vybestack/llxprt-code-core/utils/streamIdleTimeout.js';
28
+ const TASK_COMPLETE_TOOL_NAME = 'complete_task';
29
+ /**
30
+ * Executes an agent loop based on an {@link AgentDefinition}.
31
+ *
32
+ * This executor runs the agent in a loop, calling tools until it calls the
33
+ * mandatory `complete_task` tool to signal completion.
34
+ */
35
+ export class AgentExecutor {
36
+ definition;
37
+ agentId;
38
+ toolRegistry;
39
+ runtimeContext;
40
+ messageBus;
41
+ onActivity;
42
+ /**
43
+ * Creates and validates a new `AgentExecutor` instance.
44
+ *
45
+ * This method ensures that all tools specified in the agent's definition are
46
+ * safe for non-interactive use before creating the executor.
47
+ *
48
+ * @param definition The definition object for the agent.
49
+ * @param runtimeContext The global runtime configuration.
50
+ * @param onActivity An optional callback to receive activity events.
51
+ * @returns A promise that resolves to a new `AgentExecutor` instance.
52
+ */
53
+ /**
54
+ * @plan PLAN-20260309-MESSAGEBUS-DI-REMEDIATION.P05
55
+ * @requirement REQ-D01-001.2
56
+ * @pseudocode lines 56-72
57
+ */
58
+ static async create(definition, runtimeContext, messageBus, onActivity) {
59
+ /**
60
+ * @plan PLAN-20260309-MESSAGEBUS-DI-REMEDIATION.P05
61
+ * @requirement REQ-D01-001.2
62
+ * @pseudocode lines 56-72
63
+ */
64
+ const agentToolRegistry = new ToolRegistry(new CoreToolRegistryHostAdapter(runtimeContext), new CoreMessageBusAdapter(messageBus));
65
+ const parentToolRegistry = runtimeContext.getToolRegistry();
66
+ if (definition.toolConfig) {
67
+ for (const toolRef of definition.toolConfig.tools) {
68
+ if (typeof toolRef === 'string') {
69
+ // If the tool is referenced by name, retrieve it from the parent
70
+ // registry and register it with the agent's isolated registry.
71
+ const toolFromParent = parentToolRegistry.getTool(toolRef);
72
+ // eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
73
+ if (toolFromParent) {
74
+ agentToolRegistry.registerTool(toolFromParent);
75
+ }
76
+ }
77
+ else if (typeof toolRef === 'object' &&
78
+ 'name' in toolRef &&
79
+ 'build' in toolRef) {
80
+ agentToolRegistry.registerTool(toolRef);
81
+ }
82
+ // Note: Raw `FunctionDeclaration` objects in the config don't need to be
83
+ // registered; their schemas are passed directly to the model later.
84
+ }
85
+ agentToolRegistry.sortTools();
86
+ // Validate that all registered tools are safe for non-interactive
87
+ // execution.
88
+ await validateToolsForNonInteractiveUse(agentToolRegistry, definition.name);
89
+ }
90
+ return new AgentExecutor(definition, runtimeContext, agentToolRegistry, messageBus, onActivity);
91
+ }
92
+ /**
93
+ * Constructs a new AgentExecutor instance.
94
+ *
95
+ * @private This constructor is private. Use the static `create` method to
96
+ * instantiate the class.
97
+ */
98
+ constructor(definition, runtimeContext, toolRegistry, messageBus, onActivity) {
99
+ this.definition = definition;
100
+ this.runtimeContext = runtimeContext;
101
+ this.toolRegistry = toolRegistry;
102
+ this.messageBus = messageBus;
103
+ this.onActivity = onActivity;
104
+ const randomIdPart = Math.random().toString(36).slice(2, 8);
105
+ this.agentId = `${this.definition.name}-${randomIdPart}`;
106
+ }
107
+ /**
108
+ * Runs the agent.
109
+ *
110
+ * @param inputs The validated input parameters for this invocation.
111
+ * @param signal An `AbortSignal` for cancellation.
112
+ * @returns A promise that resolves to the agent's final output.
113
+ */
114
+ async run(inputs, signal) {
115
+ const startTime = Date.now();
116
+ try {
117
+ const chat = await this.createChatObject(inputs);
118
+ const tools = this.prepareToolsList();
119
+ const query = this.definition.promptConfig.query
120
+ ? templateString(this.definition.promptConfig.query, inputs)
121
+ : 'Get Started!';
122
+ const initialMessage = {
123
+ role: 'user',
124
+ parts: [{ text: query }],
125
+ };
126
+ const { terminateReason, finalResult } = await this.runAgentLoop(chat, tools, initialMessage, signal, startTime, 0);
127
+ if (terminateReason === AgentTerminateMode.GOAL) {
128
+ return {
129
+ result: finalResult ?? 'Task completed.',
130
+ terminate_reason: terminateReason,
131
+ };
132
+ }
133
+ return {
134
+ result: finalResult ?? 'Agent execution was terminated before completion.',
135
+ terminate_reason: terminateReason,
136
+ };
137
+ }
138
+ catch (error) {
139
+ this.emitActivity('ERROR', { error: String(error) });
140
+ throw error;
141
+ }
142
+ }
143
+ /** Runs the agent loop until termination, returning the reason and result. */
144
+ async runAgentLoop(chat, tools, initialMessage, signal, startTime, turnCounter) {
145
+ let finalResult = null;
146
+ let currentMessage = initialMessage;
147
+ let recoveryState = { phase: 'none' };
148
+ let recoveryModelResponseUsed = false;
149
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Agent/model streams are external runtime boundaries.
150
+ while (true) {
151
+ const outcome = await this.runAgentLoopIteration(chat, tools, signal, startTime, turnCounter, finalResult, currentMessage, recoveryState, recoveryModelResponseUsed);
152
+ if (outcome.kind === 'continue') {
153
+ recoveryState = outcome.recoveryState;
154
+ currentMessage = outcome.currentMessage;
155
+ if (outcome.recoveryModelResponseUsed !== undefined) {
156
+ recoveryModelResponseUsed = outcome.recoveryModelResponseUsed;
157
+ }
158
+ turnCounter = outcome.turnCounter;
159
+ finalResult = outcome.finalResult;
160
+ }
161
+ else {
162
+ return outcome.result;
163
+ }
164
+ }
165
+ }
166
+ /** Single iteration of the agent loop. */
167
+ async runAgentLoopIteration(chat, tools, signal, startTime, turnCounter, finalResult, currentMessage, recoveryState, recoveryModelResponseUsed) {
168
+ const recoveryDeadlineMs = recoveryState.phase === 'active' ? recoveryState.deadlineMs : undefined;
169
+ const reason = this.checkTermination(startTime, turnCounter, recoveryDeadlineMs);
170
+ if (reason !== null) {
171
+ const termOutcome = this.handleTerminationReason(reason, recoveryState, finalResult);
172
+ if (termOutcome.handled) {
173
+ return {
174
+ kind: 'continue',
175
+ recoveryState: termOutcome.newState,
176
+ currentMessage: termOutcome.currentMessage,
177
+ recoveryModelResponseUsed: undefined,
178
+ turnCounter,
179
+ finalResult,
180
+ };
181
+ }
182
+ return { kind: 'done', result: termOutcome.result };
183
+ }
184
+ return this.runAgentLoopIterationBody(chat, tools, signal, startTime, turnCounter, finalResult, currentMessage, recoveryState, recoveryModelResponseUsed);
185
+ }
186
+ /** Body of the agent loop iteration (after termination checks). */
187
+ // eslint-disable-next-line max-lines-per-function -- Agent iteration orchestration coordinates model calls, recovery handling, and tool dispatch in one control-flow boundary.
188
+ async runAgentLoopIterationBody(chat, tools, signal, startTime, turnCounter, finalResult, currentMessage, recoveryState, recoveryModelResponseUsed) {
189
+ if (signal.aborted) {
190
+ return {
191
+ kind: 'done',
192
+ result: { terminateReason: AgentTerminateMode.ABORTED, finalResult },
193
+ };
194
+ }
195
+ const recoveryAbort = recoveryState.phase === 'active' && !recoveryModelResponseUsed
196
+ ? this.createRecoveryAbortController(recoveryState.deadlineMs, signal)
197
+ : undefined;
198
+ const promptId = `${this.runtimeContext.getSessionId()}#${this.agentId}#${turnCounter}`;
199
+ const nextTurnCounter = turnCounter + 1;
200
+ let modelResult;
201
+ let functionCalls;
202
+ try {
203
+ modelResult = await this.callModel(chat, currentMessage, tools, recoveryAbort?.signal ?? signal, promptId);
204
+ functionCalls = modelResult.functionCalls;
205
+ }
206
+ catch (error) {
207
+ if (recoveryState.phase === 'active') {
208
+ // If the parent signal was aborted, terminate as ABORTED rather than
209
+ // emitting a misleading recovery-timeout outcome.
210
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- signal.aborted can be true even though the outer already-gated check passed; the catch path is reached from callModel which can throw on abort.
211
+ if (signal.aborted) {
212
+ return {
213
+ kind: 'done',
214
+ result: {
215
+ terminateReason: AgentTerminateMode.ABORTED,
216
+ finalResult,
217
+ },
218
+ };
219
+ }
220
+ this.emitRecoveryOutcome(recoveryState.originalReason, 'failure', recoveryState.originalReason, recoveryState.gracePeriodSeconds);
221
+ return {
222
+ kind: 'done',
223
+ result: {
224
+ terminateReason: recoveryState.originalReason,
225
+ finalResult: finalResult ?? 'Recovery turn timed out.',
226
+ },
227
+ };
228
+ }
229
+ throw error;
230
+ }
231
+ finally {
232
+ recoveryAbort?.abort();
233
+ }
234
+ const newRecoveryModelResponseUsed = markRecoveryResponseUsed(recoveryState, recoveryModelResponseUsed);
235
+ if (this.isAborted(signal)) {
236
+ return {
237
+ kind: 'done',
238
+ result: { terminateReason: AgentTerminateMode.ABORTED, finalResult },
239
+ };
240
+ }
241
+ const checkResult = this.checkRecoveryToolCalls(recoveryState, newRecoveryModelResponseUsed, functionCalls, finalResult);
242
+ if (checkResult) {
243
+ return { kind: 'done', result: checkResult };
244
+ }
245
+ if (functionCalls.length === 0) {
246
+ const enterResult = this.handleProtocolViolation(recoveryState, finalResult);
247
+ if (enterResult.entered) {
248
+ return {
249
+ kind: 'continue',
250
+ recoveryState: enterResult.state,
251
+ currentMessage: enterResult.warningMessage,
252
+ recoveryModelResponseUsed: false,
253
+ turnCounter: nextTurnCounter,
254
+ finalResult,
255
+ };
256
+ }
257
+ const activeGrace = recoveryState.phase === 'active' ? recoveryState.gracePeriodSeconds : 0;
258
+ this.emitRecoveryOutcome(enterResult.result.terminateReason, 'failure', enterResult.result.terminateReason, activeGrace);
259
+ return { kind: 'done', result: enterResult.result };
260
+ }
261
+ const { nextMessage, submittedOutput, taskCompleted, partialResult } = await this.processFunctionCalls(functionCalls, signal, promptId);
262
+ if (taskCompleted) {
263
+ if (recoveryState.phase === 'active') {
264
+ this.emitRecoveryOutcome(recoveryState.originalReason, 'success', AgentTerminateMode.GOAL, recoveryState.gracePeriodSeconds);
265
+ }
266
+ finalResult = submittedOutput ?? 'Task completed successfully.';
267
+ return {
268
+ kind: 'done',
269
+ result: { terminateReason: AgentTerminateMode.GOAL, finalResult },
270
+ };
271
+ }
272
+ if (recoveryState.phase === 'active' && newRecoveryModelResponseUsed) {
273
+ const fail = recoveryFailureResult(recoveryState.originalReason, finalResult);
274
+ this.emitRecoveryOutcome(recoveryState.originalReason, 'failure', fail.terminateReason, recoveryState.gracePeriodSeconds);
275
+ return { kind: 'done', result: fail };
276
+ }
277
+ const nextFinalResult = partialResult ?? finalResult;
278
+ return {
279
+ kind: 'continue',
280
+ recoveryState,
281
+ currentMessage: nextMessage,
282
+ recoveryModelResponseUsed: newRecoveryModelResponseUsed,
283
+ turnCounter: nextTurnCounter,
284
+ finalResult: nextFinalResult,
285
+ };
286
+ }
287
+ /** Check tool calls during recovery: non-complete_task = recovery failure. */
288
+ checkRecoveryToolCalls(recoveryState, recoveryModelResponseUsed, functionCalls, finalResult) {
289
+ if (recoveryState.phase !== 'active' || !recoveryModelResponseUsed) {
290
+ return null;
291
+ }
292
+ const hasCompleteTask = functionCalls.some((fc) => fc.name === TASK_COMPLETE_TOOL_NAME);
293
+ if (!hasCompleteTask) {
294
+ this.emitRecoveryOutcome(recoveryState.originalReason, 'failure', recoveryState.originalReason, recoveryState.gracePeriodSeconds);
295
+ return recoveryFailureResult(recoveryState.originalReason, finalResult);
296
+ }
297
+ // Recovery response must be exactly one complete_task call.
298
+ // If complete_task is accompanied by other tools, fail recovery immediately
299
+ // and do not execute any of the tool calls.
300
+ if (functionCalls.length > 1) {
301
+ this.emitRecoveryOutcome(recoveryState.originalReason, 'failure', recoveryState.originalReason, recoveryState.gracePeriodSeconds);
302
+ return recoveryFailureResult(recoveryState.originalReason, finalResult);
303
+ }
304
+ return null;
305
+ }
306
+ /** Handle termination-reason checks: enter recovery or return immediately. */
307
+ handleTerminationReason(reason, recoveryState, finalResult) {
308
+ if (isRecoverableTermination(reason) && recoveryState.phase === 'none') {
309
+ const gracePeriodSeconds = resolveGracePeriodSeconds(this.definition.runConfig.grace_period_seconds);
310
+ const { state, warningMessage } = enterRecovery(reason, gracePeriodSeconds, (type, data) => this.emitActivity(type, data));
311
+ return { handled: true, newState: state, currentMessage: warningMessage };
312
+ }
313
+ if (recoveryState.phase === 'active') {
314
+ const fail = recoveryFailureResult(recoveryState.originalReason, finalResult);
315
+ this.emitRecoveryOutcome(recoveryState.originalReason, 'failure', fail.terminateReason, recoveryState.gracePeriodSeconds);
316
+ return { handled: false, result: fail };
317
+ }
318
+ return { handled: false, result: { terminateReason: reason, finalResult } };
319
+ }
320
+ /** Handle the protocol-violation path (model stopped calling tools). */
321
+ handleProtocolViolation(recoveryState, finalResult) {
322
+ if (recoveryState.phase === 'none') {
323
+ const gracePeriodSeconds = resolveGracePeriodSeconds(this.definition.runConfig.grace_period_seconds);
324
+ const { state, warningMessage } = enterRecovery(AgentTerminateMode.ERROR_NO_COMPLETE_TASK_CALL, gracePeriodSeconds, (type, data) => this.emitActivity(type, data));
325
+ return { entered: true, state, warningMessage };
326
+ }
327
+ // Already in recovery: the one recovery model response didn't call
328
+ // complete_task, so this empty response confirms recovery failure.
329
+ const fail = recoveryFailureResult(recoveryState.originalReason, finalResult);
330
+ return { entered: false, result: fail };
331
+ }
332
+ isAborted(signal) {
333
+ return signal.aborted;
334
+ }
335
+ /** Create an AbortController that fires when the recovery deadline passes. */
336
+ createRecoveryAbortController(deadlineMs, parentSignal) {
337
+ const controller = new AbortController();
338
+ const remaining = deadlineMs - Date.now();
339
+ const timer = setTimeout(() => controller.abort(), Math.max(0, remaining));
340
+ const onParentAbort = () => controller.abort();
341
+ parentSignal.addEventListener('abort', onParentAbort, { once: true });
342
+ // Clean up both when our controller fires
343
+ controller.signal.addEventListener('abort', () => {
344
+ clearTimeout(timer);
345
+ parentSignal.removeEventListener('abort', onParentAbort);
346
+ }, { once: true });
347
+ return controller;
348
+ }
349
+ /** Emit a RECOVERY_OUTCOME telemetry event. */
350
+ emitRecoveryOutcome(originalReason, outcome, terminateReason, gracePeriodSeconds) {
351
+ this.emitActivity('RECOVERY_OUTCOME', {
352
+ originalReason,
353
+ outcome,
354
+ terminateReason,
355
+ gracePeriodSeconds,
356
+ });
357
+ }
358
+ /**
359
+ * Calls the generative model with the current context and tools.
360
+ *
361
+ * @returns The model's response, including any tool calls or text.
362
+ */
363
+ async callModel(chat, message, tools, signal, promptId) {
364
+ const timeoutController = new AbortController();
365
+ const timeoutSignal = timeoutController.signal;
366
+ const onAbort = () => timeoutController.abort();
367
+ signal.addEventListener('abort', onAbort, { once: true });
368
+ const messageParams = {
369
+ message: message.parts ?? [],
370
+ config: {
371
+ abortSignal: timeoutSignal,
372
+ tools: tools.length > 0 ? [{ functionDeclarations: tools }] : undefined,
373
+ },
374
+ };
375
+ let streamIterator;
376
+ const effectiveTimeoutMs = resolveStreamIdleTimeoutMs(this.runtimeContext);
377
+ try {
378
+ const responseStream = await chat.sendMessageStream(messageParams, promptId);
379
+ const functionCalls = [];
380
+ let textResponse = '';
381
+ streamIterator = responseStream[Symbol.asyncIterator]();
382
+ await this.consumeStream(streamIterator, effectiveTimeoutMs, signal, timeoutSignal, timeoutController, functionCalls, (text) => {
383
+ textResponse += text;
384
+ });
385
+ return {
386
+ functionCalls,
387
+ textResponse,
388
+ };
389
+ }
390
+ finally {
391
+ streamIterator?.return?.().catch(() => { });
392
+ timeoutController.abort();
393
+ signal.removeEventListener('abort', onAbort);
394
+ }
395
+ }
396
+ /** Consumes a response stream, accumulating function calls and text. */
397
+ async consumeStream(streamIterator, effectiveTimeoutMs, signal, timeoutSignal, timeoutController, functionCalls, onText) {
398
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, sonarjs/too-many-break-or-continue-in-loop -- Agent/model streams are external runtime boundaries despite declared types.
399
+ while (true) {
400
+ let result;
401
+ if (effectiveTimeoutMs > 0) {
402
+ result = await nextStreamEventWithIdleTimeout({
403
+ iterator: streamIterator,
404
+ timeoutMs: effectiveTimeoutMs,
405
+ signal: timeoutSignal,
406
+ onTimeout: () => {
407
+ if (signal.aborted) {
408
+ return;
409
+ }
410
+ timeoutController.abort();
411
+ },
412
+ createTimeoutError: () => createAbortError(),
413
+ });
414
+ }
415
+ else {
416
+ result = await streamIterator.next();
417
+ }
418
+ if (result.done === true) {
419
+ break;
420
+ }
421
+ const resp = result.value;
422
+ if (signal.aborted)
423
+ break;
424
+ if (resp.type === StreamEventType.CHUNK) {
425
+ this.processStreamChunk(resp.value, functionCalls, onText);
426
+ }
427
+ }
428
+ }
429
+ /** Processes a single stream chunk, extracting thoughts, function calls, and text. */
430
+ processStreamChunk(chunk, functionCalls, onText) {
431
+ const parts = chunk.candidates?.[0]?.content?.parts ?? [];
432
+ const allowedTools = getHookRestrictedAllowedTools(chunk);
433
+ const filteredParts = filterHookRestrictedParts(parts, allowedTools);
434
+ const { subject } = parseThought(filteredParts.find((p) => p.thought === true)?.text ?? '');
435
+ if (subject !== '') {
436
+ this.emitActivity('THOUGHT_CHUNK', { text: subject });
437
+ }
438
+ const partCalls = getHookRestrictedFunctionCallsFromParts(filteredParts, allowedTools);
439
+ const topLevelCalls = filterHookRestrictedFunctionCalls(chunk.functionCalls ?? [], allowedTools);
440
+ const allowedFunctionCalls = mergeHookRestrictedFunctionCalls(partCalls, topLevelCalls);
441
+ if (allowedFunctionCalls.length > 0) {
442
+ functionCalls.push(...allowedFunctionCalls);
443
+ }
444
+ const text = filteredParts
445
+ .filter((p) => p.thought !== true && typeof p.text === 'string')
446
+ .map((p) => p.text)
447
+ .join('');
448
+ if (text.length > 0) {
449
+ onText(text);
450
+ }
451
+ return hasFilteredHookRestrictedToolCalls(chunk);
452
+ }
453
+ /** Initializes a `ChatSession` instance for the agent run. */
454
+ async createChatObject(inputs) {
455
+ const { promptConfig, modelConfig } = this.definition;
456
+ if (!promptConfig.systemPrompt && !promptConfig.initialMessages) {
457
+ throw new Error('PromptConfig must define either `systemPrompt` or `initialMessages`.');
458
+ }
459
+ const startHistory = this.applyTemplateToInitialMessages(promptConfig.initialMessages ?? [], inputs);
460
+ const systemInstruction = promptConfig.systemPrompt
461
+ ? await this.buildSystemPrompt(inputs)
462
+ : undefined;
463
+ try {
464
+ const generationConfig = this.buildGenerationConfig(modelConfig, systemInstruction);
465
+ const runtimeBundle = await this.buildRuntimeBundle();
466
+ return new ChatSession(runtimeBundle.runtimeContext, runtimeBundle.contentGenerator, generationConfig, startHistory);
467
+ }
468
+ catch (error) {
469
+ await reportError(error, `Error initializing Gemini chat for agent ${this.definition.name}.`, startHistory, 'startChat');
470
+ throw new Error(`Failed to create chat object: ${error}`);
471
+ }
472
+ }
473
+ /** Builds the generation config from model config and optional system instruction. */
474
+ buildGenerationConfig(modelConfig, systemInstruction) {
475
+ const generationConfig = {
476
+ temperature: modelConfig.temp,
477
+ topP: modelConfig.top_p,
478
+ thinkingConfig: {
479
+ includeThoughts: true,
480
+ thinkingBudget: modelConfig.thinkingBudget ?? -1,
481
+ },
482
+ };
483
+ if (systemInstruction) {
484
+ generationConfig.systemInstruction = systemInstruction;
485
+ }
486
+ return generationConfig;
487
+ }
488
+ /** Builds the runtime bundle for the ChatSession instance. */
489
+ async buildRuntimeBundle() {
490
+ const settings = this.resolveSettingsSnapshot();
491
+ const runtimeState = createAgentRuntimeStateFromConfig(this.runtimeContext);
492
+ const providerRuntime = createSettingsProviderRuntimeContext({
493
+ settingsService: this.runtimeContext.getSettingsService(),
494
+ config: this.runtimeContext,
495
+ runtimeId: runtimeState.runtimeId,
496
+ metadata: { source: 'AgentExecutor.createChatObject' },
497
+ });
498
+ return loadAgentRuntime({
499
+ profile: {
500
+ config: this.runtimeContext,
501
+ state: runtimeState,
502
+ settings,
503
+ providerRuntime,
504
+ contentGeneratorConfig: this.runtimeContext.getContentGeneratorConfig(),
505
+ toolRegistry: this.toolRegistry,
506
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Agent/model streams are external runtime boundaries despite declared types.
507
+ providerManager: this.runtimeContext.getProviderManager?.(),
508
+ },
509
+ overrides: {
510
+ contentGenerator: this.tryGetContentGenerator(),
511
+ },
512
+ });
513
+ }
514
+ /** Resolves the settings snapshot from ephemeral config. */
515
+ resolveSettingsSnapshot() {
516
+ const rawCompressionThreshold = this.runtimeContext.getEphemeralSetting('compression-threshold');
517
+ const compressionThreshold = typeof rawCompressionThreshold === 'number'
518
+ ? rawCompressionThreshold
519
+ : 0.8;
520
+ const rawContextLimit = this.runtimeContext.getEphemeralSetting('context-limit');
521
+ const contextLimit = typeof rawContextLimit === 'number' &&
522
+ Number.isFinite(rawContextLimit) &&
523
+ rawContextLimit > 0
524
+ ? rawContextLimit
525
+ : undefined;
526
+ const rawPreserveThreshold = this.runtimeContext.getEphemeralSetting('compression-preserve-threshold');
527
+ const preserveThreshold = typeof rawPreserveThreshold === 'number' ? rawPreserveThreshold : 0.2;
528
+ return {
529
+ compressionThreshold,
530
+ contextLimit,
531
+ preserveThreshold,
532
+ telemetry: {
533
+ enabled: true,
534
+ target: null,
535
+ },
536
+ };
537
+ }
538
+ /** Attempts to get the content generator from the runtime context. */
539
+ tryGetContentGenerator() {
540
+ try {
541
+ return (this.runtimeContext
542
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Agent/model streams are external runtime boundaries despite declared types.
543
+ .getAgentClient?.()
544
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Agent/model streams are external runtime boundaries despite declared types.
545
+ ?.getContentGenerator());
546
+ }
547
+ catch {
548
+ return undefined;
549
+ }
550
+ }
551
+ /**
552
+ * Executes function calls requested by the model and returns the results.
553
+ *
554
+ * @returns A new `Content` object for history, any submitted output, and completion status.
555
+ */
556
+ async processFunctionCalls(functionCalls, signal, promptId) {
557
+ const allowedToolNames = new Set(this.toolRegistry.getAllToolNames());
558
+ allowedToolNames.add(TASK_COMPLETE_TOOL_NAME);
559
+ let submittedOutput = null;
560
+ let taskCompleted = false;
561
+ const toolExecutionPromises = [];
562
+ const syncResponseParts = [];
563
+ let executableFunctionCallCount = 0;
564
+ // eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
565
+ for (const [index, functionCall] of functionCalls.entries()) {
566
+ const callId = functionCall.id ?? `${promptId}-${index}`;
567
+ const args = functionCall.args ?? {};
568
+ const hookAllowedTools = getHookRestrictedAllowedToolsForFunctionCall(functionCall);
569
+ if (isHookRestrictedToolCall(functionCall, hookAllowedTools)) {
570
+ continue;
571
+ }
572
+ executableFunctionCallCount += 1;
573
+ this.emitActivity('TOOL_CALL_START', { name: functionCall.name, args });
574
+ if (functionCall.name === TASK_COMPLETE_TOOL_NAME) {
575
+ const result = this.handleCompleteTaskCall(functionCall, callId, args, taskCompleted, submittedOutput);
576
+ taskCompleted = result.taskCompleted;
577
+ submittedOutput = result.submittedOutput;
578
+ syncResponseParts.push(...result.syncParts);
579
+ continue;
580
+ }
581
+ if (!allowedToolNames.has(functionCall.name)) {
582
+ this.handleUnauthorizedToolCall(functionCall, callId, syncResponseParts);
583
+ continue;
584
+ }
585
+ toolExecutionPromises.push(this.createToolExecutionPromise(functionCall, callId, args, signal, promptId));
586
+ }
587
+ return this.assembleToolResponses(executableFunctionCallCount, syncResponseParts, toolExecutionPromises, submittedOutput, taskCompleted);
588
+ }
589
+ /** Handles a `complete_task` function call, returning updated state and response parts. */
590
+ handleCompleteTaskCall(functionCall, callId, args, currentTaskCompleted, currentSubmittedOutput) {
591
+ const syncParts = [];
592
+ if (currentTaskCompleted) {
593
+ const error = 'Task already marked complete in this turn. Ignoring duplicate call.';
594
+ syncParts.push({
595
+ functionResponse: {
596
+ name: TASK_COMPLETE_TOOL_NAME,
597
+ response: { error },
598
+ id: callId,
599
+ },
600
+ });
601
+ this.emitActivity('ERROR', {
602
+ context: 'tool_call',
603
+ name: functionCall.name,
604
+ error,
605
+ });
606
+ return {
607
+ taskCompleted: currentTaskCompleted,
608
+ submittedOutput: currentSubmittedOutput,
609
+ syncParts,
610
+ };
611
+ }
612
+ const { outputConfig } = this.definition;
613
+ if (outputConfig) {
614
+ const result = this.processCompleteTaskOutput(functionCall, callId, args, outputConfig);
615
+ syncParts.push(...result.syncParts);
616
+ return {
617
+ taskCompleted: result.taskCompleted,
618
+ submittedOutput: result.submittedOutput,
619
+ syncParts,
620
+ };
621
+ }
622
+ syncParts.push({
623
+ functionResponse: {
624
+ name: TASK_COMPLETE_TOOL_NAME,
625
+ response: { status: 'Task marked complete.' },
626
+ id: callId,
627
+ },
628
+ });
629
+ this.emitActivity('TOOL_CALL_END', {
630
+ name: functionCall.name,
631
+ output: 'Task marked complete.',
632
+ });
633
+ return {
634
+ taskCompleted: true,
635
+ submittedOutput: 'Task completed successfully.',
636
+ syncParts,
637
+ };
638
+ }
639
+ /** Processes the output argument of a `complete_task` call when outputConfig is present. */
640
+ processCompleteTaskOutput(functionCall, callId, args, outputConfig) {
641
+ const syncParts = [];
642
+ const outputName = outputConfig.outputName;
643
+ if (args[outputName] !== undefined) {
644
+ const outputValue = args[outputName];
645
+ const validationResult = outputConfig.schema.safeParse(outputValue);
646
+ if (!validationResult.success) {
647
+ const error = `Output validation failed: ${JSON.stringify(validationResult.error.flatten())}`;
648
+ syncParts.push({
649
+ functionResponse: {
650
+ name: TASK_COMPLETE_TOOL_NAME,
651
+ response: { error },
652
+ id: callId,
653
+ },
654
+ });
655
+ this.emitActivity('ERROR', {
656
+ context: 'tool_call',
657
+ name: functionCall.name,
658
+ error,
659
+ });
660
+ return { taskCompleted: false, submittedOutput: null, syncParts };
661
+ }
662
+ const validatedOutput = validationResult.data;
663
+ let submittedOutput;
664
+ if (this.definition.processOutput) {
665
+ submittedOutput = this.definition.processOutput(validatedOutput);
666
+ }
667
+ else if (typeof outputValue === 'string') {
668
+ submittedOutput = outputValue;
669
+ }
670
+ else {
671
+ submittedOutput = JSON.stringify(outputValue, null, 2);
672
+ }
673
+ syncParts.push({
674
+ functionResponse: {
675
+ name: TASK_COMPLETE_TOOL_NAME,
676
+ response: { result: 'Output submitted and task completed.' },
677
+ id: callId,
678
+ },
679
+ });
680
+ this.emitActivity('TOOL_CALL_END', {
681
+ name: functionCall.name,
682
+ output: 'Output submitted and task completed.',
683
+ });
684
+ return { taskCompleted: true, submittedOutput, syncParts };
685
+ }
686
+ // Missing required output argument
687
+ const error = `Missing required argument '${outputName}' for completion.`;
688
+ syncParts.push({
689
+ functionResponse: {
690
+ name: TASK_COMPLETE_TOOL_NAME,
691
+ response: { error },
692
+ id: callId,
693
+ },
694
+ });
695
+ this.emitActivity('ERROR', {
696
+ context: 'tool_call',
697
+ name: functionCall.name,
698
+ error,
699
+ });
700
+ return { taskCompleted: false, submittedOutput: null, syncParts };
701
+ }
702
+ /** Handles an unauthorized tool call by pushing an error response. */
703
+ handleUnauthorizedToolCall(functionCall, callId, syncResponseParts) {
704
+ const error = `Unauthorized tool call: '${functionCall.name}' is not available to this agent.`;
705
+ debugLogger.warn(`[AgentExecutor] Blocked call: ${error}`);
706
+ syncResponseParts.push({
707
+ functionResponse: {
708
+ name: functionCall.name,
709
+ id: callId,
710
+ response: { error },
711
+ },
712
+ });
713
+ this.emitActivity('ERROR', {
714
+ context: 'tool_call_unauthorized',
715
+ name: functionCall.name,
716
+ callId,
717
+ error,
718
+ });
719
+ }
720
+ /** Creates an async promise that executes a standard tool call. */
721
+ createToolExecutionPromise(functionCall, callId, args, signal, promptId) {
722
+ const hookAllowedTools = getHookRestrictedAllowedToolsForFunctionCall(functionCall);
723
+ const requestInfo = {
724
+ callId,
725
+ name: functionCall.name,
726
+ args,
727
+ isClientInitiated: true,
728
+ prompt_id: promptId,
729
+ ...(hookAllowedTools !== undefined
730
+ ? { hookRestrictedAllowedTools: hookAllowedTools }
731
+ : {}),
732
+ };
733
+ return (async () => {
734
+ const completed = await executeToolCall(this.runtimeContext, requestInfo, signal, { messageBus: this.messageBus });
735
+ const toolResponse = completed.response;
736
+ if (toolResponse.error) {
737
+ this.emitActivity('ERROR', {
738
+ context: 'tool_call',
739
+ name: functionCall.name,
740
+ error: toolResponse.error.message,
741
+ });
742
+ }
743
+ else {
744
+ this.emitActivity('TOOL_CALL_END', {
745
+ name: functionCall.name,
746
+ output: toolResponse.resultDisplay,
747
+ });
748
+ }
749
+ return {
750
+ responseParts: toolResponse.responseParts,
751
+ partialResult: typeof toolResponse.resultDisplay === 'string'
752
+ ? toolResponse.resultDisplay
753
+ : null,
754
+ };
755
+ })();
756
+ }
757
+ /** Assembles all tool response parts and returns the final result. */
758
+ async assembleToolResponses(executableFunctionCallCount, syncResponseParts, toolExecutionPromises, submittedOutput, taskCompleted) {
759
+ const asyncResults = await Promise.all(toolExecutionPromises);
760
+ const toolResponseParts = [...syncResponseParts];
761
+ let partialResult = null;
762
+ for (const result of asyncResults) {
763
+ if (result) {
764
+ toolResponseParts.push(...result.responseParts);
765
+ if (result.partialResult !== null) {
766
+ partialResult = result.partialResult;
767
+ }
768
+ }
769
+ }
770
+ if (executableFunctionCallCount > 0 &&
771
+ toolResponseParts.length === 0 &&
772
+ !taskCompleted) {
773
+ toolResponseParts.push({
774
+ text: 'All tool calls failed or were unauthorized. Please analyze the errors and try an alternative approach.',
775
+ });
776
+ }
777
+ return {
778
+ nextMessage: { role: 'user', parts: toolResponseParts },
779
+ submittedOutput,
780
+ taskCompleted,
781
+ partialResult,
782
+ };
783
+ }
784
+ /**
785
+ * Prepares the list of tool function declarations to be sent to the model.
786
+ */
787
+ prepareToolsList() {
788
+ const toolsList = [];
789
+ const { toolConfig, outputConfig } = this.definition;
790
+ if (toolConfig) {
791
+ const toolNamesToLoad = [];
792
+ for (const toolRef of toolConfig.tools) {
793
+ if (typeof toolRef === 'string') {
794
+ toolNamesToLoad.push(toolRef);
795
+ }
796
+ else if (typeof toolRef === 'object' && 'schema' in toolRef) {
797
+ // Tool instance with an explicit schema property.
798
+ toolsList.push(toolRef.schema);
799
+ }
800
+ else {
801
+ // Raw `FunctionDeclaration` object.
802
+ toolsList.push(toolRef);
803
+ }
804
+ }
805
+ // Add schemas from tools that were registered by name.
806
+ toolsList.push(...this.toolRegistry.getFunctionDeclarationsFiltered(toolNamesToLoad));
807
+ }
808
+ // Always inject complete_task.
809
+ // Configure its schema based on whether output is expected.
810
+ const completeTool = {
811
+ name: TASK_COMPLETE_TOOL_NAME,
812
+ description: outputConfig
813
+ ? 'Call this tool to submit your final answer and complete the task. This is the ONLY way to finish.'
814
+ : 'Call this tool to signal that you have completed your task. This is the ONLY way to finish.',
815
+ parameters: {
816
+ type: Type.OBJECT,
817
+ properties: {},
818
+ required: [],
819
+ },
820
+ };
821
+ if (outputConfig) {
822
+ const jsonSchema = zodToJsonSchema(outputConfig.schema);
823
+ const { $schema: _$schema, definitions: _definitions, ...schema } = jsonSchema;
824
+ completeTool.parameters.properties[outputConfig.outputName] =
825
+ schema;
826
+ completeTool.parameters.required.push(outputConfig.outputName);
827
+ }
828
+ toolsList.push(completeTool);
829
+ return toolsList;
830
+ }
831
+ /** Builds the system prompt from the agent definition and inputs. */
832
+ async buildSystemPrompt(inputs) {
833
+ const { promptConfig } = this.definition;
834
+ if (!promptConfig.systemPrompt) {
835
+ return '';
836
+ }
837
+ return buildAgentSystemPrompt(inputs, this.runtimeContext, promptConfig.systemPrompt);
838
+ }
839
+ /**
840
+ * Applies template strings to initial messages.
841
+ *
842
+ * @param initialMessages The initial messages from the prompt config.
843
+ * @param inputs The validated input parameters for this invocation.
844
+ * @returns A new array of `Content` with templated strings.
845
+ */
846
+ applyTemplateToInitialMessages(initialMessages, inputs) {
847
+ return applyTemplateToInitialMessages(initialMessages, inputs);
848
+ }
849
+ /**
850
+ * Checks if the agent should terminate due to exceeding configured limits.
851
+ *
852
+ * @param startTime The timestamp (ms) when execution started.
853
+ * @param turnCounter The current turn number.
854
+ * @param recoveryDeadlineMs If in a recovery turn, the absolute deadline (ms)
855
+ * that overrides normal max_time_minutes. Pass `undefined` when not recovering.
856
+ * @returns The reason for termination, or `null` if execution can continue.
857
+ */
858
+ checkTermination(startTime, turnCounter, recoveryDeadlineMs) {
859
+ return checkAgentTermination(this.definition.runConfig, startTime, turnCounter, recoveryDeadlineMs);
860
+ }
861
+ /** Emits an activity event to the configured callback. */
862
+ emitActivity(type, data) {
863
+ if (this.onActivity) {
864
+ const event = {
865
+ isSubagentActivityEvent: true,
866
+ agentName: this.definition.name,
867
+ type,
868
+ data,
869
+ };
870
+ this.onActivity(event);
871
+ }
872
+ }
873
+ }
874
+ //# sourceMappingURL=executor.js.map