jazz-ai 0.2.2 → 0.4.3
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/README.md +8 -6
- package/dist/cli/commands/chat-agent.d.ts +3 -1
- package/dist/cli/commands/chat-agent.d.ts.map +1 -1
- package/dist/cli/commands/chat-agent.js +180 -102
- package/dist/cli/commands/chat-agent.js.map +1 -1
- package/dist/cli/commands/edit-agent.d.ts +2 -2
- package/dist/cli/commands/edit-agent.d.ts.map +1 -1
- package/dist/cli/commands/edit-agent.js +20 -10
- package/dist/cli/commands/edit-agent.js.map +1 -1
- package/dist/cli/commands/task-agent.d.ts +1 -1
- package/dist/cli/commands/task-agent.d.ts.map +1 -1
- package/dist/cli/commands/task-agent.js +6 -6
- package/dist/cli/commands/task-agent.js.map +1 -1
- package/dist/constants/agent.d.ts +2 -0
- package/dist/constants/agent.d.ts.map +1 -0
- package/dist/constants/agent.js +5 -0
- package/dist/constants/agent.js.map +1 -0
- package/dist/core/agent/agent-prompt.d.ts +0 -1
- package/dist/core/agent/agent-prompt.d.ts.map +1 -1
- package/dist/core/agent/agent-prompt.js +7 -15
- package/dist/core/agent/agent-prompt.js.map +1 -1
- package/dist/core/agent/agent-runner.d.ts +15 -0
- package/dist/core/agent/agent-runner.d.ts.map +1 -1
- package/dist/core/agent/agent-runner.js +570 -270
- package/dist/core/agent/agent-runner.js.map +1 -1
- package/dist/core/agent/agent-service.d.ts +20 -5
- package/dist/core/agent/agent-service.d.ts.map +1 -1
- package/dist/core/agent/agent-service.js +54 -6
- package/dist/core/agent/agent-service.js.map +1 -1
- package/dist/core/agent/gmail-agent.js +7 -7
- package/dist/core/agent/gmail-agent.js.map +1 -1
- package/dist/core/agent/prompts/coder/v1.d.ts +2 -0
- package/dist/core/agent/prompts/coder/v1.d.ts.map +1 -0
- package/dist/core/agent/prompts/coder/v1.js +370 -0
- package/dist/core/agent/prompts/coder/v1.js.map +1 -0
- package/dist/core/agent/prompts/default/v2.d.ts +1 -1
- package/dist/core/agent/prompts/default/v2.d.ts.map +1 -1
- package/dist/core/agent/prompts/default/v2.js +1 -2
- package/dist/core/agent/prompts/default/v2.js.map +1 -1
- package/dist/core/agent/prompts/gmail/v1.d.ts +1 -1
- package/dist/core/agent/prompts/gmail/v1.d.ts.map +1 -1
- package/dist/core/agent/prompts/gmail/v1.js +5 -5
- package/dist/core/agent/prompts/gmail/v2.d.ts +1 -1
- package/dist/core/agent/prompts/gmail/v2.d.ts.map +1 -1
- package/dist/core/agent/prompts/gmail/v2.js +7 -7
- package/dist/core/agent/tools/base-tool.d.ts +3 -4
- package/dist/core/agent/tools/base-tool.d.ts.map +1 -1
- package/dist/core/agent/tools/base-tool.js +11 -51
- package/dist/core/agent/tools/base-tool.js.map +1 -1
- package/dist/core/agent/tools/context-utils.d.ts +6 -0
- package/dist/core/agent/tools/context-utils.d.ts.map +1 -0
- package/dist/core/agent/tools/context-utils.js +9 -0
- package/dist/core/agent/tools/context-utils.js.map +1 -0
- package/dist/core/agent/tools/env-utils.d.ts +7 -0
- package/dist/core/agent/tools/env-utils.d.ts.map +1 -0
- package/dist/core/agent/tools/env-utils.js +39 -0
- package/dist/core/agent/tools/env-utils.js.map +1 -0
- package/dist/core/agent/tools/fs-tools.d.ts.map +1 -1
- package/dist/core/agent/tools/fs-tools.js +48 -53
- package/dist/core/agent/tools/fs-tools.js.map +1 -1
- package/dist/core/agent/tools/git-tools.d.ts +5 -0
- package/dist/core/agent/tools/git-tools.d.ts.map +1 -1
- package/dist/core/agent/tools/git-tools.js +716 -154
- package/dist/core/agent/tools/git-tools.js.map +1 -1
- package/dist/core/agent/tools/gmail-tools.js +35 -35
- package/dist/core/agent/tools/gmail-tools.js.map +1 -1
- package/dist/core/agent/tools/http-tools.js +2 -2
- package/dist/core/agent/tools/http-tools.js.map +1 -1
- package/dist/core/agent/tools/register-tools.d.ts +18 -1
- package/dist/core/agent/tools/register-tools.d.ts.map +1 -1
- package/dist/core/agent/tools/register-tools.js +52 -6
- package/dist/core/agent/tools/register-tools.js.map +1 -1
- package/dist/core/agent/tools/shell-tools.d.ts.map +1 -1
- package/dist/core/agent/tools/shell-tools.js +10 -20
- package/dist/core/agent/tools/shell-tools.js.map +1 -1
- package/dist/core/agent/tools/tool-registry.d.ts +11 -7
- package/dist/core/agent/tools/tool-registry.d.ts.map +1 -1
- package/dist/core/agent/tools/tool-registry.js +45 -19
- package/dist/core/agent/tools/tool-registry.js.map +1 -1
- package/dist/core/agent/tools/web-search-tools.js +1 -1
- package/dist/core/agent/tools/web-search-tools.js.map +1 -1
- package/dist/core/agent/tracking/agent-run-tracker.d.ts +2 -0
- package/dist/core/agent/tracking/agent-run-tracker.d.ts.map +1 -1
- package/dist/core/agent/tracking/agent-run-tracker.js +40 -2
- package/dist/core/agent/tracking/agent-run-tracker.js.map +1 -1
- package/dist/core/types/index.d.ts +82 -15
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/core/types/index.js.map +1 -1
- package/dist/core/utils/markdown-renderer.d.ts +25 -0
- package/dist/core/utils/markdown-renderer.d.ts.map +1 -1
- package/dist/core/utils/markdown-renderer.js +100 -1
- package/dist/core/utils/markdown-renderer.js.map +1 -1
- package/dist/core/utils/output-renderer.d.ts +81 -0
- package/dist/core/utils/output-renderer.d.ts.map +1 -0
- package/dist/core/utils/output-renderer.js +291 -0
- package/dist/core/utils/output-renderer.js.map +1 -0
- package/dist/core/utils/output-theme.d.ts +64 -0
- package/dist/core/utils/output-theme.d.ts.map +1 -0
- package/dist/core/utils/output-theme.js +154 -0
- package/dist/core/utils/output-theme.js.map +1 -0
- package/dist/core/utils/output-writer.d.ts +79 -0
- package/dist/core/utils/output-writer.d.ts.map +1 -0
- package/dist/core/utils/output-writer.js +133 -0
- package/dist/core/utils/output-writer.js.map +1 -0
- package/dist/core/utils/runtime-detection.d.ts +28 -0
- package/dist/core/utils/runtime-detection.d.ts.map +1 -0
- package/dist/core/utils/runtime-detection.js +62 -0
- package/dist/core/utils/runtime-detection.js.map +1 -0
- package/dist/core/utils/string.d.ts +8 -0
- package/dist/core/utils/string.d.ts.map +1 -0
- package/dist/core/utils/string.js +19 -0
- package/dist/core/utils/string.js.map +1 -0
- package/dist/core/utils/thinking-renderer.d.ts +56 -0
- package/dist/core/utils/thinking-renderer.d.ts.map +1 -0
- package/dist/core/utils/thinking-renderer.js +174 -0
- package/dist/core/utils/thinking-renderer.js.map +1 -0
- package/dist/core/utils/tool-formatter.d.ts +21 -0
- package/dist/core/utils/tool-formatter.d.ts.map +1 -0
- package/dist/core/utils/tool-formatter.js +343 -0
- package/dist/core/utils/tool-formatter.js.map +1 -0
- package/dist/main.js +11 -4
- package/dist/main.js.map +1 -1
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +20 -20
- package/dist/services/config.js.map +1 -1
- package/dist/services/llm/ai-sdk-service.d.ts.map +1 -1
- package/dist/services/llm/ai-sdk-service.js +128 -37
- package/dist/services/llm/ai-sdk-service.js.map +1 -1
- package/dist/services/llm/stream-detector.d.ts +44 -0
- package/dist/services/llm/stream-detector.d.ts.map +1 -0
- package/dist/services/llm/stream-detector.js +81 -0
- package/dist/services/llm/stream-detector.js.map +1 -0
- package/dist/services/llm/stream-processor.d.ts +93 -0
- package/dist/services/llm/stream-processor.d.ts.map +1 -0
- package/dist/services/llm/stream-processor.js +330 -0
- package/dist/services/llm/stream-processor.js.map +1 -0
- package/dist/services/llm/streaming-types.d.ts +131 -0
- package/dist/services/llm/streaming-types.d.ts.map +1 -0
- package/dist/services/llm/streaming-types.js +12 -0
- package/dist/services/llm/streaming-types.js.map +1 -0
- package/dist/services/llm/types.d.ts +8 -0
- package/dist/services/llm/types.d.ts.map +1 -1
- package/dist/services/llm/types.js.map +1 -1
- package/dist/services/logger.d.ts +4 -4
- package/dist/services/logger.d.ts.map +1 -1
- package/dist/services/logger.js +23 -45
- package/dist/services/logger.js.map +1 -1
- package/dist/services/shell.d.ts.map +1 -1
- package/dist/services/shell.js +52 -18
- package/dist/services/shell.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,87 +2,367 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AgentRunner = void 0;
|
|
4
4
|
exports.executeTool = executeTool;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
5
7
|
const effect_1 = require("effect");
|
|
8
|
+
const agent_1 = require("../../constants/agent");
|
|
6
9
|
const config_1 = require("../../services/config");
|
|
10
|
+
const stream_detector_1 = require("../../services/llm/stream-detector");
|
|
7
11
|
const types_1 = require("../../services/llm/types");
|
|
8
12
|
const logger_1 = require("../../services/logger");
|
|
9
13
|
const markdown_renderer_1 = require("../utils/markdown-renderer");
|
|
14
|
+
const output_renderer_1 = require("../utils/output-renderer");
|
|
15
|
+
const tool_formatter_1 = require("../utils/tool-formatter");
|
|
10
16
|
const agent_prompt_1 = require("./agent-prompt");
|
|
11
17
|
const tool_registry_1 = require("./tools/tool-registry");
|
|
12
18
|
const agent_run_tracker_1 = require("./tracking/agent-run-tracker");
|
|
13
19
|
const tool_config_1 = require("./utils/tool-config");
|
|
20
|
+
const MAX_MESSAGES = 100;
|
|
21
|
+
const MAX_RETRIES = 3;
|
|
22
|
+
const STREAM_CREATION_TIMEOUT = effect_1.Duration.minutes(2);
|
|
23
|
+
const DEFERRED_RESPONSE_TIMEOUT = effect_1.Duration.seconds(15);
|
|
24
|
+
/**
|
|
25
|
+
* Default display configuration (applies to both modes)
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_DISPLAY_CONFIG = {
|
|
28
|
+
showThinking: true,
|
|
29
|
+
showToolExecution: true,
|
|
30
|
+
mode: "normal",
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Initialize common agent run context (tools, messages, tracker)
|
|
34
|
+
*/
|
|
35
|
+
function initializeAgentRun(options) {
|
|
36
|
+
return effect_1.Effect.gen(function* () {
|
|
37
|
+
const { agent, userInput, conversationId, userId } = options;
|
|
38
|
+
const toolRegistry = yield* tool_registry_1.ToolRegistryTag;
|
|
39
|
+
const actualConversationId = conversationId || `conv-${Date.now()}`;
|
|
40
|
+
const history = options.conversationHistory || [];
|
|
41
|
+
const agentType = agent.config.agentType;
|
|
42
|
+
const provider = agent.config.llmProvider;
|
|
43
|
+
const model = agent.config.llmModel;
|
|
44
|
+
const runTracker = (0, agent_run_tracker_1.createAgentRunTracker)({
|
|
45
|
+
agent,
|
|
46
|
+
conversationId: actualConversationId,
|
|
47
|
+
...(userId ? { userId } : {}),
|
|
48
|
+
provider,
|
|
49
|
+
model,
|
|
50
|
+
reasoningEffort: agent.config.reasoningEffort ?? "disable",
|
|
51
|
+
maxIterations: options.maxIterations ?? agent_1.MAX_AGENT_STEPS,
|
|
52
|
+
});
|
|
53
|
+
// Get and validate tools
|
|
54
|
+
const allToolNames = yield* toolRegistry.listTools();
|
|
55
|
+
const agentToolNames = (0, tool_config_1.normalizeToolConfig)(agent.config.tools, {
|
|
56
|
+
agentId: agent.id,
|
|
57
|
+
});
|
|
58
|
+
const invalidTools = agentToolNames.filter((toolName) => !allToolNames.includes(toolName));
|
|
59
|
+
if (invalidTools.length > 0) {
|
|
60
|
+
return yield* effect_1.Effect.fail(new Error(`Agent ${agent.id} references non-existent tools: ${invalidTools.join(", ")}`));
|
|
61
|
+
}
|
|
62
|
+
// Expand tool names to include approval execute tools
|
|
63
|
+
const expandedToolNameSet = new Set(agentToolNames);
|
|
64
|
+
for (const toolName of agentToolNames) {
|
|
65
|
+
const tool = yield* toolRegistry.getTool(toolName);
|
|
66
|
+
if (tool.approvalExecuteToolName) {
|
|
67
|
+
expandedToolNameSet.add(tool.approvalExecuteToolName);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const expandedToolNames = Array.from(expandedToolNameSet);
|
|
71
|
+
const allTools = yield* toolRegistry.getToolDefinitions();
|
|
72
|
+
const tools = Array.from(allTools.filter((tool) => expandedToolNames.includes(tool.function.name)));
|
|
73
|
+
// Build tool descriptions map
|
|
74
|
+
const availableTools = {};
|
|
75
|
+
for (const tool of tools) {
|
|
76
|
+
availableTools[tool.function.name] = tool.function.description;
|
|
77
|
+
}
|
|
78
|
+
// Build messages
|
|
79
|
+
const messages = yield* agent_prompt_1.agentPromptBuilder.buildAgentMessages(agentType, {
|
|
80
|
+
agentName: agent.name,
|
|
81
|
+
agentDescription: agent.description || "",
|
|
82
|
+
userInput,
|
|
83
|
+
conversationHistory: history,
|
|
84
|
+
toolNames: expandedToolNames,
|
|
85
|
+
availableTools,
|
|
86
|
+
});
|
|
87
|
+
const context = {
|
|
88
|
+
agentId: agent.id,
|
|
89
|
+
conversationId: actualConversationId,
|
|
90
|
+
...(userId ? { userId } : {}),
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
agent,
|
|
94
|
+
actualConversationId,
|
|
95
|
+
context,
|
|
96
|
+
tools,
|
|
97
|
+
expandedToolNames,
|
|
98
|
+
messages,
|
|
99
|
+
runTracker,
|
|
100
|
+
provider,
|
|
101
|
+
model,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Execute a single tool call and return result
|
|
107
|
+
*/
|
|
108
|
+
function executeToolCall(toolCall, context, displayConfig, renderer, runTracker, logger, agentId, conversationId) {
|
|
109
|
+
return effect_1.Effect.gen(function* () {
|
|
110
|
+
if (toolCall.type !== "function") {
|
|
111
|
+
return { result: null, success: false };
|
|
112
|
+
}
|
|
113
|
+
const { name, arguments: argsString } = toolCall.function;
|
|
114
|
+
(0, agent_run_tracker_1.recordToolInvocation)(runTracker, name);
|
|
115
|
+
const toolStartTime = Date.now();
|
|
116
|
+
try {
|
|
117
|
+
// Parse arguments
|
|
118
|
+
let parsed;
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(argsString);
|
|
121
|
+
}
|
|
122
|
+
catch (parseError) {
|
|
123
|
+
throw new Error(`Invalid JSON in tool arguments: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
124
|
+
}
|
|
125
|
+
const args = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
126
|
+
? parsed
|
|
127
|
+
: {};
|
|
128
|
+
// Emit tool execution start
|
|
129
|
+
if (displayConfig.showToolExecution) {
|
|
130
|
+
if (renderer) {
|
|
131
|
+
yield* renderer.handleEvent({
|
|
132
|
+
type: "tool_execution_start",
|
|
133
|
+
toolName: name,
|
|
134
|
+
toolCallId: toolCall.id,
|
|
135
|
+
arguments: args,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const argsStr = output_renderer_1.OutputRenderer.formatToolArguments(name, args);
|
|
140
|
+
process.stdout.write(`\n${chalk_1.default.cyan("⚙️")} Executing tool: ${chalk_1.default.cyan(name)}${argsStr}...`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Execute tool
|
|
144
|
+
const result = yield* executeTool(name, args, context);
|
|
145
|
+
const toolDuration = Date.now() - toolStartTime;
|
|
146
|
+
const resultString = JSON.stringify(result.result);
|
|
147
|
+
// Emit tool execution complete
|
|
148
|
+
if (displayConfig.showToolExecution) {
|
|
149
|
+
if (renderer) {
|
|
150
|
+
yield* renderer.handleEvent({
|
|
151
|
+
type: "tool_execution_complete",
|
|
152
|
+
toolCallId: toolCall.id,
|
|
153
|
+
result: resultString,
|
|
154
|
+
durationMs: toolDuration,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
if (result.success) {
|
|
159
|
+
const summary = output_renderer_1.OutputRenderer.formatToolResult(name, resultString);
|
|
160
|
+
process.stdout.write(` ${chalk_1.default.green("✓")}${summary ? ` ${summary}` : ""} ${chalk_1.default.dim(`(${toolDuration}ms)`)}\n`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const errorMsg = result.error || "Tool execution failed";
|
|
164
|
+
process.stdout.write(` ${chalk_1.default.red("✗")} ${chalk_1.default.red(`(${errorMsg})`)} ${chalk_1.default.dim(`(${toolDuration}ms)`)}\n`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { result: result.result, success: result.success };
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
// Fail fast on missing tools
|
|
172
|
+
if (error instanceof Error && error.message.startsWith("Tool not found")) {
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
const toolDuration = Date.now() - toolStartTime;
|
|
176
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
177
|
+
// Emit error
|
|
178
|
+
if (displayConfig.showToolExecution) {
|
|
179
|
+
if (renderer) {
|
|
180
|
+
yield* renderer.handleEvent({
|
|
181
|
+
type: "tool_execution_complete",
|
|
182
|
+
toolCallId: toolCall.id,
|
|
183
|
+
result: `Error: ${errorMessage}`,
|
|
184
|
+
durationMs: toolDuration,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
process.stdout.write(` ${chalk_1.default.red("✗")} ${chalk_1.default.red(`(${errorMessage})`)} ${chalk_1.default.dim(`(${toolDuration}ms)`)}\n`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
(0, agent_run_tracker_1.recordToolError)(runTracker, name, error);
|
|
192
|
+
yield* logger.error("Tool execution failed", {
|
|
193
|
+
agentId,
|
|
194
|
+
conversationId,
|
|
195
|
+
toolName: name,
|
|
196
|
+
toolCallId: toolCall.id,
|
|
197
|
+
error: errorMessage,
|
|
198
|
+
});
|
|
199
|
+
return { result: { error: errorMessage }, success: false };
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Execute all tool calls and return results
|
|
205
|
+
*/
|
|
206
|
+
function executeToolCalls(toolCalls, context, displayConfig, renderer, runTracker, logger, agentId, conversationId, agentName) {
|
|
207
|
+
return effect_1.Effect.gen(function* () {
|
|
208
|
+
const toolResults = {};
|
|
209
|
+
const toolNames = toolCalls.map((tc) => tc.function.name);
|
|
210
|
+
// Show tools detected
|
|
211
|
+
if (displayConfig.showToolExecution) {
|
|
212
|
+
if (renderer) {
|
|
213
|
+
yield* renderer.handleEvent({
|
|
214
|
+
type: "tools_detected",
|
|
215
|
+
toolNames,
|
|
216
|
+
agentName,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
const tools = toolNames.join(", ");
|
|
221
|
+
console.log(`\n${chalk_1.default.yellow("🔧")} ${chalk_1.default.yellow(agentName)} is using tools: ${chalk_1.default.cyan(tools)}\n`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Log tool details
|
|
225
|
+
const toolDetails = [];
|
|
226
|
+
for (const toolCall of toolCalls) {
|
|
227
|
+
if (toolCall.type === "function") {
|
|
228
|
+
const { name, arguments: argsString } = toolCall.function;
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(argsString);
|
|
231
|
+
const args = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
232
|
+
? parsed
|
|
233
|
+
: {};
|
|
234
|
+
const argsText = (0, tool_formatter_1.formatToolArguments)(name, args, { style: "plain" });
|
|
235
|
+
toolDetails.push(argsText ? `${name} ${argsText}` : name);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
toolDetails.push(name);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const toolsList = toolDetails.join(", ");
|
|
243
|
+
yield* logger.info(`${agentName} is using tools: ${toolsList}`);
|
|
244
|
+
// Execute each tool
|
|
245
|
+
for (const toolCall of toolCalls) {
|
|
246
|
+
const { result } = yield* executeToolCall(toolCall, context, displayConfig, renderer, runTracker, logger, agentId, conversationId);
|
|
247
|
+
toolResults[toolCall.function.name] = result;
|
|
248
|
+
}
|
|
249
|
+
return toolResults;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Trim message history to prevent unbounded growth.
|
|
254
|
+
* Always preserves the system message (first message) and keeps the most recent messages.
|
|
255
|
+
*/
|
|
256
|
+
function trimMessages(messages, logger, agentId, conversationId) {
|
|
257
|
+
if (messages.length > MAX_MESSAGES) {
|
|
258
|
+
// Always preserve the system message (first message) as it contains important context
|
|
259
|
+
const systemMessage = messages[0];
|
|
260
|
+
if (systemMessage) {
|
|
261
|
+
// Keep system message + most recent (MAX_MESSAGES - 1) messages
|
|
262
|
+
const recentMessages = messages.slice(-(MAX_MESSAGES - 1));
|
|
263
|
+
messages.length = 0;
|
|
264
|
+
messages.push(systemMessage, ...recentMessages);
|
|
265
|
+
}
|
|
266
|
+
return logger.warn("Message history trimmed to prevent memory issues", {
|
|
267
|
+
agentId,
|
|
268
|
+
conversationId,
|
|
269
|
+
maxMessages: MAX_MESSAGES,
|
|
270
|
+
trimmedCount: messages.length,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return effect_1.Effect.void;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Ensure messages array is never empty
|
|
277
|
+
*/
|
|
278
|
+
function ensureMessagesNotEmpty(messages, userInput, logger, agentId, conversationId, iteration) {
|
|
279
|
+
if (messages.length === 0) {
|
|
280
|
+
return effect_1.Effect.gen(function* () {
|
|
281
|
+
yield* logger.warn("messagesToSend was empty; using fallback single user message", {
|
|
282
|
+
agentId,
|
|
283
|
+
conversationId,
|
|
284
|
+
iteration,
|
|
285
|
+
});
|
|
286
|
+
return [
|
|
287
|
+
{
|
|
288
|
+
role: "user",
|
|
289
|
+
content: userInput && userInput.trim().length > 0 ? userInput : "Continue",
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return effect_1.Effect.succeed(messages);
|
|
295
|
+
}
|
|
14
296
|
class AgentRunner {
|
|
15
297
|
/**
|
|
16
298
|
* Run an agent conversation
|
|
17
299
|
*/
|
|
18
300
|
static run(options) {
|
|
19
301
|
return effect_1.Effect.gen(function* () {
|
|
20
|
-
const { agent, userInput, conversationId, userId, maxIterations = 8 } = options;
|
|
21
302
|
// Get services
|
|
22
|
-
const llmService = yield* types_1.LLMServiceTag;
|
|
23
|
-
const toolRegistry = yield* tool_registry_1.ToolRegistryTag;
|
|
24
303
|
const configService = yield* config_1.AgentConfigService;
|
|
25
|
-
const logger = yield* logger_1.LoggerServiceTag;
|
|
26
304
|
const appConfig = yield* configService.appConfig;
|
|
27
|
-
//
|
|
28
|
-
const
|
|
29
|
-
//
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
return yield*
|
|
52
|
-
}
|
|
53
|
-
// Automatically include approval follow-up tools (e.g., execute-* variants)
|
|
54
|
-
const expandedToolNameSet = new Set(agentToolNames);
|
|
55
|
-
for (const toolName of agentToolNames) {
|
|
56
|
-
const tool = yield* toolRegistry.getTool(toolName);
|
|
57
|
-
if (tool.approvalExecuteToolName) {
|
|
58
|
-
expandedToolNameSet.add(tool.approvalExecuteToolName);
|
|
59
|
-
}
|
|
305
|
+
// Determine if streaming should be enabled
|
|
306
|
+
const streamDetection = (0, stream_detector_1.shouldEnableStreaming)(appConfig, options.stream !== undefined ? { stream: options.stream } : {});
|
|
307
|
+
// Get display config with defaults (applies to both modes)
|
|
308
|
+
const displayConfig = {
|
|
309
|
+
showThinking: appConfig.output?.showThinking ?? DEFAULT_DISPLAY_CONFIG.showThinking,
|
|
310
|
+
showToolExecution: appConfig.output?.showToolExecution ?? DEFAULT_DISPLAY_CONFIG.showToolExecution,
|
|
311
|
+
mode: appConfig.output?.mode ?? DEFAULT_DISPLAY_CONFIG.mode,
|
|
312
|
+
colorProfile: appConfig.output?.colorProfile,
|
|
313
|
+
};
|
|
314
|
+
// Check if we should show metrics (from logging config)
|
|
315
|
+
const showMetrics = appConfig.logging?.showMetrics ?? false;
|
|
316
|
+
// Get streaming config with defaults (streaming-specific)
|
|
317
|
+
const streamingConfig = {
|
|
318
|
+
...(appConfig.output?.streaming?.enabled !== undefined
|
|
319
|
+
? { enabled: appConfig.output.streaming.enabled }
|
|
320
|
+
: {}),
|
|
321
|
+
...(appConfig.output?.streaming?.progressiveMarkdown !== undefined
|
|
322
|
+
? { progressiveMarkdown: appConfig.output.streaming.progressiveMarkdown }
|
|
323
|
+
: {}),
|
|
324
|
+
...(appConfig.output?.streaming?.textBufferMs !== undefined
|
|
325
|
+
? { textBufferMs: appConfig.output.streaming.textBufferMs }
|
|
326
|
+
: {}),
|
|
327
|
+
};
|
|
328
|
+
if (streamDetection.shouldStream) {
|
|
329
|
+
return yield* AgentRunner.runWithStreaming(options, displayConfig, streamingConfig, showMetrics);
|
|
60
330
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const allTools = yield* toolRegistry.getToolDefinitions();
|
|
64
|
-
const tools = allTools.filter((tool) => expandedToolNames.includes(tool.function.name));
|
|
65
|
-
// Build a map of available tool descriptions for prompt clarity
|
|
66
|
-
const availableTools = {};
|
|
67
|
-
for (const tool of tools) {
|
|
68
|
-
availableTools[tool.function.name] = tool.function.description;
|
|
331
|
+
else {
|
|
332
|
+
return yield* AgentRunner.runWithoutStreaming(options, displayConfig, showMetrics);
|
|
69
333
|
}
|
|
70
|
-
|
|
71
|
-
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Streaming implementation that processes LLM responses in real-time.
|
|
338
|
+
*/
|
|
339
|
+
static runWithStreaming(options, displayConfig, streamingConfig, showMetrics) {
|
|
340
|
+
return effect_1.Effect.gen(function* () {
|
|
341
|
+
const { agent, userInput, maxIterations = agent_1.MAX_AGENT_STEPS } = options;
|
|
342
|
+
const llmService = yield* types_1.LLMServiceTag;
|
|
343
|
+
const logger = yield* logger_1.LoggerServiceTag;
|
|
344
|
+
// Initialize common context
|
|
345
|
+
const runContext = yield* initializeAgentRun(options);
|
|
346
|
+
const { actualConversationId, context, tools, messages, runTracker, provider, model } = runContext;
|
|
347
|
+
// Create renderer
|
|
348
|
+
const normalizedStreamingConfig = {
|
|
349
|
+
enabled: true, // Always enabled in streaming mode
|
|
350
|
+
...(streamingConfig.progressiveMarkdown !== undefined && {
|
|
351
|
+
progressiveMarkdown: streamingConfig.progressiveMarkdown,
|
|
352
|
+
}),
|
|
353
|
+
...(streamingConfig.textBufferMs !== undefined && {
|
|
354
|
+
textBufferMs: streamingConfig.textBufferMs,
|
|
355
|
+
}),
|
|
356
|
+
};
|
|
357
|
+
const rendererConfig = {
|
|
358
|
+
displayConfig,
|
|
359
|
+
streamingConfig: normalizedStreamingConfig,
|
|
360
|
+
showMetrics,
|
|
72
361
|
agentName: agent.name,
|
|
73
|
-
|
|
74
|
-
userInput,
|
|
75
|
-
conversationHistory: history,
|
|
76
|
-
toolNames: expandedToolNames,
|
|
77
|
-
availableTools,
|
|
78
|
-
});
|
|
79
|
-
// Create execution context
|
|
80
|
-
const context = {
|
|
81
|
-
agentId: agent.id,
|
|
82
|
-
conversationId: actualConversationId,
|
|
83
|
-
...(userId ? { userId } : {}),
|
|
362
|
+
reasoningEffort: agent.config.reasoningEffort,
|
|
84
363
|
};
|
|
85
|
-
|
|
364
|
+
const renderer = new output_renderer_1.OutputRenderer(rendererConfig);
|
|
365
|
+
// Run agent loop
|
|
86
366
|
const currentMessages = [...messages];
|
|
87
367
|
let response = {
|
|
88
368
|
content: "",
|
|
@@ -90,84 +370,212 @@ class AgentRunner {
|
|
|
90
370
|
};
|
|
91
371
|
let finished = false;
|
|
92
372
|
let iterationsUsed = 0;
|
|
93
|
-
// Memory safeguard: prevent unbounded message growth
|
|
94
|
-
const MAX_MESSAGES = 100;
|
|
95
|
-
// Determine the LLM provider and model to use
|
|
96
373
|
for (let i = 0; i < maxIterations; i++) {
|
|
97
374
|
yield* effect_1.Effect.sync(() => (0, agent_run_tracker_1.beginIteration)(runTracker, i + 1));
|
|
98
375
|
try {
|
|
99
|
-
// Log
|
|
376
|
+
// Log thinking indicator
|
|
100
377
|
if (i === 0) {
|
|
101
|
-
|
|
102
|
-
yield* logger.info(message, {
|
|
103
|
-
agentId: agent.id,
|
|
104
|
-
conversationId: actualConversationId,
|
|
105
|
-
iteration: i + 1,
|
|
106
|
-
});
|
|
378
|
+
yield* logger.info(markdown_renderer_1.MarkdownRenderer.formatThinking(agent.name, true));
|
|
107
379
|
}
|
|
108
380
|
else {
|
|
109
|
-
|
|
110
|
-
yield* logger.info(message, {
|
|
111
|
-
agentId: agent.id,
|
|
112
|
-
conversationId: actualConversationId,
|
|
113
|
-
iteration: i + 1,
|
|
114
|
-
});
|
|
381
|
+
yield* logger.info(markdown_renderer_1.MarkdownRenderer.formatThinking(agent.name, false));
|
|
115
382
|
}
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
383
|
+
// Ensure messages are not empty
|
|
384
|
+
const messagesToSend = yield* ensureMessagesNotEmpty(currentMessages, userInput, logger, agent.id, actualConversationId, i + 1);
|
|
385
|
+
// Create streaming completion with retry and fallback
|
|
386
|
+
const llmOptions = {
|
|
387
|
+
model,
|
|
388
|
+
messages: messagesToSend,
|
|
389
|
+
tools,
|
|
390
|
+
toolChoice: "auto",
|
|
391
|
+
reasoning_effort: agent.config.reasoningEffort ?? "disable",
|
|
392
|
+
};
|
|
393
|
+
const completionRef = yield* effect_1.Ref.make(undefined);
|
|
394
|
+
const pendingToolCalls = [];
|
|
395
|
+
const streamingResult = yield* effect_1.Effect.retry(effect_1.Effect.gen(function* () {
|
|
396
|
+
try {
|
|
397
|
+
return yield* llmService.createStreamingChatCompletion(provider, llmOptions);
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
(0, agent_run_tracker_1.recordLLMRetry)(runTracker, error);
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
}), effect_1.Schedule.exponential("1 second").pipe(effect_1.Schedule.intersect(effect_1.Schedule.recurs(MAX_RETRIES)), effect_1.Schedule.whileInput((error) => error instanceof types_1.LLMRateLimitError))).pipe(effect_1.Effect.timeout(STREAM_CREATION_TIMEOUT), effect_1.Effect.catchAll(() => effect_1.Effect.gen(function* () {
|
|
404
|
+
yield* logger.warn("Streaming failed, falling back to non-streaming mode");
|
|
405
|
+
const fallback = yield* llmService.createChatCompletion(provider, llmOptions);
|
|
406
|
+
return {
|
|
407
|
+
stream: effect_1.Stream.empty,
|
|
408
|
+
response: effect_1.Effect.succeed(fallback),
|
|
409
|
+
cancel: effect_1.Effect.void,
|
|
410
|
+
};
|
|
411
|
+
})));
|
|
412
|
+
// Process stream events
|
|
413
|
+
const streamFiber = yield* effect_1.Effect.fork(effect_1.Stream.runForEach(streamingResult.stream, (event) => effect_1.Effect.gen(function* () {
|
|
414
|
+
yield* renderer.handleEvent(event);
|
|
415
|
+
if (event.type === "tool_call") {
|
|
416
|
+
pendingToolCalls.push(event.toolCall);
|
|
417
|
+
}
|
|
418
|
+
if (event.type === "complete") {
|
|
419
|
+
yield* effect_1.Ref.set(completionRef, event.response);
|
|
420
|
+
if (event.metrics?.firstTokenLatencyMs) {
|
|
421
|
+
(0, agent_run_tracker_1.recordFirstTokenLatency)(runTracker, event.metrics.firstTokenLatencyMs);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (event.type === "error" && !event.recoverable) {
|
|
425
|
+
yield* streamingResult.cancel;
|
|
426
|
+
}
|
|
427
|
+
})));
|
|
428
|
+
// Wait for stream completion - the stream is cancelled on completion event
|
|
429
|
+
// so the fiber should complete naturally without needing a timeout
|
|
430
|
+
const streamExit = yield* effect_1.Fiber.await(streamFiber);
|
|
431
|
+
// Get completion from stream or fallback
|
|
432
|
+
let completion;
|
|
433
|
+
if (effect_1.Exit.isFailure(streamExit)) {
|
|
434
|
+
yield* streamingResult.cancel;
|
|
435
|
+
const error = effect_1.Cause.failureOption(streamExit.cause);
|
|
436
|
+
if (effect_1.Option.isSome(error)) {
|
|
437
|
+
yield* logger.warn("Stream processing failed, using fallback");
|
|
438
|
+
completion = yield* llmService.createChatCompletion(provider, llmOptions);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
const fromRef = yield* effect_1.Ref.get(completionRef);
|
|
442
|
+
if (fromRef) {
|
|
443
|
+
completion = fromRef;
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
completion = yield* streamingResult.response.pipe(effect_1.Effect.timeout(DEFERRED_RESPONSE_TIMEOUT), effect_1.Effect.catchAll(() => effect_1.Effect.gen(function* () {
|
|
447
|
+
yield* streamingResult.cancel;
|
|
448
|
+
return yield* llmService.createChatCompletion(provider, llmOptions);
|
|
449
|
+
})));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
131
452
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
{
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
453
|
+
else {
|
|
454
|
+
const fromRef = yield* effect_1.Ref.get(completionRef);
|
|
455
|
+
if (fromRef) {
|
|
456
|
+
completion = fromRef;
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
completion = yield* streamingResult.response.pipe(effect_1.Effect.timeout(DEFERRED_RESPONSE_TIMEOUT), effect_1.Effect.catchAll(() => effect_1.Effect.gen(function* () {
|
|
460
|
+
yield* streamingResult.cancel;
|
|
461
|
+
return yield* llmService.createChatCompletion(provider, llmOptions);
|
|
462
|
+
})));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (completion.usage) {
|
|
466
|
+
(0, agent_run_tracker_1.recordLLMUsage)(runTracker, completion.usage);
|
|
467
|
+
}
|
|
468
|
+
// Add assistant response to conversation
|
|
469
|
+
currentMessages.push({
|
|
470
|
+
role: "assistant",
|
|
471
|
+
content: completion.content,
|
|
472
|
+
...(completion.toolCalls
|
|
473
|
+
? {
|
|
474
|
+
tool_calls: completion.toolCalls.map((tc) => ({
|
|
475
|
+
id: tc.id,
|
|
476
|
+
type: tc.type,
|
|
477
|
+
function: { name: tc.function.name, arguments: tc.function.arguments },
|
|
478
|
+
})),
|
|
479
|
+
}
|
|
480
|
+
: {}),
|
|
481
|
+
});
|
|
482
|
+
yield* trimMessages(currentMessages, logger, agent.id, actualConversationId);
|
|
483
|
+
// Handle tool calls
|
|
484
|
+
if (completion.toolCalls && completion.toolCalls.length > 0) {
|
|
485
|
+
const toolResults = yield* executeToolCalls(completion.toolCalls, context, displayConfig, renderer, runTracker, logger, agent.id, actualConversationId, agent.name);
|
|
486
|
+
// Add tool results to conversation
|
|
487
|
+
for (const toolCall of completion.toolCalls) {
|
|
488
|
+
if (toolCall.type === "function") {
|
|
489
|
+
const result = toolResults[toolCall.function.name];
|
|
490
|
+
currentMessages.push({
|
|
491
|
+
role: "tool",
|
|
492
|
+
name: toolCall.function.name,
|
|
493
|
+
content: JSON.stringify(result),
|
|
494
|
+
tool_call_id: toolCall.id,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
response = { ...response, toolCalls: completion.toolCalls, toolResults };
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
// No tool calls - final response
|
|
502
|
+
response = { ...response, content: completion.content };
|
|
503
|
+
yield* logger.info(markdown_renderer_1.MarkdownRenderer.formatCompletion(agent.name));
|
|
504
|
+
iterationsUsed = i + 1;
|
|
505
|
+
finished = true;
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
finally {
|
|
509
|
+
yield* effect_1.Effect.sync(() => (0, agent_run_tracker_1.completeIteration)(runTracker));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Post-loop cleanup
|
|
513
|
+
if (!finished) {
|
|
514
|
+
iterationsUsed = maxIterations;
|
|
515
|
+
yield* logger.warn(markdown_renderer_1.MarkdownRenderer.formatWarning(agent.name, `reached maximum iterations (${maxIterations}) - type 'resume' to continue`));
|
|
516
|
+
}
|
|
517
|
+
else if (!response.content?.trim() && !response.toolCalls) {
|
|
518
|
+
yield* logger.warn(markdown_renderer_1.MarkdownRenderer.formatWarning(agent.name, "model returned an empty response"));
|
|
519
|
+
}
|
|
520
|
+
// Finalize run asynchronously
|
|
521
|
+
yield* (0, agent_run_tracker_1.finalizeAgentRun)(runTracker, { iterationsUsed, finished }).pipe(effect_1.Effect.catchAll((error) => logger.warn("Failed to write agent token usage log", { error: error.message })), effect_1.Effect.fork, effect_1.Effect.asVoid);
|
|
522
|
+
return { ...response, messages: currentMessages };
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Non-streaming implementation that waits for complete LLM responses before rendering.
|
|
527
|
+
*/
|
|
528
|
+
static runWithoutStreaming(options, displayConfig, showMetrics) {
|
|
529
|
+
return effect_1.Effect.gen(function* () {
|
|
530
|
+
const { agent, userInput, maxIterations = agent_1.MAX_AGENT_STEPS } = options;
|
|
531
|
+
const llmService = yield* types_1.LLMServiceTag;
|
|
532
|
+
const logger = yield* logger_1.LoggerServiceTag;
|
|
533
|
+
// Initialize common context
|
|
534
|
+
const runContext = yield* initializeAgentRun(options);
|
|
535
|
+
const { actualConversationId, context, tools, messages, runTracker, provider, model } = runContext;
|
|
536
|
+
// Run agent loop
|
|
537
|
+
const currentMessages = [...messages];
|
|
538
|
+
let response = {
|
|
539
|
+
content: "",
|
|
540
|
+
conversationId: actualConversationId,
|
|
541
|
+
};
|
|
542
|
+
let finished = false;
|
|
543
|
+
let iterationsUsed = 0;
|
|
544
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
545
|
+
yield* effect_1.Effect.sync(() => (0, agent_run_tracker_1.beginIteration)(runTracker, i + 1));
|
|
546
|
+
try {
|
|
547
|
+
// Log thinking indicator
|
|
548
|
+
if (displayConfig.showThinking) {
|
|
549
|
+
if (i === 0) {
|
|
550
|
+
yield* logger.info(markdown_renderer_1.MarkdownRenderer.formatThinking(agent.name, true));
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
yield* logger.info(markdown_renderer_1.MarkdownRenderer.formatThinking(agent.name, false));
|
|
554
|
+
}
|
|
148
555
|
}
|
|
149
|
-
|
|
556
|
+
// Ensure messages are not empty
|
|
557
|
+
const messagesToSend = yield* ensureMessagesNotEmpty(currentMessages, userInput, logger, agent.id, actualConversationId, i + 1);
|
|
558
|
+
// Create non-streaming completion with retry
|
|
559
|
+
const llmOptions = {
|
|
560
|
+
model,
|
|
561
|
+
messages: messagesToSend,
|
|
562
|
+
tools,
|
|
563
|
+
toolChoice: "auto",
|
|
564
|
+
reasoning_effort: agent.config.reasoningEffort ?? "disable",
|
|
565
|
+
};
|
|
150
566
|
const completion = yield* effect_1.Effect.retry(effect_1.Effect.gen(function* () {
|
|
151
|
-
const llmOptions = {
|
|
152
|
-
model,
|
|
153
|
-
messages: messagesToSend,
|
|
154
|
-
tools,
|
|
155
|
-
toolChoice: "auto",
|
|
156
|
-
reasoning_effort: agent.config.reasoningEffort ?? "disable",
|
|
157
|
-
};
|
|
158
567
|
try {
|
|
159
|
-
|
|
160
|
-
return result;
|
|
568
|
+
return yield* llmService.createChatCompletion(provider, llmOptions);
|
|
161
569
|
}
|
|
162
570
|
catch (error) {
|
|
163
571
|
(0, agent_run_tracker_1.recordLLMRetry)(runTracker, error);
|
|
164
572
|
throw error;
|
|
165
573
|
}
|
|
166
|
-
}), effect_1.Schedule.exponential("1 second").pipe(effect_1.Schedule.intersect(effect_1.Schedule.recurs(
|
|
574
|
+
}), effect_1.Schedule.exponential("1 second").pipe(effect_1.Schedule.intersect(effect_1.Schedule.recurs(MAX_RETRIES)), effect_1.Schedule.whileInput((error) => error instanceof types_1.LLMRateLimitError)));
|
|
167
575
|
if (completion.usage) {
|
|
168
576
|
(0, agent_run_tracker_1.recordLLMUsage)(runTracker, completion.usage);
|
|
169
577
|
}
|
|
170
|
-
// Add
|
|
578
|
+
// Add assistant response to conversation
|
|
171
579
|
currentMessages.push({
|
|
172
580
|
role: "assistant",
|
|
173
581
|
content: completion.content,
|
|
@@ -181,142 +589,53 @@ class AgentRunner {
|
|
|
181
589
|
}
|
|
182
590
|
: {}),
|
|
183
591
|
});
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const recentMessages = currentMessages.slice(-(MAX_MESSAGES - 1));
|
|
190
|
-
currentMessages.length = 0;
|
|
191
|
-
currentMessages.push(systemMessage, ...recentMessages);
|
|
192
|
-
}
|
|
193
|
-
yield* logger.warn("Message history trimmed to prevent memory issues", {
|
|
194
|
-
agentId: agent.id,
|
|
195
|
-
conversationId: actualConversationId,
|
|
196
|
-
maxMessages: MAX_MESSAGES,
|
|
197
|
-
trimmedCount: currentMessages.length,
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
// Log assistant response if log level is debug
|
|
201
|
-
if (appConfig.logging.level === "debug") {
|
|
202
|
-
yield* logger.debug("LLM response received", {
|
|
203
|
-
agentId: agent.id,
|
|
204
|
-
conversationId: actualConversationId,
|
|
205
|
-
iteration: i + 1,
|
|
206
|
-
model: completion.model,
|
|
207
|
-
content: completion.content,
|
|
208
|
-
toolCalls: completion.toolCalls?.map((tc) => ({
|
|
209
|
-
id: tc.id,
|
|
210
|
-
name: tc.function.name,
|
|
211
|
-
arguments: tc.function.arguments,
|
|
212
|
-
})),
|
|
213
|
-
usage: completion.usage,
|
|
214
|
-
});
|
|
592
|
+
yield* trimMessages(currentMessages, logger, agent.id, actualConversationId);
|
|
593
|
+
// Format content - always use markdown since LLMs output markdown
|
|
594
|
+
let formattedContent = completion.content;
|
|
595
|
+
if (formattedContent) {
|
|
596
|
+
formattedContent = markdown_renderer_1.MarkdownRenderer.render(formattedContent);
|
|
215
597
|
}
|
|
216
|
-
//
|
|
598
|
+
// Handle tool calls
|
|
217
599
|
if (completion.toolCalls && completion.toolCalls.length > 0) {
|
|
218
|
-
const toolResults =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const message = markdown_renderer_1.MarkdownRenderer.formatToolExecution(agent.name, toolNames);
|
|
222
|
-
yield* logger.info(message, {
|
|
223
|
-
agentId: agent.id,
|
|
224
|
-
conversationId: actualConversationId,
|
|
225
|
-
toolCount: completion.toolCalls.length,
|
|
226
|
-
tools: toolNames,
|
|
227
|
-
});
|
|
228
|
-
// Execute each tool call
|
|
600
|
+
const toolResults = yield* executeToolCalls(completion.toolCalls, context, displayConfig, null, // No renderer for non-streaming
|
|
601
|
+
runTracker, logger, agent.id, actualConversationId, agent.name);
|
|
602
|
+
// Add tool results to conversation
|
|
229
603
|
for (const toolCall of completion.toolCalls) {
|
|
230
604
|
if (toolCall.type === "function") {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
catch (parseError) {
|
|
240
|
-
throw new Error(`Invalid JSON in tool arguments: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
241
|
-
}
|
|
242
|
-
const args = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
243
|
-
? parsed
|
|
244
|
-
: {};
|
|
245
|
-
// Log tool call arguments in debug mode
|
|
246
|
-
yield* logger.debug("Tool call arguments", {
|
|
247
|
-
agentId: agent.id,
|
|
248
|
-
conversationId: actualConversationId,
|
|
249
|
-
toolName: name,
|
|
250
|
-
toolCallId: toolCall.id,
|
|
251
|
-
arguments: args,
|
|
252
|
-
rawArguments: argsString,
|
|
253
|
-
});
|
|
254
|
-
// Execute the tool
|
|
255
|
-
const result = yield* executeTool(name, args, context);
|
|
256
|
-
// Log tool execution result in debug mode
|
|
257
|
-
yield* logger.debug("Tool execution result", {
|
|
258
|
-
agentId: agent.id,
|
|
259
|
-
conversationId: actualConversationId,
|
|
260
|
-
toolName: name,
|
|
261
|
-
toolCallId: toolCall.id,
|
|
262
|
-
arguments: args,
|
|
263
|
-
result: result.result,
|
|
264
|
-
});
|
|
265
|
-
// Add the tool result to the conversation
|
|
266
|
-
currentMessages.push({
|
|
267
|
-
role: "tool",
|
|
268
|
-
name,
|
|
269
|
-
content: JSON.stringify(result.result),
|
|
270
|
-
tool_call_id: toolCall.id,
|
|
271
|
-
});
|
|
272
|
-
// Store the tool result
|
|
273
|
-
toolResults[name] = result.result;
|
|
274
|
-
}
|
|
275
|
-
catch (error) {
|
|
276
|
-
// If the tool does not exist, rethrow to fail fast (never mock missing tools)
|
|
277
|
-
if (error instanceof Error && error.message.startsWith("Tool not found")) {
|
|
278
|
-
throw error;
|
|
279
|
-
}
|
|
280
|
-
// Log the tool execution error for debugging
|
|
281
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
282
|
-
(0, agent_run_tracker_1.recordToolError)(runTracker, name, error);
|
|
283
|
-
yield* logger.error("Tool execution failed", {
|
|
284
|
-
agentId: agent.id,
|
|
285
|
-
conversationId: actualConversationId,
|
|
286
|
-
toolName: name,
|
|
287
|
-
toolCallId: toolCall.id,
|
|
288
|
-
error: errorMessage,
|
|
289
|
-
});
|
|
290
|
-
// Include the tool execution error in the conversation
|
|
291
|
-
currentMessages.push({
|
|
292
|
-
role: "tool",
|
|
293
|
-
name,
|
|
294
|
-
content: `Error: ${errorMessage}`,
|
|
295
|
-
tool_call_id: toolCall.id,
|
|
296
|
-
});
|
|
297
|
-
// Store the error
|
|
298
|
-
toolResults[name] = {
|
|
299
|
-
error: errorMessage,
|
|
300
|
-
};
|
|
301
|
-
}
|
|
605
|
+
const result = toolResults[toolCall.function.name];
|
|
606
|
+
currentMessages.push({
|
|
607
|
+
role: "tool",
|
|
608
|
+
name: toolCall.function.name,
|
|
609
|
+
content: JSON.stringify(result),
|
|
610
|
+
tool_call_id: toolCall.id,
|
|
611
|
+
});
|
|
302
612
|
}
|
|
303
613
|
}
|
|
304
|
-
// Update the response with tool results
|
|
305
614
|
response = { ...response, toolCalls: completion.toolCalls, toolResults };
|
|
306
|
-
// Continue the conversation with the tool results
|
|
307
615
|
continue;
|
|
308
616
|
}
|
|
309
|
-
// No tool calls
|
|
310
|
-
response = { ...response, content:
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
617
|
+
// No tool calls - final response
|
|
618
|
+
response = { ...response, content: formattedContent };
|
|
619
|
+
// Display final response
|
|
620
|
+
if (formattedContent && formattedContent.trim().length > 0) {
|
|
621
|
+
console.log();
|
|
622
|
+
console.log(markdown_renderer_1.MarkdownRenderer.formatAgentResponse(agent.name, formattedContent));
|
|
623
|
+
console.log();
|
|
624
|
+
}
|
|
625
|
+
yield* logger.info(markdown_renderer_1.MarkdownRenderer.formatCompletion(agent.name));
|
|
626
|
+
// Show metrics if enabled
|
|
627
|
+
if (showMetrics && completion.usage) {
|
|
628
|
+
const parts = [];
|
|
629
|
+
if (completion.usage.totalTokens)
|
|
630
|
+
parts.push(`Total: ${completion.usage.totalTokens} tokens`);
|
|
631
|
+
if (completion.usage.promptTokens)
|
|
632
|
+
parts.push(`Prompt: ${completion.usage.promptTokens}`);
|
|
633
|
+
if (completion.usage.completionTokens)
|
|
634
|
+
parts.push(`Completion: ${completion.usage.completionTokens}`);
|
|
635
|
+
if (parts.length > 0) {
|
|
636
|
+
yield* logger.info(`[${parts.join(" | ")}]`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
320
639
|
iterationsUsed = i + 1;
|
|
321
640
|
finished = true;
|
|
322
641
|
break;
|
|
@@ -325,35 +644,16 @@ class AgentRunner {
|
|
|
325
644
|
yield* effect_1.Effect.sync(() => (0, agent_run_tracker_1.completeIteration)(runTracker));
|
|
326
645
|
}
|
|
327
646
|
}
|
|
328
|
-
// Post-loop
|
|
647
|
+
// Post-loop cleanup
|
|
329
648
|
if (!finished) {
|
|
330
649
|
iterationsUsed = maxIterations;
|
|
331
|
-
|
|
332
|
-
yield* logger.warn(warningMessage, {
|
|
333
|
-
agentId: agent.id,
|
|
334
|
-
conversationId: actualConversationId,
|
|
335
|
-
maxIterations,
|
|
336
|
-
});
|
|
650
|
+
yield* logger.warn(markdown_renderer_1.MarkdownRenderer.formatWarning(agent.name, `reached maximum iterations (${maxIterations}) - type 'resume' to continue`));
|
|
337
651
|
}
|
|
338
|
-
else if (
|
|
339
|
-
|
|
340
|
-
const emptyMessage = markdown_renderer_1.MarkdownRenderer.formatWarning(agent.name, "model returned an empty response");
|
|
341
|
-
yield* logger.warn(emptyMessage, {
|
|
342
|
-
agentId: agent.id,
|
|
343
|
-
conversationId: actualConversationId,
|
|
344
|
-
totalIterations: iterationsUsed,
|
|
345
|
-
});
|
|
652
|
+
else if (!response.content?.trim() && !response.toolCalls) {
|
|
653
|
+
yield* logger.warn(markdown_renderer_1.MarkdownRenderer.formatWarning(agent.name, "model returned an empty response"));
|
|
346
654
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
finished,
|
|
350
|
-
}).pipe(effect_1.Effect.catchAll((error) => logger.warn("Failed to write agent token usage log", {
|
|
351
|
-
agentId: agent.id,
|
|
352
|
-
conversationId: actualConversationId,
|
|
353
|
-
error: error.message,
|
|
354
|
-
})));
|
|
355
|
-
// Optionally persist conversation history via a storage layer in the future
|
|
356
|
-
// Return the full message history from this turn so callers can persist it
|
|
655
|
+
// Finalize run asynchronously
|
|
656
|
+
yield* (0, agent_run_tracker_1.finalizeAgentRun)(runTracker, { iterationsUsed, finished }).pipe(effect_1.Effect.catchAll((error) => logger.warn("Failed to write agent token usage log", { error: error.message })), effect_1.Effect.fork, effect_1.Effect.asVoid);
|
|
357
657
|
return { ...response, messages: currentMessages };
|
|
358
658
|
});
|
|
359
659
|
}
|