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.
Files changed (151) hide show
  1. package/README.md +8 -6
  2. package/dist/cli/commands/chat-agent.d.ts +3 -1
  3. package/dist/cli/commands/chat-agent.d.ts.map +1 -1
  4. package/dist/cli/commands/chat-agent.js +180 -102
  5. package/dist/cli/commands/chat-agent.js.map +1 -1
  6. package/dist/cli/commands/edit-agent.d.ts +2 -2
  7. package/dist/cli/commands/edit-agent.d.ts.map +1 -1
  8. package/dist/cli/commands/edit-agent.js +20 -10
  9. package/dist/cli/commands/edit-agent.js.map +1 -1
  10. package/dist/cli/commands/task-agent.d.ts +1 -1
  11. package/dist/cli/commands/task-agent.d.ts.map +1 -1
  12. package/dist/cli/commands/task-agent.js +6 -6
  13. package/dist/cli/commands/task-agent.js.map +1 -1
  14. package/dist/constants/agent.d.ts +2 -0
  15. package/dist/constants/agent.d.ts.map +1 -0
  16. package/dist/constants/agent.js +5 -0
  17. package/dist/constants/agent.js.map +1 -0
  18. package/dist/core/agent/agent-prompt.d.ts +0 -1
  19. package/dist/core/agent/agent-prompt.d.ts.map +1 -1
  20. package/dist/core/agent/agent-prompt.js +7 -15
  21. package/dist/core/agent/agent-prompt.js.map +1 -1
  22. package/dist/core/agent/agent-runner.d.ts +15 -0
  23. package/dist/core/agent/agent-runner.d.ts.map +1 -1
  24. package/dist/core/agent/agent-runner.js +570 -270
  25. package/dist/core/agent/agent-runner.js.map +1 -1
  26. package/dist/core/agent/agent-service.d.ts +20 -5
  27. package/dist/core/agent/agent-service.d.ts.map +1 -1
  28. package/dist/core/agent/agent-service.js +54 -6
  29. package/dist/core/agent/agent-service.js.map +1 -1
  30. package/dist/core/agent/gmail-agent.js +7 -7
  31. package/dist/core/agent/gmail-agent.js.map +1 -1
  32. package/dist/core/agent/prompts/coder/v1.d.ts +2 -0
  33. package/dist/core/agent/prompts/coder/v1.d.ts.map +1 -0
  34. package/dist/core/agent/prompts/coder/v1.js +370 -0
  35. package/dist/core/agent/prompts/coder/v1.js.map +1 -0
  36. package/dist/core/agent/prompts/default/v2.d.ts +1 -1
  37. package/dist/core/agent/prompts/default/v2.d.ts.map +1 -1
  38. package/dist/core/agent/prompts/default/v2.js +1 -2
  39. package/dist/core/agent/prompts/default/v2.js.map +1 -1
  40. package/dist/core/agent/prompts/gmail/v1.d.ts +1 -1
  41. package/dist/core/agent/prompts/gmail/v1.d.ts.map +1 -1
  42. package/dist/core/agent/prompts/gmail/v1.js +5 -5
  43. package/dist/core/agent/prompts/gmail/v2.d.ts +1 -1
  44. package/dist/core/agent/prompts/gmail/v2.d.ts.map +1 -1
  45. package/dist/core/agent/prompts/gmail/v2.js +7 -7
  46. package/dist/core/agent/tools/base-tool.d.ts +3 -4
  47. package/dist/core/agent/tools/base-tool.d.ts.map +1 -1
  48. package/dist/core/agent/tools/base-tool.js +11 -51
  49. package/dist/core/agent/tools/base-tool.js.map +1 -1
  50. package/dist/core/agent/tools/context-utils.d.ts +6 -0
  51. package/dist/core/agent/tools/context-utils.d.ts.map +1 -0
  52. package/dist/core/agent/tools/context-utils.js +9 -0
  53. package/dist/core/agent/tools/context-utils.js.map +1 -0
  54. package/dist/core/agent/tools/env-utils.d.ts +7 -0
  55. package/dist/core/agent/tools/env-utils.d.ts.map +1 -0
  56. package/dist/core/agent/tools/env-utils.js +39 -0
  57. package/dist/core/agent/tools/env-utils.js.map +1 -0
  58. package/dist/core/agent/tools/fs-tools.d.ts.map +1 -1
  59. package/dist/core/agent/tools/fs-tools.js +48 -53
  60. package/dist/core/agent/tools/fs-tools.js.map +1 -1
  61. package/dist/core/agent/tools/git-tools.d.ts +5 -0
  62. package/dist/core/agent/tools/git-tools.d.ts.map +1 -1
  63. package/dist/core/agent/tools/git-tools.js +716 -154
  64. package/dist/core/agent/tools/git-tools.js.map +1 -1
  65. package/dist/core/agent/tools/gmail-tools.js +35 -35
  66. package/dist/core/agent/tools/gmail-tools.js.map +1 -1
  67. package/dist/core/agent/tools/http-tools.js +2 -2
  68. package/dist/core/agent/tools/http-tools.js.map +1 -1
  69. package/dist/core/agent/tools/register-tools.d.ts +18 -1
  70. package/dist/core/agent/tools/register-tools.d.ts.map +1 -1
  71. package/dist/core/agent/tools/register-tools.js +52 -6
  72. package/dist/core/agent/tools/register-tools.js.map +1 -1
  73. package/dist/core/agent/tools/shell-tools.d.ts.map +1 -1
  74. package/dist/core/agent/tools/shell-tools.js +10 -20
  75. package/dist/core/agent/tools/shell-tools.js.map +1 -1
  76. package/dist/core/agent/tools/tool-registry.d.ts +11 -7
  77. package/dist/core/agent/tools/tool-registry.d.ts.map +1 -1
  78. package/dist/core/agent/tools/tool-registry.js +45 -19
  79. package/dist/core/agent/tools/tool-registry.js.map +1 -1
  80. package/dist/core/agent/tools/web-search-tools.js +1 -1
  81. package/dist/core/agent/tools/web-search-tools.js.map +1 -1
  82. package/dist/core/agent/tracking/agent-run-tracker.d.ts +2 -0
  83. package/dist/core/agent/tracking/agent-run-tracker.d.ts.map +1 -1
  84. package/dist/core/agent/tracking/agent-run-tracker.js +40 -2
  85. package/dist/core/agent/tracking/agent-run-tracker.js.map +1 -1
  86. package/dist/core/types/index.d.ts +82 -15
  87. package/dist/core/types/index.d.ts.map +1 -1
  88. package/dist/core/types/index.js.map +1 -1
  89. package/dist/core/utils/markdown-renderer.d.ts +25 -0
  90. package/dist/core/utils/markdown-renderer.d.ts.map +1 -1
  91. package/dist/core/utils/markdown-renderer.js +100 -1
  92. package/dist/core/utils/markdown-renderer.js.map +1 -1
  93. package/dist/core/utils/output-renderer.d.ts +81 -0
  94. package/dist/core/utils/output-renderer.d.ts.map +1 -0
  95. package/dist/core/utils/output-renderer.js +291 -0
  96. package/dist/core/utils/output-renderer.js.map +1 -0
  97. package/dist/core/utils/output-theme.d.ts +64 -0
  98. package/dist/core/utils/output-theme.d.ts.map +1 -0
  99. package/dist/core/utils/output-theme.js +154 -0
  100. package/dist/core/utils/output-theme.js.map +1 -0
  101. package/dist/core/utils/output-writer.d.ts +79 -0
  102. package/dist/core/utils/output-writer.d.ts.map +1 -0
  103. package/dist/core/utils/output-writer.js +133 -0
  104. package/dist/core/utils/output-writer.js.map +1 -0
  105. package/dist/core/utils/runtime-detection.d.ts +28 -0
  106. package/dist/core/utils/runtime-detection.d.ts.map +1 -0
  107. package/dist/core/utils/runtime-detection.js +62 -0
  108. package/dist/core/utils/runtime-detection.js.map +1 -0
  109. package/dist/core/utils/string.d.ts +8 -0
  110. package/dist/core/utils/string.d.ts.map +1 -0
  111. package/dist/core/utils/string.js +19 -0
  112. package/dist/core/utils/string.js.map +1 -0
  113. package/dist/core/utils/thinking-renderer.d.ts +56 -0
  114. package/dist/core/utils/thinking-renderer.d.ts.map +1 -0
  115. package/dist/core/utils/thinking-renderer.js +174 -0
  116. package/dist/core/utils/thinking-renderer.js.map +1 -0
  117. package/dist/core/utils/tool-formatter.d.ts +21 -0
  118. package/dist/core/utils/tool-formatter.d.ts.map +1 -0
  119. package/dist/core/utils/tool-formatter.js +343 -0
  120. package/dist/core/utils/tool-formatter.js.map +1 -0
  121. package/dist/main.js +11 -4
  122. package/dist/main.js.map +1 -1
  123. package/dist/services/config.d.ts.map +1 -1
  124. package/dist/services/config.js +20 -20
  125. package/dist/services/config.js.map +1 -1
  126. package/dist/services/llm/ai-sdk-service.d.ts.map +1 -1
  127. package/dist/services/llm/ai-sdk-service.js +128 -37
  128. package/dist/services/llm/ai-sdk-service.js.map +1 -1
  129. package/dist/services/llm/stream-detector.d.ts +44 -0
  130. package/dist/services/llm/stream-detector.d.ts.map +1 -0
  131. package/dist/services/llm/stream-detector.js +81 -0
  132. package/dist/services/llm/stream-detector.js.map +1 -0
  133. package/dist/services/llm/stream-processor.d.ts +93 -0
  134. package/dist/services/llm/stream-processor.d.ts.map +1 -0
  135. package/dist/services/llm/stream-processor.js +330 -0
  136. package/dist/services/llm/stream-processor.js.map +1 -0
  137. package/dist/services/llm/streaming-types.d.ts +131 -0
  138. package/dist/services/llm/streaming-types.d.ts.map +1 -0
  139. package/dist/services/llm/streaming-types.js +12 -0
  140. package/dist/services/llm/streaming-types.js.map +1 -0
  141. package/dist/services/llm/types.d.ts +8 -0
  142. package/dist/services/llm/types.d.ts.map +1 -1
  143. package/dist/services/llm/types.js.map +1 -1
  144. package/dist/services/logger.d.ts +4 -4
  145. package/dist/services/logger.d.ts.map +1 -1
  146. package/dist/services/logger.js +23 -45
  147. package/dist/services/logger.js.map +1 -1
  148. package/dist/services/shell.d.ts.map +1 -1
  149. package/dist/services/shell.js +52 -18
  150. package/dist/services/shell.js.map +1 -1
  151. 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
- // Generate a conversation ID if not provided
28
- const actualConversationId = conversationId || `conv-${Date.now()}`;
29
- // Use provided history if available to preserve context across turns
30
- const history = options.conversationHistory || [];
31
- const agentType = agent.config.agentType;
32
- const provider = agent.config.llmProvider;
33
- const model = agent.config.llmModel;
34
- const runTracker = (0, agent_run_tracker_1.createAgentRunTracker)({
35
- agent,
36
- conversationId: actualConversationId,
37
- ...(userId ? { userId } : {}),
38
- provider,
39
- model,
40
- reasoningEffort: agent.config.reasoningEffort ?? "disable",
41
- maxIterations,
42
- });
43
- // Get available tools for this specific agent
44
- const allToolNames = yield* toolRegistry.listTools();
45
- const agentToolNames = (0, tool_config_1.normalizeToolConfig)(agent.config.tools, {
46
- agentId: agent.id,
47
- });
48
- // Validate that all agent tools exist in the registry
49
- const invalidTools = agentToolNames.filter((toolName) => !allToolNames.includes(toolName));
50
- if (invalidTools.length > 0) {
51
- return yield* effect_1.Effect.fail(new Error(`Agent ${agent.id} references non-existent tools: ${invalidTools.join(", ")}`));
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
- const expandedToolNames = Array.from(expandedToolNameSet);
62
- // Get tool definitions for only the agent's specified tools
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
- // Build messages for the agent with only its specified tools and descriptions
71
- const messages = yield* agent_prompt_1.agentPromptBuilder.buildAgentMessages(agentType, {
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
- agentDescription: agent.description,
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
- // Run the agent loop
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 user-friendly progress for info level
376
+ // Log thinking indicator
100
377
  if (i === 0) {
101
- const message = markdown_renderer_1.MarkdownRenderer.formatThinking(agent.name, true);
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
- const message = markdown_renderer_1.MarkdownRenderer.formatThinking(agent.name, false);
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
- // Log LLM request in debug mode
117
- if (appConfig.logging.level === "debug") {
118
- yield* logger.debug("LLM request", {
119
- agentId: agent.id,
120
- conversationId: actualConversationId,
121
- iteration: i + 1,
122
- provider,
123
- model,
124
- messageCount: currentMessages.length,
125
- messages: currentMessages,
126
- tools: tools.map((t) => ({
127
- name: t.function.name,
128
- description: t.function.description,
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
- // Call the LLM with retry logic for rate limit errors
133
- let messagesToSend = currentMessages;
134
- // Secondary safety: ensure messagesToSend is never empty
135
- if (messagesToSend.length === 0) {
136
- // Fallback to a single user message if everything else failed
137
- messagesToSend = [
138
- {
139
- role: "user",
140
- content: userInput && userInput.trim().length > 0 ? userInput : "Continue",
141
- },
142
- ];
143
- yield* logger.warn("messagesToSend was empty; using fallback single user message", {
144
- agentId: agent.id,
145
- conversationId: actualConversationId,
146
- iteration: i + 1,
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
- const maxRetries = 3;
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
- const result = yield* llmService.createChatCompletion(provider, llmOptions);
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(maxRetries)), effect_1.Schedule.whileInput((error) => error instanceof types_1.LLMRateLimitError)));
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 the assistant's response to the conversation (including tool calls, if any)
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
- // Memory safeguard: trim messages if they exceed the limit
185
- if (currentMessages.length > MAX_MESSAGES) {
186
- // Keep the system message and the most recent messages
187
- const systemMessage = currentMessages[0];
188
- if (systemMessage) {
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
- // Check if the model wants to call a tool
598
+ // Handle tool calls
217
599
  if (completion.toolCalls && completion.toolCalls.length > 0) {
218
- const toolResults = {};
219
- // Log user-friendly tool execution info
220
- const toolNames = completion.toolCalls.map((tc) => tc.function.name);
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 { name, arguments: argsString } = toolCall.function;
232
- (0, agent_run_tracker_1.recordToolInvocation)(runTracker, name);
233
- try {
234
- // Parse the arguments safely with proper error handling
235
- let parsed;
236
- try {
237
- parsed = JSON.parse(argsString);
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, we have the final response
310
- response = { ...response, content: completion.content };
311
- // Log completion
312
- const completionMessage = markdown_renderer_1.MarkdownRenderer.formatCompletion(agent.name);
313
- yield* logger.info(completionMessage, {
314
- agentId: agent.id,
315
- conversationId: actualConversationId,
316
- totalIterations: i + 1,
317
- hasContent: !!completion.content,
318
- });
319
- // Mark loop as finished and break
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 diagnostics
647
+ // Post-loop cleanup
329
648
  if (!finished) {
330
649
  iterationsUsed = maxIterations;
331
- const warningMessage = markdown_renderer_1.MarkdownRenderer.formatWarning(agent.name, `reached maximum iterations (${maxIterations})`);
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 ((!response.content || response.content.trim().length === 0) &&
339
- !response.toolCalls) {
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
- yield* (0, agent_run_tracker_1.finalizeAgentRun)(runTracker, {
348
- iterationsUsed,
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
  }