@inkeep/agents-run-api 0.0.0-dev-20250910232631

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 (152) hide show
  1. package/LICENSE.md +49 -0
  2. package/README.md +117 -0
  3. package/dist/__tests__/setup.d.ts +4 -0
  4. package/dist/__tests__/setup.d.ts.map +1 -0
  5. package/dist/__tests__/setup.js +80 -0
  6. package/dist/__tests__/utils/testProject.d.ts +18 -0
  7. package/dist/__tests__/utils/testProject.d.ts.map +1 -0
  8. package/dist/__tests__/utils/testProject.js +26 -0
  9. package/dist/__tests__/utils/testRequest.d.ts +8 -0
  10. package/dist/__tests__/utils/testRequest.d.ts.map +1 -0
  11. package/dist/__tests__/utils/testRequest.js +32 -0
  12. package/dist/__tests__/utils/testTenant.d.ts +64 -0
  13. package/dist/__tests__/utils/testTenant.d.ts.map +1 -0
  14. package/dist/__tests__/utils/testTenant.js +71 -0
  15. package/dist/a2a/client.d.ts +182 -0
  16. package/dist/a2a/client.d.ts.map +1 -0
  17. package/dist/a2a/client.js +645 -0
  18. package/dist/a2a/handlers.d.ts +4 -0
  19. package/dist/a2a/handlers.d.ts.map +1 -0
  20. package/dist/a2a/handlers.js +656 -0
  21. package/dist/a2a/transfer.d.ts +18 -0
  22. package/dist/a2a/transfer.d.ts.map +1 -0
  23. package/dist/a2a/transfer.js +22 -0
  24. package/dist/a2a/types.d.ts +63 -0
  25. package/dist/a2a/types.d.ts.map +1 -0
  26. package/dist/a2a/types.js +1 -0
  27. package/dist/agents/Agent.d.ts +151 -0
  28. package/dist/agents/Agent.d.ts.map +1 -0
  29. package/dist/agents/Agent.js +1164 -0
  30. package/dist/agents/ModelFactory.d.ts +62 -0
  31. package/dist/agents/ModelFactory.d.ts.map +1 -0
  32. package/dist/agents/ModelFactory.js +208 -0
  33. package/dist/agents/SystemPromptBuilder.d.ts +14 -0
  34. package/dist/agents/SystemPromptBuilder.d.ts.map +1 -0
  35. package/dist/agents/SystemPromptBuilder.js +62 -0
  36. package/dist/agents/ToolSessionManager.d.ts +53 -0
  37. package/dist/agents/ToolSessionManager.d.ts.map +1 -0
  38. package/dist/agents/ToolSessionManager.js +106 -0
  39. package/dist/agents/artifactTools.d.ts +30 -0
  40. package/dist/agents/artifactTools.d.ts.map +1 -0
  41. package/dist/agents/artifactTools.js +463 -0
  42. package/dist/agents/generateTaskHandler.d.ts +41 -0
  43. package/dist/agents/generateTaskHandler.d.ts.map +1 -0
  44. package/dist/agents/generateTaskHandler.js +350 -0
  45. package/dist/agents/relationTools.d.ts +35 -0
  46. package/dist/agents/relationTools.d.ts.map +1 -0
  47. package/dist/agents/relationTools.js +246 -0
  48. package/dist/agents/types.d.ts +23 -0
  49. package/dist/agents/types.d.ts.map +1 -0
  50. package/dist/agents/types.js +1 -0
  51. package/dist/agents/versions/V1Config.d.ts +21 -0
  52. package/dist/agents/versions/V1Config.d.ts.map +1 -0
  53. package/dist/agents/versions/V1Config.js +285 -0
  54. package/dist/app.d.ts +5 -0
  55. package/dist/app.d.ts.map +1 -0
  56. package/dist/app.js +219 -0
  57. package/dist/data/agentGraph.d.ts +4 -0
  58. package/dist/data/agentGraph.d.ts.map +1 -0
  59. package/dist/data/agentGraph.js +73 -0
  60. package/dist/data/agents.d.ts +4 -0
  61. package/dist/data/agents.d.ts.map +1 -0
  62. package/dist/data/agents.js +78 -0
  63. package/dist/data/conversations.d.ts +59 -0
  64. package/dist/data/conversations.d.ts.map +1 -0
  65. package/dist/data/conversations.js +216 -0
  66. package/dist/data/db/clean.d.ts +6 -0
  67. package/dist/data/db/clean.d.ts.map +1 -0
  68. package/dist/data/db/clean.js +77 -0
  69. package/dist/data/db/dbClient.d.ts +3 -0
  70. package/dist/data/db/dbClient.d.ts.map +1 -0
  71. package/dist/data/db/dbClient.js +13 -0
  72. package/dist/env.d.ts +45 -0
  73. package/dist/env.d.ts.map +1 -0
  74. package/dist/env.js +64 -0
  75. package/dist/handlers/executionHandler.d.ts +36 -0
  76. package/dist/handlers/executionHandler.d.ts.map +1 -0
  77. package/dist/handlers/executionHandler.js +415 -0
  78. package/dist/index.d.ts +11 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +28 -0
  81. package/dist/instrumentation.d.ts +13 -0
  82. package/dist/instrumentation.d.ts.map +1 -0
  83. package/dist/instrumentation.js +66 -0
  84. package/dist/logger.d.ts +4 -0
  85. package/dist/logger.d.ts.map +1 -0
  86. package/dist/logger.js +32 -0
  87. package/dist/middleware/api-key-auth.d.ts +22 -0
  88. package/dist/middleware/api-key-auth.d.ts.map +1 -0
  89. package/dist/middleware/api-key-auth.js +139 -0
  90. package/dist/middleware/index.d.ts +2 -0
  91. package/dist/middleware/index.d.ts.map +1 -0
  92. package/dist/middleware/index.js +1 -0
  93. package/dist/openapi.d.ts +2 -0
  94. package/dist/openapi.d.ts.map +1 -0
  95. package/dist/openapi.js +36 -0
  96. package/dist/routes/agents.d.ts +10 -0
  97. package/dist/routes/agents.d.ts.map +1 -0
  98. package/dist/routes/agents.js +158 -0
  99. package/dist/routes/chat.d.ts +10 -0
  100. package/dist/routes/chat.d.ts.map +1 -0
  101. package/dist/routes/chat.js +307 -0
  102. package/dist/routes/chatDataStream.d.ts +10 -0
  103. package/dist/routes/chatDataStream.d.ts.map +1 -0
  104. package/dist/routes/chatDataStream.js +185 -0
  105. package/dist/routes/mcp.d.ts +10 -0
  106. package/dist/routes/mcp.d.ts.map +1 -0
  107. package/dist/routes/mcp.js +500 -0
  108. package/dist/tracer.d.ts +24 -0
  109. package/dist/tracer.d.ts.map +1 -0
  110. package/dist/tracer.js +107 -0
  111. package/dist/types/chat.d.ts +25 -0
  112. package/dist/types/chat.d.ts.map +1 -0
  113. package/dist/types/chat.js +1 -0
  114. package/dist/types/execution-context.d.ts +14 -0
  115. package/dist/types/execution-context.d.ts.map +1 -0
  116. package/dist/types/execution-context.js +14 -0
  117. package/dist/utils/agent-operations.d.ts +93 -0
  118. package/dist/utils/agent-operations.d.ts.map +1 -0
  119. package/dist/utils/agent-operations.js +78 -0
  120. package/dist/utils/artifact-component-schema.d.ts +29 -0
  121. package/dist/utils/artifact-component-schema.d.ts.map +1 -0
  122. package/dist/utils/artifact-component-schema.js +119 -0
  123. package/dist/utils/artifact-parser.d.ts +71 -0
  124. package/dist/utils/artifact-parser.d.ts.map +1 -0
  125. package/dist/utils/artifact-parser.js +253 -0
  126. package/dist/utils/cleanup.d.ts +19 -0
  127. package/dist/utils/cleanup.d.ts.map +1 -0
  128. package/dist/utils/cleanup.js +66 -0
  129. package/dist/utils/data-component-schema.d.ts +6 -0
  130. package/dist/utils/data-component-schema.d.ts.map +1 -0
  131. package/dist/utils/data-component-schema.js +43 -0
  132. package/dist/utils/graph-session.d.ts +230 -0
  133. package/dist/utils/graph-session.d.ts.map +1 -0
  134. package/dist/utils/graph-session.js +1199 -0
  135. package/dist/utils/incremental-stream-parser.d.ts +62 -0
  136. package/dist/utils/incremental-stream-parser.d.ts.map +1 -0
  137. package/dist/utils/incremental-stream-parser.js +330 -0
  138. package/dist/utils/response-formatter.d.ts +26 -0
  139. package/dist/utils/response-formatter.d.ts.map +1 -0
  140. package/dist/utils/response-formatter.js +158 -0
  141. package/dist/utils/stream-helpers.d.ts +186 -0
  142. package/dist/utils/stream-helpers.d.ts.map +1 -0
  143. package/dist/utils/stream-helpers.js +603 -0
  144. package/dist/utils/stream-registry.d.ts +18 -0
  145. package/dist/utils/stream-registry.d.ts.map +1 -0
  146. package/dist/utils/stream-registry.js +33 -0
  147. package/package.json +95 -0
  148. package/templates/v1/artifact.xml +7 -0
  149. package/templates/v1/data-component.xml +9 -0
  150. package/templates/v1/system-prompt.xml +52 -0
  151. package/templates/v1/thinking-preparation.xml +34 -0
  152. package/templates/v1/tool.xml +12 -0
@@ -0,0 +1,1164 @@
1
+ import { ContextResolver, CredentialStuffer, getContextConfigById, getCredentialReference, getFullGraphDefinition, getLedgerArtifacts, graphHasArtifactComponents, listTaskIdsByContextId, McpClient, TemplateEngine, } from '@inkeep/agents-core';
2
+ import { SpanStatusCode, trace } from '@opentelemetry/api';
3
+ import { generateObject, generateText, streamText, tool } from 'ai';
4
+ import { z } from 'zod';
5
+ import { createDefaultConversationHistoryConfig, getFormattedConversationHistory, } from '../data/conversations';
6
+ import dbClient from '../data/db/dbClient';
7
+ import { getLogger } from '../logger';
8
+ import { createSpanName, forceFlushTracer, getGlobalTracer, handleSpanError } from '../tracer';
9
+ import { generateToolId } from '../utils/agent-operations';
10
+ import { ArtifactReferenceSchema } from '../utils/artifact-component-schema';
11
+ import { jsonSchemaToZod } from '../utils/data-component-schema';
12
+ import { graphSessionManager } from '../utils/graph-session';
13
+ import { IncrementalStreamParser } from '../utils/incremental-stream-parser';
14
+ import { ResponseFormatter } from '../utils/response-formatter';
15
+ import { getStreamHelper } from '../utils/stream-registry';
16
+ import { createSaveToolResultTool } from './artifactTools';
17
+ import { ModelFactory } from './ModelFactory';
18
+ import { createDelegateToAgentTool, createTransferToAgentTool } from './relationTools';
19
+ import { SystemPromptBuilder } from './SystemPromptBuilder';
20
+ import { toolSessionManager } from './ToolSessionManager';
21
+ import { V1Config } from './versions/V1Config';
22
+ /**
23
+ * Creates a stopWhen condition that stops when any tool call name starts with the given prefix
24
+ * @param prefix - The prefix to check for in tool call names
25
+ * @returns A function that can be used as a stopWhen condition
26
+ */
27
+ export function hasToolCallWithPrefix(prefix) {
28
+ return ({ steps }) => {
29
+ const last = steps.at(-1);
30
+ if (last && 'toolCalls' in last && last.toolCalls) {
31
+ return last.toolCalls.some((tc) => tc.toolName.startsWith(prefix));
32
+ }
33
+ return false;
34
+ };
35
+ }
36
+ const logger = getLogger('Agent');
37
+ // Get tracer using centralized utility
38
+ const tracer = getGlobalTracer();
39
+ // Constants for agent configuration
40
+ const CONSTANTS = {
41
+ MAX_GENERATION_STEPS: 12,
42
+ DEFAULT_PRIMARY_MODEL: 'anthropic/claude-4-sonnet-20250514',
43
+ DEFAULT_STRUCTURED_OUTPUT_MODEL: 'openai/gpt-4.1-mini-2025-04-14',
44
+ DEFAULT_SUMMARIZER_MODEL: 'openai/gpt-4.1-nano-2025-04-14',
45
+ PHASE_1_TIMEOUT_MS: 270_000, // 4.5 minutes for streaming phase 1
46
+ NON_STREAMING_PHASE_1_TIMEOUT_MS: 90_000, // 1.5 minutes for non-streaming phase 1
47
+ PHASE_2_TIMEOUT_MS: 90_000, // 1.5 minutes for phase 2 structured output
48
+ };
49
+ // Helper function to handle empty model strings
50
+ function getModelOrDefault(modelString, defaultModel) {
51
+ return modelString?.trim() || defaultModel;
52
+ }
53
+ // Type guard to validate MCP tools have the expected AI SDK structure
54
+ function isValidTool(tool) {
55
+ return (tool &&
56
+ typeof tool === 'object' &&
57
+ typeof tool.description === 'string' &&
58
+ tool.inputSchema &&
59
+ typeof tool.execute === 'function');
60
+ }
61
+ // LLM Generated Information as a config LLM? Separate Step?
62
+ export class Agent {
63
+ config;
64
+ systemPromptBuilder = new SystemPromptBuilder('v1', new V1Config());
65
+ responseFormatter;
66
+ credentialStuffer;
67
+ streamHelper;
68
+ streamRequestId;
69
+ conversationId;
70
+ artifactComponents = [];
71
+ isDelegatedAgent = false;
72
+ contextResolver;
73
+ credentialStoreRegistry;
74
+ constructor(config, credentialStoreRegistry) {
75
+ // Store artifact components separately
76
+ this.artifactComponents = config.artifactComponents || [];
77
+ // Process dataComponents (now only component-type)
78
+ let processedDataComponents = config.dataComponents || [];
79
+ // If we have artifact components, add the default artifact data component for response hydration
80
+ if (this.artifactComponents.length > 0 &&
81
+ config.dataComponents &&
82
+ config.dataComponents.length > 0) {
83
+ processedDataComponents = [
84
+ ArtifactReferenceSchema.getDataComponent(config.tenantId, config.projectId),
85
+ ...processedDataComponents,
86
+ ];
87
+ }
88
+ this.config = {
89
+ ...config,
90
+ dataComponents: processedDataComponents,
91
+ // Set default conversation history if not provided
92
+ conversationHistoryConfig: config.conversationHistoryConfig || createDefaultConversationHistoryConfig(),
93
+ };
94
+ this.responseFormatter = new ResponseFormatter(config.tenantId);
95
+ // Store the credential store registry
96
+ this.credentialStoreRegistry = credentialStoreRegistry;
97
+ // Use provided credential store registry if available
98
+ if (credentialStoreRegistry) {
99
+ this.contextResolver = new ContextResolver(config.tenantId, config.projectId, dbClient, credentialStoreRegistry);
100
+ this.credentialStuffer = new CredentialStuffer(credentialStoreRegistry, this.contextResolver);
101
+ }
102
+ }
103
+ /**
104
+ * Get the maximum number of generation steps for this agent
105
+ * Uses agent's stopWhen.stepCountIs config or defaults to CONSTANTS.MAX_GENERATION_STEPS
106
+ */
107
+ getMaxGenerationSteps() {
108
+ return this.config.stopWhen?.stepCountIs ?? CONSTANTS.MAX_GENERATION_STEPS;
109
+ }
110
+ /**
111
+ * Get the primary model settings for text generation and thinking
112
+ * Defaults to claude-4-sonnet if not specified
113
+ */
114
+ getPrimaryModel() {
115
+ if (!this.config.models?.base) {
116
+ return { model: CONSTANTS.DEFAULT_PRIMARY_MODEL };
117
+ }
118
+ return {
119
+ model: getModelOrDefault(this.config.models.base.model, CONSTANTS.DEFAULT_PRIMARY_MODEL),
120
+ providerOptions: this.config.models.base.providerOptions,
121
+ };
122
+ }
123
+ /**
124
+ * Get the model settings for structured output generation
125
+ * Defaults to GPT-4.1-mini for structured outputs if not specified
126
+ */
127
+ getStructuredOutputModel() {
128
+ if (!this.config.models) {
129
+ return { model: CONSTANTS.DEFAULT_STRUCTURED_OUTPUT_MODEL };
130
+ }
131
+ // Use structured output config if available, otherwise fall back to base
132
+ const structuredConfig = this.config.models.structuredOutput;
133
+ const baseConfig = this.config.models.base;
134
+ // If structured output is explicitly configured, use only its config
135
+ if (structuredConfig) {
136
+ return {
137
+ model: getModelOrDefault(structuredConfig.model, CONSTANTS.DEFAULT_STRUCTURED_OUTPUT_MODEL),
138
+ providerOptions: structuredConfig.providerOptions,
139
+ };
140
+ }
141
+ // Fall back to base model settings if structured output not configured
142
+ return {
143
+ model: getModelOrDefault(baseConfig?.model, CONSTANTS.DEFAULT_STRUCTURED_OUTPUT_MODEL),
144
+ providerOptions: baseConfig?.providerOptions,
145
+ };
146
+ }
147
+ setConversationId(conversationId) {
148
+ this.conversationId = conversationId;
149
+ }
150
+ /**
151
+ * Set delegation status for this agent instance
152
+ */
153
+ setDelegationStatus(isDelegated) {
154
+ this.isDelegatedAgent = isDelegated;
155
+ }
156
+ /**
157
+ * Get streaming helper if this agent should stream to user
158
+ * Returns undefined for delegated agents to prevent streaming data operations to user
159
+ */
160
+ getStreamingHelper() {
161
+ return this.isDelegatedAgent ? undefined : this.streamHelper;
162
+ }
163
+ /**
164
+ * Wraps a tool with streaming lifecycle tracking (start, complete, error) and GraphSession recording
165
+ */
166
+ wrapToolWithStreaming(toolName, toolDefinition, streamRequestId, toolType) {
167
+ if (!toolDefinition || typeof toolDefinition !== 'object' || !('execute' in toolDefinition)) {
168
+ return toolDefinition;
169
+ }
170
+ const originalExecute = toolDefinition.execute;
171
+ return {
172
+ ...toolDefinition,
173
+ execute: async (args, context) => {
174
+ const startTime = Date.now();
175
+ // Use the AI SDK's toolCallId consistently instead of generating our own
176
+ const toolId = context?.toolCallId || generateToolId();
177
+ const activeSpan = trace.getActiveSpan();
178
+ if (activeSpan) {
179
+ activeSpan.setAttributes({
180
+ 'conversation.id': this.conversationId,
181
+ 'tool.purpose': toolDefinition.description || 'No description provided',
182
+ 'ai.toolType': toolType || 'unknown',
183
+ 'ai.agentName': this.config.name || 'unknown',
184
+ 'graph.id': this.config.graphId || 'unknown',
185
+ });
186
+ }
187
+ // Check if this is an internal tool to skip from recording
188
+ const isInternalTool = toolName.includes('save_tool_result') ||
189
+ toolName.includes('thinking_complete') ||
190
+ toolName.startsWith('transfer_to_') ||
191
+ toolName.startsWith('delegate_to_');
192
+ try {
193
+ const result = await originalExecute(args, context);
194
+ const duration = Date.now() - startTime;
195
+ // Record complete tool execution in GraphSession (skip internal tools)
196
+ if (streamRequestId && !isInternalTool) {
197
+ graphSessionManager.recordEvent(streamRequestId, 'tool_execution', this.config.id, {
198
+ toolName,
199
+ args,
200
+ result,
201
+ toolId,
202
+ duration,
203
+ });
204
+ }
205
+ return result;
206
+ }
207
+ catch (error) {
208
+ const duration = Date.now() - startTime;
209
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
210
+ // Record tool execution with error (skip internal tools)
211
+ if (streamRequestId && !isInternalTool) {
212
+ graphSessionManager.recordEvent(streamRequestId, 'tool_execution', this.config.id, {
213
+ toolName,
214
+ args,
215
+ result: { error: errorMessage },
216
+ toolId,
217
+ duration,
218
+ });
219
+ }
220
+ throw error;
221
+ }
222
+ },
223
+ };
224
+ }
225
+ getRelationTools(runtimeContext, sessionId) {
226
+ const { transferRelations = [], delegateRelations = [] } = this.config;
227
+ const createToolName = (prefix, agentId) => `${prefix}_to_${agentId.toLowerCase().replace(/\s+/g, '_')}`;
228
+ return Object.fromEntries([
229
+ ...transferRelations.map((agentConfig) => {
230
+ const toolName = createToolName('transfer', agentConfig.id);
231
+ return [
232
+ toolName,
233
+ this.wrapToolWithStreaming(toolName, createTransferToAgentTool({
234
+ transferConfig: agentConfig,
235
+ callingAgentId: this.config.id,
236
+ agent: this,
237
+ streamRequestId: runtimeContext?.metadata?.streamRequestId,
238
+ }), runtimeContext?.metadata?.streamRequestId, 'transfer'),
239
+ ];
240
+ }),
241
+ ...delegateRelations.map((relation) => {
242
+ const toolName = createToolName('delegate', relation.config.id);
243
+ return [
244
+ toolName,
245
+ this.wrapToolWithStreaming(toolName, createDelegateToAgentTool({
246
+ delegateConfig: relation,
247
+ callingAgentId: this.config.id,
248
+ tenantId: this.config.tenantId,
249
+ projectId: this.config.projectId,
250
+ graphId: this.config.graphId,
251
+ contextId: runtimeContext?.contextId || 'default', // fallback for compatibility
252
+ metadata: runtimeContext?.metadata || {
253
+ conversationId: runtimeContext?.contextId || 'default',
254
+ threadId: runtimeContext?.contextId || 'default',
255
+ streamRequestId: runtimeContext?.metadata?.streamRequestId,
256
+ apiKey: runtimeContext?.metadata?.apiKey,
257
+ },
258
+ sessionId,
259
+ agent: this,
260
+ credentialStoreRegistry: this.credentialStoreRegistry,
261
+ }), runtimeContext?.metadata?.streamRequestId, 'delegation'),
262
+ ];
263
+ }),
264
+ ]);
265
+ }
266
+ async getMcpTools(sessionId, streamRequestId) {
267
+ const tools = (await Promise.all(this.config.tools?.map((tool) => this.getMcpTool(tool)) || [])) || [];
268
+ // If no sessionId, return tools as-is (for system prompt building)
269
+ if (!sessionId) {
270
+ const combinedTools = tools.reduce((acc, tool) => {
271
+ return Object.assign(acc, tool);
272
+ }, {});
273
+ // Just wrap with streaming capability
274
+ const wrappedTools = {};
275
+ for (const [toolName, toolDef] of Object.entries(combinedTools)) {
276
+ wrappedTools[toolName] = this.wrapToolWithStreaming(toolName, toolDef, streamRequestId, 'mcp');
277
+ }
278
+ return wrappedTools;
279
+ }
280
+ // Wrap each MCP tool to record results immediately upon execution
281
+ const wrappedTools = {};
282
+ for (const toolSet of tools) {
283
+ for (const [toolName, originalTool] of Object.entries(toolSet)) {
284
+ // Type guard to ensure we have a valid AI SDK tool
285
+ if (!isValidTool(originalTool)) {
286
+ logger.error({ toolName }, 'Invalid MCP tool structure - missing required properties');
287
+ continue;
288
+ }
289
+ // First wrap with session management
290
+ const sessionWrappedTool = tool({
291
+ description: originalTool.description,
292
+ inputSchema: originalTool.inputSchema,
293
+ execute: async (args, { toolCallId }) => {
294
+ logger.debug({ toolName, toolCallId }, 'MCP Tool Called');
295
+ try {
296
+ // Call the original MCP tool with proper error handling
297
+ const result = await originalTool.execute(args, { toolCallId });
298
+ // Record the result immediately in the session manager
299
+ toolSessionManager.recordToolResult(sessionId, {
300
+ toolCallId,
301
+ toolName,
302
+ args,
303
+ result,
304
+ timestamp: Date.now(),
305
+ });
306
+ return { result, toolCallId };
307
+ }
308
+ catch (error) {
309
+ logger.error({ toolName, toolCallId, error }, 'MCP tool execution failed');
310
+ throw error;
311
+ }
312
+ },
313
+ });
314
+ // Then wrap with streaming capability
315
+ wrappedTools[toolName] = this.wrapToolWithStreaming(toolName, sessionWrappedTool, streamRequestId, 'mcp');
316
+ }
317
+ }
318
+ return wrappedTools;
319
+ }
320
+ /**
321
+ * Convert database McpTool to builder MCPToolConfig format
322
+ */
323
+ convertToMCPToolConfig(tool) {
324
+ return {
325
+ id: tool.id,
326
+ name: tool.name,
327
+ description: tool.name, // Use name as description fallback
328
+ serverUrl: tool.config.mcp.server.url,
329
+ activeTools: tool.config.mcp.activeTools,
330
+ mcpType: tool.config.mcp.server.url.includes('api.nango.dev') ? 'nango' : 'generic',
331
+ transport: tool.config.mcp.transport,
332
+ headers: tool.headers,
333
+ };
334
+ }
335
+ async getMcpTool(tool) {
336
+ const credentialReferenceId = tool.credentialReferenceId;
337
+ // Build server config with credentials using new architecture
338
+ let serverConfig;
339
+ if (credentialReferenceId && this.credentialStuffer) {
340
+ // Database lookup to get credential store configuration
341
+ const credentialReference = await getCredentialReference(dbClient)({
342
+ scopes: {
343
+ tenantId: this.config.tenantId,
344
+ projectId: this.config.projectId,
345
+ },
346
+ id: credentialReferenceId,
347
+ });
348
+ if (!credentialReference) {
349
+ throw new Error(`Credential store not found: ${credentialReferenceId}`);
350
+ }
351
+ const storeReference = {
352
+ credentialStoreId: credentialReference.credentialStoreId,
353
+ retrievalParams: credentialReference.retrievalParams || {},
354
+ };
355
+ serverConfig = await this.credentialStuffer.buildMcpServerConfig({
356
+ tenantId: this.config.tenantId,
357
+ projectId: this.config.projectId,
358
+ contextConfigId: this.config.contextConfigId || undefined,
359
+ conversationId: this.conversationId || undefined,
360
+ }, this.convertToMCPToolConfig(tool), storeReference);
361
+ }
362
+ else if (tool.headers && this.credentialStuffer) {
363
+ serverConfig = await this.credentialStuffer.buildMcpServerConfig({
364
+ tenantId: this.config.tenantId,
365
+ projectId: this.config.projectId,
366
+ contextConfigId: this.config.contextConfigId || undefined,
367
+ conversationId: this.conversationId || undefined,
368
+ }, this.convertToMCPToolConfig(tool));
369
+ }
370
+ else {
371
+ // No credentials - build basic config
372
+ serverConfig = {
373
+ type: tool.config.mcp.transport?.type || 'streamable_http',
374
+ url: tool.config.mcp.server.url,
375
+ activeTools: tool.config.mcp.activeTools,
376
+ };
377
+ }
378
+ logger.info({
379
+ toolName: tool.name,
380
+ credentialReferenceId,
381
+ transportType: serverConfig.type,
382
+ headers: tool.headers,
383
+ }, 'Built MCP server config with credentials');
384
+ // Create and connect MCP client
385
+ const client = new McpClient({
386
+ name: tool.name,
387
+ server: serverConfig,
388
+ });
389
+ await client.connect();
390
+ return client.tools();
391
+ }
392
+ getFunctionTools(streamRequestId) {
393
+ if (!this.config.functionTools)
394
+ return {};
395
+ const functionTools = {};
396
+ for (const funcTool of this.config.functionTools) {
397
+ // Convert function tool to AI SDK format and wrap with streaming
398
+ const aiTool = tool({
399
+ description: funcTool.description,
400
+ inputSchema: funcTool.schema || z.object({}),
401
+ execute: funcTool.execute,
402
+ });
403
+ functionTools[funcTool.name] = this.wrapToolWithStreaming(funcTool.name, aiTool, streamRequestId, 'tool');
404
+ }
405
+ return functionTools;
406
+ }
407
+ /**
408
+ * Get resolved context using ContextResolver - will return cached data or fetch fresh data as needed
409
+ */
410
+ async getResolvedContext(conversationId, requestContext) {
411
+ try {
412
+ if (!this.config.contextConfigId) {
413
+ logger.debug({ graphId: this.config.graphId }, 'No context config found for graph');
414
+ return null;
415
+ }
416
+ // Get context configuration
417
+ const contextConfig = await getContextConfigById(dbClient)({
418
+ scopes: { tenantId: this.config.tenantId, projectId: this.config.projectId },
419
+ id: this.config.contextConfigId,
420
+ });
421
+ if (!contextConfig) {
422
+ logger.warn({ contextConfigId: this.config.contextConfigId }, 'Context config not found');
423
+ return null;
424
+ }
425
+ if (!this.contextResolver) {
426
+ throw new Error('Context resolver not found');
427
+ }
428
+ // Resolve context with 'invocation' trigger to ensure fresh data for invocation definitions
429
+ const result = await this.contextResolver.resolve(contextConfig, {
430
+ triggerEvent: 'invocation',
431
+ conversationId,
432
+ requestContext: requestContext || {},
433
+ tenantId: this.config.tenantId,
434
+ });
435
+ // Add built-in variables to resolved context
436
+ const contextWithBuiltins = {
437
+ ...result.resolvedContext,
438
+ $now: new Date().toISOString(),
439
+ $env: process.env,
440
+ };
441
+ logger.debug({
442
+ conversationId,
443
+ contextConfigId: contextConfig.id,
444
+ resolvedKeys: Object.keys(contextWithBuiltins),
445
+ cacheHits: result.cacheHits.length,
446
+ cacheMisses: result.cacheMisses.length,
447
+ fetchedDefinitions: result.fetchedDefinitions.length,
448
+ errors: result.errors.length,
449
+ }, 'Context resolved for agent');
450
+ return contextWithBuiltins;
451
+ }
452
+ catch (error) {
453
+ logger.error({
454
+ conversationId,
455
+ error: error instanceof Error ? error.message : 'Unknown error',
456
+ }, 'Failed to get resolved context');
457
+ return null;
458
+ }
459
+ }
460
+ /**
461
+ * Get the graph prompt for this agent's graph
462
+ */
463
+ async getGraphPrompt() {
464
+ try {
465
+ const graphDefinition = await getFullGraphDefinition(dbClient)({
466
+ scopes: {
467
+ tenantId: this.config.tenantId,
468
+ projectId: this.config.projectId,
469
+ },
470
+ graphId: this.config.graphId,
471
+ });
472
+ return graphDefinition?.graphPrompt || undefined;
473
+ }
474
+ catch (error) {
475
+ logger.warn({
476
+ graphId: this.config.graphId,
477
+ error: error instanceof Error ? error.message : 'Unknown error',
478
+ }, 'Failed to get graph prompt');
479
+ return undefined;
480
+ }
481
+ }
482
+ /**
483
+ * Check if any agent in the graph has artifact components configured
484
+ */
485
+ async hasGraphArtifactComponents() {
486
+ try {
487
+ const graphDefinition = await getFullGraphDefinition(dbClient)({
488
+ scopes: {
489
+ tenantId: this.config.tenantId,
490
+ projectId: this.config.projectId,
491
+ },
492
+ graphId: this.config.graphId,
493
+ });
494
+ if (!graphDefinition) {
495
+ return false;
496
+ }
497
+ // Check if artifactComponents exists and has any entries
498
+ return !!(graphDefinition.artifactComponents &&
499
+ Object.keys(graphDefinition.artifactComponents).length > 0);
500
+ }
501
+ catch (error) {
502
+ logger.warn({
503
+ graphId: this.config.graphId,
504
+ tenantId: this.config.tenantId,
505
+ projectId: this.config.projectId,
506
+ error: error instanceof Error ? error.message : 'Unknown error',
507
+ }, 'Failed to check graph artifact components, assuming none exist');
508
+ // Fallback to current agent's artifact components if graph query fails
509
+ return this.artifactComponents.length > 0;
510
+ }
511
+ }
512
+ /**
513
+ * Build adaptive system prompt for Phase 2 structured output generation
514
+ * based on configured data components and artifact components across the graph
515
+ */
516
+ async buildPhase2SystemPrompt() {
517
+ const hasDataComponents = this.config.dataComponents && this.config.dataComponents.length > 0;
518
+ const hasArtifactComponents = await this.hasGraphArtifactComponents();
519
+ if (hasDataComponents && hasArtifactComponents) {
520
+ return `Generate the final structured JSON response using the configured data components. Intersperse artifact references throughout the dataComponents array to support each piece of information with evidence from the research above. Use exact artifact_id and task_id values from the tool outputs - never make up or modify these IDs.
521
+
522
+ Key requirements:
523
+ - Mix artifact references throughout your dataComponents array
524
+ - Each artifact reference must use EXACT IDs from tool outputs
525
+ - Reference artifacts that directly support the adjacent information
526
+ - Follow the pattern: Data → Supporting Artifact → Next Data → Next Artifact`;
527
+ }
528
+ if (hasDataComponents && !hasArtifactComponents) {
529
+ return `Generate the final structured JSON response using the configured data components. Organize the information from the research above into the appropriate structured format based on the available component schemas.
530
+
531
+ Key requirements:
532
+ - Use the exact component structure and property names
533
+ - Fill in all relevant data from the research
534
+ - Ensure data is organized logically and completely`;
535
+ }
536
+ if (!hasDataComponents && hasArtifactComponents) {
537
+ return `Generate the final structured response with artifact references based on the research above. Use the artifact reference component to cite relevant information with exact artifact_id and task_id values from the tool outputs.
538
+
539
+ Key requirements:
540
+ - Use exact artifact_id and task_id from tool outputs
541
+ - Reference artifacts that support your response
542
+ - Never make up or modify artifact IDs`;
543
+ }
544
+ // Fallback case (shouldn't happen in normal operation since we check hasStructuredOutput)
545
+ return `Generate the final response based on the research above.`;
546
+ }
547
+ async buildSystemPrompt(runtimeContext, excludeDataComponents = false) {
548
+ // Get resolved context using ContextResolver
549
+ const conversationId = runtimeContext?.metadata?.conversationId || runtimeContext?.contextId;
550
+ // Set conversation ID if available
551
+ if (conversationId) {
552
+ this.setConversationId(conversationId);
553
+ }
554
+ const resolvedContext = conversationId ? await this.getResolvedContext(conversationId) : null;
555
+ // Process agent prompt with context
556
+ let processedPrompt = this.config.agentPrompt;
557
+ if (resolvedContext) {
558
+ try {
559
+ processedPrompt = TemplateEngine.render(this.config.agentPrompt, resolvedContext, {
560
+ strict: false,
561
+ preserveUnresolved: false,
562
+ });
563
+ }
564
+ catch (error) {
565
+ logger.error({
566
+ conversationId,
567
+ error: error instanceof Error ? error.message : 'Unknown error',
568
+ }, 'Failed to process agent prompt with context, using original');
569
+ processedPrompt = this.config.agentPrompt;
570
+ }
571
+ }
572
+ // Get MCP tools, function tools, and relational tools
573
+ const streamRequestId = runtimeContext?.metadata?.streamRequestId;
574
+ const mcpTools = await this.getMcpTools(undefined, streamRequestId);
575
+ const functionTools = this.getFunctionTools(streamRequestId);
576
+ const relationTools = this.getRelationTools(runtimeContext);
577
+ // Convert ToolSet objects to ToolData array format for system prompt
578
+ const allTools = { ...mcpTools, ...functionTools, ...relationTools };
579
+ const toolDefinitions = Object.entries(allTools).map(([name, tool]) => ({
580
+ name,
581
+ description: tool.description || '',
582
+ inputSchema: tool.inputSchema || tool.parameters || {},
583
+ usageGuidelines: name.startsWith('transfer_to_') || name.startsWith('delegate_to_')
584
+ ? `Use this tool to ${name.startsWith('transfer_to_') ? 'transfer' : 'delegate'} to another agent when appropriate.`
585
+ : 'Use this tool when appropriate for the task at hand.',
586
+ }));
587
+ const referenceTaskIds = await listTaskIdsByContextId(dbClient)({
588
+ contextId: runtimeContext?.contextId || '',
589
+ });
590
+ const referenceArtifacts = [];
591
+ for (const taskId of referenceTaskIds) {
592
+ const artifacts = await getLedgerArtifacts(dbClient)({
593
+ scopes: {
594
+ tenantId: this.config.tenantId,
595
+ projectId: this.config.projectId,
596
+ },
597
+ taskId: taskId,
598
+ });
599
+ referenceArtifacts.push(...artifacts);
600
+ }
601
+ // Use component dataComponents for system prompt (artifacts already separated in constructor)
602
+ const componentDataComponents = excludeDataComponents ? [] : this.config.dataComponents || [];
603
+ // Use thinking/preparation mode when we have data components but are excluding them (Phase 1)
604
+ const isThinkingPreparation = this.config.dataComponents && this.config.dataComponents.length > 0 && excludeDataComponents;
605
+ // Get graph prompt for additional context
606
+ let graphPrompt = await this.getGraphPrompt();
607
+ // Process graph prompt with context variables
608
+ if (graphPrompt && resolvedContext) {
609
+ try {
610
+ graphPrompt = TemplateEngine.render(graphPrompt, resolvedContext, {
611
+ strict: false,
612
+ preserveUnresolved: false,
613
+ });
614
+ }
615
+ catch (error) {
616
+ logger.error({
617
+ conversationId,
618
+ error: error instanceof Error ? error.message : 'Unknown error',
619
+ }, 'Failed to process graph prompt with context, using original');
620
+ // graphPrompt remains unchanged if processing fails
621
+ }
622
+ }
623
+ const config = {
624
+ corePrompt: processedPrompt,
625
+ graphPrompt,
626
+ tools: toolDefinitions,
627
+ dataComponents: componentDataComponents,
628
+ artifacts: referenceArtifacts,
629
+ isThinkingPreparation,
630
+ hasTransferRelations: (this.config.transferRelations?.length ?? 0) > 0,
631
+ hasDelegateRelations: (this.config.delegateRelations?.length ?? 0) > 0,
632
+ };
633
+ return await this.systemPromptBuilder.buildSystemPrompt(config);
634
+ }
635
+ getArtifactTools() {
636
+ return tool({
637
+ description: 'Call this tool to get the artifact with the given artifactId. Only retrieve this when the description of the artifact is insufficient to understand the artifact and you need to see the actual artifact for more context. Please refrain from using this tool unless absolutely necessary.',
638
+ inputSchema: z.object({
639
+ artifactId: z.string().describe('The unique identifier of the artifact to get.'),
640
+ }),
641
+ execute: async ({ artifactId }) => {
642
+ logger.info({ artifactId }, 'get_artifact executed');
643
+ const artifact = await getLedgerArtifacts(dbClient)({
644
+ scopes: {
645
+ tenantId: this.config.tenantId,
646
+ projectId: this.config.projectId,
647
+ },
648
+ artifactId,
649
+ });
650
+ if (!artifact) {
651
+ throw new Error(`Artifact ${artifactId} not found`);
652
+ }
653
+ return { artifact: artifact[0] };
654
+ },
655
+ });
656
+ }
657
+ // Create the thinking_complete tool to mark end of planning phase
658
+ createThinkingCompleteTool() {
659
+ return tool({
660
+ description: 'Call when research and planning is complete, ready to generate structured response. This marks the end of the thinking phase.',
661
+ inputSchema: z.object({
662
+ complete: z.boolean().describe('Set to true when research is finished'),
663
+ }),
664
+ execute: async (params) => params,
665
+ });
666
+ }
667
+ // Provide a default tool set that is always available to the agent.
668
+ async getDefaultTools(sessionId, streamRequestId) {
669
+ const defaultTools = {};
670
+ // Add get_reference_artifact if any agent in the graph has artifact components
671
+ // This enables cross-agent artifact collaboration within the same graph
672
+ if (await this.graphHasArtifactComponents()) {
673
+ defaultTools.get_reference_artifact = this.getArtifactTools();
674
+ }
675
+ // Only add save_tool_result if this specific agent has artifact components
676
+ if (this.artifactComponents.length > 0) {
677
+ defaultTools.save_tool_result = createSaveToolResultTool(sessionId, streamRequestId, this.config.id, this.artifactComponents);
678
+ }
679
+ // Add thinking_complete tool if we have structured output components
680
+ const hasStructuredOutput = this.config.dataComponents && this.config.dataComponents.length > 0;
681
+ if (hasStructuredOutput) {
682
+ const thinkingCompleteTool = this.createThinkingCompleteTool();
683
+ if (thinkingCompleteTool) {
684
+ defaultTools.thinking_complete = this.wrapToolWithStreaming('thinking_complete', thinkingCompleteTool, streamRequestId, 'tool');
685
+ }
686
+ }
687
+ return defaultTools;
688
+ }
689
+ getStreamRequestId() {
690
+ return this.streamRequestId || '';
691
+ }
692
+ // Check if any agents in the graph have artifact components
693
+ async graphHasArtifactComponents() {
694
+ try {
695
+ return await graphHasArtifactComponents(dbClient)({
696
+ scopes: {
697
+ tenantId: this.config.tenantId,
698
+ projectId: this.config.projectId,
699
+ },
700
+ graphId: this.config.graphId,
701
+ });
702
+ }
703
+ catch (error) {
704
+ logger.error({ error, graphId: this.config.graphId }, 'Failed to check graph artifact components');
705
+ return false;
706
+ }
707
+ }
708
+ async generate(userMessage, runtimeContext) {
709
+ return tracer.startActiveSpan(createSpanName('agent.generate'), async (span) => {
710
+ // Create tool session for this execution outside try blocks
711
+ const contextId = runtimeContext?.contextId || 'default';
712
+ const taskId = runtimeContext?.metadata?.taskId || 'unknown';
713
+ const sessionId = toolSessionManager.createSession(this.config.tenantId, this.config.projectId, contextId, taskId);
714
+ try {
715
+ // Set streaming helper from registry if available
716
+ const streamRequestId = runtimeContext?.metadata?.streamRequestId;
717
+ this.streamRequestId = streamRequestId;
718
+ this.streamHelper = streamRequestId ? getStreamHelper(streamRequestId) : undefined;
719
+ const conversationId = runtimeContext?.metadata?.conversationId;
720
+ // Set conversation ID if available
721
+ if (conversationId) {
722
+ this.setConversationId(conversationId);
723
+ }
724
+ // Load all tools and both system prompts in parallel
725
+ // Note: getDefaultTools needs to be called after streamHelper is set above
726
+ const [mcpTools, systemPrompt, thinkingSystemPrompt, functionTools, relationTools, defaultTools,] = await tracer.startActiveSpan(createSpanName('agent.load_tools'), {
727
+ attributes: {
728
+ 'agent.name': this.config.name,
729
+ 'session.id': sessionId || 'none',
730
+ },
731
+ }, async (childSpan) => {
732
+ try {
733
+ const result = await Promise.all([
734
+ this.getMcpTools(sessionId, streamRequestId),
735
+ this.buildSystemPrompt(runtimeContext, false), // Normal prompt with data components
736
+ this.buildSystemPrompt(runtimeContext, true), // Thinking prompt without data components
737
+ Promise.resolve(this.getFunctionTools(streamRequestId)),
738
+ Promise.resolve(this.getRelationTools(runtimeContext, sessionId)),
739
+ this.getDefaultTools(sessionId, streamRequestId),
740
+ ]);
741
+ childSpan.setStatus({ code: SpanStatusCode.OK });
742
+ return result;
743
+ }
744
+ catch (err) {
745
+ // Use helper function for consistent error handling
746
+ handleSpanError(childSpan, err);
747
+ throw err;
748
+ }
749
+ finally {
750
+ childSpan.end();
751
+ // Force flush after critical tool loading span
752
+ await forceFlushTracer();
753
+ }
754
+ });
755
+ // Combine all tools for AI SDK
756
+ const allTools = {
757
+ ...mcpTools,
758
+ ...functionTools,
759
+ ...relationTools,
760
+ ...defaultTools,
761
+ };
762
+ // Get conversation history
763
+ let conversationHistory = '';
764
+ const historyConfig = this.config.conversationHistoryConfig ?? createDefaultConversationHistoryConfig();
765
+ if (historyConfig && historyConfig.mode !== 'none') {
766
+ if (historyConfig.mode === 'full') {
767
+ conversationHistory = await getFormattedConversationHistory({
768
+ tenantId: this.config.tenantId,
769
+ projectId: this.config.projectId,
770
+ conversationId: contextId,
771
+ currentMessage: userMessage,
772
+ options: historyConfig,
773
+ filters: {},
774
+ });
775
+ }
776
+ else if (historyConfig.mode === 'scoped') {
777
+ conversationHistory = await getFormattedConversationHistory({
778
+ tenantId: this.config.tenantId,
779
+ projectId: this.config.projectId,
780
+ conversationId: contextId,
781
+ currentMessage: userMessage,
782
+ options: historyConfig,
783
+ filters: {
784
+ agentId: this.config.id,
785
+ taskId: taskId,
786
+ },
787
+ });
788
+ }
789
+ }
790
+ // Use the primary model for text generation
791
+ const primaryModelSettings = this.getPrimaryModel();
792
+ const modelSettings = ModelFactory.prepareGenerationConfig(primaryModelSettings);
793
+ let response;
794
+ let textResponse;
795
+ // Check if we have structured output components
796
+ const hasStructuredOutput = this.config.dataComponents && this.config.dataComponents.length > 0;
797
+ // Phase 1: Stream only if no structured output needed
798
+ const shouldStreamPhase1 = this.getStreamingHelper() && !hasStructuredOutput;
799
+ // Extract maxDuration from config and convert to milliseconds, or use defaults
800
+ // Add upper bound validation to prevent extremely long timeouts
801
+ const MAX_ALLOWED_TIMEOUT_MS = 600_000; // 10 minutes maximum
802
+ const configuredTimeout = modelSettings.maxDuration
803
+ ? Math.min(modelSettings.maxDuration * 1000, MAX_ALLOWED_TIMEOUT_MS)
804
+ : shouldStreamPhase1
805
+ ? CONSTANTS.PHASE_1_TIMEOUT_MS
806
+ : CONSTANTS.NON_STREAMING_PHASE_1_TIMEOUT_MS;
807
+ // Ensure timeout doesn't exceed maximum
808
+ const timeoutMs = Math.min(configuredTimeout, MAX_ALLOWED_TIMEOUT_MS);
809
+ if (modelSettings.maxDuration && modelSettings.maxDuration * 1000 > MAX_ALLOWED_TIMEOUT_MS) {
810
+ logger.warn({
811
+ requestedTimeout: modelSettings.maxDuration * 1000,
812
+ appliedTimeout: timeoutMs,
813
+ maxAllowed: MAX_ALLOWED_TIMEOUT_MS
814
+ }, 'Requested timeout exceeded maximum allowed, capping to 10 minutes');
815
+ }
816
+ // Build messages for Phase 1 - use thinking prompt if structured output needed
817
+ const phase1SystemPrompt = hasStructuredOutput ? thinkingSystemPrompt : systemPrompt;
818
+ const messages = [];
819
+ messages.push({ role: 'system', content: phase1SystemPrompt });
820
+ if (conversationHistory.trim() !== '') {
821
+ messages.push({ role: 'user', content: conversationHistory });
822
+ }
823
+ messages.push({
824
+ role: 'user',
825
+ content: userMessage,
826
+ });
827
+ // ----- PHASE 1: Planning with tools -----
828
+ if (shouldStreamPhase1) {
829
+ // Streaming Phase 1: Natural text + tools (no structured output needed)
830
+ const streamConfig = {
831
+ ...modelSettings,
832
+ toolChoice: 'auto', // Allow natural text + tools
833
+ };
834
+ // Use streamText for Phase 1 (text-only responses)
835
+ const streamResult = streamText({
836
+ ...streamConfig,
837
+ messages,
838
+ tools: allTools,
839
+ stopWhen: async ({ steps }) => {
840
+ // Track the last step's text reasoning
841
+ const last = steps.at(-1);
842
+ if (last && 'text' in last && last.text) {
843
+ try {
844
+ await graphSessionManager.recordEvent(this.getStreamRequestId(), 'agent_reasoning', this.config.id, {
845
+ parts: [{ type: 'text', content: last.text }],
846
+ });
847
+ }
848
+ catch (error) {
849
+ logger.debug('Failed to track agent reasoning');
850
+ }
851
+ }
852
+ // Return the actual stop condition
853
+ if (last && 'toolCalls' in last && last.toolCalls) {
854
+ return last.toolCalls.some((tc) => tc.toolName.startsWith('transfer_to_'));
855
+ }
856
+ // Safety cap at configured max steps
857
+ return steps.length >= this.getMaxGenerationSteps();
858
+ },
859
+ experimental_telemetry: {
860
+ isEnabled: true,
861
+ functionId: this.config.id,
862
+ recordInputs: true,
863
+ recordOutputs: true,
864
+ },
865
+ abortSignal: AbortSignal.timeout(timeoutMs),
866
+ });
867
+ // Create incremental parser that will format and stream to user
868
+ const streamHelper = this.getStreamingHelper();
869
+ if (!streamHelper) {
870
+ throw new Error('Stream helper is unexpectedly undefined in streaming context');
871
+ }
872
+ const parser = new IncrementalStreamParser(streamHelper, this.config.tenantId, contextId);
873
+ // Process the full stream - track all events including tool calls
874
+ // Note: stopWhen will automatically stop on transfer_to_
875
+ for await (const event of streamResult.fullStream) {
876
+ switch (event.type) {
877
+ case 'text-delta':
878
+ await parser.processTextChunk(event.text);
879
+ break;
880
+ case 'tool-call':
881
+ // Mark that a tool call happened
882
+ parser.markToolResult();
883
+ break;
884
+ case 'tool-result':
885
+ // Tool result finished, next text should have spacing
886
+ parser.markToolResult();
887
+ break;
888
+ case 'finish':
889
+ // Stream finished, check if it was due to tool calls
890
+ if (event.finishReason === 'tool-calls') {
891
+ parser.markToolResult();
892
+ }
893
+ break;
894
+ // Handle other event types if needed
895
+ }
896
+ }
897
+ // Finalize the stream
898
+ await parser.finalize();
899
+ // Get the complete result for A2A protocol
900
+ response = await streamResult;
901
+ // Build formattedContent from collected parts
902
+ const collectedParts = parser.getCollectedParts();
903
+ if (collectedParts.length > 0) {
904
+ response.formattedContent = {
905
+ parts: collectedParts.map((part) => ({
906
+ kind: part.kind,
907
+ ...(part.kind === 'text' && { text: part.text }),
908
+ ...(part.kind === 'data' && { data: part.data }),
909
+ })),
910
+ };
911
+ }
912
+ }
913
+ else {
914
+ // Non-streaming Phase 1
915
+ let genConfig;
916
+ if (hasStructuredOutput) {
917
+ genConfig = {
918
+ ...modelSettings,
919
+ toolChoice: 'required', // Force tool usage, prevent text generation
920
+ };
921
+ }
922
+ else {
923
+ genConfig = {
924
+ ...modelSettings,
925
+ toolChoice: 'auto', // Allow both tools and text generation
926
+ };
927
+ }
928
+ // Use generateText for Phase 1 planning
929
+ response = await generateText({
930
+ ...genConfig,
931
+ messages,
932
+ tools: allTools,
933
+ stopWhen: async ({ steps }) => {
934
+ // Track the last step's text reasoning
935
+ const last = steps.at(-1);
936
+ if (last && 'text' in last && last.text) {
937
+ try {
938
+ await graphSessionManager.recordEvent(this.getStreamRequestId(), 'agent_reasoning', this.config.id, {
939
+ parts: [{ type: 'text', content: last.text }],
940
+ });
941
+ }
942
+ catch (error) {
943
+ logger.debug('Failed to track agent reasoning');
944
+ }
945
+ }
946
+ // Return the actual stop condition
947
+ if (last && 'toolCalls' in last && last.toolCalls) {
948
+ return last.toolCalls.some((tc) => tc.toolName.startsWith('transfer_to_') || tc.toolName === 'thinking_complete');
949
+ }
950
+ // Safety cap at configured max steps
951
+ return steps.length >= this.getMaxGenerationSteps();
952
+ },
953
+ experimental_telemetry: {
954
+ isEnabled: true,
955
+ functionId: this.config.id,
956
+ recordInputs: true,
957
+ recordOutputs: true,
958
+ metadata: {
959
+ phase: 'planning',
960
+ },
961
+ },
962
+ abortSignal: AbortSignal.timeout(timeoutMs),
963
+ });
964
+ }
965
+ // Resolve steps Promise so task handler can access the array properly
966
+ if (response.steps) {
967
+ const resolvedSteps = await response.steps;
968
+ response = { ...response, steps: resolvedSteps };
969
+ }
970
+ // ----- PHASE 2: Structured Output Generation -----
971
+ if (hasStructuredOutput && !hasToolCallWithPrefix('transfer_to_')(response)) {
972
+ // Check if thinking_complete was called (successful Phase 1)
973
+ const thinkingCompleteCall = response.steps
974
+ ?.flatMap((s) => s.toolCalls || [])
975
+ ?.find((tc) => tc.toolName === 'thinking_complete');
976
+ if (thinkingCompleteCall) {
977
+ // Build reasoning flow from Phase 1 steps
978
+ const reasoningFlow = [];
979
+ if (response.steps) {
980
+ response.steps.forEach((step) => {
981
+ // Add tool calls and results as formatted messages
982
+ if (step.toolCalls && step.toolResults) {
983
+ step.toolCalls.forEach((call, index) => {
984
+ const result = step.toolResults[index];
985
+ if (result) {
986
+ const storedResult = toolSessionManager.getToolResult(sessionId, result.toolCallId);
987
+ const toolName = storedResult?.toolName || call.toolName;
988
+ // Skip tool_thinking tool
989
+ if (toolName === 'thinking_complete') {
990
+ return;
991
+ }
992
+ // Special handling for save_artifact_tool and save_tool_result
993
+ if (toolName === 'save_artifact_tool' || toolName === 'save_tool_result') {
994
+ logger.info({ result }, 'save_artifact_tool or save_tool_result');
995
+ if (result.output.artifacts) {
996
+ for (const artifact of result.output.artifacts) {
997
+ const artifactId = artifact?.artifactId || 'N/A';
998
+ const taskId = artifact?.taskId || 'N/A';
999
+ const summaryData = artifact?.summaryData || {};
1000
+ const formattedArtifact = `## Artifact Saved
1001
+
1002
+ **Artifact ID:** ${artifactId}
1003
+ **Task ID:** ${taskId}
1004
+
1005
+ ### Summary
1006
+ ${typeof summaryData === 'string' ? summaryData : JSON.stringify(summaryData, null, 2)}
1007
+ `;
1008
+ reasoningFlow.push({
1009
+ role: 'assistant',
1010
+ content: formattedArtifact,
1011
+ });
1012
+ }
1013
+ }
1014
+ return;
1015
+ }
1016
+ // Default formatting for all other tools
1017
+ const actualResult = storedResult?.result || result.result || result;
1018
+ const actualArgs = storedResult?.args || call.args;
1019
+ const input = actualArgs ? JSON.stringify(actualArgs, null, 2) : 'No input';
1020
+ const output = typeof actualResult === 'string'
1021
+ ? actualResult
1022
+ : JSON.stringify(actualResult, null, 2);
1023
+ const formattedResult = `## Tool: ${call.toolName}
1024
+
1025
+ ### Input
1026
+ ${input}
1027
+
1028
+ ### Output
1029
+ ${output}`;
1030
+ reasoningFlow.push({
1031
+ role: 'assistant',
1032
+ content: formattedResult,
1033
+ });
1034
+ }
1035
+ });
1036
+ }
1037
+ });
1038
+ }
1039
+ // Build component schemas using reusable classes
1040
+ const componentSchemas = [];
1041
+ // Add data component schemas
1042
+ if (this.config.dataComponents && this.config.dataComponents.length > 0) {
1043
+ this.config.dataComponents.forEach((dc) => {
1044
+ const propsSchema = jsonSchemaToZod(dc.props);
1045
+ componentSchemas.push(z.object({
1046
+ id: z.string(),
1047
+ name: z.literal(dc.name),
1048
+ props: propsSchema,
1049
+ }));
1050
+ });
1051
+ }
1052
+ // Add artifact reference schema
1053
+ if (this.artifactComponents.length > 0) {
1054
+ componentSchemas.push(ArtifactReferenceSchema.getSchema());
1055
+ }
1056
+ let dataComponentsSchema;
1057
+ if (componentSchemas.length === 1) {
1058
+ dataComponentsSchema = componentSchemas[0];
1059
+ }
1060
+ else {
1061
+ dataComponentsSchema = z.union(componentSchemas);
1062
+ }
1063
+ // Phase 2: Generate structured output
1064
+ const structuredModelSettings = ModelFactory.prepareGenerationConfig(this.getStructuredOutputModel());
1065
+ const phase2TimeoutMs = structuredModelSettings.maxDuration
1066
+ ? structuredModelSettings.maxDuration * 1000
1067
+ : CONSTANTS.PHASE_2_TIMEOUT_MS;
1068
+ const structuredResponse = await generateObject({
1069
+ ...structuredModelSettings,
1070
+ messages: [
1071
+ { role: 'user', content: userMessage },
1072
+ ...reasoningFlow,
1073
+ {
1074
+ role: 'system',
1075
+ content: await this.buildPhase2SystemPrompt(),
1076
+ },
1077
+ ],
1078
+ schema: z.object({
1079
+ dataComponents: z.array(dataComponentsSchema),
1080
+ }),
1081
+ experimental_telemetry: {
1082
+ isEnabled: true,
1083
+ functionId: this.config.id,
1084
+ recordInputs: true,
1085
+ recordOutputs: true,
1086
+ metadata: {
1087
+ phase: 'structured_generation',
1088
+ },
1089
+ },
1090
+ abortSignal: AbortSignal.timeout(phase2TimeoutMs),
1091
+ });
1092
+ // Merge structured output into response
1093
+ response = {
1094
+ ...response,
1095
+ object: structuredResponse.object,
1096
+ };
1097
+ textResponse = JSON.stringify(structuredResponse.object, null, 2);
1098
+ }
1099
+ else {
1100
+ textResponse = response.text || '';
1101
+ }
1102
+ }
1103
+ else {
1104
+ textResponse = response.steps[response.steps.length - 1].text || '';
1105
+ }
1106
+ // Mark span as successful
1107
+ span.setStatus({ code: SpanStatusCode.OK });
1108
+ span.end();
1109
+ // Force flush after critical agent generation span
1110
+ await forceFlushTracer();
1111
+ // Format response - handle object vs text responses differently
1112
+ // Only format if we don't already have formattedContent from streaming
1113
+ let formattedContent = response.formattedContent || null;
1114
+ if (!formattedContent) {
1115
+ if (response.object) {
1116
+ // For object responses, replace artifact markers and convert to parts array
1117
+ formattedContent = await this.responseFormatter.formatObjectResponse(response.object, contextId);
1118
+ }
1119
+ else if (textResponse) {
1120
+ // For text responses, apply artifact marker formatting to create text/data parts
1121
+ formattedContent = await this.responseFormatter.formatResponse(textResponse, contextId);
1122
+ }
1123
+ }
1124
+ const formattedResponse = {
1125
+ ...response,
1126
+ formattedContent: formattedContent,
1127
+ };
1128
+ // Record agent generation in GraphSession
1129
+ if (streamRequestId) {
1130
+ const generationType = response.object ? 'object_generation' : 'text_generation';
1131
+ graphSessionManager.recordEvent(streamRequestId, 'agent_generate', this.config.id, {
1132
+ parts: (formattedContent?.parts || []).map((part) => ({
1133
+ type: part.kind === 'text'
1134
+ ? 'text'
1135
+ : part.kind === 'data'
1136
+ ? 'tool_result'
1137
+ : 'text',
1138
+ content: part.text || JSON.stringify(part.data),
1139
+ })),
1140
+ generationType,
1141
+ });
1142
+ }
1143
+ // Clean up session after completion
1144
+ toolSessionManager.endSession(sessionId);
1145
+ return formattedResponse;
1146
+ }
1147
+ catch (error) {
1148
+ // Clean up session on error
1149
+ toolSessionManager.endSession(sessionId);
1150
+ // Record exception and mark span as error
1151
+ span.recordException(error);
1152
+ span.setStatus({
1153
+ code: SpanStatusCode.ERROR,
1154
+ message: error.message,
1155
+ });
1156
+ span.end();
1157
+ // Force flush after error to ensure error telemetry is sent
1158
+ await forceFlushTracer();
1159
+ getLogger('Agent').error(error, 'Agent generate error');
1160
+ throw error;
1161
+ }
1162
+ });
1163
+ }
1164
+ }