@inkeep/agents-run-api 0.1.3 → 0.1.7

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