@framers/agentos 0.1.13 → 0.1.14

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 (53) hide show
  1. package/README.md +120 -81
  2. package/dist/api/AgentOS.d.ts +27 -3
  3. package/dist/api/AgentOS.d.ts.map +1 -1
  4. package/dist/api/AgentOS.js +15 -0
  5. package/dist/api/AgentOS.js.map +1 -1
  6. package/dist/api/AgentOSOrchestrator.d.ts.map +1 -1
  7. package/dist/api/AgentOSOrchestrator.js +248 -106
  8. package/dist/api/AgentOSOrchestrator.js.map +1 -1
  9. package/dist/config/RetrievalAugmentorConfiguration.d.ts +1 -1
  10. package/dist/core/observability/index.d.ts +2 -0
  11. package/dist/core/observability/index.d.ts.map +1 -1
  12. package/dist/core/observability/index.js +1 -0
  13. package/dist/core/observability/index.js.map +1 -1
  14. package/dist/core/observability/otel.d.ts +101 -0
  15. package/dist/core/observability/otel.d.ts.map +1 -0
  16. package/dist/core/observability/otel.js +344 -0
  17. package/dist/core/observability/otel.js.map +1 -0
  18. package/dist/extensions/ExtensionManager.d.ts +92 -1
  19. package/dist/extensions/ExtensionManager.d.ts.map +1 -1
  20. package/dist/extensions/ExtensionManager.js +128 -47
  21. package/dist/extensions/ExtensionManager.js.map +1 -1
  22. package/dist/extensions/packs/schema-on-demand-pack.d.ts +34 -0
  23. package/dist/extensions/packs/schema-on-demand-pack.d.ts.map +1 -0
  24. package/dist/extensions/packs/schema-on-demand-pack.js +275 -0
  25. package/dist/extensions/packs/schema-on-demand-pack.js.map +1 -0
  26. package/dist/logging/PinoLogger.d.ts.map +1 -1
  27. package/dist/logging/PinoLogger.js +13 -4
  28. package/dist/logging/PinoLogger.js.map +1 -1
  29. package/dist/rag/IRetrievalAugmentor.d.ts +1 -1
  30. package/dist/rag/IRetrievalAugmentor.d.ts.map +1 -1
  31. package/dist/rag/IVectorStore.d.ts +18 -0
  32. package/dist/rag/IVectorStore.d.ts.map +1 -1
  33. package/dist/rag/RetrievalAugmentor.d.ts +2 -0
  34. package/dist/rag/RetrievalAugmentor.d.ts.map +1 -1
  35. package/dist/rag/RetrievalAugmentor.js +121 -11
  36. package/dist/rag/RetrievalAugmentor.js.map +1 -1
  37. package/dist/rag/graphrag/GraphRAGEngine.d.ts +1 -0
  38. package/dist/rag/graphrag/GraphRAGEngine.d.ts.map +1 -1
  39. package/dist/rag/graphrag/GraphRAGEngine.js +26 -2
  40. package/dist/rag/graphrag/GraphRAGEngine.js.map +1 -1
  41. package/dist/rag/graphrag/IGraphRAG.d.ts +7 -0
  42. package/dist/rag/graphrag/IGraphRAG.d.ts.map +1 -1
  43. package/dist/rag/implementations/vector_stores/SqlVectorStore.d.ts +3 -0
  44. package/dist/rag/implementations/vector_stores/SqlVectorStore.d.ts.map +1 -1
  45. package/dist/rag/implementations/vector_stores/SqlVectorStore.js +171 -39
  46. package/dist/rag/implementations/vector_stores/SqlVectorStore.js.map +1 -1
  47. package/dist/rag/reranking/index.d.ts +1 -1
  48. package/dist/rag/reranking/index.js +1 -1
  49. package/dist/rag/reranking/providers/CohereReranker.d.ts +3 -3
  50. package/dist/rag/reranking/providers/CohereReranker.d.ts.map +1 -1
  51. package/dist/rag/reranking/providers/CohereReranker.js +8 -3
  52. package/dist/rag/reranking/providers/CohereReranker.js.map +1 -1
  53. package/package.json +5 -1
@@ -18,6 +18,7 @@ import { normalizeUsage, snapshotPersonaDetails } from '../core/orchestration/he
18
18
  import { DEFAULT_PROMPT_PROFILE_CONFIG, selectPromptProfile, } from '../core/prompting/PromptProfileRouter.js';
19
19
  import { DEFAULT_ROLLING_SUMMARY_COMPACTION_CONFIG, maybeCompactConversationMessages, } from '../core/conversation/RollingSummaryCompactor.js';
20
20
  import { DEFAULT_LONG_TERM_MEMORY_POLICY, hasAnyLongTermMemoryScope, LONG_TERM_MEMORY_POLICY_METADATA_KEY, resolveLongTermMemoryPolicy, } from '../core/conversation/LongTermMemoryPolicy.js';
21
+ import { getActiveTraceMetadata, recordAgentOSToolResultMetrics, recordAgentOSTurnMetrics, recordExceptionOnActiveSpan, runWithSpanContext, shouldIncludeTraceInAgentOSResponses, startAgentOSSpan, withAgentOSSpan, } from '../core/observability/otel.js';
21
22
  function normalizeMode(value) {
22
23
  return (value || '').trim().toLowerCase();
23
24
  }
@@ -150,6 +151,16 @@ export class AgentOSOrchestrator {
150
151
  if (!baseChunk.metadata.language)
151
152
  baseChunk.metadata.language = ctx.languageNegotiation;
152
153
  }
154
+ if (shouldIncludeTraceInAgentOSResponses() &&
155
+ (type === AgentOSResponseChunkType.METADATA_UPDATE ||
156
+ type === AgentOSResponseChunkType.FINAL_RESPONSE ||
157
+ type === AgentOSResponseChunkType.ERROR)) {
158
+ const traceMeta = getActiveTraceMetadata();
159
+ if (traceMeta) {
160
+ baseChunk.metadata = baseChunk.metadata || {};
161
+ baseChunk.metadata.trace = traceMeta;
162
+ }
163
+ }
153
164
  let chunk;
154
165
  switch (type) {
155
166
  case AgentOSResponseChunkType.TEXT_DELTA:
@@ -290,18 +301,46 @@ export class AgentOSOrchestrator {
290
301
  this.ensureInitialized();
291
302
  const agentOSStreamId = await this.dependencies.streamingManager.createStream();
292
303
  console.log(`AgentOSOrchestrator: Starting turn for AgentOS Stream ${agentOSStreamId}, User ${input.userId}, Session ${input.sessionId}`);
304
+ const rootSpan = startAgentOSSpan('agentos.turn', {
305
+ attributes: {
306
+ 'agentos.stream_id': agentOSStreamId,
307
+ 'agentos.user_id': input.userId,
308
+ 'agentos.session_id': input.sessionId,
309
+ 'agentos.conversation_id': input.conversationId ?? '',
310
+ 'agentos.persona_id': input.selectedPersonaId ?? '',
311
+ },
312
+ });
313
+ const run = async () => this._processTurnInternal(agentOSStreamId, input);
314
+ const promise = rootSpan ? runWithSpanContext(rootSpan, run) : run();
293
315
  // Execute the turn processing asynchronously without awaiting it here,
294
316
  // so this method can return the streamId quickly.
295
- this._processTurnInternal(agentOSStreamId, input).catch(async (criticalError) => {
317
+ promise
318
+ .catch(async (criticalError) => {
319
+ if (rootSpan) {
320
+ try {
321
+ rootSpan.recordException(criticalError);
322
+ }
323
+ catch {
324
+ // ignore
325
+ }
326
+ }
296
327
  console.error(`AgentOSOrchestrator: Critical unhandled error in _processTurnInternal for stream ${agentOSStreamId}:`, criticalError);
297
328
  try {
298
329
  await this.pushErrorChunk(agentOSStreamId, input.selectedPersonaId || 'unknown_persona', 'orchestrator_critical', GMIErrorCode.INTERNAL_SERVER_ERROR, `A critical orchestration error occurred: ${criticalError.message}`, { name: criticalError.name, stack: criticalError.stack });
299
- await this.dependencies.streamingManager.closeStream(agentOSStreamId, "Critical orchestrator error");
330
+ await this.dependencies.streamingManager.closeStream(agentOSStreamId, 'Critical orchestrator error');
300
331
  }
301
332
  catch (cleanupError) {
302
333
  console.error(`AgentOSOrchestrator: Error during critical error cleanup for stream ${agentOSStreamId}:`, cleanupError);
303
334
  }
304
335
  this.activeStreamContexts.delete(agentOSStreamId);
336
+ })
337
+ .finally(() => {
338
+ try {
339
+ rootSpan?.end();
340
+ }
341
+ catch {
342
+ // ignore
343
+ }
305
344
  });
306
345
  return agentOSStreamId;
307
346
  }
@@ -310,24 +349,38 @@ export class AgentOSOrchestrator {
310
349
  * @private
311
350
  */
312
351
  async _processTurnInternal(agentOSStreamId, input) {
313
- if (!input.selectedPersonaId) {
314
- throw new GMIError('AgentOSOrchestrator requires a selectedPersonaId on AgentOSInput.', GMIErrorCode.VALIDATION_ERROR);
315
- }
352
+ const turnStartedAt = Date.now();
353
+ let turnMetricsStatus = 'ok';
354
+ let turnMetricsPersonaId = input.selectedPersonaId;
355
+ let turnMetricsUsage;
356
+ const selectedPersonaId = input.selectedPersonaId;
316
357
  let gmi;
317
358
  let conversationContext;
318
359
  let currentPersonaId = input.selectedPersonaId;
319
360
  let gmiInstanceIdForChunks = 'gmi_pending_init';
320
361
  let organizationIdForMemory;
321
362
  let longTermMemoryPolicy = null;
363
+ let didForceTerminate = false;
322
364
  try {
323
- const gmiResult = await this.dependencies.gmiManager.getOrCreateGMIForSession(input.userId, input.sessionId, // This is AgentOS's session ID, GMI might have its own.
324
- input.selectedPersonaId, // Can be undefined, GMIManager handles default.
325
- input.conversationId, // Can be undefined, GMIManager might default to sessionId.
326
- input.options?.preferredModelId, input.options?.preferredProviderId, input.userApiKeys);
365
+ if (!selectedPersonaId) {
366
+ throw new GMIError('AgentOSOrchestrator requires a selectedPersonaId on AgentOSInput.', GMIErrorCode.VALIDATION_ERROR);
367
+ }
368
+ const gmiResult = await withAgentOSSpan('agentos.gmi.get_or_create', async (span) => {
369
+ span?.setAttribute('agentos.user_id', input.userId);
370
+ span?.setAttribute('agentos.session_id', input.sessionId);
371
+ span?.setAttribute('agentos.persona_id', selectedPersonaId);
372
+ if (typeof input.conversationId === 'string' && input.conversationId.trim()) {
373
+ span?.setAttribute('agentos.conversation_id', input.conversationId.trim());
374
+ }
375
+ return this.dependencies.gmiManager.getOrCreateGMIForSession(input.userId, input.sessionId, // This is AgentOS's session ID, GMI might have its own.
376
+ selectedPersonaId, input.conversationId, // Can be undefined, GMIManager might default to sessionId.
377
+ input.options?.preferredModelId, input.options?.preferredProviderId, input.userApiKeys);
378
+ });
327
379
  gmi = gmiResult.gmi;
328
380
  conversationContext = gmiResult.conversationContext;
329
381
  currentPersonaId = gmi.getCurrentPrimaryPersonaId(); // Get actual personaId from GMI
330
382
  gmiInstanceIdForChunks = gmi.getGMIId();
383
+ turnMetricsPersonaId = currentPersonaId;
331
384
  const streamContext = {
332
385
  gmi, userId: input.userId, sessionId: input.sessionId, personaId: currentPersonaId,
333
386
  conversationId: conversationContext.sessionId, // Use actual conversation ID from context
@@ -369,6 +422,7 @@ export class AgentOSOrchestrator {
369
422
  // Persist inbound user/system message to ConversationContext BEFORE any LLM call so persona switches
370
423
  // and restarts preserve memory, even if the LLM fails.
371
424
  if (this.config.enableConversationalPersistence && conversationContext) {
425
+ const persistContext = conversationContext;
372
426
  try {
373
427
  if (gmiInput.type === GMIInteractionType.TEXT && typeof gmiInput.content === 'string') {
374
428
  conversationContext.addMessage({
@@ -393,7 +447,11 @@ export class AgentOSOrchestrator {
393
447
  metadata: { agentPersonaId: currentPersonaId, source: 'agentos_input_system' },
394
448
  });
395
449
  }
396
- await this.dependencies.conversationManager.saveConversation(conversationContext);
450
+ await withAgentOSSpan('agentos.conversation.save', async (span) => {
451
+ span?.setAttribute('agentos.stage', 'inbound');
452
+ span?.setAttribute('agentos.stream_id', agentOSStreamId);
453
+ await this.dependencies.conversationManager.saveConversation(persistContext);
454
+ });
397
455
  }
398
456
  catch (persistError) {
399
457
  console.warn(`AgentOSOrchestrator: Failed to persist inbound message to ConversationContext for stream ${agentOSStreamId}.`, persistError);
@@ -615,8 +673,13 @@ export class AgentOSOrchestrator {
615
673
  }
616
674
  // Persist any compaction/router metadata updates prior to the main LLM call.
617
675
  if (this.config.enableConversationalPersistence && conversationContext) {
676
+ const persistContext = conversationContext;
618
677
  try {
619
- await this.dependencies.conversationManager.saveConversation(conversationContext);
678
+ await withAgentOSSpan('agentos.conversation.save', async (span) => {
679
+ span?.setAttribute('agentos.stage', 'metadata');
680
+ span?.setAttribute('agentos.stream_id', agentOSStreamId);
681
+ await this.dependencies.conversationManager.saveConversation(persistContext);
682
+ });
620
683
  }
621
684
  catch (metadataPersistError) {
622
685
  console.warn(`AgentOSOrchestrator: Failed to persist conversation metadata updates for stream ${agentOSStreamId}.`, metadataPersistError);
@@ -681,7 +744,6 @@ export class AgentOSOrchestrator {
681
744
  let lastGMIOutput; // To store the result from handleToolResult or final processTurnStream result
682
745
  while (continueProcessing && currentToolCallIteration < this.config.maxToolCallIterations) {
683
746
  currentToolCallIteration++;
684
- let gmiStreamIterator;
685
747
  if (lastGMIOutput?.toolCalls && lastGMIOutput.toolCalls.length > 0) {
686
748
  // This case should be handled by external call to orchestrateToolResult.
687
749
  // If GMI's handleToolResult itself requests more tools *synchronously* in its GMIOutput,
@@ -708,33 +770,41 @@ export class AgentOSOrchestrator {
708
770
  continueProcessing = false;
709
771
  break;
710
772
  }
711
- else {
712
- gmiStreamIterator = gmi.processTurnStream(gmiInput); // For initial turn or if GMI internally continues
773
+ if (!gmi) {
774
+ throw new Error('AgentOSOrchestrator: GMI not initialized (unexpected).');
713
775
  }
714
- // Consume the async generator manually so we can capture its return value (GMIOutput).
715
- // `for await...of` does not expose the generator return value, which caused placeholder
716
- // FINAL_RESPONSE payloads (e.g. "Turn processing sequence complete.").
717
- while (true) {
718
- const { value, done } = await gmiStreamIterator.next();
719
- if (done) {
720
- lastGMIOutput = value;
721
- continueProcessing = false;
722
- break;
723
- }
724
- const gmiChunk = value;
725
- await this.transformAndPushGMIChunk(agentOSStreamId, streamContext, gmiChunk);
726
- // NOTE: Tool calls may be executed internally by the GMI/tool orchestrator. Do not stop
727
- // streaming on TOOL_CALL_REQUEST; treat it as informational for observers/UI.
728
- if (gmiChunk.isFinal || gmiChunk.type === GMIOutputChunkType.FINAL_RESPONSE_MARKER) {
729
- // Still keep consuming to capture the generator's return value.
730
- continueProcessing = false;
776
+ const gmiForTurn = gmi;
777
+ await withAgentOSSpan('agentos.gmi.process_turn_stream', async (span) => {
778
+ span?.setAttribute('agentos.stream_id', agentOSStreamId);
779
+ span?.setAttribute('agentos.gmi_id', gmiInstanceIdForChunks);
780
+ span?.setAttribute('agentos.tool_call_iteration', currentToolCallIteration);
781
+ const gmiStreamIterator = gmiForTurn.processTurnStream(gmiInput); // For initial turn or if GMI internally continues
782
+ // Consume the async generator manually so we can capture its return value (GMIOutput).
783
+ // `for await...of` does not expose the generator return value, which caused placeholder
784
+ // FINAL_RESPONSE payloads (e.g. "Turn processing sequence complete.").
785
+ while (true) {
786
+ const { value, done } = await gmiStreamIterator.next();
787
+ if (done) {
788
+ lastGMIOutput = value;
789
+ continueProcessing = false;
790
+ break;
791
+ }
792
+ const gmiChunk = value;
793
+ await this.transformAndPushGMIChunk(agentOSStreamId, streamContext, gmiChunk);
794
+ // NOTE: Tool calls may be executed internally by the GMI/tool orchestrator. Do not stop
795
+ // streaming on TOOL_CALL_REQUEST; treat it as informational for observers/UI.
796
+ if (gmiChunk.isFinal || gmiChunk.type === GMIOutputChunkType.FINAL_RESPONSE_MARKER) {
797
+ // Still keep consuming to capture the generator's return value.
798
+ continueProcessing = false;
799
+ }
731
800
  }
732
- }
801
+ });
733
802
  if (!continueProcessing)
734
803
  break; // Exit the while loop
735
804
  } // End while
736
805
  if (currentToolCallIteration >= this.config.maxToolCallIterations && continueProcessing) {
737
806
  console.warn(`AgentOSOrchestrator: Max tool call iterations reached for stream ${agentOSStreamId}. Forcing termination.`);
807
+ didForceTerminate = true;
738
808
  await this.pushErrorChunk(agentOSStreamId, currentPersonaId, gmiInstanceIdForChunks, GMIErrorCode.RATE_LIMIT_EXCEEDED, // Or a more specific code
739
809
  'Agent reached maximum tool call iterations.', { maxIterations: this.config.maxToolCallIterations });
740
810
  }
@@ -750,8 +820,21 @@ export class AgentOSOrchestrator {
750
820
  isFinal: true,
751
821
  responseText: gmi ? 'Processing complete.' : 'Processing ended.',
752
822
  };
823
+ const normalizedUsage = normalizeUsage(finalGMIStateForResponse.usage);
824
+ if (normalizedUsage) {
825
+ turnMetricsUsage = {
826
+ totalTokens: normalizedUsage.totalTokens,
827
+ promptTokens: normalizedUsage.promptTokens,
828
+ completionTokens: normalizedUsage.completionTokens,
829
+ totalCostUSD: typeof normalizedUsage.totalCostUSD === 'number' ? normalizedUsage.totalCostUSD : undefined,
830
+ };
831
+ }
832
+ if (didForceTerminate || Boolean(finalGMIStateForResponse.error)) {
833
+ turnMetricsStatus = 'error';
834
+ }
753
835
  // Persist assistant output into ConversationContext for durable memory / prompt reconstruction.
754
836
  if (this.config.enableConversationalPersistence && conversationContext) {
837
+ const persistContext = conversationContext;
755
838
  try {
756
839
  if (typeof finalGMIStateForResponse.responseText === 'string' && finalGMIStateForResponse.responseText.trim()) {
757
840
  conversationContext.addMessage({
@@ -768,7 +851,11 @@ export class AgentOSOrchestrator {
768
851
  metadata: { agentPersonaId: currentPersonaId, source: 'agentos_output_tool_calls' },
769
852
  });
770
853
  }
771
- await this.dependencies.conversationManager.saveConversation(conversationContext);
854
+ await withAgentOSSpan('agentos.conversation.save', async (span) => {
855
+ span?.setAttribute('agentos.stage', 'assistant_output');
856
+ span?.setAttribute('agentos.stream_id', agentOSStreamId);
857
+ await this.dependencies.conversationManager.saveConversation(persistContext);
858
+ });
772
859
  }
773
860
  catch (persistError) {
774
861
  console.warn(`AgentOSOrchestrator: Failed to persist assistant output to ConversationContext for stream ${agentOSStreamId}.`, persistError);
@@ -783,7 +870,7 @@ export class AgentOSOrchestrator {
783
870
  finalUiCommands: finalGMIStateForResponse.uiCommands,
784
871
  audioOutput: finalGMIStateForResponse.audioOutput,
785
872
  imageOutput: finalGMIStateForResponse.imageOutput,
786
- usage: normalizeUsage(finalGMIStateForResponse.usage),
873
+ usage: normalizedUsage,
787
874
  reasoningTrace: finalGMIStateForResponse.reasoningTrace,
788
875
  error: finalGMIStateForResponse.error,
789
876
  updatedConversationContext: conversationContext ? conversationContext.toJSON() : undefined,
@@ -792,13 +879,21 @@ export class AgentOSOrchestrator {
792
879
  await this.dependencies.streamingManager.closeStream(agentOSStreamId, "Processing complete.");
793
880
  }
794
881
  catch (error) {
882
+ turnMetricsStatus = 'error';
883
+ recordExceptionOnActiveSpan(error, `Error in orchestrateTurn for stream ${agentOSStreamId}`);
795
884
  const gmiErr = GMIError.wrap?.(error, GMIErrorCode.GMI_PROCESSING_ERROR, `Error in orchestrateTurn for stream ${agentOSStreamId}`) ||
796
885
  new GMIError(`Error in orchestrateTurn for stream ${agentOSStreamId}: ${error.message}`, GMIErrorCode.GMI_PROCESSING_ERROR, error);
797
886
  console.error(`AgentOSOrchestrator: Error during _processTurnInternal for stream ${agentOSStreamId}:`, gmiErr);
798
- await this.pushErrorChunk(agentOSStreamId, currentPersonaId, gmiInstanceIdForChunks, gmiErr.code, gmiErr.message, gmiErr.details);
887
+ await this.pushErrorChunk(agentOSStreamId, currentPersonaId ?? 'unknown_persona', gmiInstanceIdForChunks, gmiErr.code, gmiErr.message, gmiErr.details);
799
888
  await this.dependencies.streamingManager.closeStream(agentOSStreamId, "Error during turn processing.");
800
889
  }
801
890
  finally {
891
+ recordAgentOSTurnMetrics({
892
+ durationMs: Date.now() - turnStartedAt,
893
+ status: turnMetricsStatus,
894
+ personaId: turnMetricsPersonaId,
895
+ usage: turnMetricsUsage,
896
+ });
802
897
  // Stream is closed explicitly in the success/error paths; this finally block always
803
898
  // clears internal state to avoid leaks.
804
899
  this.activeStreamContexts.delete(agentOSStreamId);
@@ -823,6 +918,7 @@ export class AgentOSOrchestrator {
823
918
  */
824
919
  async orchestrateToolResult(agentOSStreamId, toolCallId, toolName, toolOutput, isSuccess, errorMessage) {
825
920
  this.ensureInitialized();
921
+ const startedAt = Date.now();
826
922
  const streamContext = this.activeStreamContexts.get(agentOSStreamId);
827
923
  if (!streamContext) {
828
924
  const errMsg = `Orchestrator: Received tool result for unknown or inactive streamId: ${agentOSStreamId}. Tool: ${toolName}, CallID: ${toolCallId}`;
@@ -837,86 +933,128 @@ export class AgentOSOrchestrator {
837
933
  : { type: 'error', error: { code: 'EXTERNAL_TOOL_ERROR', message: errorMessage || `External tool '${toolName}' execution failed.` } };
838
934
  console.log(`AgentOSOrchestrator: Feeding tool result for stream ${agentOSStreamId}, GMI ${gmiInstanceIdForChunks}, tool call ${toolCallId} (${toolName}) back to GMI.`);
839
935
  try {
840
- // Emit the tool result itself as a chunk
841
- await this.pushChunkToStream(agentOSStreamId, AgentOSResponseChunkType.TOOL_RESULT_EMISSION, gmiInstanceIdForChunks, personaId, false, { toolCallId, toolName, toolResult: toolOutput, isSuccess, errorMessage });
842
- // Persist tool result into ConversationContext for durable memory / prompt reconstruction.
843
- if (this.config.enableConversationalPersistence && conversationContext) {
936
+ await withAgentOSSpan('agentos.tool_result', async (span) => {
937
+ span?.setAttribute('agentos.stream_id', agentOSStreamId);
938
+ span?.setAttribute('agentos.gmi_id', gmiInstanceIdForChunks);
939
+ span?.setAttribute('agentos.tool_call_id', toolCallId);
940
+ span?.setAttribute('agentos.tool_name', toolName);
941
+ span?.setAttribute('agentos.tool_success', isSuccess);
844
942
  try {
845
- conversationContext.addMessage({
846
- role: MessageRole.TOOL,
847
- content: typeof toolOutput === 'string' ? toolOutput : JSON.stringify(toolOutput),
848
- tool_call_id: toolCallId,
849
- name: toolName,
850
- metadata: { agentPersonaId: personaId, source: 'agentos_tool_result', isSuccess },
851
- });
852
- await this.dependencies.conversationManager.saveConversation(conversationContext);
853
- }
854
- catch (persistError) {
855
- console.warn(`AgentOSOrchestrator: Failed to persist tool result to ConversationContext for stream ${agentOSStreamId}.`, persistError);
856
- }
857
- }
858
- // GMI processes the tool result and gives a *final output for that step*
859
- const gmiOutputAfterTool = await gmi.handleToolResult(toolCallId, toolName, toolResultPayload, userId, userApiKeys || {});
860
- // Process the GMIOutput (which is not a stream of chunks)
861
- await this.processGMIOutput(agentOSStreamId, streamContext, gmiOutputAfterTool, false);
862
- // If GMIOutput indicates further tool calls are needed by the GMI
863
- if (gmiOutputAfterTool.toolCalls && gmiOutputAfterTool.toolCalls.length > 0) {
864
- await this.pushChunkToStream(agentOSStreamId, AgentOSResponseChunkType.TOOL_CALL_REQUEST, gmiInstanceIdForChunks, personaId, false, // Not final, more interaction expected
865
- { toolCalls: gmiOutputAfterTool.toolCalls, rationale: gmiOutputAfterTool.responseText || "Agent requires further tool execution." });
866
- // The orchestrator now waits for another external call to `orchestrateToolResult` for these new calls.
867
- }
868
- else if (gmiOutputAfterTool.isFinal) {
869
- if (this.config.enableConversationalPersistence && conversationContext) {
870
- try {
871
- if (typeof gmiOutputAfterTool.responseText === 'string' && gmiOutputAfterTool.responseText.trim()) {
943
+ // Emit the tool result itself as a chunk
944
+ await this.pushChunkToStream(agentOSStreamId, AgentOSResponseChunkType.TOOL_RESULT_EMISSION, gmiInstanceIdForChunks, personaId, false, { toolCallId, toolName, toolResult: toolOutput, isSuccess, errorMessage });
945
+ // Persist tool result into ConversationContext for durable memory / prompt reconstruction.
946
+ if (this.config.enableConversationalPersistence && conversationContext) {
947
+ try {
872
948
  conversationContext.addMessage({
873
- role: MessageRole.ASSISTANT,
874
- content: gmiOutputAfterTool.responseText,
875
- metadata: { agentPersonaId: personaId, source: 'agentos_output' },
949
+ role: MessageRole.TOOL,
950
+ content: typeof toolOutput === 'string' ? toolOutput : JSON.stringify(toolOutput),
951
+ tool_call_id: toolCallId,
952
+ name: toolName,
953
+ metadata: { agentPersonaId: personaId, source: 'agentos_tool_result', isSuccess },
876
954
  });
877
- }
878
- else if (gmiOutputAfterTool.toolCalls && gmiOutputAfterTool.toolCalls.length > 0) {
879
- conversationContext.addMessage({
880
- role: MessageRole.ASSISTANT,
881
- content: null,
882
- tool_calls: gmiOutputAfterTool.toolCalls,
883
- metadata: { agentPersonaId: personaId, source: 'agentos_output_tool_calls' },
955
+ await withAgentOSSpan('agentos.conversation.save', async (child) => {
956
+ child?.setAttribute('agentos.stage', 'tool_result');
957
+ child?.setAttribute('agentos.stream_id', agentOSStreamId);
958
+ await this.dependencies.conversationManager.saveConversation(conversationContext);
884
959
  });
885
960
  }
886
- await this.dependencies.conversationManager.saveConversation(conversationContext);
961
+ catch (persistError) {
962
+ console.warn(`AgentOSOrchestrator: Failed to persist tool result to ConversationContext for stream ${agentOSStreamId}.`, persistError);
963
+ }
964
+ }
965
+ // GMI processes the tool result and gives a *final output for that step*
966
+ const gmiOutputAfterTool = await withAgentOSSpan('agentos.gmi.handle_tool_result', async (child) => {
967
+ child?.setAttribute('agentos.stream_id', agentOSStreamId);
968
+ child?.setAttribute('agentos.tool_call_id', toolCallId);
969
+ child?.setAttribute('agentos.tool_name', toolName);
970
+ child?.setAttribute('agentos.tool_success', isSuccess);
971
+ return gmi.handleToolResult(toolCallId, toolName, toolResultPayload, userId, userApiKeys || {});
972
+ });
973
+ // Process the GMIOutput (which is not a stream of chunks)
974
+ await this.processGMIOutput(agentOSStreamId, streamContext, gmiOutputAfterTool, false);
975
+ // If GMIOutput indicates further tool calls are needed by the GMI
976
+ if (gmiOutputAfterTool.toolCalls && gmiOutputAfterTool.toolCalls.length > 0) {
977
+ await this.pushChunkToStream(agentOSStreamId, AgentOSResponseChunkType.TOOL_CALL_REQUEST, gmiInstanceIdForChunks, personaId, false, // Not final, more interaction expected
978
+ {
979
+ toolCalls: gmiOutputAfterTool.toolCalls,
980
+ rationale: gmiOutputAfterTool.responseText || 'Agent requires further tool execution.',
981
+ });
982
+ // The orchestrator now waits for another external call to `orchestrateToolResult` for these new calls.
887
983
  }
888
- catch (persistError) {
889
- console.warn(`AgentOSOrchestrator: Failed to persist assistant output after tool result for stream ${agentOSStreamId}.`, persistError);
984
+ else if (gmiOutputAfterTool.isFinal) {
985
+ if (this.config.enableConversationalPersistence && conversationContext) {
986
+ try {
987
+ if (typeof gmiOutputAfterTool.responseText === 'string' &&
988
+ gmiOutputAfterTool.responseText.trim()) {
989
+ conversationContext.addMessage({
990
+ role: MessageRole.ASSISTANT,
991
+ content: gmiOutputAfterTool.responseText,
992
+ metadata: { agentPersonaId: personaId, source: 'agentos_output' },
993
+ });
994
+ }
995
+ else if (gmiOutputAfterTool.toolCalls && gmiOutputAfterTool.toolCalls.length > 0) {
996
+ conversationContext.addMessage({
997
+ role: MessageRole.ASSISTANT,
998
+ content: null,
999
+ tool_calls: gmiOutputAfterTool.toolCalls,
1000
+ metadata: { agentPersonaId: personaId, source: 'agentos_output_tool_calls' },
1001
+ });
1002
+ }
1003
+ await withAgentOSSpan('agentos.conversation.save', async (child) => {
1004
+ child?.setAttribute('agentos.stage', 'assistant_output_after_tool');
1005
+ child?.setAttribute('agentos.stream_id', agentOSStreamId);
1006
+ await this.dependencies.conversationManager.saveConversation(conversationContext);
1007
+ });
1008
+ }
1009
+ catch (persistError) {
1010
+ console.warn(`AgentOSOrchestrator: Failed to persist assistant output after tool result for stream ${agentOSStreamId}.`, persistError);
1011
+ }
1012
+ }
1013
+ // If it's final and no more tool calls, the interaction for this GMI processing cycle might be done.
1014
+ // Push a final response marker or the already pushed final data from processGMIOutput takes precedence.
1015
+ await this.pushChunkToStream(agentOSStreamId, AgentOSResponseChunkType.FINAL_RESPONSE, gmiInstanceIdForChunks, personaId, true, {
1016
+ finalResponseText: gmiOutputAfterTool.responseText,
1017
+ finalToolCalls: gmiOutputAfterTool.toolCalls,
1018
+ finalUiCommands: gmiOutputAfterTool.uiCommands,
1019
+ audioOutput: gmiOutputAfterTool.audioOutput,
1020
+ imageOutput: gmiOutputAfterTool.imageOutput,
1021
+ usage: normalizeUsage(gmiOutputAfterTool.usage),
1022
+ reasoningTrace: gmiOutputAfterTool.reasoningTrace,
1023
+ error: gmiOutputAfterTool.error,
1024
+ updatedConversationContext: conversationContext.toJSON(),
1025
+ activePersonaDetails: snapshotPersonaDetails(gmi.getPersona?.()),
1026
+ });
1027
+ this.activeStreamContexts.delete(agentOSStreamId); // Clean up context for this completed flow
1028
+ await this.dependencies.streamingManager.closeStream(agentOSStreamId, 'Tool processing complete and final response generated.');
890
1029
  }
1030
+ // If not final and no tool calls, the GMI might have provided intermediate text.
1031
+ // The stream remains open for further GMI internal processing or new user input.
891
1032
  }
892
- // If it's final and no more tool calls, the interaction for this GMI processing cycle might be done.
893
- // Push a final response marker or the already pushed final data from processGMIOutput takes precedence.
894
- await this.pushChunkToStream(agentOSStreamId, AgentOSResponseChunkType.FINAL_RESPONSE, gmiInstanceIdForChunks, personaId, true, {
895
- finalResponseText: gmiOutputAfterTool.responseText,
896
- finalToolCalls: gmiOutputAfterTool.toolCalls,
897
- finalUiCommands: gmiOutputAfterTool.uiCommands,
898
- audioOutput: gmiOutputAfterTool.audioOutput,
899
- imageOutput: gmiOutputAfterTool.imageOutput,
900
- usage: normalizeUsage(gmiOutputAfterTool.usage),
901
- reasoningTrace: gmiOutputAfterTool.reasoningTrace,
902
- error: gmiOutputAfterTool.error,
903
- updatedConversationContext: conversationContext.toJSON(),
904
- activePersonaDetails: snapshotPersonaDetails(gmi.getPersona?.()),
905
- });
906
- this.activeStreamContexts.delete(agentOSStreamId); // Clean up context for this completed flow
907
- await this.dependencies.streamingManager.closeStream(agentOSStreamId, "Tool processing complete and final response generated.");
908
- }
909
- // If not final and no tool calls, the GMI might have provided intermediate text.
910
- // The stream remains open for further GMI internal processing or new user input.
1033
+ catch (error) {
1034
+ const gmiErr = GMIError.wrap?.(error, GMIErrorCode.TOOL_ERROR, `Error in orchestrateToolResult for stream ${agentOSStreamId}`) ||
1035
+ new GMIError(`Error in orchestrateToolResult for stream ${agentOSStreamId}: ${error.message}`, GMIErrorCode.TOOL_ERROR, error);
1036
+ console.error(`AgentOSOrchestrator: Critical error processing tool result for stream ${agentOSStreamId}:`, gmiErr);
1037
+ await this.pushErrorChunk(agentOSStreamId, personaId, gmiInstanceIdForChunks, gmiErr.code, gmiErr.message, gmiErr.details);
1038
+ this.activeStreamContexts.delete(agentOSStreamId);
1039
+ await this.dependencies.streamingManager.closeStream(agentOSStreamId, 'Critical error during tool result processing.');
1040
+ throw gmiErr; // Re-throw to signal failure to caller if necessary
1041
+ }
1042
+ });
1043
+ recordAgentOSToolResultMetrics({
1044
+ durationMs: Date.now() - startedAt,
1045
+ status: 'ok',
1046
+ toolName,
1047
+ toolSuccess: isSuccess,
1048
+ });
911
1049
  }
912
1050
  catch (error) {
913
- const gmiErr = GMIError.wrap?.(error, GMIErrorCode.TOOL_ERROR, `Error in orchestrateToolResult for stream ${agentOSStreamId}`) ||
914
- new GMIError(`Error in orchestrateToolResult for stream ${agentOSStreamId}: ${error.message}`, GMIErrorCode.TOOL_ERROR, error);
915
- console.error(`AgentOSOrchestrator: Critical error processing tool result for stream ${agentOSStreamId}:`, gmiErr);
916
- await this.pushErrorChunk(agentOSStreamId, personaId, gmiInstanceIdForChunks, gmiErr.code, gmiErr.message, gmiErr.details);
917
- this.activeStreamContexts.delete(agentOSStreamId);
918
- await this.dependencies.streamingManager.closeStream(agentOSStreamId, "Critical error during tool result processing.");
919
- throw gmiErr; // Re-throw to signal failure to caller if necessary
1051
+ recordAgentOSToolResultMetrics({
1052
+ durationMs: Date.now() - startedAt,
1053
+ status: 'error',
1054
+ toolName,
1055
+ toolSuccess: isSuccess,
1056
+ });
1057
+ throw error;
920
1058
  }
921
1059
  }
922
1060
  /**
@@ -948,7 +1086,11 @@ export class AgentOSOrchestrator {
948
1086
  // to decide on looping or yielding ToolCallRequestChunks.
949
1087
  if (gmiOutput.isFinal && (!gmiOutput.toolCalls || gmiOutput.toolCalls.length === 0)) {
950
1088
  if (this.config.enableConversationalPersistence && conversationContext) {
951
- await this.dependencies.conversationManager.saveConversation(conversationContext);
1089
+ await withAgentOSSpan('agentos.conversation.save', async (span) => {
1090
+ span?.setAttribute('agentos.stage', 'gmi_output_final');
1091
+ span?.setAttribute('agentos.stream_id', agentOSStreamId);
1092
+ await this.dependencies.conversationManager.saveConversation(conversationContext);
1093
+ });
952
1094
  }
953
1095
  // This is a final response without further tool calls
954
1096
  await this.pushChunkToStream(agentOSStreamId, AgentOSResponseChunkType.FINAL_RESPONSE, gmiInstanceIdForChunks, personaId, true, {