@framers/agentos 0.10.22 → 0.10.24

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 (204) hide show
  1. package/README.md +1 -1
  2. package/dist/api/AgentOS.d.ts.map +1 -1
  3. package/dist/api/AgentOS.js +3 -2
  4. package/dist/api/AgentOS.js.map +1 -1
  5. package/dist/api/agent.d.ts.map +1 -1
  6. package/dist/api/agent.js +25 -10
  7. package/dist/api/agent.js.map +1 -1
  8. package/dist/api/embedText.d.ts.map +1 -1
  9. package/dist/api/embedText.js +53 -17
  10. package/dist/api/embedText.js.map +1 -1
  11. package/dist/api/generateImage.d.ts.map +1 -1
  12. package/dist/api/generateImage.js +51 -7
  13. package/dist/api/generateImage.js.map +1 -1
  14. package/dist/api/generateText.d.ts +50 -7
  15. package/dist/api/generateText.d.ts.map +1 -1
  16. package/dist/api/generateText.js +265 -35
  17. package/dist/api/generateText.js.map +1 -1
  18. package/dist/api/model.d.ts +25 -1
  19. package/dist/api/model.d.ts.map +1 -1
  20. package/dist/api/model.js +108 -3
  21. package/dist/api/model.js.map +1 -1
  22. package/dist/api/runtime/SelfImprovementSessionManager.d.ts +34 -2
  23. package/dist/api/runtime/SelfImprovementSessionManager.d.ts.map +1 -1
  24. package/dist/api/runtime/SelfImprovementSessionManager.js +119 -33
  25. package/dist/api/runtime/SelfImprovementSessionManager.js.map +1 -1
  26. package/dist/api/runtime/global-default.d.ts +9 -1
  27. package/dist/api/runtime/global-default.d.ts.map +1 -1
  28. package/dist/api/runtime/global-default.js.map +1 -1
  29. package/dist/api/runtime/provider-defaults.d.ts.map +1 -1
  30. package/dist/api/runtime/provider-defaults.js +10 -4
  31. package/dist/api/runtime/provider-defaults.js.map +1 -1
  32. package/dist/api/runtime/tool-emulation/history.d.ts +16 -0
  33. package/dist/api/runtime/tool-emulation/history.d.ts.map +1 -0
  34. package/dist/api/runtime/tool-emulation/history.js +89 -0
  35. package/dist/api/runtime/tool-emulation/history.js.map +1 -0
  36. package/dist/api/runtime/tool-emulation/index.d.ts +1 -0
  37. package/dist/api/runtime/tool-emulation/index.d.ts.map +1 -1
  38. package/dist/api/runtime/tool-emulation/index.js +1 -0
  39. package/dist/api/runtime/tool-emulation/index.js.map +1 -1
  40. package/dist/api/runtime/tool-emulation/loop.d.ts +6 -0
  41. package/dist/api/runtime/tool-emulation/loop.d.ts.map +1 -1
  42. package/dist/api/runtime/tool-emulation/loop.js +1 -0
  43. package/dist/api/runtime/tool-emulation/loop.js.map +1 -1
  44. package/dist/api/sessionTranscript.d.ts +34 -0
  45. package/dist/api/sessionTranscript.d.ts.map +1 -1
  46. package/dist/api/sessionTranscript.js +61 -1
  47. package/dist/api/sessionTranscript.js.map +1 -1
  48. package/dist/api/streamText.d.ts.map +1 -1
  49. package/dist/api/streamText.js +120 -24
  50. package/dist/api/streamText.js.map +1 -1
  51. package/dist/api/types/AgentOSInput.d.ts +11 -0
  52. package/dist/api/types/AgentOSInput.d.ts.map +1 -1
  53. package/dist/api/types.d.ts +14 -6
  54. package/dist/api/types.d.ts.map +1 -1
  55. package/dist/api/types.js.map +1 -1
  56. package/dist/cognition/emergent/AdaptPersonalityTool.d.ts +13 -4
  57. package/dist/cognition/emergent/AdaptPersonalityTool.d.ts.map +1 -1
  58. package/dist/cognition/emergent/AdaptPersonalityTool.js +32 -23
  59. package/dist/cognition/emergent/AdaptPersonalityTool.js.map +1 -1
  60. package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts +18 -6
  61. package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts.map +1 -1
  62. package/dist/cognition/emergent/EmergentCapabilityEngine.js.map +1 -1
  63. package/dist/cognition/emergent/SelfEvaluateTool.d.ts +10 -3
  64. package/dist/cognition/emergent/SelfEvaluateTool.d.ts.map +1 -1
  65. package/dist/cognition/emergent/SelfEvaluateTool.js +3 -2
  66. package/dist/cognition/emergent/SelfEvaluateTool.js.map +1 -1
  67. package/dist/cognition/substrate/CognitiveMemoryBridge.d.ts +51 -2
  68. package/dist/cognition/substrate/CognitiveMemoryBridge.d.ts.map +1 -1
  69. package/dist/cognition/substrate/CognitiveMemoryBridge.js +84 -2
  70. package/dist/cognition/substrate/CognitiveMemoryBridge.js.map +1 -1
  71. package/dist/cognition/substrate/ConversationHistoryManager.d.ts +8 -4
  72. package/dist/cognition/substrate/ConversationHistoryManager.d.ts.map +1 -1
  73. package/dist/cognition/substrate/ConversationHistoryManager.js +17 -5
  74. package/dist/cognition/substrate/ConversationHistoryManager.js.map +1 -1
  75. package/dist/cognition/substrate/GMI.d.ts +92 -5
  76. package/dist/cognition/substrate/GMI.d.ts.map +1 -1
  77. package/dist/cognition/substrate/GMI.js +291 -65
  78. package/dist/cognition/substrate/GMI.js.map +1 -1
  79. package/dist/cognition/substrate/GMIManager.d.ts +12 -0
  80. package/dist/cognition/substrate/GMIManager.d.ts.map +1 -1
  81. package/dist/cognition/substrate/GMIManager.js +22 -16
  82. package/dist/cognition/substrate/GMIManager.js.map +1 -1
  83. package/dist/cognition/substrate/IGMI.d.ts +23 -0
  84. package/dist/cognition/substrate/IGMI.d.ts.map +1 -1
  85. package/dist/cognition/substrate/IGMI.js.map +1 -1
  86. package/dist/cognition/substrate/MetapromptExecutor.d.ts +183 -28
  87. package/dist/cognition/substrate/MetapromptExecutor.d.ts.map +1 -1
  88. package/dist/cognition/substrate/MetapromptExecutor.js +431 -108
  89. package/dist/cognition/substrate/MetapromptExecutor.js.map +1 -1
  90. package/dist/cognition/substrate/personas/IPersonaDefinition.d.ts +5 -0
  91. package/dist/cognition/substrate/personas/IPersonaDefinition.d.ts.map +1 -1
  92. package/dist/cognition/substrate/personas/PersonaValidation.d.ts.map +1 -1
  93. package/dist/cognition/substrate/personas/PersonaValidation.js +30 -0
  94. package/dist/cognition/substrate/personas/PersonaValidation.js.map +1 -1
  95. package/dist/cognition/substrate/personas/SoulLoader.d.ts +14 -1
  96. package/dist/cognition/substrate/personas/SoulLoader.d.ts.map +1 -1
  97. package/dist/cognition/substrate/personas/SoulLoader.js +45 -12
  98. package/dist/cognition/substrate/personas/SoulLoader.js.map +1 -1
  99. package/dist/cognition/substrate/personas/definitions/default_assistant_persona.json +3 -35
  100. package/dist/cognition/substrate/personas/hexaco.d.ts +34 -0
  101. package/dist/cognition/substrate/personas/hexaco.d.ts.map +1 -0
  102. package/dist/cognition/substrate/personas/hexaco.js +66 -0
  103. package/dist/cognition/substrate/personas/hexaco.js.map +1 -0
  104. package/dist/cognition/substrate/userFeedback.d.ts +69 -0
  105. package/dist/cognition/substrate/userFeedback.d.ts.map +1 -0
  106. package/dist/cognition/substrate/userFeedback.js +107 -0
  107. package/dist/cognition/substrate/userFeedback.js.map +1 -0
  108. package/dist/core/conversation/ConversationMessage.d.ts +9 -0
  109. package/dist/core/conversation/ConversationMessage.d.ts.map +1 -1
  110. package/dist/core/conversation/ConversationMessage.js.map +1 -1
  111. package/dist/core/llm/IPromptEngine.d.ts +9 -0
  112. package/dist/core/llm/IPromptEngine.d.ts.map +1 -1
  113. package/dist/core/llm/IPromptEngine.js.map +1 -1
  114. package/dist/core/llm/PromptEngine.d.ts +30 -1
  115. package/dist/core/llm/PromptEngine.d.ts.map +1 -1
  116. package/dist/core/llm/PromptEngine.js +229 -92
  117. package/dist/core/llm/PromptEngine.js.map +1 -1
  118. package/dist/core/llm/providers/AIModelProviderManager.d.ts +20 -0
  119. package/dist/core/llm/providers/AIModelProviderManager.d.ts.map +1 -1
  120. package/dist/core/llm/providers/AIModelProviderManager.js +35 -5
  121. package/dist/core/llm/providers/AIModelProviderManager.js.map +1 -1
  122. package/dist/core/llm/providers/IProvider.d.ts +28 -8
  123. package/dist/core/llm/providers/IProvider.d.ts.map +1 -1
  124. package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts +135 -49
  125. package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts.map +1 -1
  126. package/dist/core/llm/providers/implementations/AnthropicProvider.js +323 -114
  127. package/dist/core/llm/providers/implementations/AnthropicProvider.js.map +1 -1
  128. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts.map +1 -1
  129. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js +15 -1
  130. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js.map +1 -1
  131. package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts +1 -1
  132. package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts.map +1 -1
  133. package/dist/core/llm/providers/implementations/GeminiCLIProvider.js +51 -24
  134. package/dist/core/llm/providers/implementations/GeminiCLIProvider.js.map +1 -1
  135. package/dist/core/llm/providers/implementations/GeminiProvider.d.ts +25 -6
  136. package/dist/core/llm/providers/implementations/GeminiProvider.d.ts.map +1 -1
  137. package/dist/core/llm/providers/implementations/GeminiProvider.js +108 -90
  138. package/dist/core/llm/providers/implementations/GeminiProvider.js.map +1 -1
  139. package/dist/core/llm/providers/implementations/MistralProvider.d.ts +11 -0
  140. package/dist/core/llm/providers/implementations/MistralProvider.d.ts.map +1 -1
  141. package/dist/core/llm/providers/implementations/MistralProvider.js +32 -2
  142. package/dist/core/llm/providers/implementations/MistralProvider.js.map +1 -1
  143. package/dist/core/llm/providers/implementations/OllamaProvider.js +7 -7
  144. package/dist/core/llm/providers/implementations/OllamaProvider.js.map +1 -1
  145. package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts +53 -2
  146. package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts.map +1 -1
  147. package/dist/core/llm/providers/implementations/OpenAIProvider.js +84 -17
  148. package/dist/core/llm/providers/implementations/OpenAIProvider.js.map +1 -1
  149. package/dist/core/llm/providers/implementations/OpenRouterProvider.d.ts.map +1 -1
  150. package/dist/core/llm/providers/implementations/OpenRouterProvider.js +8 -3
  151. package/dist/core/llm/providers/implementations/OpenRouterProvider.js.map +1 -1
  152. package/dist/core/llm/providers/implementations/TogetherProvider.d.ts +3 -3
  153. package/dist/core/llm/providers/implementations/TogetherProvider.d.ts.map +1 -1
  154. package/dist/core/llm/providers/implementations/TogetherProvider.js +25 -13
  155. package/dist/core/llm/providers/implementations/TogetherProvider.js.map +1 -1
  156. package/dist/core/llm/providers/implementations/cacheLeakDetector.d.ts +15 -7
  157. package/dist/core/llm/providers/implementations/cacheLeakDetector.d.ts.map +1 -1
  158. package/dist/core/llm/providers/implementations/cacheLeakDetector.js +12 -7
  159. package/dist/core/llm/providers/implementations/cacheLeakDetector.js.map +1 -1
  160. package/dist/core/llm/providers/model-cache-capabilities.d.ts +24 -9
  161. package/dist/core/llm/providers/model-cache-capabilities.d.ts.map +1 -1
  162. package/dist/core/llm/providers/model-cache-capabilities.js +59 -44
  163. package/dist/core/llm/providers/model-cache-capabilities.js.map +1 -1
  164. package/dist/core/llm/providers/model-effort.d.ts +14 -0
  165. package/dist/core/llm/providers/model-effort.d.ts.map +1 -1
  166. package/dist/core/llm/providers/model-effort.js +22 -0
  167. package/dist/core/llm/providers/model-effort.js.map +1 -1
  168. package/dist/core/llm/providers/model-forced-tool-choice.d.ts +13 -6
  169. package/dist/core/llm/providers/model-forced-tool-choice.d.ts.map +1 -1
  170. package/dist/core/llm/providers/model-forced-tool-choice.js +14 -7
  171. package/dist/core/llm/providers/model-forced-tool-choice.js.map +1 -1
  172. package/dist/core/llm/providers/model-thinking.d.ts +64 -13
  173. package/dist/core/llm/providers/model-thinking.d.ts.map +1 -1
  174. package/dist/core/llm/providers/model-thinking.js +52 -12
  175. package/dist/core/llm/providers/model-thinking.js.map +1 -1
  176. package/dist/core/llm/providers/openrouter-only-params.d.ts +26 -7
  177. package/dist/core/llm/providers/openrouter-only-params.d.ts.map +1 -1
  178. package/dist/core/llm/providers/openrouter-only-params.js +58 -9
  179. package/dist/core/llm/providers/openrouter-only-params.js.map +1 -1
  180. package/dist/core/llm/providers/url-secrets.d.ts +42 -0
  181. package/dist/core/llm/providers/url-secrets.d.ts.map +1 -0
  182. package/dist/core/llm/providers/url-secrets.js +64 -0
  183. package/dist/core/llm/providers/url-secrets.js.map +1 -0
  184. package/dist/core/llm/streaming/StreamingBatcher.d.ts +4 -3
  185. package/dist/core/llm/streaming/StreamingBatcher.d.ts.map +1 -1
  186. package/dist/core/llm/streaming/StreamingBatcher.js +8 -3
  187. package/dist/core/llm/streaming/StreamingBatcher.js.map +1 -1
  188. package/dist/core/safety/LLMProviderHealthRegistry.d.ts +4 -0
  189. package/dist/core/safety/LLMProviderHealthRegistry.d.ts.map +1 -1
  190. package/dist/core/safety/LLMProviderHealthRegistry.js +26 -0
  191. package/dist/core/safety/LLMProviderHealthRegistry.js.map +1 -1
  192. package/dist/core/utils/sha256.d.ts +19 -0
  193. package/dist/core/utils/sha256.d.ts.map +1 -0
  194. package/dist/core/utils/sha256.js +101 -0
  195. package/dist/core/utils/sha256.js.map +1 -0
  196. package/dist/index.d.ts +1 -1
  197. package/dist/index.d.ts.map +1 -1
  198. package/dist/index.js +1 -1
  199. package/dist/index.js.map +1 -1
  200. package/dist/orchestration/pipeline/query/types.js +3 -3
  201. package/dist/orchestration/pipeline/query/types.js.map +1 -1
  202. package/dist/orchestration/planning/ProviderAssignmentEngine.js +2 -2
  203. package/dist/orchestration/planning/ProviderAssignmentEngine.js.map +1 -1
  204. package/package.json +1 -1
@@ -21,6 +21,7 @@ import { ConversationHistoryManager } from './ConversationHistoryManager.js';
21
21
  import { CognitiveMemoryBridge } from './CognitiveMemoryBridge.js';
22
22
  import { SentimentTracker } from './SentimentTracker.js';
23
23
  import { MetapromptExecutor } from './MetapromptExecutor.js';
24
+ import { feedbackTraceMessage } from './userFeedback.js';
24
25
  const DEFAULT_MAX_CONVERSATION_HISTORY_TURNS = 20;
25
26
  const DEFAULT_SELF_REFLECTION_INTERVAL_TURNS = 5;
26
27
  const MAX_REASONING_TRACE_ENTRIES = 500; // Limit trace size in memory
@@ -38,6 +39,14 @@ export class GMI {
38
39
  */
39
40
  constructor(gmiId) {
40
41
  this.isInitialized = false; // Maintained as per user-provided GMI.ts
42
+ /**
43
+ * Turn ownership. Each processTurnStream call takes the next number and
44
+ * owns the lifecycle state until a newer turn starts; a turn that no longer
45
+ * owns it (a failed turn whose generator is drained after the next turn
46
+ * began) leaves the state and the trace's turn id to the newer turn.
47
+ */
48
+ this.turnSequence = 0;
49
+ this.stateOwnerTurn = 0;
41
50
  // (Self-reflection state is owned by MetapromptExecutor)
42
51
  // Cognitive Memory Bridge
43
52
  this.memoryBridge = null;
@@ -237,6 +246,57 @@ export class GMI {
237
246
  getCognitiveMemoryManager() {
238
247
  return this.cognitiveMemory;
239
248
  }
249
+ /**
250
+ * Records user feedback on this instance. Adds a reasoning-trace entry
251
+ * (WARNING for negative feedback, DEBUG otherwise) and, when cognitive memory
252
+ * is configured, encodes the feedback as an episodic memory of the user. A
253
+ * correction is also encoded as a semantic memory so later turns can recall
254
+ * it. Memory failures are traced by the bridge and never thrown.
255
+ *
256
+ * @param feedback - Normalized feedback plus the id of the user who sent it.
257
+ */
258
+ async recordUserFeedback(feedback) {
259
+ const { userId, polarity, score, text, correctedContent, targetMessageId, tags } = feedback;
260
+ this.addTraceEntry(polarity === 'negative' ? ReasoningEntryType.WARNING : ReasoningEntryType.DEBUG, feedbackTraceMessage(polarity), { userId, polarity, score, text, correctedContent, targetMessageId, tags });
261
+ // An empty user id falls back to the bridge's default scope (the current user).
262
+ const scopeId = typeof userId === 'string' && userId.trim() ? userId.trim() : undefined;
263
+ const scoreLabel = score !== undefined ? `, score ${score}` : '';
264
+ await this.memoryBridge?.encode(`User feedback (${polarity}${scoreLabel})${text ? `: ${text}` : ''}`, {
265
+ type: 'episodic',
266
+ sourceType: 'user_statement',
267
+ role: 'user',
268
+ scopeId,
269
+ tags: ['user_feedback', `feedback_${polarity}`, ...(tags ?? [])],
270
+ });
271
+ if (correctedContent) {
272
+ const target = targetMessageId ? ` for message ${targetMessageId}` : '';
273
+ await this.memoryBridge?.encode(`User correction${target}: ${correctedContent}`, {
274
+ type: 'semantic',
275
+ sourceType: 'user_statement',
276
+ role: 'user',
277
+ scopeId,
278
+ tags: ['user_feedback', 'user_correction'],
279
+ });
280
+ }
281
+ }
282
+ /**
283
+ * Sets one personality trait on this instance only. The persona definition
284
+ * is shared by every GMI of that persona (it is the GMIManager registry
285
+ * object), so the change is made on a copy that replaces this instance's
286
+ * active persona; other sessions and GMIs created later keep the original.
287
+ *
288
+ * @param trait - Trait key, e.g. `openness` or `honesty`.
289
+ * @param value - New trait value.
290
+ * @throws {GMIError} When the GMI has not been initialized.
291
+ */
292
+ setPersonalityTrait(trait, value) {
293
+ const persona = this.getPersona();
294
+ this.activePersona = {
295
+ ...persona,
296
+ personalityTraits: { ...(persona.personalityTraits ?? {}), [trait]: value },
297
+ };
298
+ this.addTraceEntry(ReasoningEntryType.STATE_CHANGE, `Personality trait '${trait}' set to ${value}.`, { trait, value });
299
+ }
240
300
  /**
241
301
  * Adds an entry to the GMI's reasoning trace.
242
302
  * @private
@@ -282,6 +342,33 @@ export class GMI {
282
342
  : '';
283
343
  return organizationId || undefined;
284
344
  }
345
+ /**
346
+ * Error results for the calls of a tool round that produced none: the call
347
+ * that was running when the round failed and the calls not yet started.
348
+ * Recording them leaves a history in which every tool call the assistant
349
+ * message declared has an answer, which the next request needs (providers
350
+ * reject an unanswered tool call, and a later turn replays this history).
351
+ *
352
+ * @param requests - Every call the round's assistant message declared, in order.
353
+ * @param finishedCount - How many of them already produced a result.
354
+ * @param inFlight - The call that was running when the round failed, if any.
355
+ * @param error - Why the round stopped.
356
+ * @returns One error result per unanswered call.
357
+ */
358
+ resultsForUnfinishedToolCalls(requests, finishedCount, inFlight, error) {
359
+ const reason = error instanceof Error ? error.message : String(error);
360
+ return requests.slice(finishedCount).map((request) => ({
361
+ toolCallId: request.id,
362
+ toolName: request.name,
363
+ output: undefined,
364
+ isError: true,
365
+ errorDetails: {
366
+ message: request === inFlight
367
+ ? `Tool '${request.name}' failed: ${reason}`
368
+ : `Tool '${request.name}' was not run because the tool round stopped: ${reason}`,
369
+ },
370
+ }));
371
+ }
285
372
  buildToolSessionData(turnInput) {
286
373
  const sessionId = typeof turnInput.sessionId === 'string' ? turnInput.sessionId.trim() : '';
287
374
  const conversationId = this.getConversationIdForTurn(turnInput);
@@ -299,15 +386,17 @@ export class GMI {
299
386
  return Object.keys(sessionData).length > 0 ? sessionData : undefined;
300
387
  }
301
388
  /**
302
- * Ensures the GMI is initialized and in a READY state.
389
+ * Ensures the GMI is initialized and idle: READY, or ERRORED after a failed
390
+ * turn. ERRORED only records that the last turn failed; the instance still
391
+ * works, so it does not block the next turn or the memory callbacks.
303
392
  * @private
304
393
  */
305
394
  ensureReady(additionallyAllowedStates = []) {
306
395
  if (!this.isInitialized) {
307
396
  throw new GMIError(`GMI (ID: ${this.gmiId}) is not initialized.`, GMIErrorCode.NOT_INITIALIZED);
308
397
  }
309
- if (this.state !== GMIPrimeState.READY &&
310
- !additionallyAllowedStates.includes(this.state)) {
398
+ const isIdle = this.state === GMIPrimeState.READY || this.state === GMIPrimeState.ERRORED;
399
+ if (!isIdle && !additionallyAllowedStates.includes(this.state)) {
311
400
  throw new GMIError(`GMI (ID: ${this.gmiId}) is not in READY state. Current state: ${this.state}.`, GMIErrorCode.INVALID_STATE, { currentGMIState: this.state });
312
401
  }
313
402
  }
@@ -359,6 +448,58 @@ export class GMI {
359
448
  };
360
449
  return context;
361
450
  }
451
+ /**
452
+ * Assembles the conversation history and user input for one model call of
453
+ * a turn.
454
+ *
455
+ * The history is the conversation before this turn, then this turn's own
456
+ * messages. The conversation before the turn is the host's durable history
457
+ * (`metadata.conversationHistoryForPrompt`) when the host passed a non-empty
458
+ * one, which ends before the current user message; otherwise it is this
459
+ * GMI's history as the turn found it, without the user message the turn
460
+ * recorded. The turn's own messages are that user message and the assistant
461
+ * replies and tool results of earlier calls in the turn, so a tool round's
462
+ * calls and results reach the next call on either history.
463
+ *
464
+ * On a turn's first call, plain text input travels as `userInput`, which the
465
+ * chat template places last and attaches retrieved context to; the durable
466
+ * path has always sent it that way. Structured (multimodal) input stays a
467
+ * history message, so its parts reach the provider intact and only once.
468
+ *
469
+ * @param turn - Where the turn's messages come from.
470
+ * @param turn.durableHistory - The host's history before this turn, or
471
+ * `null` to use this GMI's own history.
472
+ * @param turn.historyAtTurnStart - This GMI's history once the turn input
473
+ * was recorded.
474
+ * @param turn.currentUserMessage - The user message the turn input added,
475
+ * when the turn is user-initiated.
476
+ * @param turn.turnMessages - Assistant replies and tool results the turn has
477
+ * added so far.
478
+ * @returns The `conversationHistory` and `userInput` prompt components,
479
+ * and how many history messages belong to this turn (token budgeting
480
+ * leaves those whole).
481
+ */
482
+ buildPromptConversation(turn) {
483
+ const { durableHistory, historyAtTurnStart, currentUserMessage, turnMessages } = turn;
484
+ const toConversationMessage = (message) => this.conversationHistoryManager.convertToConversationMessage(message);
485
+ const historyBeforeTurn = durableHistory ??
486
+ historyAtTurnStart
487
+ .filter((message) => message !== currentUserMessage)
488
+ .map(toConversationMessage);
489
+ const userContent = currentUserMessage?.content;
490
+ const userText = turnMessages.length === 0 && typeof userContent === 'string' && userContent !== ''
491
+ ? userContent
492
+ : null;
493
+ const turnHistory = [
494
+ ...(currentUserMessage && userText === null ? [currentUserMessage] : []),
495
+ ...turnMessages,
496
+ ];
497
+ return {
498
+ conversationHistory: [...historyBeforeTurn, ...turnHistory.map(toConversationMessage)],
499
+ userInput: userText,
500
+ currentTurnMessageCount: turnHistory.length,
501
+ };
502
+ }
362
503
  /**
363
504
  * Determines if RAG retrieval should be triggered based on the current query and persona configuration.
364
505
  * @private
@@ -385,19 +526,19 @@ export class GMI {
385
526
  return false;
386
527
  }
387
528
  /**
388
- * Determines the prompt format type based on model provider.
389
- * @param modelDetails - Model metadata from the provider manager.
390
- * @param providerId - The provider identifier.
391
- * @returns The prompt format type string.
529
+ * Returns the PromptEngine template GMI builds its prompts with.
530
+ *
531
+ * GMI hands the constructed prompt to `IProvider.generateCompletionStream`,
532
+ * whose input is an OpenAI-style `ChatMessage[]` for every provider; each
533
+ * provider converts that array to its own wire format (Anthropic messages
534
+ * with a separate system field, Gemini contents with a systemInstruction).
535
+ * A provider-specific template would hand the provider another shape; the
536
+ * `anthropic_messages` template, for one, returns an object with the
537
+ * system prompt split out, which AnthropicProvider cannot iterate.
538
+ *
539
+ * @returns Always `'openai_chat'`.
392
540
  */
393
- determinePromptFormat(modelDetails, providerId) {
394
- const pid = (modelDetails?.providerId || providerId || '').toLowerCase();
395
- if (pid.includes('anthropic'))
396
- return 'anthropic_messages';
397
- if (pid.includes('google') || pid.includes('gemini'))
398
- return 'google_gemini';
399
- if (pid.includes('cohere'))
400
- return 'cohere_chat';
541
+ determinePromptFormat() {
401
542
  return 'openai_chat';
402
543
  }
403
544
  /**
@@ -420,7 +561,14 @@ export class GMI {
420
561
  ? [GMIPrimeState.PROCESSING, GMIPrimeState.AWAITING_TOOL_RESULT]
421
562
  : [];
422
563
  this.ensureReady(continuationAllowedStates);
564
+ if (this.state === GMIPrimeState.ERRORED) {
565
+ this.addTraceEntry(ReasoningEntryType.WARNING, 'Previous turn failed; starting a new turn.');
566
+ }
423
567
  this.state = GMIPrimeState.PROCESSING;
568
+ const turnNumber = ++this.turnSequence;
569
+ this.stateOwnerTurn = turnNumber;
570
+ // False once a newer turn has started; lifecycle writes below check it.
571
+ const ownsState = () => this.stateOwnerTurn === turnNumber;
424
572
  const turnId = turnInput.interactionId || `turn-${uuidv4()}`;
425
573
  // Store turnId on reasoningTrace for current turn
426
574
  if (this.reasoningTrace) {
@@ -463,7 +611,23 @@ export class GMI {
463
611
  const maxHistoryMessages = this.activePersona.conversationContextConfig?.maxMessages ||
464
612
  this.activePersona.memoryConfig?.conversationContext?.maxMessages ||
465
613
  DEFAULT_MAX_CONVERSATION_HISTORY_TURNS;
614
+ const messagesBeforeInput = new Set(this.conversationHistoryManager.history);
466
615
  this.conversationHistoryManager.update(turnInput, maxHistoryMessages);
616
+ // The turn's prompt sources: this GMI's history once the input is
617
+ // recorded, the user message the input added (user-initiated turns
618
+ // only), the host's durable history when it passed one, and the
619
+ // assistant replies and tool results the loop below adds.
620
+ const historyAtTurnStart = [...this.conversationHistoryManager.history];
621
+ const currentUserMessage = historyAtTurnStart.find((message) => message.role === 'user' && !messagesBeforeInput.has(message));
622
+ const currentTurnText = currentUserMessage?.content
623
+ ? (typeof currentUserMessage.content === 'string'
624
+ ? currentUserMessage.content
625
+ : JSON.stringify(currentUserMessage.content))
626
+ : '';
627
+ const durableHistoryForPrompt = Array.isArray(turnInput.metadata?.conversationHistoryForPrompt) && turnInput.metadata?.conversationHistoryForPrompt.length > 0
628
+ ? turnInput.metadata?.conversationHistoryForPrompt
629
+ : null;
630
+ const turnMessages = [];
467
631
  // Analyze sentiment of user input only when sentiment tracking is enabled
468
632
  if (this.activePersona.sentimentTracking?.enabled) {
469
633
  const lastMsg = this.conversationHistoryManager.history.length > 0
@@ -511,14 +675,10 @@ export class GMI {
511
675
  ? turnInput.metadata.longTermMemoryContext.trim()
512
676
  : "";
513
677
  let assembledMemoryContext = null;
514
- const lastMessage = this.conversationHistoryManager.history.length > 0 ? this.conversationHistoryManager.history[this.conversationHistoryManager.history.length - 1] : null;
515
- const isUserInitiatedTurn = lastMessage?.role === 'user';
516
- const currentTurnText = isUserInitiatedTurn && lastMessage?.content
517
- ? (typeof lastMessage.content === 'string'
518
- ? lastMessage.content
519
- : JSON.stringify(lastMessage.content))
520
- : '';
521
- if (this.retrievalAugmentor && this.activePersona.memoryConfig?.ragConfig?.enabled && isUserInitiatedTurn && lastMessage?.content) {
678
+ // Retrieval and memory assembly run for a user turn's first model
679
+ // call only; later calls continue from tool results.
680
+ const isUserInitiatedTurn = currentUserMessage !== undefined && turnMessages.length === 0;
681
+ if (this.retrievalAugmentor && this.activePersona.memoryConfig?.ragConfig?.enabled && isUserInitiatedTurn && currentTurnText) {
522
682
  const currentQueryForRag = currentTurnText;
523
683
  if (this.shouldTriggerRAGRetrieval(currentQueryForRag)) {
524
684
  this.addTraceEntry(ReasoningEntryType.RAG_QUERY_START, "RAG retrieval triggered.", { queryPreview: currentQueryForRag.substring(0, 100) });
@@ -550,7 +710,12 @@ export class GMI {
550
710
  }
551
711
  }
552
712
  if (isUserInitiatedTurn && currentTurnText) {
553
- assembledMemoryContext = await this.memoryBridge?.assembleContext(currentTurnText) ?? null;
713
+ // Recall is limited to this turn's user, session and conversation scopes.
714
+ assembledMemoryContext = await this.memoryBridge?.assembleContext(currentTurnText, {
715
+ sessionId: turnInput.sessionId,
716
+ conversationId: this.getConversationIdForTurn(turnInput),
717
+ organizationId: this.getOrganizationIdForTurn(turnInput),
718
+ }) ?? null;
554
719
  }
555
720
  const promptExecContext = this.buildPromptExecutionContext();
556
721
  const baseSystemPrompts = Array.isArray(this.activePersona.baseSystemPrompt)
@@ -597,13 +762,17 @@ export class GMI {
597
762
  priority: 57,
598
763
  });
599
764
  }
600
- const durableHistoryForPrompt = Array.isArray(turnInput.metadata?.conversationHistoryForPrompt) && turnInput.metadata?.conversationHistoryForPrompt.length > 0
601
- ? turnInput.metadata?.conversationHistoryForPrompt
602
- : null;
765
+ const promptConversation = this.buildPromptConversation({
766
+ durableHistory: durableHistoryForPrompt,
767
+ historyAtTurnStart,
768
+ currentUserMessage,
769
+ turnMessages,
770
+ });
603
771
  const promptComponents = {
604
772
  systemPrompts,
605
- conversationHistory: durableHistoryForPrompt ?? this.conversationHistoryManager.buildForPrompt(),
606
- userInput: isUserInitiatedTurn ? currentTurnText : null,
773
+ conversationHistory: promptConversation.conversationHistory,
774
+ currentTurnMessageCount: promptConversation.currentTurnMessageCount,
775
+ userInput: promptConversation.userInput,
607
776
  retrievedContext: [
608
777
  assembledMemoryContext?.contextText,
609
778
  augmentedContextFromRAG,
@@ -624,7 +793,7 @@ export class GMI {
624
793
  providerId: modelDetails?.providerId || providerIdForModel || this.llmProviderManager.getProviderForModel(modelIdToUse)?.providerId || 'unknown',
625
794
  maxContextTokens: modelDetails?.contextWindowSize || 8192, // Default fallback
626
795
  capabilities: modelDetails?.capabilities || [],
627
- promptFormatType: this.determinePromptFormat(modelDetails, providerIdForModel),
796
+ promptFormatType: this.determinePromptFormat(),
628
797
  toolSupport: {
629
798
  supported: modelDetails?.capabilities.includes('tool_use') || false,
630
799
  format: this.determineToolFormat(modelDetails, providerIdForModel),
@@ -632,6 +801,16 @@ export class GMI {
632
801
  };
633
802
  const promptEngineResult = await this.promptEngine.constructPrompt(promptComponents, modelTargetInfo, promptExecContext);
634
803
  promptEngineResult.issues?.forEach(issue => this.addTraceEntry(ReasoningEntryType.WARNING, `Prompt Engine Issue: ${issue.message}`, issue));
804
+ // A failed template leaves the prompt empty, and every provider takes
805
+ // a non-empty ChatMessage[]; fail the turn rather than send nothing.
806
+ const promptMessages = promptEngineResult.prompt;
807
+ if (!Array.isArray(promptMessages) || promptMessages.length === 0) {
808
+ throw new GMIError(`Prompt construction produced no chat messages for model '${modelTargetInfo.modelId}' (template '${promptEngineResult.metadata?.templateUsed ?? 'unknown'}').`, GMIErrorCode.GMI_PROCESSING_ERROR, {
809
+ turnId,
810
+ templateUsed: promptEngineResult.metadata?.templateUsed,
811
+ issues: promptEngineResult.issues?.filter((issue) => issue.type === 'error'),
812
+ });
813
+ }
635
814
  this.addTraceEntry(ReasoningEntryType.PROMPT_CONSTRUCTION_COMPLETE, `Prompt constructed for model ${modelTargetInfo.modelId}.`);
636
815
  const provider = this.llmProviderManager.getProvider(modelTargetInfo.providerId);
637
816
  if (!provider) {
@@ -680,7 +859,7 @@ export class GMI {
680
859
  // turn is unchanged on the normal path.
681
860
  let currentIterationThinkingBlocks = [];
682
861
  let textDeltaEmitted = false;
683
- for await (const chunk of provider.generateCompletionStream(modelTargetInfo.modelId, promptEngineResult.prompt, llmOptions)) {
862
+ for await (const chunk of provider.generateCompletionStream(modelTargetInfo.modelId, promptMessages, llmOptions)) {
684
863
  if (chunk.error) {
685
864
  throw new GMIError(`LLM stream error: ${chunk.error.message}`, GMIErrorCode.LLM_PROVIDER_ERROR, chunk.error.details);
686
865
  }
@@ -732,7 +911,7 @@ export class GMI {
732
911
  if (!textDeltaEmitted && aggregatedResponseText) {
733
912
  yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.TEXT_DELTA, aggregatedResponseText);
734
913
  }
735
- this.conversationHistoryManager.push({
914
+ const assistantMessage = {
736
915
  role: 'assistant',
737
916
  content: currentIterationTextResponse || null,
738
917
  tool_calls: currentIterationToolCallRequests.length > 0
@@ -744,42 +923,68 @@ export class GMI {
744
923
  }))
745
924
  : undefined,
746
925
  ...(currentIterationThinkingBlocks.length > 0 && { thinkingBlocks: currentIterationThinkingBlocks }),
747
- });
926
+ };
927
+ this.conversationHistoryManager.push(assistantMessage);
928
+ turnMessages.push(assistantMessage);
748
929
  if (currentIterationToolCallRequests.length > 0) {
749
- this.state = GMIPrimeState.AWAITING_TOOL_RESULT;
930
+ if (ownsState())
931
+ this.state = GMIPrimeState.AWAITING_TOOL_RESULT;
750
932
  const toolExecutionResults = [];
751
- for (const toolCallReq of currentIterationToolCallRequests) {
752
- const requestDetails = {
753
- toolCallRequest: toolCallReq,
754
- gmiId: this.gmiId, personaId: this.activePersona.id,
755
- personaCapabilities: this.activePersona.allowedCapabilities || [],
756
- userContext: this.currentUserContext, correlationId: turnId,
757
- sessionData: this.buildToolSessionData(turnInput),
758
- };
759
- this.addTraceEntry(ReasoningEntryType.TOOL_EXECUTION_START, `Orchestrating tool: ${toolCallReq.name}`, { reqId: toolCallReq.id });
760
- const result = await this.toolOrchestrator.processToolCall(requestDetails);
761
- toolExecutionResults.push(result);
762
- this.addTraceEntry(ReasoningEntryType.TOOL_EXECUTION_RESULT, `Tool '${toolCallReq.name}' result. Success: ${!result.isError}`, { result });
763
- if (result.isError && plannedToolFailureMode === 'fail_closed') {
764
- throw new GMIError(`Tool '${toolCallReq.name}' failed and execution policy is fail_closed.`, GMIErrorCode.TOOL_ERROR, {
765
- toolCallId: toolCallReq.id,
766
- toolName: toolCallReq.name,
767
- errorDetails: result.errorDetails,
768
- });
933
+ // The call awaiting its result; cleared once processToolCall returns.
934
+ let callInFlight;
935
+ let toolRoundFailure;
936
+ try {
937
+ for (const toolCallReq of currentIterationToolCallRequests) {
938
+ const requestDetails = {
939
+ toolCallRequest: toolCallReq,
940
+ gmiId: this.gmiId, personaId: this.activePersona.id,
941
+ personaCapabilities: this.activePersona.allowedCapabilities || [],
942
+ userContext: this.currentUserContext, correlationId: turnId,
943
+ sessionData: this.buildToolSessionData(turnInput),
944
+ };
945
+ this.addTraceEntry(ReasoningEntryType.TOOL_EXECUTION_START, `Orchestrating tool: ${toolCallReq.name}`, { reqId: toolCallReq.id });
946
+ callInFlight = toolCallReq;
947
+ const result = await this.toolOrchestrator.processToolCall(requestDetails);
948
+ callInFlight = undefined;
949
+ toolExecutionResults.push(result);
950
+ this.addTraceEntry(ReasoningEntryType.TOOL_EXECUTION_RESULT, `Tool '${toolCallReq.name}' result. Success: ${!result.isError}`, { result });
951
+ if (result.isError && plannedToolFailureMode === 'fail_closed') {
952
+ throw new GMIError(`Tool '${toolCallReq.name}' failed and execution policy is fail_closed.`, GMIErrorCode.TOOL_ERROR, {
953
+ toolCallId: toolCallReq.id,
954
+ toolName: toolCallReq.name,
955
+ errorDetails: result.errorDetails,
956
+ });
957
+ }
769
958
  }
770
959
  }
771
- toolExecutionResults.forEach(tcResult => this.conversationHistoryManager.updateWithToolResult(tcResult));
960
+ catch (error) {
961
+ toolRoundFailure = { error };
962
+ toolExecutionResults.push(...this.resultsForUnfinishedToolCalls(currentIterationToolCallRequests, toolExecutionResults.length, callInFlight, error));
963
+ }
964
+ // Recorded on success and failure alike, so the history this turn
965
+ // leaves answers every call its assistant message declared.
966
+ for (const tcResult of toolExecutionResults) {
967
+ turnMessages.push(this.conversationHistoryManager.updateWithToolResult(tcResult));
968
+ }
969
+ if (toolRoundFailure)
970
+ throw toolRoundFailure.error;
772
971
  currentIterationTextResponse = ""; // Reset for next iteration if any
773
972
  currentIterationToolCallRequests = []; // Reset
774
- this.state = GMIPrimeState.PROCESSING;
973
+ if (ownsState())
974
+ this.state = GMIPrimeState.PROCESSING;
775
975
  continue main_processing_loop;
776
976
  }
777
977
  break main_processing_loop; // Break if no tool calls
778
978
  }
779
979
  await this.memoryBridge?.syncForTurn(turnInput, aggregatedResponseText);
780
980
  await this.performPostTurnIngestion(this.stringifyTurnContent(turnInput.content) ?? '', aggregatedResponseText);
781
- // Check and trigger all metaprompts (turn_interval, event_based, manual)
782
- await this.metapromptExecutor.checkAndTriggerMetaprompts(turnId);
981
+ // Check and trigger all metaprompts (turn_interval, event_based, manual).
982
+ // Only user messages count toward turn_interval triggers: tool
983
+ // continuations, system messages and tool responses do not.
984
+ const isUserTurn = turnInput.metadata?.isToolContinuation !== true &&
985
+ (turnInput.type === GMIInteractionType.TEXT ||
986
+ turnInput.type === GMIInteractionType.MULTIMODAL_CONTENT);
987
+ await this.metapromptExecutor.checkAndTriggerMetaprompts(turnId, { countTurn: isUserTurn });
783
988
  // Prepare the final GMIOutput for the generator's return value
784
989
  const finalTurnOutput = {
785
990
  isFinal: true,
@@ -794,7 +999,8 @@ export class GMI {
794
999
  }
795
1000
  catch (error) {
796
1001
  const gmiError = createGMIErrorFromError(error, GMIErrorCode.GMI_PROCESSING_ERROR, { turnId }, `Error in GMI turn '${turnId}'.`);
797
- this.state = GMIPrimeState.ERRORED;
1002
+ if (ownsState())
1003
+ this.state = GMIPrimeState.ERRORED;
798
1004
  lastErrorForOutput = { code: gmiError.code, message: gmiError.message, details: gmiError.details };
799
1005
  this.addTraceEntry(ReasoningEntryType.ERROR, `GMI processing error: ${gmiError.message}`, gmiError.toPlainObject());
800
1006
  console.error(`GMI (ID: ${this.gmiId}) error in turn '${turnId}':`, gmiError);
@@ -808,13 +1014,19 @@ export class GMI {
808
1014
  };
809
1015
  }
810
1016
  finally {
811
- if (this.state !== GMIPrimeState.ERRORED && this.state !== GMIPrimeState.AWAITING_TOOL_RESULT) {
1017
+ // A newer turn may already own the lifecycle state (this generator was
1018
+ // drained after a failure, once the next turn had started); leave the
1019
+ // state and the trace's turn id to that turn.
1020
+ if (ownsState() &&
1021
+ this.state !== GMIPrimeState.ERRORED &&
1022
+ this.state !== GMIPrimeState.AWAITING_TOOL_RESULT) {
812
1023
  this.state = GMIPrimeState.READY;
813
1024
  }
814
1025
  // This final chunk is part of the stream, not the return value of the generator
815
1026
  yield this.createOutputChunk(turnInput.interactionId, GMIOutputChunkType.FINAL_RESPONSE_MARKER, 'Turn processing sequence complete.', { isFinal: true });
816
1027
  this.addTraceEntry(ReasoningEntryType.INTERACTION_END, `Turn '${turnId}' finished. GMI State: ${this.state}.`);
817
- if (this.reasoningTrace)
1028
+ // Checked after the yield: a newer turn can start while this one waits there.
1029
+ if (ownsState() && this.reasoningTrace)
818
1030
  this.reasoningTrace.turnId = undefined;
819
1031
  }
820
1032
  }
@@ -1006,10 +1218,13 @@ export class GMI {
1006
1218
  }
1007
1219
  }
1008
1220
  if (!providerId && modelId.includes('/')) {
1009
- const parts = modelId.split('/');
1010
- if (parts.length >= 2) { // Can be "openai/gpt-3.5-turbo" or "ollama/modelname/variant"
1011
- providerId = parts[0];
1012
- // modelId = parts.slice(1).join('/'); // Keep full model name if provider prefix was there
1221
+ // A namespaced id names its provider only when that provider is
1222
+ // registered here. Otherwise it is a router's model id (OpenRouter
1223
+ // lists `openai/gpt-4o-mini`), and the registry lookup below finds the
1224
+ // provider that serves it.
1225
+ const prefix = modelId.split('/')[0];
1226
+ if (prefix && this.llmProviderManager.getProvider(prefix)) {
1227
+ providerId = prefix;
1013
1228
  }
1014
1229
  }
1015
1230
  if (!providerId) {
@@ -1021,9 +1236,15 @@ export class GMI {
1021
1236
  throw new GMIError(`Cannot determine providerId for model '${modelId}'. No explicit providerId, unable to infer from modelId, and no default provider found for it.`, GMIErrorCode.CONFIGURATION_ERROR, { modelId });
1022
1237
  }
1023
1238
  }
1024
- // Ensure modelId doesn't contain the provider prefix if providerId is now set
1239
+ // Drop the provider prefix from the model id. OpenRouter's own ids are
1240
+ // namespaced slugs, and some name OpenRouter itself (`openrouter/auto`),
1241
+ // so there the prefix goes only when it wraps another vendor's slug
1242
+ // (`openrouter/openai/gpt-4o` sends `openai/gpt-4o`).
1025
1243
  if (modelId.startsWith(providerId + '/')) {
1026
- modelId = modelId.substring(providerId.length + 1);
1244
+ const unprefixed = modelId.substring(providerId.length + 1);
1245
+ if (providerId !== 'openrouter' || unprefixed.includes('/')) {
1246
+ modelId = unprefixed;
1247
+ }
1027
1248
  }
1028
1249
  return { modelId, providerId };
1029
1250
  }
@@ -1119,6 +1340,11 @@ export class GMI {
1119
1340
  this.state = GMIPrimeState.SHUTTING_DOWN;
1120
1341
  this.addTraceEntry(ReasoningEntryType.LIFECYCLE, "GMI shutting down.");
1121
1342
  try {
1343
+ // Give metaprompt work still in flight a bounded window to store its
1344
+ // updates before the memories it writes to are closed, then stop the
1345
+ // queue: work not yet started is skipped, and late results are dropped.
1346
+ await this.metapromptExecutor?.drain();
1347
+ this.metapromptExecutor?.close();
1122
1348
  await this.cognitiveMemory?.shutdown?.();
1123
1349
  await this.workingMemory?.close?.();
1124
1350
  // Shared dependencies (tool orchestrator, retrieval augmentor, utility AI, etc.) are owned by