agent-world 0.12.3 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +105 -17
  2. package/dist/cli/commands.d.ts +7 -1
  3. package/dist/cli/commands.js +27 -10
  4. package/dist/cli/hitl.d.ts +9 -2
  5. package/dist/cli/hitl.js +61 -20
  6. package/dist/cli/index.js +250 -96
  7. package/dist/cli/system-events.d.ts +27 -0
  8. package/dist/cli/system-events.js +63 -0
  9. package/dist/core/activity-tracker.d.ts +38 -2
  10. package/dist/core/activity-tracker.d.ts.map +1 -1
  11. package/dist/core/activity-tracker.js +62 -9
  12. package/dist/core/activity-tracker.js.map +1 -1
  13. package/dist/core/anthropic-direct.d.ts +2 -0
  14. package/dist/core/anthropic-direct.d.ts.map +1 -1
  15. package/dist/core/anthropic-direct.js +43 -1
  16. package/dist/core/anthropic-direct.js.map +1 -1
  17. package/dist/core/chat-constants.d.ts +12 -0
  18. package/dist/core/chat-constants.d.ts.map +1 -1
  19. package/dist/core/chat-constants.js +5 -0
  20. package/dist/core/chat-constants.js.map +1 -1
  21. package/dist/core/create-agent-tool.d.ts +28 -25
  22. package/dist/core/create-agent-tool.d.ts.map +1 -1
  23. package/dist/core/create-agent-tool.js +264 -141
  24. package/dist/core/create-agent-tool.js.map +1 -1
  25. package/dist/core/events/index.d.ts +5 -2
  26. package/dist/core/events/index.d.ts.map +1 -1
  27. package/dist/core/events/index.js +5 -2
  28. package/dist/core/events/index.js.map +1 -1
  29. package/dist/core/events/memory-manager.d.ts +26 -1
  30. package/dist/core/events/memory-manager.d.ts.map +1 -1
  31. package/dist/core/events/memory-manager.js +877 -72
  32. package/dist/core/events/memory-manager.js.map +1 -1
  33. package/dist/core/events/orchestrator.d.ts +8 -0
  34. package/dist/core/events/orchestrator.d.ts.map +1 -1
  35. package/dist/core/events/orchestrator.js +214 -38
  36. package/dist/core/events/orchestrator.js.map +1 -1
  37. package/dist/core/events/persistence.d.ts +21 -14
  38. package/dist/core/events/persistence.d.ts.map +1 -1
  39. package/dist/core/events/persistence.js +100 -61
  40. package/dist/core/events/persistence.js.map +1 -1
  41. package/dist/core/events/publishers.d.ts +13 -16
  42. package/dist/core/events/publishers.d.ts.map +1 -1
  43. package/dist/core/events/publishers.js +54 -55
  44. package/dist/core/events/publishers.js.map +1 -1
  45. package/dist/core/events/subscribers.d.ts +17 -14
  46. package/dist/core/events/subscribers.d.ts.map +1 -1
  47. package/dist/core/events/subscribers.js +68 -147
  48. package/dist/core/events/subscribers.js.map +1 -1
  49. package/dist/core/events/title-scheduler.d.ts +27 -0
  50. package/dist/core/events/title-scheduler.d.ts.map +1 -0
  51. package/dist/core/events/title-scheduler.js +135 -0
  52. package/dist/core/events/title-scheduler.js.map +1 -0
  53. package/dist/core/events/tool-bridge-logging.d.ts +4 -1
  54. package/dist/core/events/tool-bridge-logging.d.ts.map +1 -1
  55. package/dist/core/events/tool-bridge-logging.js +112 -13
  56. package/dist/core/events/tool-bridge-logging.js.map +1 -1
  57. package/dist/core/events-metadata.d.ts.map +1 -1
  58. package/dist/core/events-metadata.js +8 -4
  59. package/dist/core/events-metadata.js.map +1 -1
  60. package/dist/core/export.d.ts +1 -1
  61. package/dist/core/export.d.ts.map +1 -1
  62. package/dist/core/export.js +2 -15
  63. package/dist/core/export.js.map +1 -1
  64. package/dist/core/feature-path-logging.d.ts +50 -0
  65. package/dist/core/feature-path-logging.d.ts.map +1 -0
  66. package/dist/core/feature-path-logging.js +130 -0
  67. package/dist/core/feature-path-logging.js.map +1 -0
  68. package/dist/core/file-tools.d.ts +57 -1
  69. package/dist/core/file-tools.d.ts.map +1 -1
  70. package/dist/core/file-tools.js +329 -29
  71. package/dist/core/file-tools.js.map +1 -1
  72. package/dist/core/google-direct.d.ts +6 -1
  73. package/dist/core/google-direct.d.ts.map +1 -1
  74. package/dist/core/google-direct.js +76 -7
  75. package/dist/core/google-direct.js.map +1 -1
  76. package/dist/core/heartbeat.d.ts +34 -0
  77. package/dist/core/heartbeat.d.ts.map +1 -0
  78. package/dist/core/heartbeat.js +153 -0
  79. package/dist/core/heartbeat.js.map +1 -0
  80. package/dist/core/hitl-tool.d.ts +73 -0
  81. package/dist/core/hitl-tool.d.ts.map +1 -0
  82. package/dist/core/hitl-tool.js +284 -0
  83. package/dist/core/hitl-tool.js.map +1 -0
  84. package/dist/core/hitl.d.ts +85 -8
  85. package/dist/core/hitl.d.ts.map +1 -1
  86. package/dist/core/hitl.js +375 -61
  87. package/dist/core/hitl.js.map +1 -1
  88. package/dist/core/index.d.ts +12 -7
  89. package/dist/core/index.d.ts.map +1 -1
  90. package/dist/core/index.js +11 -6
  91. package/dist/core/index.js.map +1 -1
  92. package/dist/core/llm-manager.d.ts +17 -0
  93. package/dist/core/llm-manager.d.ts.map +1 -1
  94. package/dist/core/llm-manager.js +335 -43
  95. package/dist/core/llm-manager.js.map +1 -1
  96. package/dist/core/load-skill-tool.d.ts +36 -3
  97. package/dist/core/load-skill-tool.d.ts.map +1 -1
  98. package/dist/core/load-skill-tool.js +807 -93
  99. package/dist/core/load-skill-tool.js.map +1 -1
  100. package/dist/core/logger.d.ts +14 -0
  101. package/dist/core/logger.d.ts.map +1 -1
  102. package/dist/core/logger.js +15 -0
  103. package/dist/core/logger.js.map +1 -1
  104. package/dist/core/managers.d.ts +41 -52
  105. package/dist/core/managers.d.ts.map +1 -1
  106. package/dist/core/managers.js +422 -533
  107. package/dist/core/managers.js.map +1 -1
  108. package/dist/core/mcp-server-registry.d.ts +19 -2
  109. package/dist/core/mcp-server-registry.d.ts.map +1 -1
  110. package/dist/core/mcp-server-registry.js +168 -12
  111. package/dist/core/mcp-server-registry.js.map +1 -1
  112. package/dist/core/message-cutoff.d.ts +29 -0
  113. package/dist/core/message-cutoff.d.ts.map +1 -0
  114. package/dist/core/message-cutoff.js +63 -0
  115. package/dist/core/message-cutoff.js.map +1 -0
  116. package/dist/core/message-edit-manager.d.ts +54 -0
  117. package/dist/core/message-edit-manager.d.ts.map +1 -0
  118. package/dist/core/message-edit-manager.js +602 -0
  119. package/dist/core/message-edit-manager.js.map +1 -0
  120. package/dist/core/message-prep.d.ts +2 -0
  121. package/dist/core/message-prep.d.ts.map +1 -1
  122. package/dist/core/message-prep.js +39 -12
  123. package/dist/core/message-prep.js.map +1 -1
  124. package/dist/core/message-processing-control.d.ts +1 -0
  125. package/dist/core/message-processing-control.d.ts.map +1 -1
  126. package/dist/core/message-processing-control.js +23 -6
  127. package/dist/core/message-processing-control.js.map +1 -1
  128. package/dist/core/openai-direct.d.ts +9 -3
  129. package/dist/core/openai-direct.d.ts.map +1 -1
  130. package/dist/core/openai-direct.js +267 -33
  131. package/dist/core/openai-direct.js.map +1 -1
  132. package/dist/core/optional-tracers/opik-runtime.d.ts +32 -0
  133. package/dist/core/optional-tracers/opik-runtime.d.ts.map +1 -0
  134. package/dist/core/optional-tracers/opik-runtime.js +141 -0
  135. package/dist/core/optional-tracers/opik-runtime.js.map +1 -0
  136. package/dist/core/queue-manager.d.ts +84 -0
  137. package/dist/core/queue-manager.d.ts.map +1 -0
  138. package/dist/core/queue-manager.js +814 -0
  139. package/dist/core/queue-manager.js.map +1 -0
  140. package/dist/core/reasoning-controls.d.ts +30 -0
  141. package/dist/core/reasoning-controls.d.ts.map +1 -0
  142. package/dist/core/reasoning-controls.js +118 -0
  143. package/dist/core/reasoning-controls.js.map +1 -0
  144. package/dist/core/reliability-config.d.ts +82 -0
  145. package/dist/core/reliability-config.d.ts.map +1 -0
  146. package/dist/core/reliability-config.js +106 -0
  147. package/dist/core/reliability-config.js.map +1 -0
  148. package/dist/core/reliability-runtime.d.ts +53 -0
  149. package/dist/core/reliability-runtime.d.ts.map +1 -0
  150. package/dist/core/reliability-runtime.js +92 -0
  151. package/dist/core/reliability-runtime.js.map +1 -0
  152. package/dist/core/security/guardrails.d.ts +21 -0
  153. package/dist/core/security/guardrails.d.ts.map +1 -0
  154. package/dist/core/security/guardrails.js +111 -0
  155. package/dist/core/security/guardrails.js.map +1 -0
  156. package/dist/core/send-message-tool.d.ts +79 -0
  157. package/dist/core/send-message-tool.d.ts.map +1 -0
  158. package/dist/core/send-message-tool.js +222 -0
  159. package/dist/core/send-message-tool.js.map +1 -0
  160. package/dist/core/shell-cmd-tool.d.ts +82 -1
  161. package/dist/core/shell-cmd-tool.d.ts.map +1 -1
  162. package/dist/core/shell-cmd-tool.js +854 -42
  163. package/dist/core/shell-cmd-tool.js.map +1 -1
  164. package/dist/core/skill-registry.d.ts +2 -0
  165. package/dist/core/skill-registry.d.ts.map +1 -1
  166. package/dist/core/skill-registry.js +52 -2
  167. package/dist/core/skill-registry.js.map +1 -1
  168. package/dist/core/storage/eventStorage/fileEventStorage.d.ts +5 -0
  169. package/dist/core/storage/eventStorage/fileEventStorage.d.ts.map +1 -1
  170. package/dist/core/storage/eventStorage/fileEventStorage.js +61 -0
  171. package/dist/core/storage/eventStorage/fileEventStorage.js.map +1 -1
  172. package/dist/core/storage/eventStorage/memoryEventStorage.d.ts +5 -0
  173. package/dist/core/storage/eventStorage/memoryEventStorage.d.ts.map +1 -1
  174. package/dist/core/storage/eventStorage/memoryEventStorage.js +34 -0
  175. package/dist/core/storage/eventStorage/memoryEventStorage.js.map +1 -1
  176. package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts +1 -0
  177. package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts.map +1 -1
  178. package/dist/core/storage/eventStorage/sqliteEventStorage.js +19 -2
  179. package/dist/core/storage/eventStorage/sqliteEventStorage.js.map +1 -1
  180. package/dist/core/storage/eventStorage/types.d.ts +6 -0
  181. package/dist/core/storage/eventStorage/types.d.ts.map +1 -1
  182. package/dist/core/storage/eventStorage/types.js +1 -0
  183. package/dist/core/storage/eventStorage/types.js.map +1 -1
  184. package/dist/core/storage/eventStorage/validation.d.ts.map +1 -1
  185. package/dist/core/storage/eventStorage/validation.js +2 -1
  186. package/dist/core/storage/eventStorage/validation.js.map +1 -1
  187. package/dist/core/storage/github-world-import.d.ts +84 -0
  188. package/dist/core/storage/github-world-import.d.ts.map +1 -0
  189. package/dist/core/storage/github-world-import.js +365 -0
  190. package/dist/core/storage/github-world-import.js.map +1 -0
  191. package/dist/core/storage/memory-storage.d.ts +19 -8
  192. package/dist/core/storage/memory-storage.d.ts.map +1 -1
  193. package/dist/core/storage/memory-storage.js +147 -49
  194. package/dist/core/storage/memory-storage.js.map +1 -1
  195. package/dist/core/storage/queue-storage.d.ts +1 -0
  196. package/dist/core/storage/queue-storage.d.ts.map +1 -1
  197. package/dist/core/storage/queue-storage.js +3 -2
  198. package/dist/core/storage/queue-storage.js.map +1 -1
  199. package/dist/core/storage/sqlite-storage.d.ts +14 -9
  200. package/dist/core/storage/sqlite-storage.d.ts.map +1 -1
  201. package/dist/core/storage/sqlite-storage.js +131 -154
  202. package/dist/core/storage/sqlite-storage.js.map +1 -1
  203. package/dist/core/storage/storage-factory.d.ts +3 -0
  204. package/dist/core/storage/storage-factory.d.ts.map +1 -1
  205. package/dist/core/storage/storage-factory.js +175 -89
  206. package/dist/core/storage/storage-factory.js.map +1 -1
  207. package/dist/core/storage/world-storage.d.ts +1 -1
  208. package/dist/core/storage/world-storage.d.ts.map +1 -1
  209. package/dist/core/storage/world-storage.js +5 -1
  210. package/dist/core/storage/world-storage.js.map +1 -1
  211. package/dist/core/storage-init.d.ts +11 -0
  212. package/dist/core/storage-init.d.ts.map +1 -0
  213. package/dist/core/storage-init.js +122 -0
  214. package/dist/core/storage-init.js.map +1 -0
  215. package/dist/core/subscription.d.ts +8 -1
  216. package/dist/core/subscription.d.ts.map +1 -1
  217. package/dist/core/subscription.js +130 -23
  218. package/dist/core/subscription.js.map +1 -1
  219. package/dist/core/tool-approval.d.ts +45 -0
  220. package/dist/core/tool-approval.d.ts.map +1 -0
  221. package/dist/core/tool-approval.js +223 -0
  222. package/dist/core/tool-approval.js.map +1 -0
  223. package/dist/core/tool-execution-envelope.d.ts +87 -0
  224. package/dist/core/tool-execution-envelope.d.ts.map +1 -0
  225. package/dist/core/tool-execution-envelope.js +168 -0
  226. package/dist/core/tool-execution-envelope.js.map +1 -0
  227. package/dist/core/tool-utils.d.ts +9 -2
  228. package/dist/core/tool-utils.d.ts.map +1 -1
  229. package/dist/core/tool-utils.js +122 -28
  230. package/dist/core/tool-utils.js.map +1 -1
  231. package/dist/core/types.d.ts +69 -36
  232. package/dist/core/types.d.ts.map +1 -1
  233. package/dist/core/types.js +3 -2
  234. package/dist/core/types.js.map +1 -1
  235. package/dist/core/utils.d.ts +16 -0
  236. package/dist/core/utils.d.ts.map +1 -1
  237. package/dist/core/utils.js +99 -24
  238. package/dist/core/utils.js.map +1 -1
  239. package/dist/core/web-fetch-tool.d.ts +72 -0
  240. package/dist/core/web-fetch-tool.d.ts.map +1 -0
  241. package/dist/core/web-fetch-tool.js +491 -0
  242. package/dist/core/web-fetch-tool.js.map +1 -0
  243. package/dist/core/world-registry.d.ts +84 -0
  244. package/dist/core/world-registry.d.ts.map +1 -0
  245. package/dist/core/world-registry.js +247 -0
  246. package/dist/core/world-registry.js.map +1 -0
  247. package/dist/public/assets/index-Be-1xtV-.js +104 -0
  248. package/dist/public/assets/index-tsDdiXDU.css +1 -0
  249. package/dist/public/index.html +2 -2
  250. package/dist/public/mcp-sandbox-proxy.html +148 -0
  251. package/dist/server/api.js +288 -58
  252. package/dist/server/error-response.d.ts +27 -0
  253. package/dist/server/error-response.js +77 -0
  254. package/dist/server/index.d.ts +2 -1
  255. package/dist/server/index.js +6 -2
  256. package/dist/server/sse-handler.d.ts +13 -2
  257. package/dist/server/sse-handler.js +194 -26
  258. package/migrations/0015_add_message_queue.sql +36 -0
  259. package/migrations/0016_add_world_heartbeat.sql +13 -0
  260. package/migrations/0017_add_title_provenance.sql +7 -0
  261. package/package.json +31 -10
  262. package/dist/public/assets/index-BO20H4xt.js +0 -96
  263. package/dist/public/assets/index-ETY7W5_S.css +0 -1
@@ -94,6 +94,16 @@
94
94
  * - Queue-based serialization prevents API rate limits and resource conflicts
95
95
  *
96
96
  * Recent Changes:
97
+ * - 2026-03-13: Added streamed `reasoningContent` forwarding and world-variable reasoning-effort propagation for OpenAI-compatible and Google direct providers.
98
+ * - 2026-03-06: Moved `shell_cmd` working-directory prompt guidance into tool-aware system-message injection.
99
+ * - 2026-03-06: Widened queue timeout field typing to `number` so runtime timeout overrides compile cleanly.
100
+ * - 2026-03-05: Added chat-scoped LLM timeout status system events (`taking too long` warning + hard-timeout event), enforced timeout-triggered abort signaling in queue processing, and classified queue timeouts separately from user cancellations.
101
+ * - 2026-03-05: Switched LLM queue timeout defaults to shared reliability config.
102
+ * - 2026-03-04: Azure client creation now maps `agent.model` to Azure deployment name (with config deployment fallback) so world/agent model selection controls deployment URL routing.
103
+ * - 2026-02-28: Added canonical feature-path diagnostics (`llm.prep`, `llm.request.*`, `llm.response.*`) with opt-in raw payload logging and correlation metadata.
104
+ * - 2026-02-24: Required explicit chatId for streaming SSE emission and propagated chatId through start/chunk/end/error events for strict chat-scoped frontend filtering.
105
+ * - 2026-02-20: Switched injected tool-usage guidance to shared `buildToolUsagePromptSection()` so HITL and other tool rules are centralized in one utility.
106
+ * - 2026-02-20: Updated injected tool-usage guidance to direct LLMs to use `human_intervention_request` for human clarifications and confirmations.
97
107
  * - 2026-02-13: Reclassified stop-triggered aborts as cancellation/info logs (not errors) in queue and non-streaming paths.
98
108
  * - 2026-02-13: Added merged external+queue abort-signal support so chat stop requests can cancel follow-up continuation calls.
99
109
  * - 2026-02-13: Added chat-scoped LLM cancellation controls so Electron stop requests can abort active and queued calls by `worldId` + `chatId`.
@@ -117,9 +127,11 @@ import { filterClientSideMessages } from './message-prep.js';
117
127
  import { createClientForProvider, streamOpenAIResponse, generateOpenAIResponse } from './openai-direct.js';
118
128
  import { createAnthropicClientForAgent, streamAnthropicResponse, generateAnthropicResponse } from './anthropic-direct.js';
119
129
  import { createGoogleClientForAgent, streamGoogleResponse, generateGoogleResponse } from './google-direct.js';
120
- import { generateId } from './utils.js';
130
+ import { buildToolUsagePromptSection, generateId, getDefaultWorkingDirectory, getEnvValueFromText } from './utils.js';
121
131
  import { createCategoryLogger } from './logger.js';
132
+ import { buildFeaturePathCorrelation, mergeFeaturePathData, sanitizeRawPayloadForLog, shouldEmitRawLog } from './feature-path-logging.js';
122
133
  import { createStorageWithWrappers } from './storage/storage-factory.js';
134
+ import { RELIABILITY_CONFIG } from './reliability-config.js';
123
135
  // Granular function-specific loggers for detailed debugging control
124
136
  const loggerQueue = createCategoryLogger('llm.queue');
125
137
  const loggerStreaming = createCategoryLogger('llm.streaming');
@@ -127,6 +139,11 @@ const loggerGeneration = createCategoryLogger('llm.generation');
127
139
  const loggerProvider = createCategoryLogger('llm.provider');
128
140
  const loggerMCP = createCategoryLogger('llm.mcp');
129
141
  const loggerUtil = createCategoryLogger('llm.util');
142
+ const loggerPrep = createCategoryLogger('llm.prep');
143
+ const loggerRequestMeta = createCategoryLogger('llm.request.meta');
144
+ const loggerRequestRaw = createCategoryLogger('llm.request.raw');
145
+ const loggerResponseMeta = createCategoryLogger('llm.response.meta');
146
+ const loggerResponseRaw = createCategoryLogger('llm.response.raw');
130
147
  import { getLLMProviderConfig } from './llm-config.js';
131
148
  // LLM Integration Utilities
132
149
  function stripCustomFields(message) {
@@ -142,17 +159,90 @@ function stripCustomFieldsFromMessages(messages) {
142
159
  // Then strip custom fields
143
160
  return filteredMessages.map(stripCustomFields);
144
161
  }
162
+ function summarizeMessagesForLLM(messages) {
163
+ return {
164
+ messageCount: messages.length,
165
+ systemMessages: messages.filter(m => m.role === 'system').length,
166
+ userMessages: messages.filter(m => m.role === 'user').length,
167
+ assistantMessages: messages.filter(m => m.role === 'assistant').length,
168
+ toolMessages: messages.filter(m => m.role === 'tool').length,
169
+ };
170
+ }
171
+ function emitLLMRequestDiagnostics(params) {
172
+ const correlation = buildFeaturePathCorrelation({
173
+ worldId: params.world.id,
174
+ chatId: params.chatId,
175
+ agentId: params.agent.id,
176
+ messageId: params.messageId,
177
+ turnId: params.messageId,
178
+ });
179
+ const messageSummary = summarizeMessagesForLLM(params.preparedMessages);
180
+ const toolNames = Object.keys(params.mcpTools);
181
+ loggerPrep.debug('Prepared messages for LLM request', mergeFeaturePathData(correlation, {
182
+ ...messageSummary,
183
+ toolCount: toolNames.length,
184
+ }));
185
+ loggerRequestMeta.debug('LLM request ready', mergeFeaturePathData(correlation, {
186
+ provider: params.agent.provider,
187
+ model: params.agent.model,
188
+ ...messageSummary,
189
+ toolCount: toolNames.length,
190
+ toolNames,
191
+ }));
192
+ if (shouldEmitRawLog('llm.request.raw')) {
193
+ loggerRequestRaw.debug('LLM request payload', mergeFeaturePathData(correlation, {
194
+ provider: params.agent.provider,
195
+ model: params.agent.model,
196
+ payload: sanitizeRawPayloadForLog({
197
+ messages: params.preparedMessages,
198
+ tools: params.mcpTools,
199
+ }),
200
+ }));
201
+ }
202
+ }
203
+ function emitLLMResponseDiagnostics(params) {
204
+ const correlation = buildFeaturePathCorrelation({
205
+ worldId: params.world.id,
206
+ chatId: params.chatId,
207
+ agentId: params.agent.id,
208
+ messageId: params.messageId,
209
+ turnId: params.messageId,
210
+ });
211
+ loggerResponseMeta.debug('LLM response received', mergeFeaturePathData(correlation, {
212
+ provider: params.agent.provider,
213
+ model: params.agent.model,
214
+ responseType: params.response.type,
215
+ contentLength: params.response.content?.length || 0,
216
+ toolCallCount: params.response.tool_calls?.length || 0,
217
+ }));
218
+ if (shouldEmitRawLog('llm.response.raw')) {
219
+ loggerResponseRaw.debug('LLM response payload', mergeFeaturePathData(correlation, {
220
+ provider: params.agent.provider,
221
+ model: params.agent.model,
222
+ payload: sanitizeRawPayloadForLog(params.response),
223
+ }));
224
+ }
225
+ }
145
226
  /**
146
227
  * Append tool usage guidance to system message when tools are available
147
228
  * Returns a new array with updated system message (doesn't mutate original)
148
229
  */
149
- function appendToolRulesToSystemMessage(messages, hasMCPTools) {
150
- if (!hasMCPTools || messages.length === 0 || messages[0].role !== 'system') {
230
+ export function appendToolRulesToSystemMessage(messages, toolNames, options) {
231
+ if (messages.length === 0 || messages[0].role !== 'system') {
151
232
  return messages;
152
233
  }
153
234
  const systemMessage = messages[0];
154
- // Simple guidance: Only use tools when user explicitly requests an action
155
- const toolRules = '\n\nYou have access to tools. Use them only when the user explicitly requests an action.';
235
+ const normalizedToolNames = new Set(toolNames.map((toolName) => String(toolName || '').trim().toLowerCase()).filter(Boolean));
236
+ const toolUsageSection = buildToolUsagePromptSection({ toolNames });
237
+ const workingDirectory = typeof options?.workingDirectory === 'string' ? options.workingDirectory.trim() : '';
238
+ const shellExecutionRule = normalizedToolNames.has('shell_cmd') && workingDirectory
239
+ ? 'When using `shell_cmd`, execute commands only within this trusted working directory scope: ' + workingDirectory
240
+ : '';
241
+ const injectedSections = [shellExecutionRule, toolUsageSection].filter(Boolean);
242
+ if (injectedSections.length === 0) {
243
+ return messages;
244
+ }
245
+ const toolRules = `\n\n${injectedSections.join('\n\n')}`;
156
246
  return [
157
247
  { ...systemMessage, content: systemMessage.content + toolRules },
158
248
  ...messages.slice(1)
@@ -171,13 +261,25 @@ function normalizeChatId(chatId) {
171
261
  return '__none__';
172
262
  return String(chatId);
173
263
  }
264
+ function createLLMQueueTimeoutError(agentId, timeoutMs) {
265
+ const error = new Error(`LLM call timeout after ${timeoutMs}ms for agent ${agentId}`);
266
+ error.name = 'LLMQueueTimeoutError';
267
+ error.code = 'LLM_QUEUE_TIMEOUT';
268
+ return error;
269
+ }
270
+ function isLLMQueueTimeoutError(error) {
271
+ return Boolean(error &&
272
+ typeof error === 'object' &&
273
+ 'code' in error &&
274
+ error.code === 'LLM_QUEUE_TIMEOUT');
275
+ }
174
276
  class LLMQueue {
175
277
  queue = [];
176
278
  processing = false;
177
279
  activeItem = null;
178
280
  maxQueueSize = 100; // Prevent memory issues
179
- processingTimeoutMs = 900000; // 15 minute max processing time per call (for long-running tools)
180
- async add(agentId, worldId, chatId, task) {
281
+ processingTimeoutMs = RELIABILITY_CONFIG.llm.processingTimeoutMs; // 15 minute max processing time per call (for long-running tools)
282
+ async add(agentId, worldId, chatId, task, options) {
181
283
  // Prevent queue overflow
182
284
  if (this.queue.length >= this.maxQueueSize) {
183
285
  throw new Error(`LLM queue is full (${this.maxQueueSize} items). Please try again later.`);
@@ -192,6 +294,8 @@ class LLMQueue {
192
294
  abortController: new AbortController(),
193
295
  canceled: false,
194
296
  execute: task,
297
+ onTakingTooLong: options?.onTakingTooLong,
298
+ onTimedOut: options?.onTimedOut,
195
299
  resolve,
196
300
  reject
197
301
  };
@@ -214,13 +318,13 @@ class LLMQueue {
214
318
  this.activeItem = item;
215
319
  const taskStartTime = Date.now();
216
320
  loggerQueue.debug(`LLMQueue: Processing task for agent=${item.agentId}, world=${item.worldId}, chat=${normalizeChatId(item.chatId)}, queueItemId=${item.id}`);
217
- // Add processing timeout to prevent stuck queue
321
+ // Add processing timeout to prevent stuck queue.
218
322
  const processPromise = item.execute(item.abortController.signal);
219
- // Store timeout ID so we can cancel it if process completes first
323
+ // Store timeout IDs so we can cancel them on all exits.
220
324
  let timeoutId;
221
325
  let warningTimeoutId;
222
- // Warn if processing takes more than 50% of timeout
223
- const warningThreshold = this.processingTimeoutMs * 0.5;
326
+ // Warn if processing exceeds configured threshold ratio of timeout.
327
+ const warningThreshold = this.processingTimeoutMs * RELIABILITY_CONFIG.llm.warningThresholdRatio;
224
328
  warningTimeoutId = setTimeout(() => {
225
329
  const elapsed = Date.now() - taskStartTime;
226
330
  loggerQueue.warn(`LLM task is taking longer than expected`, {
@@ -230,22 +334,74 @@ class LLMQueue {
230
334
  timeoutMs: this.processingTimeoutMs,
231
335
  percentComplete: Math.round((elapsed / this.processingTimeoutMs) * 100)
232
336
  });
337
+ try {
338
+ item.onTakingTooLong?.({
339
+ elapsedMs: elapsed,
340
+ timeoutMs: this.processingTimeoutMs,
341
+ });
342
+ }
343
+ catch (callbackError) {
344
+ loggerQueue.warn('LLM queue taking-too-long callback failed', {
345
+ agentId: item.agentId,
346
+ worldId: item.worldId,
347
+ chatId: normalizeChatId(item.chatId),
348
+ queueItemId: item.id,
349
+ error: callbackError instanceof Error ? callbackError.message : String(callbackError)
350
+ });
351
+ }
233
352
  }, warningThreshold);
234
353
  const timeoutPromise = new Promise((_, reject) => {
235
354
  timeoutId = setTimeout(() => {
236
- reject(new Error(`LLM call timeout after ${this.processingTimeoutMs}ms for agent ${item.agentId}`));
355
+ const elapsed = Date.now() - taskStartTime;
356
+ if (!item.abortController.signal.aborted) {
357
+ item.abortController.abort();
358
+ }
359
+ try {
360
+ item.onTimedOut?.({
361
+ elapsedMs: elapsed,
362
+ timeoutMs: this.processingTimeoutMs,
363
+ });
364
+ }
365
+ catch (callbackError) {
366
+ loggerQueue.warn('LLM queue timeout callback failed', {
367
+ agentId: item.agentId,
368
+ worldId: item.worldId,
369
+ chatId: normalizeChatId(item.chatId),
370
+ queueItemId: item.id,
371
+ error: callbackError instanceof Error ? callbackError.message : String(callbackError)
372
+ });
373
+ }
374
+ reject(createLLMQueueTimeoutError(item.agentId, this.processingTimeoutMs));
237
375
  }, this.processingTimeoutMs);
238
376
  });
239
- const result = await Promise.race([processPromise, timeoutPromise]);
240
- // Clear both timeouts to prevent Jest from hanging
241
- clearTimeout(timeoutId);
242
- clearTimeout(warningTimeoutId);
377
+ let result;
378
+ try {
379
+ result = await Promise.race([processPromise, timeoutPromise]);
380
+ }
381
+ finally {
382
+ if (timeoutId) {
383
+ clearTimeout(timeoutId);
384
+ }
385
+ if (warningTimeoutId) {
386
+ clearTimeout(warningTimeoutId);
387
+ }
388
+ }
243
389
  item.resolve(result);
244
390
  loggerQueue.debug(`LLMQueue: Finished processing task for agent=${item.agentId}, world=${item.worldId}, queueItemId=${item.id}`);
245
391
  }
246
392
  catch (error) {
247
- const wasCanceled = item.canceled || item.abortController.signal.aborted || isAbortError(error);
248
- if (wasCanceled) {
393
+ const isTimeout = isLLMQueueTimeoutError(error);
394
+ const wasCanceled = !isTimeout && (item.canceled || item.abortController.signal.aborted || isAbortError(error));
395
+ if (isTimeout) {
396
+ loggerQueue.warn('LLM queue call timed out', {
397
+ agentId: item.agentId,
398
+ worldId: item.worldId,
399
+ chatId: normalizeChatId(item.chatId),
400
+ queueItemId: item.id,
401
+ reason: error instanceof Error ? error.message : String(error)
402
+ });
403
+ }
404
+ else if (wasCanceled) {
249
405
  loggerQueue.info('LLM queue call canceled', {
250
406
  agentId: item.agentId,
251
407
  worldId: item.worldId,
@@ -322,8 +478,8 @@ class LLMQueue {
322
478
  }
323
479
  // Set processing timeout (useful for testing or adjusting for long-running operations)
324
480
  setProcessingTimeout(timeoutMs) {
325
- if (timeoutMs < 1000) {
326
- throw new Error('Processing timeout must be at least 1000ms');
481
+ if (timeoutMs < RELIABILITY_CONFIG.llm.minProcessingTimeoutMs) {
482
+ throw new Error(`Processing timeout must be at least ${RELIABILITY_CONFIG.llm.minProcessingTimeoutMs}ms`);
327
483
  }
328
484
  this.processingTimeoutMs = timeoutMs;
329
485
  loggerQueue.info('LLM queue processing timeout updated', { timeoutMs });
@@ -345,6 +501,18 @@ function isAbortError(error) {
345
501
  const message = error instanceof Error ? error.message : String(error);
346
502
  return message.toLowerCase().includes('abort');
347
503
  }
504
+ function emitLLMTimeoutSystemStatus(world, chatId, content) {
505
+ const scopedChatId = typeof chatId === 'string' ? chatId.trim() : '';
506
+ if (!scopedChatId) {
507
+ return;
508
+ }
509
+ world.eventEmitter.emit('system', {
510
+ content,
511
+ timestamp: new Date(),
512
+ messageId: generateId(),
513
+ chatId: scopedChatId,
514
+ });
515
+ }
348
516
  function createCombinedAbortSignal(first, second) {
349
517
  const signals = [first, second].filter((value) => Boolean(value));
350
518
  if (signals.length === 0) {
@@ -380,24 +548,36 @@ export async function streamAgentResponse(world, agent, messages, publishSSE, ch
380
548
  if (abortSignal?.aborted) {
381
549
  throw new DOMException(`LLM call aborted before queue for agent ${agent.id}`, 'AbortError');
382
550
  }
551
+ const normalizedChatId = typeof chatId === 'string' ? chatId.trim() : '';
552
+ const resolvedChatId = normalizedChatId || null;
553
+ if (!resolvedChatId) {
554
+ throw new Error(`streamAgentResponse: chatId is required for agent ${agent.id}`);
555
+ }
383
556
  // Queue the LLM call to ensure serialized execution
384
- return llmQueue.add(agent.id, world.id, chatId, async (queueAbortSignal) => {
557
+ return llmQueue.add(agent.id, world.id, resolvedChatId, async (queueAbortSignal) => {
385
558
  const { signal: mergedAbortSignal, dispose } = createCombinedAbortSignal(queueAbortSignal, abortSignal);
386
559
  try {
387
560
  if (mergedAbortSignal?.aborted) {
388
561
  throw new DOMException(`LLM call aborted before execution for agent ${agent.id}`, 'AbortError');
389
562
  }
390
- return await executeStreamAgentResponse(world, agent, messages, publishSSE, mergedAbortSignal);
563
+ return await executeStreamAgentResponse(world, agent, messages, publishSSE, resolvedChatId, mergedAbortSignal);
391
564
  }
392
565
  finally {
393
566
  dispose();
394
567
  }
568
+ }, {
569
+ onTakingTooLong: ({ elapsedMs, timeoutMs }) => {
570
+ emitLLMTimeoutSystemStatus(world, resolvedChatId, `LLM processing taking too long for ${agent.id} (elapsed ${Math.floor(elapsedMs / 1000)}s, timeout ${Math.floor(timeoutMs / 1000)}s).`);
571
+ },
572
+ onTimedOut: ({ timeoutMs }) => {
573
+ emitLLMTimeoutSystemStatus(world, resolvedChatId, `LLM processing timed out for ${agent.id} after ${Math.floor(timeoutMs / 1000)}s.`);
574
+ },
395
575
  });
396
576
  }
397
577
  /**
398
578
  * Internal streaming implementation (executed within queue)
399
579
  */
400
- async function executeStreamAgentResponse(world, agent, messages, publishSSE, abortSignal) {
580
+ async function executeStreamAgentResponse(world, agent, messages, publishSSE, chatId, abortSignal) {
401
581
  const messageId = generateId();
402
582
  try {
403
583
  if (abortSignal?.aborted) {
@@ -407,7 +587,8 @@ async function executeStreamAgentResponse(world, agent, messages, publishSSE, ab
407
587
  publishSSE(world, {
408
588
  agentName: agent.id,
409
589
  type: 'start',
410
- messageId
590
+ messageId,
591
+ chatId
411
592
  });
412
593
  loggerStreaming.debug(`LLM: Starting streaming response for agent=${agent.id}, world=${world.id}, messageId=${messageId}`);
413
594
  // Convert messages for LLM (strip custom fields)
@@ -415,9 +596,11 @@ async function executeStreamAgentResponse(world, agent, messages, publishSSE, ab
415
596
  let preparedMessages = stripCustomFieldsFromMessages(messages);
416
597
  // Get MCP tools for this world
417
598
  const mcpTools = await getMCPToolsForWorld(world.id);
418
- const hasMCPTools = Object.keys(mcpTools).length > 0;
599
+ const mcpToolNames = Object.keys(mcpTools);
600
+ const hasMCPTools = mcpToolNames.length > 0;
601
+ const workingDirectory = getEnvValueFromText(world.variables, 'working_directory') || getDefaultWorkingDirectory();
419
602
  // Add tool usage instructions to system message when tools are available
420
- preparedMessages = appendToolRulesToSystemMessage(preparedMessages, hasMCPTools);
603
+ preparedMessages = appendToolRulesToSystemMessage(preparedMessages, mcpToolNames, { workingDirectory });
421
604
  if (hasMCPTools) {
422
605
  loggerMCP.debug(`LLM: Including ${Object.keys(mcpTools).length} MCP tools for agent=${agent.id}, world=${world.id}`);
423
606
  // Debug: Log complete tool definitions being sent to LLM
@@ -430,28 +613,71 @@ async function executeStreamAgentResponse(world, agent, messages, publishSSE, ab
430
613
  });
431
614
  }
432
615
  }
616
+ emitLLMRequestDiagnostics({
617
+ world,
618
+ agent,
619
+ chatId,
620
+ messageId,
621
+ preparedMessages,
622
+ mcpTools,
623
+ });
433
624
  // Use direct OpenAI integration for OpenAI providers
434
625
  if (isOpenAIProvider(agent.provider)) {
435
626
  const client = createOpenAIClientForAgent(agent);
436
- const response = await streamOpenAIResponse(client, agent.model, preparedMessages, agent, mcpTools, world, (content) => publishSSE(world, { agentName: agent.id, type: 'chunk', content, messageId }), messageId, abortSignal);
627
+ const response = await streamOpenAIResponse(client, agent.model, preparedMessages, agent, mcpTools, world, (chunk) => publishSSE(world, {
628
+ agentName: agent.id,
629
+ type: 'chunk',
630
+ content: chunk.content,
631
+ reasoningContent: chunk.reasoningContent,
632
+ messageId,
633
+ chatId,
634
+ }), messageId, abortSignal);
635
+ emitLLMResponseDiagnostics({
636
+ world,
637
+ agent,
638
+ chatId,
639
+ messageId,
640
+ response,
641
+ });
437
642
  // Emit end event after streaming completes
438
- publishSSE(world, { agentName: agent.id, type: 'end', messageId });
643
+ publishSSE(world, { agentName: agent.id, type: 'end', messageId, chatId });
439
644
  return { response, messageId };
440
645
  }
441
646
  // Use direct Anthropic integration for Anthropic provider
442
647
  if (isAnthropicProvider(agent.provider)) {
443
648
  const client = createAnthropicClientForAgent(agent);
444
- const response = await streamAnthropicResponse(client, agent.model, preparedMessages, agent, mcpTools, world, (content) => publishSSE(world, { agentName: agent.id, type: 'chunk', content, messageId }), messageId, abortSignal);
649
+ const response = await streamAnthropicResponse(client, agent.model, preparedMessages, agent, mcpTools, world, (content) => publishSSE(world, { agentName: agent.id, type: 'chunk', content, messageId, chatId }), messageId, abortSignal);
650
+ emitLLMResponseDiagnostics({
651
+ world,
652
+ agent,
653
+ chatId,
654
+ messageId,
655
+ response,
656
+ });
445
657
  // Emit end event after streaming completes
446
- publishSSE(world, { agentName: agent.id, type: 'end', messageId });
658
+ publishSSE(world, { agentName: agent.id, type: 'end', messageId, chatId });
447
659
  return { response, messageId };
448
660
  }
449
661
  // Use direct Google integration for Google provider
450
662
  if (isGoogleProvider(agent.provider)) {
451
663
  const client = createGoogleClientForAgent(agent);
452
- const response = await streamGoogleResponse(client, agent.model, preparedMessages, agent, mcpTools, world, (content) => publishSSE(world, { agentName: agent.id, type: 'chunk', content, messageId }), messageId, abortSignal);
664
+ const response = await streamGoogleResponse(client, agent.model, preparedMessages, agent, mcpTools, world, (chunk) => publishSSE(world, {
665
+ agentName: agent.id,
666
+ type: 'chunk',
667
+ content: chunk.content,
668
+ reasoningContent: chunk.reasoningContent,
669
+ messageId,
670
+ chatId,
671
+ }), messageId, abortSignal);
672
+ emitLLMResponseDiagnostics({
673
+ world,
674
+ agent,
675
+ chatId,
676
+ messageId,
677
+ response,
678
+ });
453
679
  // Emit end event after streaming completes
454
- publishSSE(world, { agentName: agent.id, type: 'end', messageId });
680
+ publishSSE(world, { agentName: agent.id, type: 'end', messageId, chatId });
455
681
  return { response, messageId };
456
682
  }
457
683
  // All providers now use direct integrations - no AI SDK needed
@@ -462,7 +688,8 @@ async function executeStreamAgentResponse(world, agent, messages, publishSSE, ab
462
688
  publishSSE(world, {
463
689
  agentName: agent.id,
464
690
  type: 'end',
465
- messageId
691
+ messageId,
692
+ chatId
466
693
  });
467
694
  loggerStreaming.info(`LLM: Streaming response canceled for agent=${agent.id}, world=${world.id}, messageId=${messageId}`);
468
695
  throw new Error(`LLM call canceled for agent ${agent.id}`);
@@ -472,9 +699,16 @@ async function executeStreamAgentResponse(world, agent, messages, publishSSE, ab
472
699
  agentName: agent.id,
473
700
  type: 'error',
474
701
  error: error.message,
475
- messageId
702
+ messageId,
703
+ chatId
704
+ });
705
+ loggerStreaming.error('LLM: Error during streaming response', {
706
+ agentId: agent.id,
707
+ worldId: world.id,
708
+ chatId,
709
+ messageId,
710
+ error: error instanceof Error ? error.message : String(error)
476
711
  });
477
- loggerStreaming.error(`LLM: Error during streaming response for agent=${agent.id}, world=${world.id}, messageId=${messageId}, error=${error.message}`);
478
712
  throw error;
479
713
  }
480
714
  }
@@ -485,24 +719,36 @@ export async function generateAgentResponse(world, agent, messages, _publishSSE,
485
719
  if (abortSignal?.aborted) {
486
720
  throw new DOMException(`LLM call aborted before queue for agent ${agent.id}`, 'AbortError');
487
721
  }
722
+ const normalizedChatId = typeof chatId === 'string' ? chatId.trim() : '';
723
+ const resolvedChatId = normalizedChatId || null;
724
+ if (!resolvedChatId) {
725
+ throw new Error(`generateAgentResponse: chatId is required for agent ${agent.id}`);
726
+ }
488
727
  // Queue the LLM call to ensure serialized execution
489
- return llmQueue.add(agent.id, world.id, chatId, async (queueAbortSignal) => {
728
+ return llmQueue.add(agent.id, world.id, resolvedChatId, async (queueAbortSignal) => {
490
729
  const { signal: mergedAbortSignal, dispose } = createCombinedAbortSignal(queueAbortSignal, abortSignal);
491
730
  try {
492
731
  if (mergedAbortSignal?.aborted) {
493
732
  throw new DOMException(`LLM call aborted before execution for agent ${agent.id}`, 'AbortError');
494
733
  }
495
- return await executeGenerateAgentResponse(world, agent, messages, skipTools, mergedAbortSignal);
734
+ return await executeGenerateAgentResponse(world, agent, messages, skipTools, resolvedChatId, mergedAbortSignal);
496
735
  }
497
736
  finally {
498
737
  dispose();
499
738
  }
739
+ }, {
740
+ onTakingTooLong: ({ elapsedMs, timeoutMs }) => {
741
+ emitLLMTimeoutSystemStatus(world, resolvedChatId, `LLM processing taking too long for ${agent.id} (elapsed ${Math.floor(elapsedMs / 1000)}s, timeout ${Math.floor(timeoutMs / 1000)}s).`);
742
+ },
743
+ onTimedOut: ({ timeoutMs }) => {
744
+ emitLLMTimeoutSystemStatus(world, resolvedChatId, `LLM processing timed out for ${agent.id} after ${Math.floor(timeoutMs / 1000)}s.`);
745
+ },
500
746
  });
501
747
  }
502
748
  /**
503
749
  * Internal generation implementation (executed within queue)
504
750
  */
505
- async function executeGenerateAgentResponse(world, agent, messages, skipTools, abortSignal) {
751
+ async function executeGenerateAgentResponse(world, agent, messages, skipTools, chatId = null, abortSignal) {
506
752
  if (abortSignal?.aborted) {
507
753
  throw new DOMException('LLM call aborted before start', 'AbortError');
508
754
  }
@@ -512,9 +758,19 @@ async function executeGenerateAgentResponse(world, agent, messages, skipTools, a
512
758
  let preparedMessages = stripCustomFieldsFromMessages(messages);
513
759
  // Get MCP tools for this world (skip if requested, e.g., for title generation)
514
760
  const mcpTools = skipTools ? {} : await getMCPToolsForWorld(world.id);
515
- const hasMCPTools = Object.keys(mcpTools).length > 0;
761
+ const mcpToolNames = Object.keys(mcpTools);
762
+ const hasMCPTools = mcpToolNames.length > 0;
763
+ const workingDirectory = getEnvValueFromText(world.variables, 'working_directory') || getDefaultWorkingDirectory();
516
764
  // Add tool usage instructions to system message when tools are available
517
- preparedMessages = appendToolRulesToSystemMessage(preparedMessages, hasMCPTools);
765
+ preparedMessages = appendToolRulesToSystemMessage(preparedMessages, mcpToolNames, { workingDirectory });
766
+ emitLLMRequestDiagnostics({
767
+ world,
768
+ agent,
769
+ chatId,
770
+ messageId,
771
+ preparedMessages,
772
+ mcpTools,
773
+ });
518
774
  if (hasMCPTools) {
519
775
  loggerMCP.debug(`LLM: Including ${Object.keys(mcpTools).length} MCP tools for agent=${agent.id}, world=${world.id}`);
520
776
  // Debug: Log complete tool definitions being sent to LLM
@@ -548,6 +804,13 @@ async function executeGenerateAgentResponse(world, agent, messages, skipTools, a
548
804
  agent.lastActive = new Date();
549
805
  agent.llmCallCount++;
550
806
  agent.lastLLMCall = new Date();
807
+ emitLLMResponseDiagnostics({
808
+ world,
809
+ agent,
810
+ chatId,
811
+ messageId,
812
+ response,
813
+ });
551
814
  loggerGeneration.debug(`LLM: Finished non-streaming OpenAI response for agent=${agent.id}, world=${world.id}`, {
552
815
  responseType: response.type,
553
816
  contentLength: response.content?.length || 0,
@@ -565,6 +828,13 @@ async function executeGenerateAgentResponse(world, agent, messages, skipTools, a
565
828
  agent.lastActive = new Date();
566
829
  agent.llmCallCount++;
567
830
  agent.lastLLMCall = new Date();
831
+ emitLLMResponseDiagnostics({
832
+ world,
833
+ agent,
834
+ chatId,
835
+ messageId,
836
+ response,
837
+ });
568
838
  loggerGeneration.debug(`LLM: Finished non-streaming Anthropic response for agent=${agent.id}, world=${world.id}`, {
569
839
  responseType: response.type,
570
840
  contentLength: response.content?.length || 0,
@@ -582,6 +852,13 @@ async function executeGenerateAgentResponse(world, agent, messages, skipTools, a
582
852
  agent.lastActive = new Date();
583
853
  agent.llmCallCount++;
584
854
  agent.lastLLMCall = new Date();
855
+ emitLLMResponseDiagnostics({
856
+ world,
857
+ agent,
858
+ chatId,
859
+ messageId,
860
+ response,
861
+ });
585
862
  loggerGeneration.debug(`LLM: Finished non-streaming Google response for agent=${agent.id}, world=${world.id}`, {
586
863
  responseType: response.type,
587
864
  contentLength: response.content?.length || 0,
@@ -599,7 +876,13 @@ async function executeGenerateAgentResponse(world, agent, messages, skipTools, a
599
876
  loggerGeneration.info(`LLM: Non-streaming response canceled for agent=${agent.id}, world=${world.id}, messageId=${messageId}`);
600
877
  throw new Error(`LLM call canceled for agent ${agent.id}`);
601
878
  }
602
- loggerGeneration.error(`LLM: Error during non-streaming response for agent=${agent.id}, world=${world.id}, error=${error.message}`);
879
+ loggerGeneration.error('LLM: Error during non-streaming response', {
880
+ agentId: agent.id,
881
+ worldId: world.id,
882
+ chatId,
883
+ messageId,
884
+ error: error instanceof Error ? error.message : String(error)
885
+ });
603
886
  throw error;
604
887
  }
605
888
  }
@@ -654,8 +937,17 @@ function createOpenAIClientForAgent(agent) {
654
937
  switch (agent.provider) {
655
938
  case LLMProvider.OPENAI:
656
939
  return createClientForProvider('openai', config);
657
- case LLMProvider.AZURE:
658
- return createClientForProvider('azure', config);
940
+ case LLMProvider.AZURE: {
941
+ const configuredDeployment = typeof config.deployment === 'string'
942
+ ? config.deployment.trim()
943
+ : '';
944
+ const modelDeployment = typeof agent.model === 'string' ? agent.model.trim() : '';
945
+ // For Azure, deployment is selected in the URL path. Prefer runtime model when provided.
946
+ return createClientForProvider('azure', {
947
+ ...config,
948
+ deployment: modelDeployment || configuredDeployment,
949
+ });
950
+ }
659
951
  case LLMProvider.OPENAI_COMPATIBLE:
660
952
  return createClientForProvider('openai-compatible', config);
661
953
  case LLMProvider.XAI: