@inkeep/agents-run-api 0.39.4 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase1/system-prompt.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase1/thinking-preparation.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase1/tool.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase2/data-component.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase2/data-components.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/phase2/system-prompt.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/shared/artifact-retrieval-guidance.js +5 -0
- package/dist/_virtual/_raw_/home/runner/work/agents/agents/agents-run-api/templates/v1/shared/artifact.js +5 -0
- package/dist/a2a/client.d.ts +184 -0
- package/dist/a2a/client.js +510 -0
- package/dist/a2a/handlers.d.ts +7 -0
- package/dist/a2a/handlers.js +560 -0
- package/dist/a2a/transfer.d.ts +22 -0
- package/dist/a2a/transfer.js +46 -0
- package/dist/a2a/types.d.ts +79 -0
- package/dist/a2a/types.js +22 -0
- package/dist/agents/Agent.d.ts +266 -0
- package/dist/agents/Agent.js +1927 -0
- package/dist/agents/ModelFactory.d.ts +63 -0
- package/dist/agents/ModelFactory.js +194 -0
- package/dist/agents/SystemPromptBuilder.d.ts +21 -0
- package/dist/agents/SystemPromptBuilder.js +48 -0
- package/dist/agents/ToolSessionManager.d.ts +63 -0
- package/dist/agents/ToolSessionManager.js +146 -0
- package/dist/agents/generateTaskHandler.d.ts +49 -0
- package/dist/agents/generateTaskHandler.js +521 -0
- package/dist/agents/relationTools.d.ts +57 -0
- package/dist/agents/relationTools.js +262 -0
- package/dist/agents/types.d.ts +28 -0
- package/dist/agents/types.js +1 -0
- package/dist/agents/versions/v1/Phase1Config.d.ts +27 -0
- package/dist/agents/versions/v1/Phase1Config.js +424 -0
- package/dist/agents/versions/v1/Phase2Config.d.ts +31 -0
- package/dist/agents/versions/v1/Phase2Config.js +330 -0
- package/dist/constants/execution-limits/defaults.d.ts +51 -0
- package/dist/constants/execution-limits/defaults.js +52 -0
- package/dist/constants/execution-limits/index.d.ts +6 -0
- package/dist/constants/execution-limits/index.js +21 -0
- package/dist/create-app.d.ts +9 -0
- package/dist/create-app.js +195 -0
- package/dist/data/agent.d.ts +7 -0
- package/dist/data/agent.js +72 -0
- package/dist/data/agents.d.ts +34 -0
- package/dist/data/agents.js +139 -0
- package/dist/data/conversations.d.ts +128 -0
- package/dist/data/conversations.js +522 -0
- package/dist/data/db/dbClient.d.ts +6 -0
- package/dist/data/db/dbClient.js +17 -0
- package/dist/env.d.ts +57 -0
- package/dist/env.js +1 -2
- package/dist/handlers/executionHandler.d.ts +39 -0
- package/dist/handlers/executionHandler.js +456 -0
- package/dist/index.d.ts +8 -29
- package/dist/index.js +5 -11235
- package/dist/instrumentation.d.ts +1 -2
- package/dist/instrumentation.js +66 -3
- package/dist/{logger2.js → logger.d.ts} +1 -2
- package/dist/logger.js +1 -1
- package/dist/middleware/api-key-auth.d.ts +26 -0
- package/dist/middleware/api-key-auth.js +240 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +3 -0
- package/dist/openapi.d.ts +4 -0
- package/dist/openapi.js +54 -0
- package/dist/routes/agents.d.ts +12 -0
- package/dist/routes/agents.js +147 -0
- package/dist/routes/chat.d.ts +13 -0
- package/dist/routes/chat.js +293 -0
- package/dist/routes/chatDataStream.d.ts +13 -0
- package/dist/routes/chatDataStream.js +352 -0
- package/dist/routes/mcp.d.ts +13 -0
- package/dist/routes/mcp.js +495 -0
- package/dist/services/AgentSession.d.ts +356 -0
- package/dist/services/AgentSession.js +1208 -0
- package/dist/services/ArtifactParser.d.ts +105 -0
- package/dist/services/ArtifactParser.js +338 -0
- package/dist/services/ArtifactService.d.ts +123 -0
- package/dist/services/ArtifactService.js +612 -0
- package/dist/services/BaseCompressor.d.ts +183 -0
- package/dist/services/BaseCompressor.js +504 -0
- package/dist/services/ConversationCompressor.d.ts +32 -0
- package/dist/services/ConversationCompressor.js +91 -0
- package/dist/services/IncrementalStreamParser.d.ts +98 -0
- package/dist/services/IncrementalStreamParser.js +327 -0
- package/dist/services/MidGenerationCompressor.d.ts +63 -0
- package/dist/services/MidGenerationCompressor.js +104 -0
- package/dist/services/PendingToolApprovalManager.d.ts +62 -0
- package/dist/services/PendingToolApprovalManager.js +133 -0
- package/dist/services/ResponseFormatter.d.ts +39 -0
- package/dist/services/ResponseFormatter.js +152 -0
- package/dist/tools/NativeSandboxExecutor.d.ts +38 -0
- package/dist/tools/NativeSandboxExecutor.js +432 -0
- package/dist/tools/SandboxExecutorFactory.d.ts +36 -0
- package/dist/tools/SandboxExecutorFactory.js +80 -0
- package/dist/tools/VercelSandboxExecutor.d.ts +71 -0
- package/dist/tools/VercelSandboxExecutor.js +340 -0
- package/dist/tools/distill-conversation-history-tool.d.ts +62 -0
- package/dist/tools/distill-conversation-history-tool.js +206 -0
- package/dist/tools/distill-conversation-tool.d.ts +41 -0
- package/dist/tools/distill-conversation-tool.js +141 -0
- package/dist/tools/sandbox-utils.d.ts +18 -0
- package/dist/tools/sandbox-utils.js +53 -0
- package/dist/types/chat.d.ts +27 -0
- package/dist/types/chat.js +1 -0
- package/dist/types/execution-context.d.ts +46 -0
- package/dist/types/execution-context.js +27 -0
- package/dist/types/xml.d.ts +5 -0
- package/dist/utils/SchemaProcessor.d.ts +52 -0
- package/dist/utils/SchemaProcessor.js +182 -0
- package/dist/utils/agent-operations.d.ts +62 -0
- package/dist/utils/agent-operations.js +53 -0
- package/dist/utils/artifact-component-schema.d.ts +42 -0
- package/dist/utils/artifact-component-schema.js +186 -0
- package/dist/utils/cleanup.d.ts +21 -0
- package/dist/utils/cleanup.js +59 -0
- package/dist/utils/data-component-schema.d.ts +2 -0
- package/dist/utils/data-component-schema.js +3 -0
- package/dist/utils/default-status-schemas.d.ts +20 -0
- package/dist/utils/default-status-schemas.js +24 -0
- package/dist/utils/json-postprocessor.d.ts +13 -0
- package/dist/{json-postprocessor.cjs → utils/json-postprocessor.js} +1 -2
- package/dist/utils/model-context-utils.d.ts +39 -0
- package/dist/utils/model-context-utils.js +181 -0
- package/dist/utils/model-resolver.d.ts +6 -0
- package/dist/utils/model-resolver.js +34 -0
- package/dist/utils/schema-validation.d.ts +44 -0
- package/dist/utils/schema-validation.js +97 -0
- package/dist/utils/stream-helpers.d.ts +197 -0
- package/dist/utils/stream-helpers.js +518 -0
- package/dist/utils/stream-registry.d.ts +22 -0
- package/dist/utils/stream-registry.js +34 -0
- package/dist/utils/token-estimator.d.ts +69 -0
- package/dist/utils/token-estimator.js +53 -0
- package/dist/utils/tracer.d.ts +7 -0
- package/dist/utils/tracer.js +7 -0
- package/package.json +5 -20
- package/dist/SandboxExecutorFactory.cjs +0 -895
- package/dist/SandboxExecutorFactory.js +0 -893
- package/dist/SandboxExecutorFactory.js.map +0 -1
- package/dist/chunk-VBDAOXYI.cjs +0 -927
- package/dist/chunk-VBDAOXYI.js +0 -832
- package/dist/chunk-VBDAOXYI.js.map +0 -1
- package/dist/chunk.cjs +0 -34
- package/dist/conversations.cjs +0 -7
- package/dist/conversations.js +0 -7
- package/dist/conversations2.cjs +0 -209
- package/dist/conversations2.js +0 -180
- package/dist/conversations2.js.map +0 -1
- package/dist/dbClient.cjs +0 -9676
- package/dist/dbClient.js +0 -9670
- package/dist/dbClient.js.map +0 -1
- package/dist/dbClient2.cjs +0 -5
- package/dist/dbClient2.js +0 -5
- package/dist/env.cjs +0 -59
- package/dist/env.js.map +0 -1
- package/dist/execution-limits.cjs +0 -260
- package/dist/execution-limits.js +0 -63
- package/dist/execution-limits.js.map +0 -1
- package/dist/index.cjs +0 -11260
- package/dist/index.d.cts +0 -36
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/instrumentation.cjs +0 -12
- package/dist/instrumentation.d.cts +0 -18
- package/dist/instrumentation.d.cts.map +0 -1
- package/dist/instrumentation.d.ts.map +0 -1
- package/dist/instrumentation2.cjs +0 -116
- package/dist/instrumentation2.js +0 -69
- package/dist/instrumentation2.js.map +0 -1
- package/dist/json-postprocessor.js +0 -20
- package/dist/json-postprocessor.js.map +0 -1
- package/dist/logger.cjs +0 -5
- package/dist/logger2.cjs +0 -1
- package/dist/nodefs.cjs +0 -29
- package/dist/nodefs.js +0 -27
- package/dist/nodefs.js.map +0 -1
- package/dist/opfs-ahp.cjs +0 -367
- package/dist/opfs-ahp.js +0 -368
- package/dist/opfs-ahp.js.map +0 -1
|
@@ -0,0 +1,1927 @@
|
|
|
1
|
+
import { getLogger } from "../logger.js";
|
|
2
|
+
import dbClient_default from "../data/db/dbClient.js";
|
|
3
|
+
import { AGENT_EXECUTION_MAX_GENERATION_STEPS, FUNCTION_TOOL_EXECUTION_TIMEOUT_MS_DEFAULT, FUNCTION_TOOL_SANDBOX_VCPUS_DEFAULT, LLM_GENERATION_FIRST_CALL_TIMEOUT_MS_NON_STREAMING, LLM_GENERATION_FIRST_CALL_TIMEOUT_MS_STREAMING, LLM_GENERATION_MAX_ALLOWED_TIMEOUT_MS, LLM_GENERATION_SUBSEQUENT_CALL_TIMEOUT_MS } from "../constants/execution-limits/index.js";
|
|
4
|
+
import { toolSessionManager } from "./ToolSessionManager.js";
|
|
5
|
+
import { getCompressionConfigForModel } from "../utils/model-context-utils.js";
|
|
6
|
+
import { setSpanWithError as setSpanWithError$1, tracer } from "../utils/tracer.js";
|
|
7
|
+
import { getModelAwareCompressionConfig } from "../services/BaseCompressor.js";
|
|
8
|
+
import "../services/ConversationCompressor.js";
|
|
9
|
+
import { createDefaultConversationHistoryConfig, getConversationHistoryWithCompression } from "../data/conversations.js";
|
|
10
|
+
import { getStreamHelper } from "../utils/stream-registry.js";
|
|
11
|
+
import { agentSessionManager } from "../services/AgentSession.js";
|
|
12
|
+
import { IncrementalStreamParser } from "../services/IncrementalStreamParser.js";
|
|
13
|
+
import { MidGenerationCompressor } from "../services/MidGenerationCompressor.js";
|
|
14
|
+
import { pendingToolApprovalManager } from "../services/PendingToolApprovalManager.js";
|
|
15
|
+
import { ResponseFormatter } from "../services/ResponseFormatter.js";
|
|
16
|
+
import { generateToolId } from "../utils/agent-operations.js";
|
|
17
|
+
import { jsonSchemaToZod } from "../utils/data-component-schema.js";
|
|
18
|
+
import { ArtifactCreateSchema, ArtifactReferenceSchema } from "../utils/artifact-component-schema.js";
|
|
19
|
+
import { withJsonPostProcessing } from "../utils/json-postprocessor.js";
|
|
20
|
+
import { calculateBreakdownTotal, estimateTokens } from "../utils/token-estimator.js";
|
|
21
|
+
import { createDelegateToAgentTool, createTransferToAgentTool } from "./relationTools.js";
|
|
22
|
+
import { SystemPromptBuilder } from "./SystemPromptBuilder.js";
|
|
23
|
+
import { Phase1Config } from "./versions/v1/Phase1Config.js";
|
|
24
|
+
import { Phase2Config } from "./versions/v1/Phase2Config.js";
|
|
25
|
+
import { z } from "@hono/zod-openapi";
|
|
26
|
+
import { ContextResolver, CredentialStuffer, MCPServerType, MCPTransportType, McpClient, ModelFactory, TemplateEngine, agentHasArtifactComponents, createMessage, generateId, getContextConfigById, getCredentialReference, getFullAgentDefinition, getFunction, getFunctionToolsForSubAgent, getLedgerArtifacts, getToolsForAgent, getUserScopedCredentialReference, listTaskIdsByContextId, parseEmbeddedJson } from "@inkeep/agents-core";
|
|
27
|
+
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
28
|
+
import { generateObject, generateText, streamObject, streamText, tool } from "ai";
|
|
29
|
+
|
|
30
|
+
//#region src/agents/Agent.ts
|
|
31
|
+
/**
|
|
32
|
+
* Creates a stopWhen condition that stops when any tool call name starts with the given prefix
|
|
33
|
+
* @param prefix - The prefix to check for in tool call names
|
|
34
|
+
* @returns A function that can be used as a stopWhen condition
|
|
35
|
+
*/
|
|
36
|
+
function hasToolCallWithPrefix(prefix) {
|
|
37
|
+
return ({ steps }) => {
|
|
38
|
+
const last = steps.at(-1);
|
|
39
|
+
if (last && "toolCalls" in last && last.toolCalls) return last.toolCalls.some((tc) => tc.toolName.startsWith(prefix));
|
|
40
|
+
return false;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const logger = getLogger("Agent");
|
|
44
|
+
function validateModel(modelString, modelType) {
|
|
45
|
+
if (!modelString?.trim()) throw new Error(`${modelType} model is required. Please configure models at the project level.`);
|
|
46
|
+
return modelString.trim();
|
|
47
|
+
}
|
|
48
|
+
function isValidTool(tool$1) {
|
|
49
|
+
return tool$1 && typeof tool$1 === "object" && typeof tool$1.description === "string" && tool$1.inputSchema && typeof tool$1.execute === "function";
|
|
50
|
+
}
|
|
51
|
+
var Agent = class {
|
|
52
|
+
config;
|
|
53
|
+
systemPromptBuilder = new SystemPromptBuilder("v1", new Phase1Config());
|
|
54
|
+
credentialStuffer;
|
|
55
|
+
streamHelper;
|
|
56
|
+
streamRequestId;
|
|
57
|
+
conversationId;
|
|
58
|
+
delegationId;
|
|
59
|
+
artifactComponents = [];
|
|
60
|
+
isDelegatedAgent = false;
|
|
61
|
+
contextResolver;
|
|
62
|
+
credentialStoreRegistry;
|
|
63
|
+
mcpClientCache = /* @__PURE__ */ new Map();
|
|
64
|
+
mcpConnectionLocks = /* @__PURE__ */ new Map();
|
|
65
|
+
currentCompressor = null;
|
|
66
|
+
constructor(config, credentialStoreRegistry) {
|
|
67
|
+
this.artifactComponents = config.artifactComponents || [];
|
|
68
|
+
let processedDataComponents = config.dataComponents || [];
|
|
69
|
+
if (processedDataComponents.length > 0) processedDataComponents.push({
|
|
70
|
+
id: "text-content",
|
|
71
|
+
name: "Text",
|
|
72
|
+
description: "Natural conversational text for the user - write naturally without mentioning technical details. Avoid redundancy and repetition with data components.",
|
|
73
|
+
props: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: { text: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "Natural conversational text - respond as if having a normal conversation, never mention JSON, components, schemas, or technical implementation. Avoid redundancy and repetition with data components."
|
|
78
|
+
} },
|
|
79
|
+
required: ["text"]
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
if (this.artifactComponents.length > 0 && config.dataComponents && config.dataComponents.length > 0) processedDataComponents = [ArtifactReferenceSchema.getDataComponent(config.tenantId, config.projectId), ...processedDataComponents];
|
|
83
|
+
this.config = {
|
|
84
|
+
...config,
|
|
85
|
+
dataComponents: processedDataComponents,
|
|
86
|
+
conversationHistoryConfig: config.conversationHistoryConfig || createDefaultConversationHistoryConfig()
|
|
87
|
+
};
|
|
88
|
+
this.credentialStoreRegistry = credentialStoreRegistry;
|
|
89
|
+
if (credentialStoreRegistry) {
|
|
90
|
+
this.contextResolver = new ContextResolver(config.tenantId, config.projectId, dbClient_default, 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 AGENT_EXECUTION_MAX_GENERATION_STEPS
|
|
97
|
+
*/
|
|
98
|
+
getMaxGenerationSteps() {
|
|
99
|
+
return this.config.stopWhen?.stepCountIs ?? AGENT_EXECUTION_MAX_GENERATION_STEPS;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Sanitizes tool names at runtime for AI SDK compatibility.
|
|
103
|
+
* The AI SDK requires tool names to match pattern ^[a-zA-Z0-9_-]{1,128}$
|
|
104
|
+
*/
|
|
105
|
+
sanitizeToolsForAISDK(tools) {
|
|
106
|
+
const sanitizedTools = {};
|
|
107
|
+
for (const [originalKey, toolDef] of Object.entries(tools)) {
|
|
108
|
+
let sanitizedKey = originalKey.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
109
|
+
sanitizedKey = sanitizedKey.replace(/_+/g, "_");
|
|
110
|
+
sanitizedKey = sanitizedKey.replace(/^_+|_+$/g, "");
|
|
111
|
+
if (!sanitizedKey || sanitizedKey.length === 0) sanitizedKey = "unnamed_tool";
|
|
112
|
+
if (sanitizedKey.length > 100) sanitizedKey = sanitizedKey.substring(0, 100);
|
|
113
|
+
let sanitizedId = (toolDef.id || originalKey).replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
114
|
+
sanitizedId = sanitizedId.replace(/_+/g, "_");
|
|
115
|
+
sanitizedId = sanitizedId.replace(/^_+|_+$/g, "");
|
|
116
|
+
if (sanitizedId.length > 128) sanitizedId = sanitizedId.substring(0, 128);
|
|
117
|
+
sanitizedTools[sanitizedKey] = {
|
|
118
|
+
...toolDef,
|
|
119
|
+
id: sanitizedId
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return sanitizedTools;
|
|
123
|
+
}
|
|
124
|
+
#createRelationToolName(prefix, targetId) {
|
|
125
|
+
return `${prefix}_to_${targetId.toLowerCase().replace(/\s+/g, "_")}`;
|
|
126
|
+
}
|
|
127
|
+
#getRelationshipIdForTool(toolName, toolType) {
|
|
128
|
+
if (toolType === "mcp") return (this.config.tools?.find((tool$1) => {
|
|
129
|
+
if (tool$1.config?.type !== "mcp") return false;
|
|
130
|
+
if (tool$1.availableTools?.some((available) => available.name === toolName)) return true;
|
|
131
|
+
if (tool$1.config.mcp.activeTools?.includes(toolName)) return true;
|
|
132
|
+
return tool$1.name === toolName;
|
|
133
|
+
}))?.relationshipId;
|
|
134
|
+
if (toolType === "delegation") return this.config.delegateRelations.find((relation) => this.#createRelationToolName("delegate", relation.config.id) === toolName)?.config.relationId;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get the primary model settings for text generation and thinking
|
|
138
|
+
* Requires model to be configured at project level
|
|
139
|
+
*/
|
|
140
|
+
getPrimaryModel() {
|
|
141
|
+
if (!this.config.models?.base) throw new Error("Base model configuration is required. Please configure models at the project level.");
|
|
142
|
+
return {
|
|
143
|
+
model: validateModel(this.config.models.base.model, "Base"),
|
|
144
|
+
providerOptions: this.config.models.base.providerOptions
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get the model settings for structured output generation
|
|
149
|
+
* Falls back to base model if structured output not configured
|
|
150
|
+
*/
|
|
151
|
+
getStructuredOutputModel() {
|
|
152
|
+
if (!this.config.models) throw new Error("Model configuration is required. Please configure models at the project level.");
|
|
153
|
+
const structuredConfig = this.config.models.structuredOutput;
|
|
154
|
+
const baseConfig = this.config.models.base;
|
|
155
|
+
if (structuredConfig) return {
|
|
156
|
+
model: validateModel(structuredConfig.model, "Structured output"),
|
|
157
|
+
providerOptions: structuredConfig.providerOptions
|
|
158
|
+
};
|
|
159
|
+
if (!baseConfig) throw new Error("Base model configuration is required for structured output fallback. Please configure models at the project level.");
|
|
160
|
+
return {
|
|
161
|
+
model: validateModel(baseConfig.model, "Base (fallback for structured output)"),
|
|
162
|
+
providerOptions: baseConfig.providerOptions
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get the model settings for summarization/distillation
|
|
167
|
+
* Falls back to base model if summarizer not configured
|
|
168
|
+
*/
|
|
169
|
+
getSummarizerModel() {
|
|
170
|
+
if (!this.config.models) throw new Error("Model configuration is required. Please configure models at the project level.");
|
|
171
|
+
const summarizerConfig = this.config.models.summarizer;
|
|
172
|
+
const baseConfig = this.config.models.base;
|
|
173
|
+
if (summarizerConfig) return {
|
|
174
|
+
model: validateModel(summarizerConfig.model, "Summarizer"),
|
|
175
|
+
providerOptions: summarizerConfig.providerOptions
|
|
176
|
+
};
|
|
177
|
+
if (!baseConfig) throw new Error("Base model configuration is required for summarizer fallback. Please configure models at the project level.");
|
|
178
|
+
return {
|
|
179
|
+
model: validateModel(baseConfig.model, "Base (fallback for summarizer)"),
|
|
180
|
+
providerOptions: baseConfig.providerOptions
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
setConversationId(conversationId) {
|
|
184
|
+
this.conversationId = conversationId;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Simple compression fallback: drop oldest messages to fit under token limit
|
|
188
|
+
*/
|
|
189
|
+
/**
|
|
190
|
+
* Set delegation status for this agent instance
|
|
191
|
+
*/
|
|
192
|
+
setDelegationStatus(isDelegated) {
|
|
193
|
+
this.isDelegatedAgent = isDelegated;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Set delegation ID for this agent instance
|
|
197
|
+
*/
|
|
198
|
+
setDelegationId(delegationId) {
|
|
199
|
+
this.delegationId = delegationId;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get streaming helper if this agent should stream to user
|
|
203
|
+
* Returns undefined for delegated agents to prevent streaming data operations to user
|
|
204
|
+
*/
|
|
205
|
+
getStreamingHelper() {
|
|
206
|
+
return this.isDelegatedAgent ? void 0 : this.streamHelper;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Wraps a tool with streaming lifecycle tracking (start, complete, error) and AgentSession recording
|
|
210
|
+
*/
|
|
211
|
+
wrapToolWithStreaming(toolName, toolDefinition, streamRequestId, toolType, options) {
|
|
212
|
+
if (!toolDefinition || typeof toolDefinition !== "object" || !("execute" in toolDefinition)) return toolDefinition;
|
|
213
|
+
const relationshipId = this.#getRelationshipIdForTool(toolName, toolType);
|
|
214
|
+
const originalExecute = toolDefinition.execute;
|
|
215
|
+
return {
|
|
216
|
+
...toolDefinition,
|
|
217
|
+
execute: async (args, context$1) => {
|
|
218
|
+
const startTime = Date.now();
|
|
219
|
+
const toolCallId = context$1?.toolCallId || generateToolId();
|
|
220
|
+
const activeSpan = trace.getActiveSpan();
|
|
221
|
+
if (activeSpan) {
|
|
222
|
+
const attributes = {
|
|
223
|
+
"conversation.id": this.conversationId,
|
|
224
|
+
"tool.purpose": toolDefinition.description || "No description provided",
|
|
225
|
+
"ai.toolType": toolType || "unknown",
|
|
226
|
+
"subAgent.name": this.config.name || "unknown",
|
|
227
|
+
"subAgent.id": this.config.id || "unknown",
|
|
228
|
+
"agent.id": this.config.agentId || "unknown"
|
|
229
|
+
};
|
|
230
|
+
if (options?.mcpServerId) attributes["ai.toolCall.mcpServerId"] = options.mcpServerId;
|
|
231
|
+
if (options?.mcpServerName) attributes["ai.toolCall.mcpServerName"] = options.mcpServerName;
|
|
232
|
+
activeSpan.setAttributes(attributes);
|
|
233
|
+
}
|
|
234
|
+
const isInternalTool = toolName.includes("save_tool_result") || toolName.includes("thinking_complete") || toolName.startsWith("transfer_to_");
|
|
235
|
+
const needsApproval = options?.needsApproval || false;
|
|
236
|
+
if (streamRequestId && !isInternalTool) {
|
|
237
|
+
const toolCallData = {
|
|
238
|
+
toolName,
|
|
239
|
+
input: args,
|
|
240
|
+
toolCallId,
|
|
241
|
+
relationshipId
|
|
242
|
+
};
|
|
243
|
+
if (needsApproval) {
|
|
244
|
+
toolCallData.needsApproval = true;
|
|
245
|
+
toolCallData.conversationId = this.conversationId;
|
|
246
|
+
}
|
|
247
|
+
await agentSessionManager.recordEvent(streamRequestId, "tool_call", this.config.id, toolCallData);
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const result = await originalExecute(args, context$1);
|
|
251
|
+
const duration = Date.now() - startTime;
|
|
252
|
+
const toolResultConversationId = this.getToolResultConversationId();
|
|
253
|
+
if (streamRequestId && !isInternalTool && toolResultConversationId) try {
|
|
254
|
+
const messagePayload = {
|
|
255
|
+
id: generateId(),
|
|
256
|
+
tenantId: this.config.tenantId,
|
|
257
|
+
projectId: this.config.projectId,
|
|
258
|
+
conversationId: toolResultConversationId,
|
|
259
|
+
role: "assistant",
|
|
260
|
+
content: { text: this.formatToolResult(toolName, args, result, toolCallId) },
|
|
261
|
+
visibility: "internal",
|
|
262
|
+
messageType: "tool-result",
|
|
263
|
+
fromSubAgentId: this.config.id,
|
|
264
|
+
metadata: { a2a_metadata: {
|
|
265
|
+
toolName,
|
|
266
|
+
toolCallId,
|
|
267
|
+
timestamp: Date.now(),
|
|
268
|
+
delegationId: this.delegationId,
|
|
269
|
+
isDelegated: this.isDelegatedAgent
|
|
270
|
+
} }
|
|
271
|
+
};
|
|
272
|
+
await createMessage(dbClient_default)(messagePayload);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
logger.warn({
|
|
275
|
+
error,
|
|
276
|
+
toolName,
|
|
277
|
+
toolCallId,
|
|
278
|
+
conversationId: toolResultConversationId
|
|
279
|
+
}, "Failed to store tool result in conversation history");
|
|
280
|
+
}
|
|
281
|
+
if (streamRequestId && !isInternalTool) agentSessionManager.recordEvent(streamRequestId, "tool_result", this.config.id, {
|
|
282
|
+
toolName,
|
|
283
|
+
output: result,
|
|
284
|
+
toolCallId,
|
|
285
|
+
duration,
|
|
286
|
+
relationshipId,
|
|
287
|
+
needsApproval
|
|
288
|
+
});
|
|
289
|
+
return result;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
const duration = Date.now() - startTime;
|
|
292
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
293
|
+
if (streamRequestId && !isInternalTool) agentSessionManager.recordEvent(streamRequestId, "tool_result", this.config.id, {
|
|
294
|
+
toolName,
|
|
295
|
+
output: null,
|
|
296
|
+
toolCallId,
|
|
297
|
+
duration,
|
|
298
|
+
error: errorMessage,
|
|
299
|
+
relationshipId,
|
|
300
|
+
needsApproval
|
|
301
|
+
});
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
getRelationTools(runtimeContext, sessionId) {
|
|
308
|
+
const { transferRelations = [], delegateRelations = [] } = this.config;
|
|
309
|
+
return Object.fromEntries([...transferRelations.map((agentConfig) => {
|
|
310
|
+
const toolName = this.#createRelationToolName("transfer", agentConfig.id);
|
|
311
|
+
return [toolName, this.wrapToolWithStreaming(toolName, createTransferToAgentTool({
|
|
312
|
+
transferConfig: agentConfig,
|
|
313
|
+
callingAgentId: this.config.id,
|
|
314
|
+
subAgent: this,
|
|
315
|
+
streamRequestId: runtimeContext?.metadata?.streamRequestId
|
|
316
|
+
}), runtimeContext?.metadata?.streamRequestId, "transfer")];
|
|
317
|
+
}), ...delegateRelations.map((relation) => {
|
|
318
|
+
const toolName = this.#createRelationToolName("delegate", relation.config.id);
|
|
319
|
+
return [toolName, this.wrapToolWithStreaming(toolName, createDelegateToAgentTool({
|
|
320
|
+
delegateConfig: relation,
|
|
321
|
+
callingAgentId: this.config.id,
|
|
322
|
+
tenantId: this.config.tenantId,
|
|
323
|
+
projectId: this.config.projectId,
|
|
324
|
+
agentId: this.config.agentId,
|
|
325
|
+
contextId: runtimeContext?.contextId || "default",
|
|
326
|
+
metadata: runtimeContext?.metadata || {
|
|
327
|
+
conversationId: runtimeContext?.contextId || "default",
|
|
328
|
+
threadId: runtimeContext?.contextId || "default",
|
|
329
|
+
streamRequestId: runtimeContext?.metadata?.streamRequestId,
|
|
330
|
+
apiKey: runtimeContext?.metadata?.apiKey
|
|
331
|
+
},
|
|
332
|
+
sessionId,
|
|
333
|
+
subAgent: this,
|
|
334
|
+
credentialStoreRegistry: this.credentialStoreRegistry
|
|
335
|
+
}), runtimeContext?.metadata?.streamRequestId, "delegation")];
|
|
336
|
+
})]);
|
|
337
|
+
}
|
|
338
|
+
async getMcpTools(sessionId, streamRequestId) {
|
|
339
|
+
const mcpTools = this.config.tools?.filter((tool$1) => {
|
|
340
|
+
return tool$1.config?.type === "mcp";
|
|
341
|
+
}) || [];
|
|
342
|
+
const tools = await Promise.all(mcpTools.map((tool$1) => this.getMcpTool(tool$1)) || []) || [];
|
|
343
|
+
if (!sessionId) {
|
|
344
|
+
const wrappedTools$1 = {};
|
|
345
|
+
for (const toolSet of tools) for (const [toolName, toolDef] of Object.entries(toolSet.tools)) {
|
|
346
|
+
const needsApproval = toolSet.toolPolicies?.[toolName]?.needsApproval || false;
|
|
347
|
+
const enhancedTool = {
|
|
348
|
+
...toolDef,
|
|
349
|
+
needsApproval
|
|
350
|
+
};
|
|
351
|
+
wrappedTools$1[toolName] = this.wrapToolWithStreaming(toolName, enhancedTool, streamRequestId, "mcp", {
|
|
352
|
+
needsApproval,
|
|
353
|
+
mcpServerId: toolSet.mcpServerId,
|
|
354
|
+
mcpServerName: toolSet.mcpServerName
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
return wrappedTools$1;
|
|
358
|
+
}
|
|
359
|
+
const wrappedTools = {};
|
|
360
|
+
for (const toolResult of tools) for (const [toolName, originalTool] of Object.entries(toolResult.tools)) {
|
|
361
|
+
if (!isValidTool(originalTool)) {
|
|
362
|
+
logger.error({ toolName }, "Invalid MCP tool structure - missing required properties");
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
const needsApproval = toolResult.toolPolicies?.[toolName]?.needsApproval || false;
|
|
366
|
+
logger.debug({
|
|
367
|
+
toolName,
|
|
368
|
+
toolPolicies: toolResult.toolPolicies,
|
|
369
|
+
needsApproval,
|
|
370
|
+
policyForThisTool: toolResult.toolPolicies?.[toolName]
|
|
371
|
+
}, "Tool approval check");
|
|
372
|
+
const sessionWrappedTool = tool({
|
|
373
|
+
description: originalTool.description,
|
|
374
|
+
inputSchema: originalTool.inputSchema,
|
|
375
|
+
execute: async (args, { toolCallId }) => {
|
|
376
|
+
let processedArgs;
|
|
377
|
+
try {
|
|
378
|
+
processedArgs = parseEmbeddedJson(args);
|
|
379
|
+
if (JSON.stringify(args) !== JSON.stringify(processedArgs)) logger.warn({
|
|
380
|
+
toolName,
|
|
381
|
+
toolCallId
|
|
382
|
+
}, "Fixed stringified JSON parameters (indicates schema ambiguity)");
|
|
383
|
+
} catch (error) {
|
|
384
|
+
logger.warn({
|
|
385
|
+
toolName,
|
|
386
|
+
toolCallId,
|
|
387
|
+
error: error.message
|
|
388
|
+
}, "Failed to parse embedded JSON, using original args");
|
|
389
|
+
processedArgs = args;
|
|
390
|
+
}
|
|
391
|
+
const finalArgs = processedArgs;
|
|
392
|
+
if (needsApproval) {
|
|
393
|
+
logger.info({
|
|
394
|
+
toolName,
|
|
395
|
+
toolCallId,
|
|
396
|
+
args: finalArgs
|
|
397
|
+
}, "Tool requires approval - waiting for user response");
|
|
398
|
+
const currentSpan = trace.getActiveSpan();
|
|
399
|
+
if (currentSpan) currentSpan.addEvent("tool.approval.requested", {
|
|
400
|
+
"tool.name": toolName,
|
|
401
|
+
"tool.callId": toolCallId,
|
|
402
|
+
"subAgent.id": this.config.id
|
|
403
|
+
});
|
|
404
|
+
tracer.startActiveSpan("tool.approval_requested", { attributes: {
|
|
405
|
+
"tool.name": toolName,
|
|
406
|
+
"tool.callId": toolCallId,
|
|
407
|
+
"subAgent.id": this.config.id,
|
|
408
|
+
"subAgent.name": this.config.name
|
|
409
|
+
} }, (requestSpan) => {
|
|
410
|
+
requestSpan.setStatus({ code: SpanStatusCode.OK });
|
|
411
|
+
requestSpan.end();
|
|
412
|
+
});
|
|
413
|
+
const approvalResult = await pendingToolApprovalManager.waitForApproval(toolCallId, toolName, args, this.conversationId || "unknown", this.config.id);
|
|
414
|
+
if (!approvalResult.approved) return tracer.startActiveSpan("tool.approval_denied", { attributes: {
|
|
415
|
+
"tool.name": toolName,
|
|
416
|
+
"tool.callId": toolCallId,
|
|
417
|
+
"subAgent.id": this.config.id,
|
|
418
|
+
"subAgent.name": this.config.name
|
|
419
|
+
} }, (denialSpan) => {
|
|
420
|
+
logger.info({
|
|
421
|
+
toolName,
|
|
422
|
+
toolCallId,
|
|
423
|
+
reason: approvalResult.reason
|
|
424
|
+
}, "Tool execution denied by user");
|
|
425
|
+
denialSpan.setStatus({ code: SpanStatusCode.OK });
|
|
426
|
+
denialSpan.end();
|
|
427
|
+
return `User denied approval to run this tool: ${approvalResult.reason}`;
|
|
428
|
+
});
|
|
429
|
+
tracer.startActiveSpan("tool.approval_approved", { attributes: {
|
|
430
|
+
"tool.name": toolName,
|
|
431
|
+
"tool.callId": toolCallId,
|
|
432
|
+
"subAgent.id": this.config.id,
|
|
433
|
+
"subAgent.name": this.config.name
|
|
434
|
+
} }, (approvedSpan) => {
|
|
435
|
+
logger.info({
|
|
436
|
+
toolName,
|
|
437
|
+
toolCallId
|
|
438
|
+
}, "Tool approved, continuing with execution");
|
|
439
|
+
approvedSpan.setStatus({ code: SpanStatusCode.OK });
|
|
440
|
+
approvedSpan.end();
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
logger.debug({
|
|
444
|
+
toolName,
|
|
445
|
+
toolCallId
|
|
446
|
+
}, "MCP Tool Called");
|
|
447
|
+
try {
|
|
448
|
+
const rawResult = await originalTool.execute(finalArgs, { toolCallId });
|
|
449
|
+
if (rawResult && typeof rawResult === "object" && rawResult.isError) {
|
|
450
|
+
const errorMessage = rawResult.content?.[0]?.text || "MCP tool returned an error";
|
|
451
|
+
logger.error({
|
|
452
|
+
toolName,
|
|
453
|
+
toolCallId,
|
|
454
|
+
errorMessage,
|
|
455
|
+
rawResult
|
|
456
|
+
}, "MCP tool returned error status");
|
|
457
|
+
toolSessionManager.recordToolResult(sessionId, {
|
|
458
|
+
toolCallId,
|
|
459
|
+
toolName,
|
|
460
|
+
args: finalArgs,
|
|
461
|
+
result: {
|
|
462
|
+
error: errorMessage,
|
|
463
|
+
failed: true
|
|
464
|
+
},
|
|
465
|
+
timestamp: Date.now()
|
|
466
|
+
});
|
|
467
|
+
if (streamRequestId) {
|
|
468
|
+
const relationshipId = this.#getRelationshipIdForTool(toolName, "mcp");
|
|
469
|
+
agentSessionManager.recordEvent(streamRequestId, "error", this.config.id, {
|
|
470
|
+
message: `MCP tool "${toolName}" failed: ${errorMessage}`,
|
|
471
|
+
code: "mcp_tool_error",
|
|
472
|
+
severity: "error",
|
|
473
|
+
context: {
|
|
474
|
+
toolName,
|
|
475
|
+
toolCallId,
|
|
476
|
+
errorMessage,
|
|
477
|
+
relationshipId
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
const activeSpan = trace.getActiveSpan();
|
|
482
|
+
if (activeSpan) {
|
|
483
|
+
const error = /* @__PURE__ */ new Error(`Tool "${toolName}" failed: ${errorMessage}. This tool is currently unavailable. Please try a different approach or inform the user of the issue.`);
|
|
484
|
+
activeSpan.recordException(error);
|
|
485
|
+
activeSpan.setStatus({
|
|
486
|
+
code: SpanStatusCode.ERROR,
|
|
487
|
+
message: `MCP tool returned error: ${errorMessage}`
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
throw new Error(`Tool "${toolName}" failed: ${errorMessage}. This tool is currently unavailable. Please try a different approach or inform the user of the issue.`);
|
|
491
|
+
}
|
|
492
|
+
const parsedResult = parseEmbeddedJson(rawResult);
|
|
493
|
+
const enhancedResult = this.enhanceToolResultWithStructureHints(parsedResult);
|
|
494
|
+
toolSessionManager.recordToolResult(sessionId, {
|
|
495
|
+
toolCallId,
|
|
496
|
+
toolName,
|
|
497
|
+
args: finalArgs,
|
|
498
|
+
result: enhancedResult,
|
|
499
|
+
timestamp: Date.now()
|
|
500
|
+
});
|
|
501
|
+
return {
|
|
502
|
+
result: enhancedResult,
|
|
503
|
+
toolCallId
|
|
504
|
+
};
|
|
505
|
+
} catch (error) {
|
|
506
|
+
logger.error({
|
|
507
|
+
toolName,
|
|
508
|
+
toolCallId,
|
|
509
|
+
error
|
|
510
|
+
}, "MCP tool execution failed");
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
wrappedTools[toolName] = this.wrapToolWithStreaming(toolName, sessionWrappedTool, streamRequestId, "mcp", {
|
|
516
|
+
needsApproval,
|
|
517
|
+
mcpServerId: toolResult.mcpServerId,
|
|
518
|
+
mcpServerName: toolResult.mcpServerName
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
return wrappedTools;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Convert database McpTool to builder MCPToolConfig format
|
|
525
|
+
*/
|
|
526
|
+
convertToMCPToolConfig(tool$1, agentToolRelationHeaders) {
|
|
527
|
+
if (tool$1.config.type !== "mcp") throw new Error(`Cannot convert non-MCP tool to MCP config: ${tool$1.id}`);
|
|
528
|
+
return {
|
|
529
|
+
id: tool$1.id,
|
|
530
|
+
name: tool$1.name,
|
|
531
|
+
description: tool$1.name,
|
|
532
|
+
serverUrl: tool$1.config.mcp.server.url,
|
|
533
|
+
activeTools: tool$1.config.mcp.activeTools,
|
|
534
|
+
mcpType: tool$1.config.mcp.server.url.includes("api.nango.dev") ? MCPServerType.nango : MCPServerType.generic,
|
|
535
|
+
transport: tool$1.config.mcp.transport,
|
|
536
|
+
headers: {
|
|
537
|
+
...tool$1.headers,
|
|
538
|
+
...agentToolRelationHeaders
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
async getMcpTool(tool$1) {
|
|
543
|
+
const cacheKey = `${this.config.tenantId}-${this.config.projectId}-${tool$1.id}-${tool$1.credentialReferenceId || "no-cred"}`;
|
|
544
|
+
const credentialReferenceId = tool$1.credentialReferenceId;
|
|
545
|
+
const toolRelation = (await getToolsForAgent(dbClient_default)({ scopes: {
|
|
546
|
+
tenantId: this.config.tenantId,
|
|
547
|
+
projectId: this.config.projectId,
|
|
548
|
+
agentId: this.config.agentId,
|
|
549
|
+
subAgentId: this.config.id
|
|
550
|
+
} })).data.find((t) => t.toolId === tool$1.id);
|
|
551
|
+
const agentToolRelationHeaders = toolRelation?.headers || void 0;
|
|
552
|
+
const selectedTools = toolRelation?.selectedTools || void 0;
|
|
553
|
+
const toolPolicies = toolRelation?.toolPolicies || {};
|
|
554
|
+
let serverConfig;
|
|
555
|
+
const isUserScoped = tool$1.credentialScope === "user";
|
|
556
|
+
const userId = this.config.userId;
|
|
557
|
+
if (isUserScoped && userId && this.credentialStuffer) {
|
|
558
|
+
const userCredentialReference = await getUserScopedCredentialReference(dbClient_default)({
|
|
559
|
+
scopes: {
|
|
560
|
+
tenantId: this.config.tenantId,
|
|
561
|
+
projectId: this.config.projectId
|
|
562
|
+
},
|
|
563
|
+
toolId: tool$1.id,
|
|
564
|
+
userId
|
|
565
|
+
});
|
|
566
|
+
if (userCredentialReference) {
|
|
567
|
+
const storeReference = {
|
|
568
|
+
credentialStoreId: userCredentialReference.credentialStoreId,
|
|
569
|
+
retrievalParams: userCredentialReference.retrievalParams || {}
|
|
570
|
+
};
|
|
571
|
+
serverConfig = await this.credentialStuffer.buildMcpServerConfig({
|
|
572
|
+
tenantId: this.config.tenantId,
|
|
573
|
+
projectId: this.config.projectId,
|
|
574
|
+
contextConfigId: this.config.contextConfigId || void 0,
|
|
575
|
+
conversationId: this.conversationId || void 0
|
|
576
|
+
}, this.convertToMCPToolConfig(tool$1, agentToolRelationHeaders), storeReference, selectedTools);
|
|
577
|
+
} else {
|
|
578
|
+
logger.warn({
|
|
579
|
+
toolId: tool$1.id,
|
|
580
|
+
userId
|
|
581
|
+
}, "User-scoped tool has no credential connected for this user");
|
|
582
|
+
serverConfig = await this.credentialStuffer.buildMcpServerConfig({
|
|
583
|
+
tenantId: this.config.tenantId,
|
|
584
|
+
projectId: this.config.projectId,
|
|
585
|
+
contextConfigId: this.config.contextConfigId || void 0,
|
|
586
|
+
conversationId: this.conversationId || void 0
|
|
587
|
+
}, this.convertToMCPToolConfig(tool$1, agentToolRelationHeaders), void 0, selectedTools);
|
|
588
|
+
}
|
|
589
|
+
} else if (credentialReferenceId && this.credentialStuffer) {
|
|
590
|
+
const credentialReference = await getCredentialReference(dbClient_default)({
|
|
591
|
+
scopes: {
|
|
592
|
+
tenantId: this.config.tenantId,
|
|
593
|
+
projectId: this.config.projectId
|
|
594
|
+
},
|
|
595
|
+
id: credentialReferenceId
|
|
596
|
+
});
|
|
597
|
+
if (!credentialReference) throw new Error(`Credential store not found: ${credentialReferenceId}`);
|
|
598
|
+
const storeReference = {
|
|
599
|
+
credentialStoreId: credentialReference.credentialStoreId,
|
|
600
|
+
retrievalParams: credentialReference.retrievalParams || {}
|
|
601
|
+
};
|
|
602
|
+
serverConfig = await this.credentialStuffer.buildMcpServerConfig({
|
|
603
|
+
tenantId: this.config.tenantId,
|
|
604
|
+
projectId: this.config.projectId,
|
|
605
|
+
contextConfigId: this.config.contextConfigId || void 0,
|
|
606
|
+
conversationId: this.conversationId || void 0
|
|
607
|
+
}, this.convertToMCPToolConfig(tool$1, agentToolRelationHeaders), storeReference, selectedTools);
|
|
608
|
+
} else if (this.credentialStuffer) serverConfig = await this.credentialStuffer.buildMcpServerConfig({
|
|
609
|
+
tenantId: this.config.tenantId,
|
|
610
|
+
projectId: this.config.projectId,
|
|
611
|
+
contextConfigId: this.config.contextConfigId || void 0,
|
|
612
|
+
conversationId: this.conversationId || void 0
|
|
613
|
+
}, this.convertToMCPToolConfig(tool$1, agentToolRelationHeaders), void 0, selectedTools);
|
|
614
|
+
else {
|
|
615
|
+
if (tool$1.config.type !== "mcp") throw new Error(`Cannot build server config for non-MCP tool: ${tool$1.id}`);
|
|
616
|
+
serverConfig = {
|
|
617
|
+
type: tool$1.config.mcp.transport?.type || MCPTransportType.streamableHttp,
|
|
618
|
+
url: tool$1.config.mcp.server.url,
|
|
619
|
+
activeTools: tool$1.config.mcp.activeTools,
|
|
620
|
+
selectedTools,
|
|
621
|
+
headers: agentToolRelationHeaders
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
if (serverConfig.url?.toString().includes("composio.dev")) {
|
|
625
|
+
const urlObj = new URL(serverConfig.url.toString());
|
|
626
|
+
if (isUserScoped && userId) urlObj.searchParams.set("user_id", userId);
|
|
627
|
+
else urlObj.searchParams.set("user_id", `${this.config.tenantId}||${this.config.projectId}`);
|
|
628
|
+
serverConfig.url = urlObj.toString();
|
|
629
|
+
}
|
|
630
|
+
logger.info({
|
|
631
|
+
toolName: tool$1.name,
|
|
632
|
+
credentialReferenceId,
|
|
633
|
+
transportType: serverConfig.type,
|
|
634
|
+
headers: tool$1.headers
|
|
635
|
+
}, "Built MCP server config with credentials");
|
|
636
|
+
let client = this.mcpClientCache.get(cacheKey);
|
|
637
|
+
if (client && !client.isConnected()) {
|
|
638
|
+
this.mcpClientCache.delete(cacheKey);
|
|
639
|
+
client = void 0;
|
|
640
|
+
}
|
|
641
|
+
if (!client) {
|
|
642
|
+
let connectionPromise = this.mcpConnectionLocks.get(cacheKey);
|
|
643
|
+
if (!connectionPromise) {
|
|
644
|
+
connectionPromise = this.createMcpConnection(tool$1, serverConfig);
|
|
645
|
+
this.mcpConnectionLocks.set(cacheKey, connectionPromise);
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
client = await connectionPromise;
|
|
649
|
+
this.mcpClientCache.set(cacheKey, client);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
this.mcpConnectionLocks.delete(cacheKey);
|
|
652
|
+
logger.error({
|
|
653
|
+
toolName: tool$1.name,
|
|
654
|
+
subAgentId: this.config.id,
|
|
655
|
+
cacheKey,
|
|
656
|
+
error: error instanceof Error ? error.message : String(error)
|
|
657
|
+
}, "MCP connection failed");
|
|
658
|
+
throw error;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
const tools = await client.tools();
|
|
662
|
+
if (!tools || Object.keys(tools).length === 0) {
|
|
663
|
+
const streamRequestId = this.getStreamRequestId();
|
|
664
|
+
if (streamRequestId) tracer.startActiveSpan("ai.toolCall", { attributes: {
|
|
665
|
+
"ai.toolCall.name": tool$1.name,
|
|
666
|
+
"ai.toolCall.args": JSON.stringify({ operation: "mcp_tool_discovery" }),
|
|
667
|
+
"ai.toolCall.result": JSON.stringify({
|
|
668
|
+
status: "no_tools_available",
|
|
669
|
+
message: `MCP server has 0 effective tools. Double check the selected tools in your agent and the active tools in the MCP server configuration.`,
|
|
670
|
+
serverUrl: tool$1.config.type === "mcp" ? tool$1.config.mcp.server.url : "unknown",
|
|
671
|
+
originalToolName: tool$1.name
|
|
672
|
+
}),
|
|
673
|
+
"ai.toolType": "mcp",
|
|
674
|
+
"subAgent.name": this.config.name || "unknown",
|
|
675
|
+
"subAgent.id": this.config.id || "unknown",
|
|
676
|
+
"conversation.id": this.conversationId || "unknown",
|
|
677
|
+
"agent.id": this.config.agentId || "unknown",
|
|
678
|
+
"tenant.id": this.config.tenantId || "unknown",
|
|
679
|
+
"project.id": this.config.projectId || "unknown"
|
|
680
|
+
} }, (span) => {
|
|
681
|
+
setSpanWithError$1(span, /* @__PURE__ */ new Error(`0 effective tools available for ${tool$1.name}`));
|
|
682
|
+
agentSessionManager.recordEvent(streamRequestId, "error", this.config.id, {
|
|
683
|
+
message: `MCP server has 0 effective tools. Double check the selected tools in your graph and the active tools in the MCP server configuration.`,
|
|
684
|
+
code: "no_tools_available",
|
|
685
|
+
severity: "error",
|
|
686
|
+
context: {
|
|
687
|
+
toolName: tool$1.name,
|
|
688
|
+
serverUrl: tool$1.config.type === "mcp" ? tool$1.config.mcp.server.url : "unknown",
|
|
689
|
+
operation: "mcp_tool_discovery"
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
span.end();
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
return {
|
|
696
|
+
tools,
|
|
697
|
+
toolPolicies,
|
|
698
|
+
mcpServerId: tool$1.id,
|
|
699
|
+
mcpServerName: tool$1.name
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
async createMcpConnection(tool$1, serverConfig) {
|
|
703
|
+
const client = new McpClient({
|
|
704
|
+
name: tool$1.name,
|
|
705
|
+
server: serverConfig
|
|
706
|
+
});
|
|
707
|
+
try {
|
|
708
|
+
await client.connect();
|
|
709
|
+
return client;
|
|
710
|
+
} catch (error) {
|
|
711
|
+
logger.error({
|
|
712
|
+
toolName: tool$1.name,
|
|
713
|
+
subAgentId: this.config.id,
|
|
714
|
+
error: error instanceof Error ? error.message : String(error)
|
|
715
|
+
}, "Agent failed to connect to MCP server");
|
|
716
|
+
if (error instanceof Error) {
|
|
717
|
+
if (error?.cause && JSON.stringify(error.cause).includes("ECONNREFUSED")) throw new Error("Connection refused. Please check if the MCP server is running.");
|
|
718
|
+
if (error.message.includes("404")) throw new Error("Error accessing endpoint (HTTP 404)");
|
|
719
|
+
throw new Error(`MCP server connection failed: ${error.message}`);
|
|
720
|
+
}
|
|
721
|
+
throw error;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
async getFunctionTools(sessionId, streamRequestId) {
|
|
725
|
+
const functionTools = {};
|
|
726
|
+
try {
|
|
727
|
+
const functionToolsData = (await getFunctionToolsForSubAgent(dbClient_default)({
|
|
728
|
+
scopes: {
|
|
729
|
+
tenantId: this.config.tenantId,
|
|
730
|
+
projectId: this.config.projectId,
|
|
731
|
+
agentId: this.config.agentId
|
|
732
|
+
},
|
|
733
|
+
subAgentId: this.config.id
|
|
734
|
+
})).data || [];
|
|
735
|
+
if (functionToolsData.length === 0) return functionTools;
|
|
736
|
+
const { SandboxExecutorFactory } = await import("../tools/SandboxExecutorFactory.js");
|
|
737
|
+
const sandboxExecutor = SandboxExecutorFactory.getInstance();
|
|
738
|
+
for (const functionToolDef of functionToolsData) {
|
|
739
|
+
const functionId = functionToolDef.functionId;
|
|
740
|
+
if (!functionId) {
|
|
741
|
+
logger.warn({ functionToolId: functionToolDef.id }, "Function tool missing functionId reference");
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
const functionData = await getFunction(dbClient_default)({
|
|
745
|
+
functionId,
|
|
746
|
+
scopes: {
|
|
747
|
+
tenantId: this.config.tenantId || "default",
|
|
748
|
+
projectId: this.config.projectId || "default"
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
if (!functionData) {
|
|
752
|
+
logger.warn({
|
|
753
|
+
functionId,
|
|
754
|
+
functionToolId: functionToolDef.id
|
|
755
|
+
}, "Function not found in functions table");
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const zodSchema = jsonSchemaToZod(functionData.inputSchema);
|
|
759
|
+
const aiTool = tool({
|
|
760
|
+
description: functionToolDef.description || functionToolDef.name,
|
|
761
|
+
inputSchema: zodSchema,
|
|
762
|
+
execute: async (args, { toolCallId }) => {
|
|
763
|
+
let processedArgs;
|
|
764
|
+
try {
|
|
765
|
+
processedArgs = parseEmbeddedJson(args);
|
|
766
|
+
if (JSON.stringify(args) !== JSON.stringify(processedArgs)) logger.warn({
|
|
767
|
+
toolName: functionToolDef.name,
|
|
768
|
+
toolCallId
|
|
769
|
+
}, "Fixed stringified JSON parameters (indicates schema ambiguity)");
|
|
770
|
+
} catch (error) {
|
|
771
|
+
logger.warn({
|
|
772
|
+
toolName: functionToolDef.name,
|
|
773
|
+
toolCallId,
|
|
774
|
+
error: error.message
|
|
775
|
+
}, "Failed to parse embedded JSON, using original args");
|
|
776
|
+
processedArgs = args;
|
|
777
|
+
}
|
|
778
|
+
const finalArgs = processedArgs;
|
|
779
|
+
logger.debug({
|
|
780
|
+
toolName: functionToolDef.name,
|
|
781
|
+
toolCallId,
|
|
782
|
+
args: finalArgs
|
|
783
|
+
}, "Function Tool Called");
|
|
784
|
+
try {
|
|
785
|
+
const defaultSandboxConfig = {
|
|
786
|
+
provider: "native",
|
|
787
|
+
runtime: "node22",
|
|
788
|
+
timeout: FUNCTION_TOOL_EXECUTION_TIMEOUT_MS_DEFAULT,
|
|
789
|
+
vcpus: FUNCTION_TOOL_SANDBOX_VCPUS_DEFAULT
|
|
790
|
+
};
|
|
791
|
+
const result = await sandboxExecutor.executeFunctionTool(functionToolDef.id, finalArgs, {
|
|
792
|
+
description: functionToolDef.description || functionToolDef.name,
|
|
793
|
+
inputSchema: functionData.inputSchema || {},
|
|
794
|
+
executeCode: functionData.executeCode,
|
|
795
|
+
dependencies: functionData.dependencies || {},
|
|
796
|
+
sandboxConfig: this.config.sandboxConfig || defaultSandboxConfig
|
|
797
|
+
});
|
|
798
|
+
toolSessionManager.recordToolResult(sessionId || "", {
|
|
799
|
+
toolCallId,
|
|
800
|
+
toolName: functionToolDef.name,
|
|
801
|
+
args: finalArgs,
|
|
802
|
+
result,
|
|
803
|
+
timestamp: Date.now()
|
|
804
|
+
});
|
|
805
|
+
return {
|
|
806
|
+
result,
|
|
807
|
+
toolCallId
|
|
808
|
+
};
|
|
809
|
+
} catch (error) {
|
|
810
|
+
logger.error({
|
|
811
|
+
toolName: functionToolDef.name,
|
|
812
|
+
toolCallId,
|
|
813
|
+
error: error instanceof Error ? error.message : String(error)
|
|
814
|
+
}, "Function tool execution failed");
|
|
815
|
+
throw error;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
functionTools[functionToolDef.name] = this.wrapToolWithStreaming(functionToolDef.name, aiTool, streamRequestId || "", "tool");
|
|
820
|
+
}
|
|
821
|
+
} catch (error) {
|
|
822
|
+
logger.error({ error }, "Failed to load function tools from database");
|
|
823
|
+
}
|
|
824
|
+
return functionTools;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Get resolved context using ContextResolver - will return cached data or fetch fresh data as needed
|
|
828
|
+
*/
|
|
829
|
+
async getResolvedContext(conversationId, headers$1) {
|
|
830
|
+
try {
|
|
831
|
+
if (!this.config.contextConfigId) {
|
|
832
|
+
logger.debug({ agentId: this.config.agentId }, "No context config found for agent");
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
const contextConfig = await getContextConfigById(dbClient_default)({
|
|
836
|
+
scopes: {
|
|
837
|
+
tenantId: this.config.tenantId,
|
|
838
|
+
projectId: this.config.projectId,
|
|
839
|
+
agentId: this.config.agentId
|
|
840
|
+
},
|
|
841
|
+
id: this.config.contextConfigId
|
|
842
|
+
});
|
|
843
|
+
if (!contextConfig) {
|
|
844
|
+
logger.warn({ contextConfigId: this.config.contextConfigId }, "Context config not found");
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
if (!this.contextResolver) throw new Error("Context resolver not found");
|
|
848
|
+
const result = await this.contextResolver.resolve(contextConfig, {
|
|
849
|
+
triggerEvent: "invocation",
|
|
850
|
+
conversationId,
|
|
851
|
+
headers: headers$1 || {},
|
|
852
|
+
tenantId: this.config.tenantId
|
|
853
|
+
});
|
|
854
|
+
const contextWithBuiltins = {
|
|
855
|
+
...result.resolvedContext,
|
|
856
|
+
$env: process.env
|
|
857
|
+
};
|
|
858
|
+
logger.debug({
|
|
859
|
+
conversationId,
|
|
860
|
+
contextConfigId: contextConfig.id,
|
|
861
|
+
resolvedKeys: Object.keys(contextWithBuiltins),
|
|
862
|
+
cacheHits: result.cacheHits.length,
|
|
863
|
+
cacheMisses: result.cacheMisses.length,
|
|
864
|
+
fetchedDefinitions: result.fetchedDefinitions.length,
|
|
865
|
+
errors: result.errors.length
|
|
866
|
+
}, "Context resolved for agent");
|
|
867
|
+
return contextWithBuiltins;
|
|
868
|
+
} catch (error) {
|
|
869
|
+
logger.error({
|
|
870
|
+
conversationId,
|
|
871
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
872
|
+
}, "Failed to get resolved context");
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Get the agent prompt for this agent's agent
|
|
878
|
+
*/
|
|
879
|
+
async getPrompt() {
|
|
880
|
+
try {
|
|
881
|
+
return (await getFullAgentDefinition(dbClient_default)({ scopes: {
|
|
882
|
+
tenantId: this.config.tenantId,
|
|
883
|
+
projectId: this.config.projectId,
|
|
884
|
+
agentId: this.config.agentId
|
|
885
|
+
} }))?.prompt || void 0;
|
|
886
|
+
} catch (error) {
|
|
887
|
+
logger.warn({
|
|
888
|
+
agentId: this.config.agentId,
|
|
889
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
890
|
+
}, "Failed to get agent prompt");
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Check if any agent in the agent has artifact components configured
|
|
896
|
+
*/
|
|
897
|
+
async hasAgentArtifactComponents() {
|
|
898
|
+
try {
|
|
899
|
+
const agentDefinition = await getFullAgentDefinition(dbClient_default)({ scopes: {
|
|
900
|
+
tenantId: this.config.tenantId,
|
|
901
|
+
projectId: this.config.projectId,
|
|
902
|
+
agentId: this.config.agentId
|
|
903
|
+
} });
|
|
904
|
+
if (!agentDefinition) return false;
|
|
905
|
+
return Object.values(agentDefinition.subAgents).some((subAgent) => "artifactComponents" in subAgent && subAgent.artifactComponents && subAgent.artifactComponents.length > 0);
|
|
906
|
+
} catch (error) {
|
|
907
|
+
logger.warn({
|
|
908
|
+
agentId: this.config.agentId,
|
|
909
|
+
tenantId: this.config.tenantId,
|
|
910
|
+
projectId: this.config.projectId,
|
|
911
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
912
|
+
}, "Failed to check agent artifact components, assuming none exist");
|
|
913
|
+
return this.artifactComponents.length > 0;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Build adaptive system prompt for Phase 2 structured output generation
|
|
918
|
+
* based on configured data components and artifact components across the agent
|
|
919
|
+
*/
|
|
920
|
+
async buildPhase2SystemPrompt(runtimeContext) {
|
|
921
|
+
const phase2Config = new Phase2Config();
|
|
922
|
+
const compressionConfig = getModelAwareCompressionConfig();
|
|
923
|
+
const hasAgentArtifactComponents = await this.hasAgentArtifactComponents() || compressionConfig.enabled;
|
|
924
|
+
const conversationId = runtimeContext?.metadata?.conversationId || runtimeContext?.contextId;
|
|
925
|
+
const resolvedContext = conversationId ? await this.getResolvedContext(conversationId) : null;
|
|
926
|
+
let processedPrompt = this.config.prompt || "";
|
|
927
|
+
if (resolvedContext && this.config.prompt) try {
|
|
928
|
+
processedPrompt = TemplateEngine.render(this.config.prompt, resolvedContext, {
|
|
929
|
+
strict: false,
|
|
930
|
+
preserveUnresolved: false
|
|
931
|
+
});
|
|
932
|
+
} catch (error) {
|
|
933
|
+
logger.error({
|
|
934
|
+
conversationId,
|
|
935
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
936
|
+
}, "Failed to process agent prompt with context for Phase 2, using original");
|
|
937
|
+
processedPrompt = this.config.prompt;
|
|
938
|
+
}
|
|
939
|
+
const referenceTaskIds = await listTaskIdsByContextId(dbClient_default)({ contextId: this.conversationId || "" });
|
|
940
|
+
const referenceArtifacts = [];
|
|
941
|
+
for (const taskId of referenceTaskIds) {
|
|
942
|
+
const artifacts = await getLedgerArtifacts(dbClient_default)({
|
|
943
|
+
scopes: {
|
|
944
|
+
tenantId: this.config.tenantId,
|
|
945
|
+
projectId: this.config.projectId
|
|
946
|
+
},
|
|
947
|
+
taskId
|
|
948
|
+
});
|
|
949
|
+
referenceArtifacts.push(...artifacts);
|
|
950
|
+
}
|
|
951
|
+
return phase2Config.assemblePhase2Prompt({
|
|
952
|
+
corePrompt: processedPrompt,
|
|
953
|
+
dataComponents: this.config.dataComponents || [],
|
|
954
|
+
artifactComponents: this.artifactComponents,
|
|
955
|
+
hasArtifactComponents: this.artifactComponents && this.artifactComponents.length > 0,
|
|
956
|
+
hasAgentArtifactComponents,
|
|
957
|
+
artifacts: referenceArtifacts
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
async buildSystemPrompt(runtimeContext, excludeDataComponents = false) {
|
|
961
|
+
const conversationId = runtimeContext?.metadata?.conversationId || runtimeContext?.contextId;
|
|
962
|
+
if (conversationId) this.setConversationId(conversationId);
|
|
963
|
+
const resolvedContext = conversationId ? await this.getResolvedContext(conversationId) : null;
|
|
964
|
+
let processedPrompt = this.config.prompt || "";
|
|
965
|
+
if (resolvedContext && this.config.prompt) try {
|
|
966
|
+
processedPrompt = TemplateEngine.render(this.config.prompt, resolvedContext, {
|
|
967
|
+
strict: false,
|
|
968
|
+
preserveUnresolved: false
|
|
969
|
+
});
|
|
970
|
+
} catch (error) {
|
|
971
|
+
logger.error({
|
|
972
|
+
conversationId,
|
|
973
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
974
|
+
}, "Failed to process agent prompt with context, using original");
|
|
975
|
+
processedPrompt = this.config.prompt;
|
|
976
|
+
}
|
|
977
|
+
const streamRequestId = runtimeContext?.metadata?.streamRequestId;
|
|
978
|
+
const mcpTools = await this.getMcpTools(void 0, streamRequestId);
|
|
979
|
+
const functionTools = await this.getFunctionTools(streamRequestId || "");
|
|
980
|
+
const relationTools = this.getRelationTools(runtimeContext);
|
|
981
|
+
const allTools = {
|
|
982
|
+
...mcpTools,
|
|
983
|
+
...functionTools,
|
|
984
|
+
...relationTools
|
|
985
|
+
};
|
|
986
|
+
logger.info({
|
|
987
|
+
mcpTools: Object.keys(mcpTools),
|
|
988
|
+
functionTools: Object.keys(functionTools),
|
|
989
|
+
relationTools: Object.keys(relationTools),
|
|
990
|
+
allTools: Object.keys(allTools),
|
|
991
|
+
functionToolsDetails: Object.entries(functionTools).map(([name, tool$1]) => ({
|
|
992
|
+
name,
|
|
993
|
+
hasExecute: typeof tool$1.execute === "function",
|
|
994
|
+
hasDescription: !!tool$1.description,
|
|
995
|
+
hasInputSchema: !!tool$1.inputSchema
|
|
996
|
+
}))
|
|
997
|
+
}, "Tools loaded for agent");
|
|
998
|
+
const toolDefinitions = Object.entries(allTools).map(([name, tool$1]) => ({
|
|
999
|
+
name,
|
|
1000
|
+
description: tool$1.description || "",
|
|
1001
|
+
inputSchema: tool$1.inputSchema || tool$1.parameters || {},
|
|
1002
|
+
usageGuidelines: name.startsWith("transfer_to_") || name.startsWith("delegate_to_") ? `Use this tool to ${name.startsWith("transfer_to_") ? "transfer" : "delegate"} to another agent when appropriate.` : "Use this tool when appropriate for the task at hand."
|
|
1003
|
+
}));
|
|
1004
|
+
const { getConversationScopedArtifacts } = await import("../data/conversations.js");
|
|
1005
|
+
const historyConfig = this.config.conversationHistoryConfig ?? createDefaultConversationHistoryConfig();
|
|
1006
|
+
const referenceArtifacts = await getConversationScopedArtifacts({
|
|
1007
|
+
tenantId: this.config.tenantId,
|
|
1008
|
+
projectId: this.config.projectId,
|
|
1009
|
+
conversationId: runtimeContext?.contextId || "",
|
|
1010
|
+
historyConfig
|
|
1011
|
+
});
|
|
1012
|
+
const componentDataComponents = excludeDataComponents ? [] : this.config.dataComponents || [];
|
|
1013
|
+
const isThinkingPreparation = this.config.dataComponents && this.config.dataComponents.length > 0 && excludeDataComponents;
|
|
1014
|
+
let prompt = await this.getPrompt();
|
|
1015
|
+
if (prompt && resolvedContext) try {
|
|
1016
|
+
prompt = TemplateEngine.render(prompt, resolvedContext, {
|
|
1017
|
+
strict: false,
|
|
1018
|
+
preserveUnresolved: false
|
|
1019
|
+
});
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
logger.error({
|
|
1022
|
+
conversationId,
|
|
1023
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1024
|
+
}, "Failed to process agent prompt with context, using original");
|
|
1025
|
+
}
|
|
1026
|
+
const shouldIncludeArtifactComponents = !excludeDataComponents;
|
|
1027
|
+
const compressionConfig = getModelAwareCompressionConfig();
|
|
1028
|
+
const hasAgentArtifactComponents = await this.hasAgentArtifactComponents() || compressionConfig.enabled;
|
|
1029
|
+
const config = {
|
|
1030
|
+
corePrompt: processedPrompt,
|
|
1031
|
+
prompt,
|
|
1032
|
+
tools: toolDefinitions,
|
|
1033
|
+
dataComponents: componentDataComponents,
|
|
1034
|
+
artifacts: referenceArtifacts,
|
|
1035
|
+
artifactComponents: shouldIncludeArtifactComponents ? this.artifactComponents : [],
|
|
1036
|
+
hasAgentArtifactComponents,
|
|
1037
|
+
isThinkingPreparation,
|
|
1038
|
+
hasTransferRelations: (this.config.transferRelations?.length ?? 0) > 0,
|
|
1039
|
+
hasDelegateRelations: (this.config.delegateRelations?.length ?? 0) > 0
|
|
1040
|
+
};
|
|
1041
|
+
return await this.systemPromptBuilder.buildSystemPrompt(config);
|
|
1042
|
+
}
|
|
1043
|
+
getArtifactTools() {
|
|
1044
|
+
return tool({
|
|
1045
|
+
description: "Call this tool to get the complete artifact data with the given artifactId. This retrieves the full artifact content (not just the summary). Only use this when you need the complete artifact data and the summary shown in your context is insufficient.",
|
|
1046
|
+
inputSchema: z.object({
|
|
1047
|
+
artifactId: z.string().describe("The unique identifier of the artifact to get."),
|
|
1048
|
+
toolCallId: z.string().describe("The tool call ID associated with this artifact.")
|
|
1049
|
+
}),
|
|
1050
|
+
execute: async ({ artifactId, toolCallId }) => {
|
|
1051
|
+
logger.info({
|
|
1052
|
+
artifactId,
|
|
1053
|
+
toolCallId
|
|
1054
|
+
}, "get_artifact_full executed");
|
|
1055
|
+
const streamRequestId = this.getStreamRequestId();
|
|
1056
|
+
const artifactService = agentSessionManager.getArtifactService(streamRequestId);
|
|
1057
|
+
if (!artifactService) throw new Error(`ArtifactService not found for session ${streamRequestId}`);
|
|
1058
|
+
const artifactData = await artifactService.getArtifactFull(artifactId, toolCallId);
|
|
1059
|
+
if (!artifactData) throw new Error(`Artifact ${artifactId} with toolCallId ${toolCallId} not found`);
|
|
1060
|
+
return {
|
|
1061
|
+
artifactId: artifactData.artifactId,
|
|
1062
|
+
name: artifactData.name,
|
|
1063
|
+
description: artifactData.description,
|
|
1064
|
+
type: artifactData.type,
|
|
1065
|
+
data: artifactData.data
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
createThinkingCompleteTool() {
|
|
1071
|
+
return tool({
|
|
1072
|
+
description: "🚨 CRITICAL: Call this tool IMMEDIATELY when you have gathered enough information to answer the user. This is MANDATORY - you CANNOT provide text responses in thinking mode, only tool calls. Call thinking_complete as soon as you have sufficient data to generate a structured response.",
|
|
1073
|
+
inputSchema: z.object({
|
|
1074
|
+
complete: z.boolean().describe("ALWAYS set to true - marks end of research phase"),
|
|
1075
|
+
summary: z.string().describe("Brief summary of what information was gathered and why it is sufficient to answer the user")
|
|
1076
|
+
}),
|
|
1077
|
+
execute: async (params) => params
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
async getDefaultTools(streamRequestId) {
|
|
1081
|
+
const defaultTools = {};
|
|
1082
|
+
const compressionConfig = getModelAwareCompressionConfig();
|
|
1083
|
+
if (await this.agentHasArtifactComponents() || compressionConfig.enabled) defaultTools.get_reference_artifact = this.getArtifactTools();
|
|
1084
|
+
if (this.config.dataComponents && this.config.dataComponents.length > 0) {
|
|
1085
|
+
const thinkingCompleteTool = this.createThinkingCompleteTool();
|
|
1086
|
+
if (thinkingCompleteTool) defaultTools.thinking_complete = this.wrapToolWithStreaming("thinking_complete", thinkingCompleteTool, streamRequestId, "tool");
|
|
1087
|
+
}
|
|
1088
|
+
logger.info({
|
|
1089
|
+
agentId: this.config.id,
|
|
1090
|
+
streamRequestId
|
|
1091
|
+
}, "Adding compress_context tool to defaultTools");
|
|
1092
|
+
defaultTools.compress_context = tool({
|
|
1093
|
+
description: "Manually compress the current conversation context to save space. Use when shifting topics, completing major tasks, or when context feels cluttered.",
|
|
1094
|
+
inputSchema: z.object({ reason: z.string().describe("Why you are requesting compression (e.g., \"shifting from research to coding\", \"completed analysis phase\")") }),
|
|
1095
|
+
execute: async ({ reason }) => {
|
|
1096
|
+
logger.info({
|
|
1097
|
+
agentId: this.config.id,
|
|
1098
|
+
streamRequestId,
|
|
1099
|
+
reason
|
|
1100
|
+
}, "Manual compression requested by LLM");
|
|
1101
|
+
if (this.currentCompressor) this.currentCompressor.requestManualCompression(reason);
|
|
1102
|
+
return {
|
|
1103
|
+
status: "compression_requested",
|
|
1104
|
+
reason,
|
|
1105
|
+
message: "Context compression will be applied on the next generation step. Previous work has been summarized and saved as artifacts."
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
logger.info("getDefaultTools returning tools:", Object.keys(defaultTools).join(", "));
|
|
1110
|
+
return defaultTools;
|
|
1111
|
+
}
|
|
1112
|
+
getStreamRequestId() {
|
|
1113
|
+
return this.streamRequestId || "";
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Format tool result for storage in conversation history
|
|
1117
|
+
*/
|
|
1118
|
+
formatToolResult(toolName, args, result, toolCallId) {
|
|
1119
|
+
const input = args ? JSON.stringify(args, null, 2) : "No input";
|
|
1120
|
+
let parsedResult = result;
|
|
1121
|
+
if (typeof result === "string") try {
|
|
1122
|
+
parsedResult = JSON.parse(result);
|
|
1123
|
+
} catch (e) {}
|
|
1124
|
+
const cleanResult = parsedResult && typeof parsedResult === "object" && !Array.isArray(parsedResult) ? {
|
|
1125
|
+
...parsedResult,
|
|
1126
|
+
result: parsedResult.result && typeof parsedResult.result === "object" && !Array.isArray(parsedResult.result) ? Object.fromEntries(Object.entries(parsedResult.result).filter(([key]) => key !== "_structureHints")) : parsedResult.result
|
|
1127
|
+
} : parsedResult;
|
|
1128
|
+
return `## Tool: ${toolName}
|
|
1129
|
+
|
|
1130
|
+
### 🔧 TOOL_CALL_ID: ${toolCallId}
|
|
1131
|
+
|
|
1132
|
+
### Input
|
|
1133
|
+
${input}
|
|
1134
|
+
|
|
1135
|
+
### Output
|
|
1136
|
+
${typeof cleanResult === "string" ? cleanResult : JSON.stringify(cleanResult, null, 2)}`;
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Get the conversation ID for storing tool results
|
|
1140
|
+
* Always uses the real conversation ID - delegation filtering happens at query time
|
|
1141
|
+
*/
|
|
1142
|
+
getToolResultConversationId() {
|
|
1143
|
+
return this.conversationId;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Analyze tool result structure and add helpful path hints for artifact creation
|
|
1147
|
+
* Only adds hints when artifact components are available
|
|
1148
|
+
*/
|
|
1149
|
+
enhanceToolResultWithStructureHints(result) {
|
|
1150
|
+
if (!result) return result;
|
|
1151
|
+
if (!this.artifactComponents || this.artifactComponents.length === 0) return result;
|
|
1152
|
+
let parsedForAnalysis = result;
|
|
1153
|
+
if (typeof result === "string") try {
|
|
1154
|
+
parsedForAnalysis = parseEmbeddedJson(result);
|
|
1155
|
+
} catch (_error) {
|
|
1156
|
+
parsedForAnalysis = result;
|
|
1157
|
+
}
|
|
1158
|
+
if (!parsedForAnalysis || typeof parsedForAnalysis !== "object") return result;
|
|
1159
|
+
const findAllPaths = (obj, prefix = "result", depth = 0) => {
|
|
1160
|
+
if (depth > 8) return [];
|
|
1161
|
+
const paths = [];
|
|
1162
|
+
if (Array.isArray(obj)) {
|
|
1163
|
+
if (obj.length > 0) {
|
|
1164
|
+
paths.push(`${prefix}[array-${obj.length}-items]`);
|
|
1165
|
+
if (obj[0] && typeof obj[0] === "object") {
|
|
1166
|
+
const sampleItem = obj[0];
|
|
1167
|
+
Object.keys(sampleItem).forEach((key) => {
|
|
1168
|
+
const value = sampleItem[key];
|
|
1169
|
+
if (typeof value === "string" && value.length < 50) paths.push(`${prefix}[?${key}=='${value}']`);
|
|
1170
|
+
else if (typeof value === "boolean") paths.push(`${prefix}[?${key}==${value}]`);
|
|
1171
|
+
else if (key === "id" || key === "name" || key === "type") paths.push(`${prefix}[?${key}=='value']`);
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
paths.push(...findAllPaths(obj[0], `${prefix}[?field=='value']`, depth + 1));
|
|
1175
|
+
}
|
|
1176
|
+
} else if (obj && typeof obj === "object") Object.entries(obj).forEach(([key, value]) => {
|
|
1177
|
+
const currentPath = `${prefix}.${key}`;
|
|
1178
|
+
if (value && typeof value === "object") {
|
|
1179
|
+
if (Array.isArray(value)) paths.push(`${currentPath}[array]`);
|
|
1180
|
+
else paths.push(`${currentPath}[object]`);
|
|
1181
|
+
paths.push(...findAllPaths(value, currentPath, depth + 1));
|
|
1182
|
+
} else paths.push(`${currentPath}[${typeof value}]`);
|
|
1183
|
+
});
|
|
1184
|
+
return paths;
|
|
1185
|
+
};
|
|
1186
|
+
const findCommonFields = (obj, depth = 0) => {
|
|
1187
|
+
if (depth > 5) return /* @__PURE__ */ new Set();
|
|
1188
|
+
const fields = /* @__PURE__ */ new Set();
|
|
1189
|
+
if (Array.isArray(obj)) obj.slice(0, 3).forEach((item) => {
|
|
1190
|
+
if (item && typeof item === "object") Object.keys(item).forEach((key) => {
|
|
1191
|
+
fields.add(key);
|
|
1192
|
+
});
|
|
1193
|
+
});
|
|
1194
|
+
else if (obj && typeof obj === "object") {
|
|
1195
|
+
Object.keys(obj).forEach((key) => {
|
|
1196
|
+
fields.add(key);
|
|
1197
|
+
});
|
|
1198
|
+
Object.values(obj).forEach((value) => {
|
|
1199
|
+
findCommonFields(value, depth + 1).forEach((field) => {
|
|
1200
|
+
fields.add(field);
|
|
1201
|
+
});
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
return fields;
|
|
1205
|
+
};
|
|
1206
|
+
const findUsefulSelectors = (obj, prefix = "result", depth = 0) => {
|
|
1207
|
+
if (depth > 5) return [];
|
|
1208
|
+
const selectors = [];
|
|
1209
|
+
if (Array.isArray(obj) && obj.length > 0) {
|
|
1210
|
+
const firstItem = obj[0];
|
|
1211
|
+
if (firstItem && typeof firstItem === "object") {
|
|
1212
|
+
if (firstItem.title) selectors.push(`${prefix}[?title=='${String(firstItem.title).replace(/'/g, "\\'")}'] | [0]`);
|
|
1213
|
+
if (firstItem.type) selectors.push(`${prefix}[?type=='${firstItem.type}'] | [0]`);
|
|
1214
|
+
if (firstItem.record_type) selectors.push(`${prefix}[?record_type=='${firstItem.record_type}'] | [0]`);
|
|
1215
|
+
if (firstItem.url) selectors.push(`${prefix}[?url!=null] | [0]`);
|
|
1216
|
+
if (firstItem.type && firstItem.title) selectors.push(`${prefix}[?type=='${firstItem.type}' && title=='${String(firstItem.title).replace(/'/g, "\\'")}'] | [0]`);
|
|
1217
|
+
selectors.push(`${prefix}[0]`);
|
|
1218
|
+
}
|
|
1219
|
+
} else if (obj && typeof obj === "object") Object.entries(obj).forEach(([key, value]) => {
|
|
1220
|
+
if (typeof value === "object" && value !== null) selectors.push(...findUsefulSelectors(value, `${prefix}.${key}`, depth + 1));
|
|
1221
|
+
});
|
|
1222
|
+
return selectors;
|
|
1223
|
+
};
|
|
1224
|
+
const findNestedContentPaths = (obj, prefix = "result", depth = 0) => {
|
|
1225
|
+
if (depth > 6) return [];
|
|
1226
|
+
const paths = [];
|
|
1227
|
+
if (obj && typeof obj === "object") Object.entries(obj).forEach(([key, value]) => {
|
|
1228
|
+
const currentPath = `${prefix}.${key}`;
|
|
1229
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
1230
|
+
const firstItem = value[0];
|
|
1231
|
+
if (firstItem && typeof firstItem === "object") {
|
|
1232
|
+
if (firstItem.type === "document" || firstItem.type === "text") {
|
|
1233
|
+
paths.push(`${currentPath}[?type=='document'] | [0]`);
|
|
1234
|
+
paths.push(`${currentPath}[?type=='text'] | [0]`);
|
|
1235
|
+
if (firstItem.title) {
|
|
1236
|
+
const titleSample = String(firstItem.title).slice(0, 20);
|
|
1237
|
+
paths.push(`${currentPath}[?title && contains(title, '${titleSample.split(" ")[0]}')] | [0]`);
|
|
1238
|
+
}
|
|
1239
|
+
if (firstItem.record_type) paths.push(`${currentPath}[?record_type=='${firstItem.record_type}'] | [0]`);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
paths.push(...findNestedContentPaths(value, currentPath, depth + 1));
|
|
1243
|
+
} else if (value && typeof value === "object") paths.push(...findNestedContentPaths(value, currentPath, depth + 1));
|
|
1244
|
+
});
|
|
1245
|
+
return paths;
|
|
1246
|
+
};
|
|
1247
|
+
try {
|
|
1248
|
+
const allPaths = findAllPaths(parsedForAnalysis);
|
|
1249
|
+
const commonFields = Array.from(findCommonFields(parsedForAnalysis)).slice(0, 15);
|
|
1250
|
+
const usefulSelectors = findUsefulSelectors(parsedForAnalysis).slice(0, 10);
|
|
1251
|
+
const nestedContentPaths = findNestedContentPaths(parsedForAnalysis).slice(0, 8);
|
|
1252
|
+
const terminalPaths = allPaths.filter((p) => p.includes("[string]") || p.includes("[number]") || p.includes("[boolean]")).slice(0, 20);
|
|
1253
|
+
const arrayPaths = allPaths.filter((p) => p.includes("[array")).slice(0, 15);
|
|
1254
|
+
const objectPaths = allPaths.filter((p) => p.includes("[object]")).slice(0, 15);
|
|
1255
|
+
const allSelectors = [...usefulSelectors, ...nestedContentPaths];
|
|
1256
|
+
const uniqueSelectors = [...new Set(allSelectors)].slice(0, 15);
|
|
1257
|
+
return {
|
|
1258
|
+
...result,
|
|
1259
|
+
_structureHints: {
|
|
1260
|
+
terminalPaths,
|
|
1261
|
+
arrayPaths,
|
|
1262
|
+
objectPaths,
|
|
1263
|
+
commonFields,
|
|
1264
|
+
exampleSelectors: uniqueSelectors,
|
|
1265
|
+
deepStructureExamples: nestedContentPaths,
|
|
1266
|
+
maxDepthFound: Math.max(...allPaths.map((p) => (p.match(/\./g) || []).length)),
|
|
1267
|
+
totalPathsFound: allPaths.length,
|
|
1268
|
+
artifactGuidance: {
|
|
1269
|
+
creationFirst: "🚨 CRITICAL: Artifacts must be CREATED before they can be referenced. Use ArtifactCreate_[Type] components FIRST, then reference with Artifact components only if citing the SAME artifact again.",
|
|
1270
|
+
baseSelector: "🎯 CRITICAL: Use base_selector to navigate to ONE specific item. For deeply nested structures with repeated keys, use full paths with specific filtering (e.g., \"result.data.content.items[?type=='guide' && status=='active']\")",
|
|
1271
|
+
detailsSelector: "📝 Use relative selectors for specific fields (e.g., \"title\", \"metadata.category\", \"properties.status\", \"content.details\")",
|
|
1272
|
+
avoidLiterals: "❌ NEVER use literal values - always use field selectors to extract from data",
|
|
1273
|
+
avoidArrays: "✨ ALWAYS filter arrays to single items using [?condition] - NEVER use [*] notation which returns arrays",
|
|
1274
|
+
nestedKeys: "🔑 For structures with repeated keys (like result.content.data.content.items.content), use full paths with filtering at each level",
|
|
1275
|
+
filterTips: "💡 Use compound filters for precision: [?type=='document' && category=='api']",
|
|
1276
|
+
forbiddenSyntax: "🚫 FORBIDDEN JMESPATH PATTERNS:\n❌ NEVER: [?title~'.*text.*'] (regex patterns with ~ operator)\n❌ NEVER: [?field~'pattern.*'] (any ~ operator usage)\n❌ NEVER: [?title~'Slack.*Discord.*'] (regex wildcards)\n❌ NEVER: [?name~'https://.*'] (regex in URL matching)\n❌ NEVER: [?text ~ contains(@, 'word')] (~ with @ operator)\n❌ NEVER: contains(@, 'text') (@ operator usage)\n❌ NEVER: [?field==\"value\"] (double quotes in filters)\n❌ NEVER: result.items[?type=='doc'][?status=='active'] (chained filters)\n✅ USE INSTEAD:\n✅ [?contains(title, 'text')] (contains function)\n✅ [?title=='exact match'] (exact string matching)\n✅ [?contains(title, 'Slack') && contains(title, 'Discord')] (compound conditions)\n✅ [?starts_with(url, 'https://')] (starts_with function)\n✅ [?type=='doc' && status=='active'] (single filter with &&)",
|
|
1277
|
+
pathDepth: `📏 This structure goes ${Math.max(...allPaths.map((p) => (p.match(/\./g) || []).length))} levels deep - use full paths to avoid ambiguity`
|
|
1278
|
+
},
|
|
1279
|
+
note: `Comprehensive structure analysis: ${allPaths.length} paths found, ${Math.max(...allPaths.map((p) => (p.match(/\./g) || []).length))} levels deep. Use specific filtering for precise selection.`
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
logger.warn({ error }, "Failed to enhance tool result with structure hints");
|
|
1284
|
+
return result;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
async agentHasArtifactComponents() {
|
|
1288
|
+
try {
|
|
1289
|
+
return await agentHasArtifactComponents(dbClient_default)({ scopes: {
|
|
1290
|
+
tenantId: this.config.tenantId,
|
|
1291
|
+
projectId: this.config.projectId,
|
|
1292
|
+
agentId: this.config.agentId
|
|
1293
|
+
} });
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
logger.error({
|
|
1296
|
+
error,
|
|
1297
|
+
agentId: this.config.agentId
|
|
1298
|
+
}, "Failed to check agent artifact components");
|
|
1299
|
+
return false;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
async generate(userMessage, runtimeContext) {
|
|
1303
|
+
return tracer.startActiveSpan("agent.generate", { attributes: {
|
|
1304
|
+
"subAgent.id": this.config.id,
|
|
1305
|
+
"subAgent.name": this.config.name
|
|
1306
|
+
} }, async (span) => {
|
|
1307
|
+
const { contextId, taskId, streamRequestId, sessionId } = this.setupGenerationContext(runtimeContext);
|
|
1308
|
+
try {
|
|
1309
|
+
const { systemPrompt, thinkingSystemPrompt, sanitizedTools, contextBreakdown: initialContextBreakdown } = await this.loadToolsAndPrompts(sessionId, streamRequestId, runtimeContext);
|
|
1310
|
+
if (streamRequestId && this.artifactComponents.length > 0) agentSessionManager.updateArtifactComponents(streamRequestId, this.artifactComponents);
|
|
1311
|
+
const conversationId = runtimeContext?.metadata?.conversationId;
|
|
1312
|
+
if (conversationId) this.setConversationId(conversationId);
|
|
1313
|
+
const { conversationHistory, contextBreakdown } = await this.buildConversationHistory(contextId, taskId, userMessage, streamRequestId, initialContextBreakdown);
|
|
1314
|
+
span.setAttributes({
|
|
1315
|
+
"context.breakdown.system_template_tokens": contextBreakdown.systemPromptTemplate,
|
|
1316
|
+
"context.breakdown.core_instructions_tokens": contextBreakdown.coreInstructions,
|
|
1317
|
+
"context.breakdown.agent_prompt_tokens": contextBreakdown.agentPrompt,
|
|
1318
|
+
"context.breakdown.tools_tokens": contextBreakdown.toolsSection,
|
|
1319
|
+
"context.breakdown.artifacts_tokens": contextBreakdown.artifactsSection,
|
|
1320
|
+
"context.breakdown.data_components_tokens": contextBreakdown.dataComponents,
|
|
1321
|
+
"context.breakdown.artifact_components_tokens": contextBreakdown.artifactComponents,
|
|
1322
|
+
"context.breakdown.transfer_instructions_tokens": contextBreakdown.transferInstructions,
|
|
1323
|
+
"context.breakdown.delegation_instructions_tokens": contextBreakdown.delegationInstructions,
|
|
1324
|
+
"context.breakdown.thinking_preparation_tokens": contextBreakdown.thinkingPreparation,
|
|
1325
|
+
"context.breakdown.conversation_history_tokens": contextBreakdown.conversationHistory,
|
|
1326
|
+
"context.breakdown.total_tokens": contextBreakdown.total
|
|
1327
|
+
});
|
|
1328
|
+
const { primaryModelSettings, modelSettings, hasStructuredOutput, shouldStreamPhase1, timeoutMs } = this.configureModelSettings();
|
|
1329
|
+
let response;
|
|
1330
|
+
let textResponse;
|
|
1331
|
+
const messages = this.buildInitialMessages(systemPrompt, thinkingSystemPrompt, hasStructuredOutput, conversationHistory, userMessage);
|
|
1332
|
+
const { originalMessageCount, compressor } = this.setupCompression(messages, sessionId, contextId, primaryModelSettings);
|
|
1333
|
+
if (shouldStreamPhase1) {
|
|
1334
|
+
const streamConfig = {
|
|
1335
|
+
...modelSettings,
|
|
1336
|
+
toolChoice: "auto"
|
|
1337
|
+
};
|
|
1338
|
+
const streamResult = streamText(this.buildBaseGenerationConfig(streamConfig, messages, sanitizedTools, compressor, originalMessageCount, timeoutMs, "auto", void 0, false, contextBreakdown.total));
|
|
1339
|
+
const parser = this.setupStreamParser(sessionId, contextId);
|
|
1340
|
+
await this.processStreamEvents(streamResult, parser);
|
|
1341
|
+
response = await streamResult;
|
|
1342
|
+
response = this.formatStreamingResponse(response, parser);
|
|
1343
|
+
} else {
|
|
1344
|
+
const toolChoice = hasStructuredOutput ? "required" : "auto";
|
|
1345
|
+
response = await generateText(this.buildBaseGenerationConfig(modelSettings, messages, sanitizedTools, compressor, originalMessageCount, timeoutMs, toolChoice, "planning", true, contextBreakdown.total));
|
|
1346
|
+
}
|
|
1347
|
+
if (response.steps) {
|
|
1348
|
+
const resolvedSteps = await response.steps;
|
|
1349
|
+
response = {
|
|
1350
|
+
...response,
|
|
1351
|
+
steps: resolvedSteps
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
if (hasStructuredOutput && !hasToolCallWithPrefix("transfer_to_")(response)) if (response.steps?.flatMap((s) => s.toolCalls || [])?.find((tc) => tc.toolName === "thinking_complete")) {
|
|
1355
|
+
const reasoningFlow = this.buildReasoningFlow(response, sessionId);
|
|
1356
|
+
const dataComponentsSchema = this.buildDataComponentsSchema();
|
|
1357
|
+
const structuredModelSettings = ModelFactory.prepareGenerationConfig(this.getStructuredOutputModel());
|
|
1358
|
+
const phase2TimeoutMs = this.calculatePhase2Timeout(structuredModelSettings);
|
|
1359
|
+
if (this.getStreamingHelper()) {
|
|
1360
|
+
const phase2Messages = await this.buildPhase2Messages(runtimeContext, conversationHistory, userMessage, reasoningFlow);
|
|
1361
|
+
const result = await this.executeStreamingPhase2(structuredModelSettings, phase2Messages, dataComponentsSchema, phase2TimeoutMs, sessionId, contextId, response);
|
|
1362
|
+
response = result;
|
|
1363
|
+
textResponse = result.textResponse;
|
|
1364
|
+
} else {
|
|
1365
|
+
const phase2Messages = await this.buildPhase2Messages(runtimeContext, conversationHistory, userMessage, reasoningFlow);
|
|
1366
|
+
const result = await this.executeNonStreamingPhase2(structuredModelSettings, phase2Messages, dataComponentsSchema, phase2TimeoutMs, response);
|
|
1367
|
+
response = result;
|
|
1368
|
+
textResponse = result.textResponse;
|
|
1369
|
+
}
|
|
1370
|
+
} else textResponse = response.text || "";
|
|
1371
|
+
else textResponse = response.steps[response.steps.length - 1].text || "";
|
|
1372
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
1373
|
+
span.end();
|
|
1374
|
+
const formattedResponse = await this.formatFinalResponse(response, textResponse, sessionId, contextId);
|
|
1375
|
+
if (streamRequestId) {
|
|
1376
|
+
const generationType = response.object ? "object_generation" : "text_generation";
|
|
1377
|
+
agentSessionManager.recordEvent(streamRequestId, "agent_generate", this.config.id, {
|
|
1378
|
+
parts: (formattedResponse.formattedContent?.parts || []).map((part) => ({
|
|
1379
|
+
type: part.kind === "text" ? "text" : part.kind === "data" ? "tool_result" : "text",
|
|
1380
|
+
content: part.text || JSON.stringify(part.data)
|
|
1381
|
+
})),
|
|
1382
|
+
generationType
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
if (compressor) compressor.fullCleanup();
|
|
1386
|
+
this.currentCompressor = null;
|
|
1387
|
+
return formattedResponse;
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
this.handleGenerationError(error, span);
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Setup generation context and initialize streaming helper
|
|
1395
|
+
*/
|
|
1396
|
+
setupGenerationContext(runtimeContext) {
|
|
1397
|
+
const contextId = runtimeContext?.contextId || "default";
|
|
1398
|
+
const taskId = runtimeContext?.metadata?.taskId || "unknown";
|
|
1399
|
+
const streamRequestId = runtimeContext?.metadata?.streamRequestId;
|
|
1400
|
+
const sessionId = streamRequestId || "fallback-session";
|
|
1401
|
+
this.streamRequestId = streamRequestId;
|
|
1402
|
+
this.streamHelper = streamRequestId ? getStreamHelper(streamRequestId) : void 0;
|
|
1403
|
+
if (streamRequestId && this.artifactComponents.length > 0) agentSessionManager.updateArtifactComponents(streamRequestId, this.artifactComponents);
|
|
1404
|
+
const conversationId = runtimeContext?.metadata?.conversationId;
|
|
1405
|
+
if (conversationId) this.setConversationId(conversationId);
|
|
1406
|
+
return {
|
|
1407
|
+
contextId,
|
|
1408
|
+
taskId,
|
|
1409
|
+
streamRequestId,
|
|
1410
|
+
sessionId
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Load all tools and system prompts in parallel, then combine and sanitize them
|
|
1415
|
+
*/
|
|
1416
|
+
async loadToolsAndPrompts(sessionId, streamRequestId, runtimeContext) {
|
|
1417
|
+
const [mcpTools, systemPromptResult, thinkingSystemPromptResult, functionTools, relationTools, defaultTools] = await tracer.startActiveSpan("agent.load_tools", { attributes: {
|
|
1418
|
+
"subAgent.name": this.config.name,
|
|
1419
|
+
"session.id": sessionId || "none"
|
|
1420
|
+
} }, async (childSpan) => {
|
|
1421
|
+
try {
|
|
1422
|
+
const result = await Promise.all([
|
|
1423
|
+
this.getMcpTools(sessionId, streamRequestId),
|
|
1424
|
+
this.buildSystemPrompt(runtimeContext, false),
|
|
1425
|
+
this.buildSystemPrompt(runtimeContext, true),
|
|
1426
|
+
this.getFunctionTools(sessionId, streamRequestId),
|
|
1427
|
+
Promise.resolve(this.getRelationTools(runtimeContext, sessionId)),
|
|
1428
|
+
this.getDefaultTools(streamRequestId)
|
|
1429
|
+
]);
|
|
1430
|
+
childSpan.setStatus({ code: SpanStatusCode.OK });
|
|
1431
|
+
return result;
|
|
1432
|
+
} catch (err) {
|
|
1433
|
+
setSpanWithError$1(childSpan, err instanceof Error ? err : new Error(String(err)));
|
|
1434
|
+
throw err;
|
|
1435
|
+
} finally {
|
|
1436
|
+
childSpan.end();
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1439
|
+
const systemPrompt = systemPromptResult.prompt;
|
|
1440
|
+
const thinkingSystemPrompt = thinkingSystemPromptResult.prompt;
|
|
1441
|
+
const contextBreakdown = systemPromptResult.breakdown;
|
|
1442
|
+
const allTools = {
|
|
1443
|
+
...mcpTools,
|
|
1444
|
+
...functionTools,
|
|
1445
|
+
...relationTools,
|
|
1446
|
+
...defaultTools
|
|
1447
|
+
};
|
|
1448
|
+
return {
|
|
1449
|
+
systemPrompt,
|
|
1450
|
+
thinkingSystemPrompt,
|
|
1451
|
+
sanitizedTools: this.sanitizeToolsForAISDK(allTools),
|
|
1452
|
+
contextBreakdown
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Build conversation history based on configuration mode and filters
|
|
1457
|
+
*/
|
|
1458
|
+
async buildConversationHistory(contextId, taskId, userMessage, streamRequestId, initialContextBreakdown) {
|
|
1459
|
+
let conversationHistory = "";
|
|
1460
|
+
const historyConfig = this.config.conversationHistoryConfig ?? createDefaultConversationHistoryConfig();
|
|
1461
|
+
if (historyConfig && historyConfig.mode !== "none") {
|
|
1462
|
+
if (historyConfig.mode === "full") {
|
|
1463
|
+
const filters = {
|
|
1464
|
+
delegationId: this.delegationId,
|
|
1465
|
+
isDelegated: this.isDelegatedAgent
|
|
1466
|
+
};
|
|
1467
|
+
conversationHistory = await getConversationHistoryWithCompression({
|
|
1468
|
+
tenantId: this.config.tenantId,
|
|
1469
|
+
projectId: this.config.projectId,
|
|
1470
|
+
conversationId: contextId,
|
|
1471
|
+
currentMessage: userMessage,
|
|
1472
|
+
options: historyConfig,
|
|
1473
|
+
filters,
|
|
1474
|
+
summarizerModel: this.getSummarizerModel(),
|
|
1475
|
+
streamRequestId,
|
|
1476
|
+
fullContextSize: initialContextBreakdown.total
|
|
1477
|
+
});
|
|
1478
|
+
} else if (historyConfig.mode === "scoped") conversationHistory = await getConversationHistoryWithCompression({
|
|
1479
|
+
tenantId: this.config.tenantId,
|
|
1480
|
+
projectId: this.config.projectId,
|
|
1481
|
+
conversationId: contextId,
|
|
1482
|
+
currentMessage: userMessage,
|
|
1483
|
+
options: historyConfig,
|
|
1484
|
+
filters: {
|
|
1485
|
+
subAgentId: this.config.id,
|
|
1486
|
+
taskId,
|
|
1487
|
+
delegationId: this.delegationId,
|
|
1488
|
+
isDelegated: this.isDelegatedAgent
|
|
1489
|
+
},
|
|
1490
|
+
summarizerModel: this.getSummarizerModel(),
|
|
1491
|
+
streamRequestId,
|
|
1492
|
+
fullContextSize: initialContextBreakdown.total
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
const conversationHistoryTokens = estimateTokens(conversationHistory);
|
|
1496
|
+
const updatedContextBreakdown = {
|
|
1497
|
+
...initialContextBreakdown,
|
|
1498
|
+
conversationHistory: conversationHistoryTokens
|
|
1499
|
+
};
|
|
1500
|
+
calculateBreakdownTotal(updatedContextBreakdown);
|
|
1501
|
+
return {
|
|
1502
|
+
conversationHistory,
|
|
1503
|
+
contextBreakdown: updatedContextBreakdown
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Configure model settings, timeouts, and streaming behavior
|
|
1508
|
+
*/
|
|
1509
|
+
configureModelSettings() {
|
|
1510
|
+
const primaryModelSettings = this.getPrimaryModel();
|
|
1511
|
+
const modelSettings = ModelFactory.prepareGenerationConfig(primaryModelSettings);
|
|
1512
|
+
const hasStructuredOutput = Boolean(this.config.dataComponents && this.config.dataComponents.length > 0);
|
|
1513
|
+
const shouldStreamPhase1 = this.getStreamingHelper() && !hasStructuredOutput;
|
|
1514
|
+
const configuredTimeout = modelSettings.maxDuration ? Math.min(modelSettings.maxDuration * 1e3, LLM_GENERATION_MAX_ALLOWED_TIMEOUT_MS) : shouldStreamPhase1 ? LLM_GENERATION_FIRST_CALL_TIMEOUT_MS_STREAMING : LLM_GENERATION_FIRST_CALL_TIMEOUT_MS_NON_STREAMING;
|
|
1515
|
+
const timeoutMs = Math.min(configuredTimeout, LLM_GENERATION_MAX_ALLOWED_TIMEOUT_MS);
|
|
1516
|
+
if (modelSettings.maxDuration && modelSettings.maxDuration * 1e3 > LLM_GENERATION_MAX_ALLOWED_TIMEOUT_MS) logger.warn({
|
|
1517
|
+
requestedTimeout: modelSettings.maxDuration * 1e3,
|
|
1518
|
+
appliedTimeout: timeoutMs,
|
|
1519
|
+
maxAllowed: LLM_GENERATION_MAX_ALLOWED_TIMEOUT_MS
|
|
1520
|
+
}, "Requested timeout exceeded maximum allowed, capping to 10 minutes");
|
|
1521
|
+
return {
|
|
1522
|
+
primaryModelSettings,
|
|
1523
|
+
modelSettings: {
|
|
1524
|
+
...modelSettings,
|
|
1525
|
+
maxDuration: timeoutMs / 1e3
|
|
1526
|
+
},
|
|
1527
|
+
hasStructuredOutput,
|
|
1528
|
+
shouldStreamPhase1,
|
|
1529
|
+
timeoutMs
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Build initial messages array with system prompt and user content
|
|
1534
|
+
*/
|
|
1535
|
+
buildInitialMessages(systemPrompt, thinkingSystemPrompt, hasStructuredOutput, conversationHistory, userMessage) {
|
|
1536
|
+
const phase1SystemPrompt = hasStructuredOutput ? thinkingSystemPrompt : systemPrompt;
|
|
1537
|
+
const messages = [];
|
|
1538
|
+
messages.push({
|
|
1539
|
+
role: "system",
|
|
1540
|
+
content: phase1SystemPrompt
|
|
1541
|
+
});
|
|
1542
|
+
if (conversationHistory.trim() !== "") messages.push({
|
|
1543
|
+
role: "user",
|
|
1544
|
+
content: conversationHistory
|
|
1545
|
+
});
|
|
1546
|
+
messages.push({
|
|
1547
|
+
role: "user",
|
|
1548
|
+
content: userMessage
|
|
1549
|
+
});
|
|
1550
|
+
return messages;
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Setup compression for the current generation
|
|
1554
|
+
*/
|
|
1555
|
+
setupCompression(messages, sessionId, contextId, primaryModelSettings) {
|
|
1556
|
+
const originalMessageCount = messages.length;
|
|
1557
|
+
const compressionConfigResult = getCompressionConfigForModel(primaryModelSettings);
|
|
1558
|
+
const compressionConfig = {
|
|
1559
|
+
hardLimit: compressionConfigResult.hardLimit,
|
|
1560
|
+
safetyBuffer: compressionConfigResult.safetyBuffer,
|
|
1561
|
+
enabled: compressionConfigResult.enabled
|
|
1562
|
+
};
|
|
1563
|
+
const compressor = compressionConfig.enabled ? new MidGenerationCompressor(sessionId, contextId, this.config.tenantId, this.config.projectId, compressionConfig, this.getSummarizerModel(), primaryModelSettings) : null;
|
|
1564
|
+
this.currentCompressor = compressor;
|
|
1565
|
+
return {
|
|
1566
|
+
originalMessageCount,
|
|
1567
|
+
compressor
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Prepare step function for streaming with compression logic
|
|
1572
|
+
*/
|
|
1573
|
+
async handlePrepareStepCompression(stepMessages, compressor, originalMessageCount, fullContextSize) {
|
|
1574
|
+
if (!compressor) return {};
|
|
1575
|
+
if (compressor.isCompressionNeeded(stepMessages)) {
|
|
1576
|
+
logger.info({ compressorState: compressor.getState() }, "Triggering layered mid-generation compression");
|
|
1577
|
+
const originalMessages = stepMessages.slice(0, originalMessageCount);
|
|
1578
|
+
const generatedMessages = stepMessages.slice(originalMessageCount);
|
|
1579
|
+
if (generatedMessages.length > 0) {
|
|
1580
|
+
const compressionResult = await compressor.safeCompress(generatedMessages, fullContextSize);
|
|
1581
|
+
if (Array.isArray(compressionResult.summary)) {
|
|
1582
|
+
const compressedMessages = compressionResult.summary;
|
|
1583
|
+
logger.info({
|
|
1584
|
+
originalTotal: stepMessages.length,
|
|
1585
|
+
compressed: originalMessages.length + compressedMessages.length,
|
|
1586
|
+
originalKept: originalMessages.length,
|
|
1587
|
+
generatedCompressed: compressedMessages.length
|
|
1588
|
+
}, "Simple compression fallback applied");
|
|
1589
|
+
return { messages: [...originalMessages, ...compressedMessages] };
|
|
1590
|
+
}
|
|
1591
|
+
const finalMessages = [...originalMessages];
|
|
1592
|
+
if (compressionResult.summary.text_messages && compressionResult.summary.text_messages.length > 0) finalMessages.push(...compressionResult.summary.text_messages);
|
|
1593
|
+
const summaryData = {
|
|
1594
|
+
high_level: compressionResult.summary?.high_level,
|
|
1595
|
+
user_intent: compressionResult.summary?.user_intent,
|
|
1596
|
+
decisions: compressionResult.summary?.decisions,
|
|
1597
|
+
open_questions: compressionResult.summary?.open_questions,
|
|
1598
|
+
next_steps: compressionResult.summary?.next_steps,
|
|
1599
|
+
related_artifacts: compressionResult.summary?.related_artifacts
|
|
1600
|
+
};
|
|
1601
|
+
if (summaryData.related_artifacts && summaryData.related_artifacts.length > 0) summaryData.related_artifacts = summaryData.related_artifacts.map((artifact) => ({
|
|
1602
|
+
...artifact,
|
|
1603
|
+
artifact_reference: `<artifact:ref id="${artifact.id}" tool="${artifact.tool_call_id}" />`
|
|
1604
|
+
}));
|
|
1605
|
+
const summaryMessage = JSON.stringify(summaryData);
|
|
1606
|
+
finalMessages.push({
|
|
1607
|
+
role: "user",
|
|
1608
|
+
content: `Based on your research, here's what you've discovered: ${summaryMessage}
|
|
1609
|
+
|
|
1610
|
+
**IMPORTANT**: If you have enough information from this compressed research to answer my original question, please provide your answer now. Only continue with additional tool calls if you need critical missing information that wasn't captured in the research above. When referencing any artifacts from the compressed research, you MUST use <artifact:ref id="artifact_id" tool="tool_call_id" /> tags with the exact IDs from the related_artifacts above.`
|
|
1611
|
+
});
|
|
1612
|
+
logger.info({
|
|
1613
|
+
originalTotal: stepMessages.length,
|
|
1614
|
+
compressed: finalMessages.length,
|
|
1615
|
+
originalKept: originalMessages.length,
|
|
1616
|
+
generatedCompressed: generatedMessages.length
|
|
1617
|
+
}, "AI compression completed successfully");
|
|
1618
|
+
return { messages: finalMessages };
|
|
1619
|
+
}
|
|
1620
|
+
return {};
|
|
1621
|
+
}
|
|
1622
|
+
return {};
|
|
1623
|
+
}
|
|
1624
|
+
async handleStopWhenConditions(steps, includeThinkingComplete = false) {
|
|
1625
|
+
const last = steps.at(-1);
|
|
1626
|
+
if (last && "text" in last && last.text) try {
|
|
1627
|
+
await agentSessionManager.recordEvent(this.getStreamRequestId(), "agent_reasoning", this.config.id, { parts: [{
|
|
1628
|
+
type: "text",
|
|
1629
|
+
content: last.text
|
|
1630
|
+
}] });
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
logger.debug({ error }, "Failed to track agent reasoning");
|
|
1633
|
+
}
|
|
1634
|
+
if (!includeThinkingComplete && last && last["content"] && last["content"].length > 0) {
|
|
1635
|
+
const lastContent = last["content"][last["content"].length - 1];
|
|
1636
|
+
if (lastContent["type"] === "tool-error") {
|
|
1637
|
+
const error = lastContent["error"];
|
|
1638
|
+
if (error && typeof error === "object" && "name" in error && error.name === "connection_refused") return true;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (steps.length >= 2) {
|
|
1642
|
+
const previousStep = steps[steps.length - 2];
|
|
1643
|
+
if (previousStep && "toolCalls" in previousStep && previousStep.toolCalls) {
|
|
1644
|
+
const stopToolNames = includeThinkingComplete ? ["transfer_to_", "thinking_complete"] : ["transfer_to_"];
|
|
1645
|
+
if (previousStep.toolCalls.some((tc) => stopToolNames.some((toolName) => toolName.endsWith("_") ? tc.toolName.startsWith(toolName) : tc.toolName === toolName)) && "toolResults" in previousStep && previousStep.toolResults) return true;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
return steps.length >= this.getMaxGenerationSteps();
|
|
1649
|
+
}
|
|
1650
|
+
setupStreamParser(sessionId, contextId) {
|
|
1651
|
+
const streamHelper = this.getStreamingHelper();
|
|
1652
|
+
if (!streamHelper) throw new Error("Stream helper is unexpectedly undefined in streaming context");
|
|
1653
|
+
const session = toolSessionManager.getSession(sessionId);
|
|
1654
|
+
const artifactParserOptions = {
|
|
1655
|
+
sessionId,
|
|
1656
|
+
taskId: session?.taskId,
|
|
1657
|
+
projectId: session?.projectId,
|
|
1658
|
+
artifactComponents: this.artifactComponents,
|
|
1659
|
+
streamRequestId: this.getStreamRequestId(),
|
|
1660
|
+
subAgentId: this.config.id
|
|
1661
|
+
};
|
|
1662
|
+
return new IncrementalStreamParser(streamHelper, this.config.tenantId, contextId, artifactParserOptions);
|
|
1663
|
+
}
|
|
1664
|
+
buildTelemetryConfig(phase) {
|
|
1665
|
+
return {
|
|
1666
|
+
isEnabled: true,
|
|
1667
|
+
functionId: this.config.id,
|
|
1668
|
+
recordInputs: true,
|
|
1669
|
+
recordOutputs: true,
|
|
1670
|
+
metadata: {
|
|
1671
|
+
...phase && { phase },
|
|
1672
|
+
subAgentId: this.config.id,
|
|
1673
|
+
subAgentName: this.config.name
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
buildBaseGenerationConfig(modelSettings, messages, sanitizedTools, compressor, originalMessageCount, timeoutMs, toolChoice = "auto", phase, includeThinkingComplete = false, fullContextSize) {
|
|
1678
|
+
return {
|
|
1679
|
+
...modelSettings,
|
|
1680
|
+
toolChoice,
|
|
1681
|
+
messages,
|
|
1682
|
+
tools: sanitizedTools,
|
|
1683
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
1684
|
+
return await this.handlePrepareStepCompression(stepMessages, compressor, originalMessageCount, fullContextSize);
|
|
1685
|
+
},
|
|
1686
|
+
stopWhen: async ({ steps }) => {
|
|
1687
|
+
return await this.handleStopWhenConditions(steps, includeThinkingComplete);
|
|
1688
|
+
},
|
|
1689
|
+
experimental_telemetry: this.buildTelemetryConfig(phase),
|
|
1690
|
+
abortSignal: AbortSignal.timeout(timeoutMs)
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
buildReasoningFlow(response, sessionId) {
|
|
1694
|
+
const reasoningFlow = [];
|
|
1695
|
+
const compressionSummary = this.currentCompressor?.getCompressionSummary();
|
|
1696
|
+
if (compressionSummary) {
|
|
1697
|
+
const summaryContent = JSON.stringify(compressionSummary, null, 2);
|
|
1698
|
+
reasoningFlow.push({
|
|
1699
|
+
role: "assistant",
|
|
1700
|
+
content: `## Research Summary (Compressed)\n\nBased on tool executions, here's the comprehensive summary:\n\n\`\`\`json\n${summaryContent}\n\`\`\`\n\nThis summary represents all tool execution results in compressed form. Full details are preserved in artifacts.`
|
|
1701
|
+
});
|
|
1702
|
+
} else if (response.steps) response.steps.forEach((step) => {
|
|
1703
|
+
if (step.toolCalls && step.toolResults) step.toolCalls.forEach((call, index) => {
|
|
1704
|
+
const result = step.toolResults[index];
|
|
1705
|
+
if (result) {
|
|
1706
|
+
const storedResult = toolSessionManager.getToolResult(sessionId, result.toolCallId);
|
|
1707
|
+
if ((storedResult?.toolName || call.toolName) === "thinking_complete") return;
|
|
1708
|
+
const actualResult = storedResult?.result || result.result || result;
|
|
1709
|
+
const actualArgs = storedResult?.args || call.args;
|
|
1710
|
+
const cleanResult = actualResult && typeof actualResult === "object" && !Array.isArray(actualResult) ? Object.fromEntries(Object.entries(actualResult).filter(([key]) => key !== "_structureHints")) : actualResult;
|
|
1711
|
+
const input = actualArgs ? JSON.stringify(actualArgs, null, 2) : "No input";
|
|
1712
|
+
const output = typeof cleanResult === "string" ? cleanResult : JSON.stringify(cleanResult, null, 2);
|
|
1713
|
+
let structureHintsFormatted = "";
|
|
1714
|
+
if (actualResult?._structureHints && this.artifactComponents && this.artifactComponents.length > 0) {
|
|
1715
|
+
const hints = actualResult._structureHints;
|
|
1716
|
+
structureHintsFormatted = `
|
|
1717
|
+
### 📊 Structure Hints for Artifact Creation
|
|
1718
|
+
|
|
1719
|
+
**Terminal Field Paths (${hints.terminalPaths?.length || 0} found):**
|
|
1720
|
+
${hints.terminalPaths?.map((path) => ` • ${path}`).join("\n") || " None detected"}
|
|
1721
|
+
|
|
1722
|
+
**Array Structures (${hints.arrayPaths?.length || 0} found):**
|
|
1723
|
+
${hints.arrayPaths?.map((path) => ` • ${path}`).join("\n") || " None detected"}
|
|
1724
|
+
|
|
1725
|
+
**Object Structures (${hints.objectPaths?.length || 0} found):**
|
|
1726
|
+
${hints.objectPaths?.map((path) => ` • ${path}`).join("\n") || " None detected"}
|
|
1727
|
+
|
|
1728
|
+
**Example Selectors:**
|
|
1729
|
+
${hints.exampleSelectors?.map((sel) => ` • ${sel}`).join("\n") || " None detected"}
|
|
1730
|
+
|
|
1731
|
+
**Common Fields:**
|
|
1732
|
+
${hints.commonFields?.map((field) => ` • ${field}`).join("\n") || " None detected"}
|
|
1733
|
+
|
|
1734
|
+
**Structure Stats:** ${hints.totalPathsFound || 0} total paths, ${hints.maxDepthFound || 0} levels deep
|
|
1735
|
+
|
|
1736
|
+
**Note:** ${hints.note || "Use these paths for artifact base selectors."}
|
|
1737
|
+
|
|
1738
|
+
**Forbidden Syntax:** ${hints.forbiddenSyntax || "Use these paths for artifact base selectors."}
|
|
1739
|
+
`;
|
|
1740
|
+
}
|
|
1741
|
+
const formattedResult = `## Tool: ${call.toolName}
|
|
1742
|
+
|
|
1743
|
+
### 🔧 TOOL_CALL_ID: ${result.toolCallId}
|
|
1744
|
+
|
|
1745
|
+
### Input
|
|
1746
|
+
${input}
|
|
1747
|
+
|
|
1748
|
+
### Output
|
|
1749
|
+
${output}${structureHintsFormatted}`;
|
|
1750
|
+
reasoningFlow.push({
|
|
1751
|
+
role: "assistant",
|
|
1752
|
+
content: formattedResult
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
});
|
|
1756
|
+
});
|
|
1757
|
+
return reasoningFlow;
|
|
1758
|
+
}
|
|
1759
|
+
buildDataComponentsSchema() {
|
|
1760
|
+
const componentSchemas = [];
|
|
1761
|
+
if (this.config.dataComponents && this.config.dataComponents.length > 0) this.config.dataComponents.forEach((dc) => {
|
|
1762
|
+
const propsSchema = jsonSchemaToZod(dc.props);
|
|
1763
|
+
componentSchemas.push(z.object({
|
|
1764
|
+
id: z.string(),
|
|
1765
|
+
name: z.literal(dc.name),
|
|
1766
|
+
props: propsSchema
|
|
1767
|
+
}));
|
|
1768
|
+
});
|
|
1769
|
+
if (this.artifactComponents.length > 0) {
|
|
1770
|
+
const artifactCreateSchemas = ArtifactCreateSchema.getSchemas(this.artifactComponents);
|
|
1771
|
+
componentSchemas.push(...artifactCreateSchemas);
|
|
1772
|
+
componentSchemas.push(ArtifactReferenceSchema.getSchema());
|
|
1773
|
+
}
|
|
1774
|
+
let dataComponentsSchema;
|
|
1775
|
+
if (componentSchemas.length === 1) dataComponentsSchema = componentSchemas[0];
|
|
1776
|
+
else dataComponentsSchema = z.union(componentSchemas);
|
|
1777
|
+
return dataComponentsSchema;
|
|
1778
|
+
}
|
|
1779
|
+
calculatePhase2Timeout(structuredModelSettings) {
|
|
1780
|
+
const configuredPhase2Timeout = structuredModelSettings.maxDuration ? Math.min(structuredModelSettings.maxDuration * 1e3, LLM_GENERATION_MAX_ALLOWED_TIMEOUT_MS) : LLM_GENERATION_SUBSEQUENT_CALL_TIMEOUT_MS;
|
|
1781
|
+
const phase2TimeoutMs = Math.min(configuredPhase2Timeout, LLM_GENERATION_MAX_ALLOWED_TIMEOUT_MS);
|
|
1782
|
+
if (structuredModelSettings.maxDuration && structuredModelSettings.maxDuration * 1e3 > LLM_GENERATION_MAX_ALLOWED_TIMEOUT_MS) logger.warn({
|
|
1783
|
+
requestedTimeout: structuredModelSettings.maxDuration * 1e3,
|
|
1784
|
+
appliedTimeout: phase2TimeoutMs,
|
|
1785
|
+
maxAllowed: LLM_GENERATION_MAX_ALLOWED_TIMEOUT_MS,
|
|
1786
|
+
phase: "structured_generation"
|
|
1787
|
+
}, "Phase 2 requested timeout exceeded maximum allowed, capping to 10 minutes");
|
|
1788
|
+
return phase2TimeoutMs;
|
|
1789
|
+
}
|
|
1790
|
+
async buildPhase2Messages(runtimeContext, conversationHistory, userMessage, reasoningFlow) {
|
|
1791
|
+
const phase2Messages = [{
|
|
1792
|
+
role: "system",
|
|
1793
|
+
content: await this.buildPhase2SystemPrompt(runtimeContext)
|
|
1794
|
+
}];
|
|
1795
|
+
if (conversationHistory.trim() !== "") phase2Messages.push({
|
|
1796
|
+
role: "user",
|
|
1797
|
+
content: conversationHistory
|
|
1798
|
+
});
|
|
1799
|
+
phase2Messages.push({
|
|
1800
|
+
role: "user",
|
|
1801
|
+
content: userMessage
|
|
1802
|
+
});
|
|
1803
|
+
phase2Messages.push(...reasoningFlow);
|
|
1804
|
+
if (reasoningFlow.length > 0 && reasoningFlow[reasoningFlow.length - 1]?.role === "assistant") phase2Messages.push({
|
|
1805
|
+
role: "user",
|
|
1806
|
+
content: "Continue with the structured response."
|
|
1807
|
+
});
|
|
1808
|
+
return phase2Messages;
|
|
1809
|
+
}
|
|
1810
|
+
async executeStreamingPhase2(structuredModelSettings, phase2Messages, dataComponentsSchema, phase2TimeoutMs, sessionId, contextId, response) {
|
|
1811
|
+
const streamResult = streamObject({
|
|
1812
|
+
...structuredModelSettings,
|
|
1813
|
+
messages: phase2Messages,
|
|
1814
|
+
schema: z.object({ dataComponents: z.array(dataComponentsSchema) }),
|
|
1815
|
+
experimental_telemetry: this.buildTelemetryConfig("structured_generation"),
|
|
1816
|
+
abortSignal: AbortSignal.timeout(phase2TimeoutMs)
|
|
1817
|
+
});
|
|
1818
|
+
const parser = this.setupStreamParser(sessionId, contextId);
|
|
1819
|
+
for await (const delta of streamResult.partialObjectStream) if (delta) await parser.processObjectDelta(delta);
|
|
1820
|
+
await parser.finalize();
|
|
1821
|
+
const structuredResponse = await streamResult;
|
|
1822
|
+
const collectedParts = parser.getCollectedParts();
|
|
1823
|
+
if (collectedParts.length > 0) response.formattedContent = { parts: collectedParts.map((part) => ({
|
|
1824
|
+
kind: part.kind,
|
|
1825
|
+
...part.kind === "text" && { text: part.text },
|
|
1826
|
+
...part.kind === "data" && { data: part.data }
|
|
1827
|
+
})) };
|
|
1828
|
+
return {
|
|
1829
|
+
...response,
|
|
1830
|
+
object: structuredResponse.object,
|
|
1831
|
+
textResponse: JSON.stringify(structuredResponse.object, null, 2)
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
async executeNonStreamingPhase2(structuredModelSettings, phase2Messages, dataComponentsSchema, phase2TimeoutMs, response) {
|
|
1835
|
+
const structuredResponse = await generateObject(withJsonPostProcessing({
|
|
1836
|
+
...structuredModelSettings,
|
|
1837
|
+
messages: phase2Messages,
|
|
1838
|
+
schema: z.object({ dataComponents: z.array(dataComponentsSchema) }),
|
|
1839
|
+
experimental_telemetry: this.buildTelemetryConfig("structured_generation"),
|
|
1840
|
+
abortSignal: AbortSignal.timeout(phase2TimeoutMs)
|
|
1841
|
+
}));
|
|
1842
|
+
return {
|
|
1843
|
+
...response,
|
|
1844
|
+
object: structuredResponse.object,
|
|
1845
|
+
textResponse: JSON.stringify(structuredResponse.object, null, 2)
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
async formatFinalResponse(response, textResponse, sessionId, contextId) {
|
|
1849
|
+
let formattedContent = response.formattedContent || null;
|
|
1850
|
+
if (!formattedContent) {
|
|
1851
|
+
const session = toolSessionManager.getSession(sessionId);
|
|
1852
|
+
const responseFormatter = new ResponseFormatter(this.config.tenantId, {
|
|
1853
|
+
sessionId,
|
|
1854
|
+
taskId: session?.taskId,
|
|
1855
|
+
projectId: session?.projectId,
|
|
1856
|
+
contextId,
|
|
1857
|
+
artifactComponents: this.artifactComponents,
|
|
1858
|
+
streamRequestId: this.getStreamRequestId(),
|
|
1859
|
+
subAgentId: this.config.id
|
|
1860
|
+
});
|
|
1861
|
+
if (response.object) formattedContent = await responseFormatter.formatObjectResponse(response.object, contextId);
|
|
1862
|
+
else if (textResponse) formattedContent = await responseFormatter.formatResponse(textResponse, contextId);
|
|
1863
|
+
}
|
|
1864
|
+
return {
|
|
1865
|
+
...response,
|
|
1866
|
+
formattedContent
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
handleGenerationError(error, span) {
|
|
1870
|
+
if (this.currentCompressor) this.currentCompressor.fullCleanup();
|
|
1871
|
+
this.currentCompressor = null;
|
|
1872
|
+
const errorToThrow = error instanceof Error ? error : new Error(String(error));
|
|
1873
|
+
setSpanWithError$1(span, errorToThrow);
|
|
1874
|
+
span.end();
|
|
1875
|
+
throw errorToThrow;
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Public cleanup method for external lifecycle management (e.g., session cleanup)
|
|
1879
|
+
* Performs full cleanup of compression state when agent/session is ending
|
|
1880
|
+
*/
|
|
1881
|
+
cleanupCompression() {
|
|
1882
|
+
if (this.currentCompressor) {
|
|
1883
|
+
this.currentCompressor.fullCleanup();
|
|
1884
|
+
this.currentCompressor = null;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
async processStreamEvents(streamResult, parser) {
|
|
1888
|
+
for await (const event of streamResult.fullStream) switch (event.type) {
|
|
1889
|
+
case "text-delta":
|
|
1890
|
+
await parser.processTextChunk(event.text);
|
|
1891
|
+
break;
|
|
1892
|
+
case "tool-call":
|
|
1893
|
+
parser.markToolResult();
|
|
1894
|
+
break;
|
|
1895
|
+
case "tool-result":
|
|
1896
|
+
parser.markToolResult();
|
|
1897
|
+
break;
|
|
1898
|
+
case "finish":
|
|
1899
|
+
if (event.finishReason === "tool-calls") parser.markToolResult();
|
|
1900
|
+
break;
|
|
1901
|
+
case "error": {
|
|
1902
|
+
if (event.error instanceof Error) throw event.error;
|
|
1903
|
+
const errorMessage = event.error?.error?.message;
|
|
1904
|
+
throw new Error(errorMessage);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
await parser.finalize();
|
|
1908
|
+
}
|
|
1909
|
+
formatStreamingResponse(response, parser) {
|
|
1910
|
+
const collectedParts = parser.getCollectedParts();
|
|
1911
|
+
if (collectedParts.length > 0) response.formattedContent = { parts: collectedParts.map((part) => ({
|
|
1912
|
+
kind: part.kind,
|
|
1913
|
+
...part.kind === "text" && { text: part.text },
|
|
1914
|
+
...part.kind === "data" && { data: part.data }
|
|
1915
|
+
})) };
|
|
1916
|
+
const streamedContent = parser.getAllStreamedContent();
|
|
1917
|
+
if (streamedContent.length > 0) response.streamedContent = { parts: streamedContent.map((part) => ({
|
|
1918
|
+
kind: part.kind,
|
|
1919
|
+
...part.kind === "text" && { text: part.text },
|
|
1920
|
+
...part.kind === "data" && { data: part.data }
|
|
1921
|
+
})) };
|
|
1922
|
+
return response;
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1925
|
+
|
|
1926
|
+
//#endregion
|
|
1927
|
+
export { Agent, hasToolCallWithPrefix };
|