@opperai/agents 0.1.1 → 0.2.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.
package/dist/index.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var crypto = require('crypto');
4
4
  var zod = require('zod');
5
+ var fs = require('fs');
5
6
  var opperai = require('opperai');
6
7
  var zodToJsonSchema = require('zod-to-json-schema');
7
8
  var index_js = require('@modelcontextprotocol/sdk/client/index.js');
@@ -393,7 +394,11 @@ var HookEvents = {
393
394
  ToolError: "tool:error",
394
395
  MemoryRead: "memory:read",
395
396
  MemoryWrite: "memory:write",
396
- MemoryError: "memory:error"
397
+ MemoryError: "memory:error",
398
+ StreamStart: "stream:start",
399
+ StreamChunk: "stream:chunk",
400
+ StreamEnd: "stream:end",
401
+ StreamError: "stream:error"
397
402
  };
398
403
  var HookManager = class {
399
404
  registry = /* @__PURE__ */ new Map();
@@ -465,8 +470,307 @@ var HookManager = class {
465
470
  };
466
471
  var createHookManager = (logger) => new HookManager(logger);
467
472
 
473
+ // src/base/events.ts
474
+ var AgentEvents = {
475
+ StreamStart: HookEvents.StreamStart,
476
+ StreamChunk: HookEvents.StreamChunk,
477
+ StreamEnd: HookEvents.StreamEnd,
478
+ StreamError: HookEvents.StreamError
479
+ };
480
+ var AgentEventEmitter = class {
481
+ registry = /* @__PURE__ */ new Map();
482
+ logger;
483
+ constructor(logger) {
484
+ this.logger = logger ?? getDefaultLogger();
485
+ }
486
+ on(event, listener) {
487
+ const listeners = this.registry.get(event) ?? /* @__PURE__ */ new Set();
488
+ listeners.add(listener);
489
+ this.registry.set(event, listeners);
490
+ return () => this.off(event, listener);
491
+ }
492
+ once(event, listener) {
493
+ const wrapper = (payload) => {
494
+ try {
495
+ listener(payload);
496
+ } finally {
497
+ this.off(event, wrapper);
498
+ }
499
+ };
500
+ return this.on(event, wrapper);
501
+ }
502
+ off(event, listener) {
503
+ const listeners = this.registry.get(event);
504
+ if (!listeners) {
505
+ return;
506
+ }
507
+ listeners.delete(listener);
508
+ if (listeners.size === 0) {
509
+ this.registry.delete(event);
510
+ }
511
+ }
512
+ emit(event, payload) {
513
+ const listeners = Array.from(
514
+ this.registry.get(event) ?? /* @__PURE__ */ new Set()
515
+ );
516
+ if (listeners.length === 0) {
517
+ return;
518
+ }
519
+ for (const listener of listeners) {
520
+ try {
521
+ listener(payload);
522
+ } catch (error) {
523
+ this.logger.warn(`Agent event listener failed for "${event}"`, {
524
+ event,
525
+ error: error instanceof Error ? error.message : String(error)
526
+ });
527
+ }
528
+ }
529
+ }
530
+ removeAllListeners(event) {
531
+ if (event) {
532
+ this.registry.delete(event);
533
+ return;
534
+ }
535
+ this.registry.clear();
536
+ }
537
+ listenerCount(event) {
538
+ if (event) {
539
+ return this.registry.get(event)?.size ?? 0;
540
+ }
541
+ let total = 0;
542
+ for (const listeners of this.registry.values()) {
543
+ total += listeners.size;
544
+ }
545
+ return total;
546
+ }
547
+ };
548
+
468
549
  // src/base/agent.ts
469
550
  init_tool();
551
+ function sanitizeId(name) {
552
+ return name.replace(/\s/g, "_").replace(/-/g, "_").replace(/\./g, "_").replace(/[()]/g, "");
553
+ }
554
+ function sanitizeLabel(text) {
555
+ let cleaned = text.replace(/"/g, "'").replace(/\n/g, " ").replace(/`/g, "");
556
+ cleaned = cleaned.split(".")[0]?.trim() || cleaned.trim();
557
+ if (cleaned.length > 45) {
558
+ cleaned = cleaned.substring(0, 42) + "...";
559
+ }
560
+ return cleaned;
561
+ }
562
+ function getSchemaName(schema) {
563
+ if (!schema) {
564
+ return "N/A";
565
+ }
566
+ try {
567
+ const schemaAny = schema;
568
+ if (schemaAny.description) {
569
+ return schemaAny.description;
570
+ }
571
+ if (schemaAny._def?.description) {
572
+ return schemaAny._def.description;
573
+ }
574
+ const typeName = schemaAny._def?.typeName;
575
+ if (typeName === "ZodObject" && schemaAny._def) {
576
+ const shape = schemaAny._def.shape?.();
577
+ if (shape && typeof shape === "object") {
578
+ const keys = Object.keys(shape);
579
+ if (keys.length > 0) {
580
+ const keyStr = keys.length > 3 ? `{${keys.slice(0, 3).join(", ")}...}` : `{${keys.join(", ")}}`;
581
+ return keyStr;
582
+ }
583
+ }
584
+ return "Object";
585
+ }
586
+ if (typeName) {
587
+ return typeName.replace("Zod", "").replace("Type", "");
588
+ }
589
+ return "Any";
590
+ } catch {
591
+ return "Any";
592
+ }
593
+ }
594
+ function extractParameters(schema) {
595
+ if (!schema) {
596
+ return null;
597
+ }
598
+ try {
599
+ const schemaAny = schema;
600
+ if (schemaAny._def?.typeName === "ZodObject") {
601
+ const shape = schemaAny._def.shape?.();
602
+ if (shape && typeof shape === "object") {
603
+ const params = [];
604
+ for (const [key, value] of Object.entries(shape)) {
605
+ const fieldType = value?._def?.typeName;
606
+ if (fieldType) {
607
+ const cleanType = fieldType.replace("Zod", "").toLowerCase();
608
+ params.push(`${key}: ${cleanType}`);
609
+ } else {
610
+ params.push(key);
611
+ }
612
+ }
613
+ return params.length > 0 ? params : null;
614
+ }
615
+ }
616
+ return null;
617
+ } catch {
618
+ return null;
619
+ }
620
+ }
621
+ function getRegisteredHooks(hooks) {
622
+ const events = [];
623
+ const allEvents = Object.values(HookEvents);
624
+ for (const event of allEvents) {
625
+ if (hooks.listenerCount(event) > 0) {
626
+ events.push(event);
627
+ }
628
+ }
629
+ return events;
630
+ }
631
+ function getWrappedAgent(tool2) {
632
+ const wrapped = tool2.metadata?.["wrappedAgent"];
633
+ if (wrapped && typeof wrapped === "object" && "name" in wrapped) {
634
+ return wrapped;
635
+ }
636
+ return null;
637
+ }
638
+ function isMcpTool(tool2) {
639
+ return tool2.metadata?.["provider"] === "mcp";
640
+ }
641
+ async function generateAgentFlowDiagram(agent, options = {}) {
642
+ const visited = options._visited ?? /* @__PURE__ */ new Set();
643
+ const isRoot = visited.size === 0;
644
+ const lines = [];
645
+ if (isRoot) {
646
+ lines.push("```mermaid");
647
+ lines.push("graph TB");
648
+ }
649
+ const agentId = sanitizeId(agent.name);
650
+ if (visited.has(agentId)) {
651
+ return "";
652
+ }
653
+ visited.add(agentId);
654
+ let agentLabel = `\u{1F916} ${agent.name}`;
655
+ if (agent.description && agent.description !== `Agent: ${agent.name}` && agent.description !== agent.name) {
656
+ const desc = sanitizeLabel(agent.description);
657
+ agentLabel += `<br/><i>${desc}</i>`;
658
+ }
659
+ const inputSchemaName = getSchemaName(agent.inputSchema);
660
+ const outputSchemaName = getSchemaName(agent.outputSchema);
661
+ agentLabel += `<br/>In: ${inputSchemaName} | Out: ${outputSchemaName}`;
662
+ lines.push(` ${agentId}["${agentLabel}"]:::agent`);
663
+ const agentWithHooks = agent;
664
+ const registeredHooks = getRegisteredHooks(agentWithHooks.hooks);
665
+ if (registeredHooks.length > 0) {
666
+ const hookId = `${agentId}_hooks`;
667
+ const hookNames = registeredHooks.slice(0, 3).join(", ");
668
+ const hookLabel = registeredHooks.length > 3 ? `${hookNames} +${registeredHooks.length - 3}` : hookNames;
669
+ lines.push(` ${hookId}["\u{1FA9D} ${hookLabel}"]:::hook`);
670
+ lines.push(` ${agentId} -.-> ${hookId}`);
671
+ }
672
+ const tools = agent.getTools();
673
+ const regularTools = [];
674
+ const mcpToolsByServer = /* @__PURE__ */ new Map();
675
+ const agentTools = [];
676
+ for (const tool2 of tools) {
677
+ const wrappedAgent = getWrappedAgent(tool2);
678
+ if (wrappedAgent) {
679
+ agentTools.push(tool2);
680
+ } else if (isMcpTool(tool2)) {
681
+ const serverName = tool2.metadata?.["server"] ?? "unknown";
682
+ const serverTools = mcpToolsByServer.get(serverName) ?? [];
683
+ serverTools.push(tool2);
684
+ mcpToolsByServer.set(serverName, serverTools);
685
+ } else {
686
+ regularTools.push(tool2);
687
+ }
688
+ }
689
+ for (const tool2 of regularTools) {
690
+ const toolId = sanitizeId(`${agentId}_${tool2.name}`);
691
+ let toolLabel = `\u2699\uFE0F ${sanitizeLabel(tool2.name)}`;
692
+ if (tool2.description) {
693
+ const desc = sanitizeLabel(tool2.description);
694
+ toolLabel += `<br/><i>${desc}</i>`;
695
+ }
696
+ const params = extractParameters(tool2.schema);
697
+ if (params && params.length > 0) {
698
+ const paramsStr = params.length > 3 ? `${params.slice(0, 3).join(", ")}...` : params.join(", ");
699
+ toolLabel += `<br/>(${paramsStr})`;
700
+ }
701
+ lines.push(` ${toolId}["${toolLabel}"]:::tool`);
702
+ lines.push(` ${agentId} --> ${toolId}`);
703
+ }
704
+ if (mcpToolsByServer.size > 0) {
705
+ for (const [serverName, serverTools] of mcpToolsByServer.entries()) {
706
+ if (options.includeMcpTools) {
707
+ for (const tool2 of serverTools) {
708
+ const toolId = sanitizeId(`${agentId}_${tool2.name}`);
709
+ let toolLabel = `\u2699\uFE0F ${sanitizeLabel(tool2.name)}`;
710
+ if (tool2.description) {
711
+ const desc = sanitizeLabel(tool2.description);
712
+ toolLabel += `<br/><i>${desc}</i>`;
713
+ }
714
+ toolLabel += `<br/>(MCP: ${sanitizeLabel(serverName)})`;
715
+ lines.push(` ${toolId}["${toolLabel}"]:::tool`);
716
+ lines.push(` ${agentId} --> ${toolId}`);
717
+ }
718
+ } else {
719
+ const providerId = sanitizeId(`${agentId}_provider_${serverName}`);
720
+ lines.push(` ${providerId}["\u{1F50C} ${serverName}"]:::provider`);
721
+ lines.push(` ${agentId} --> ${providerId}`);
722
+ }
723
+ }
724
+ }
725
+ for (const tool2 of agentTools) {
726
+ const wrappedAgent = getWrappedAgent(tool2);
727
+ if (wrappedAgent) {
728
+ const subAgentId = sanitizeId(wrappedAgent.name);
729
+ lines.push(` ${agentId} --> ${subAgentId}`);
730
+ const subDiagram = await generateAgentFlowDiagram(wrappedAgent, {
731
+ ...options,
732
+ _visited: visited
733
+ });
734
+ const subLines = subDiagram.split("\n").filter(
735
+ (line) => line && !line.includes("```mermaid") && !line.includes("```") && !line.includes("%% Styling") && !line.includes("classDef")
736
+ );
737
+ lines.push(...subLines);
738
+ }
739
+ }
740
+ if (isRoot) {
741
+ lines.push("");
742
+ lines.push(" %% Styling - Opper Brand Colors");
743
+ lines.push(
744
+ " classDef agent fill:#8CF0DC,stroke:#1B2E40,stroke-width:3px,color:#1B2E40"
745
+ );
746
+ lines.push(
747
+ " classDef tool fill:#FFD7D7,stroke:#3C3CAF,stroke-width:2px,color:#1B2E40"
748
+ );
749
+ lines.push(
750
+ " classDef schema fill:#F8F8F8,stroke:#3C3CAF,stroke-width:2px,color:#1B2E40"
751
+ );
752
+ lines.push(
753
+ " classDef hook fill:#FFB186,stroke:#3C3CAF,stroke-width:2px,color:#1B2E40"
754
+ );
755
+ lines.push(
756
+ " classDef provider fill:#8CECF2,stroke:#1B2E40,stroke-width:2px,color:#1B2E40"
757
+ );
758
+ lines.push("```");
759
+ }
760
+ const diagram = lines.join("\n");
761
+ if (options.outputPath && isRoot) {
762
+ let filePath = options.outputPath;
763
+ if (!filePath.endsWith(".md")) {
764
+ filePath += ".md";
765
+ }
766
+ const fileContent = `# Agent Flow: ${agent.name}
767
+
768
+ ${diagram}`;
769
+ await fs.promises.writeFile(filePath, fileContent, "utf-8");
770
+ return filePath;
771
+ }
772
+ return diagram;
773
+ }
470
774
  var MemoryEntryMetadataSchema = zod.z.object({
471
775
  createdAt: zod.z.number(),
472
776
  updatedAt: zod.z.number(),
@@ -601,6 +905,10 @@ var BaseAgent = class {
601
905
  * Whether memory is enabled
602
906
  */
603
907
  enableMemory;
908
+ /**
909
+ * Whether streaming is enabled
910
+ */
911
+ enableStreaming;
604
912
  /**
605
913
  * Memory instance for persistent storage (null if disabled or initialization failed)
606
914
  */
@@ -613,6 +921,10 @@ var BaseAgent = class {
613
921
  * Hook manager for lifecycle events
614
922
  */
615
923
  hooks;
924
+ /**
925
+ * Event dispatcher for runtime events (notably streaming)
926
+ */
927
+ events;
616
928
  /**
617
929
  * Registry of available tools
618
930
  */
@@ -642,12 +954,26 @@ var BaseAgent = class {
642
954
  this.inputSchema = config.inputSchema;
643
955
  this.outputSchema = config.outputSchema;
644
956
  this.enableMemory = config.enableMemory ?? false;
957
+ this.enableStreaming = config.enableStreaming ?? false;
645
958
  this.metadata = { ...config.metadata ?? {} };
646
959
  this.hooks = new HookManager();
960
+ this.events = new AgentEventEmitter();
647
961
  this.tools = /* @__PURE__ */ new Map();
648
962
  this.baseTools = /* @__PURE__ */ new Map();
649
963
  this.toolProviders = /* @__PURE__ */ new Set();
650
964
  this.providerToolRegistry = /* @__PURE__ */ new Map();
965
+ if (config.onStreamStart) {
966
+ this.on(HookEvents.StreamStart, config.onStreamStart);
967
+ }
968
+ if (config.onStreamChunk) {
969
+ this.on(HookEvents.StreamChunk, config.onStreamChunk);
970
+ }
971
+ if (config.onStreamEnd) {
972
+ this.on(HookEvents.StreamEnd, config.onStreamEnd);
973
+ }
974
+ if (config.onStreamError) {
975
+ this.on(HookEvents.StreamError, config.onStreamError);
976
+ }
651
977
  this.opperConfig = {
652
978
  apiKey: config.opperConfig?.apiKey ?? process.env["OPPER_API_KEY"],
653
979
  baseUrl: config.opperConfig?.baseUrl,
@@ -800,7 +1126,9 @@ var BaseAgent = class {
800
1126
  ...this.inputSchema && { schema: this.inputSchema },
801
1127
  metadata: {
802
1128
  isAgent: true,
803
- agentName: this.name
1129
+ agentName: this.name,
1130
+ wrappedAgent: this
1131
+ // Store reference to this agent for visualization
804
1132
  },
805
1133
  execute: async (input, executionContext) => {
806
1134
  try {
@@ -827,6 +1155,35 @@ var BaseAgent = class {
827
1155
  registerHook(event, handler) {
828
1156
  return this.hooks.on(event, handler);
829
1157
  }
1158
+ /**
1159
+ * Register an event listener.
1160
+ *
1161
+ * @param event - Event name
1162
+ * @param listener - Listener callback
1163
+ * @returns Cleanup function to unregister the listener
1164
+ */
1165
+ on(event, listener) {
1166
+ return this.events.on(event, listener);
1167
+ }
1168
+ /**
1169
+ * Register a one-time event listener that removes itself after the first call.
1170
+ *
1171
+ * @param event - Event name
1172
+ * @param listener - Listener callback
1173
+ * @returns Cleanup function (no-op once listener fires)
1174
+ */
1175
+ once(event, listener) {
1176
+ return this.events.once(event, listener);
1177
+ }
1178
+ /**
1179
+ * Remove a previously registered event listener.
1180
+ *
1181
+ * @param event - Event name
1182
+ * @param listener - Listener callback to remove
1183
+ */
1184
+ off(event, listener) {
1185
+ this.events.off(event, listener);
1186
+ }
830
1187
  /**
831
1188
  * Trigger a hook event with a payload.
832
1189
  * Swallows errors to prevent hook failures from breaking agent execution.
@@ -841,6 +1198,15 @@ var BaseAgent = class {
841
1198
  console.warn(`Hook error for event ${event}:`, error);
842
1199
  }
843
1200
  }
1201
+ /**
1202
+ * Emit a runtime event to listeners.
1203
+ *
1204
+ * @param event - Event name
1205
+ * @param payload - Event payload
1206
+ */
1207
+ emitAgentEvent(event, payload) {
1208
+ this.events.emit(event, payload);
1209
+ }
844
1210
  /**
845
1211
  * Execute a tool with proper context, hooks, and error handling.
846
1212
  *
@@ -998,6 +1364,30 @@ var BaseAgent = class {
998
1364
  });
999
1365
  await Promise.allSettled(teardownPromises);
1000
1366
  }
1367
+ /**
1368
+ * Generate a Mermaid flowchart diagram visualizing the agent's structure and flow.
1369
+ * Shows tools, hooks, schemas, providers, and nested agents.
1370
+ *
1371
+ * @param options - Visualization options
1372
+ * @returns Mermaid markdown string, or file path if outputPath was provided
1373
+ *
1374
+ * @example
1375
+ * ```typescript
1376
+ * // Generate diagram string
1377
+ * const diagram = await agent.visualizeFlow();
1378
+ * console.log(diagram);
1379
+ *
1380
+ * // Save to file
1381
+ * const path = await agent.visualizeFlow({ outputPath: "agent_flow.md" });
1382
+ * console.log(`Saved to ${path}`);
1383
+ *
1384
+ * // Include MCP tools details
1385
+ * const diagram = await agent.visualizeFlow({ includeMcpTools: true });
1386
+ * ```
1387
+ */
1388
+ async visualizeFlow(options) {
1389
+ return generateAgentFlowDiagram(this, options);
1390
+ }
1001
1391
  };
1002
1392
 
1003
1393
  // src/index.ts
@@ -1090,7 +1480,7 @@ var ToolExecutionSummarySchema = zod.z.object({
1090
1480
 
1091
1481
  // package.json
1092
1482
  var package_default = {
1093
- version: "0.1.1"};
1483
+ version: "0.2.0"};
1094
1484
 
1095
1485
  // src/utils/version.ts
1096
1486
  var SDK_NAME = "@opperai/agents";
@@ -1153,7 +1543,7 @@ var OpperClient = class {
1153
1543
  return this.withRetry(async () => {
1154
1544
  const inputSchema = this.toJsonSchema(options.inputSchema);
1155
1545
  const outputSchema = this.toJsonSchema(options.outputSchema);
1156
- const response = await this.client.call({
1546
+ const callPayload = {
1157
1547
  name: options.name,
1158
1548
  instructions: options.instructions,
1159
1549
  input: options.input,
@@ -1161,7 +1551,8 @@ var OpperClient = class {
1161
1551
  ...outputSchema && { outputSchema },
1162
1552
  ...options.model && { model: options.model },
1163
1553
  ...options.parentSpanId && { parentSpanId: options.parentSpanId }
1164
- });
1554
+ };
1555
+ const response = options.signal ? await this.client.call(callPayload, { signal: options.signal }) : await this.client.call(callPayload);
1165
1556
  const usagePayload = extractUsage(response);
1166
1557
  const costPayload = extractCost(response);
1167
1558
  const usage = {
@@ -1179,6 +1570,36 @@ var OpperClient = class {
1179
1570
  return result;
1180
1571
  }, options.name);
1181
1572
  }
1573
+ /**
1574
+ * Stream a call to Opper with retry logic
1575
+ */
1576
+ async stream(options) {
1577
+ return this.withRetry(async () => {
1578
+ const inputSchema = this.toJsonSchema(options.inputSchema);
1579
+ const outputSchema = this.toJsonSchema(options.outputSchema);
1580
+ const streamPayload = {
1581
+ name: options.name,
1582
+ instructions: options.instructions,
1583
+ input: options.input,
1584
+ ...inputSchema && { inputSchema },
1585
+ ...outputSchema && { outputSchema },
1586
+ ...options.model && { model: options.model },
1587
+ ...options.parentSpanId && { parentSpanId: options.parentSpanId }
1588
+ };
1589
+ const response = options.signal ? await this.client.stream(streamPayload, { signal: options.signal }) : await this.client.stream(streamPayload);
1590
+ const iterable = {
1591
+ async *[Symbol.asyncIterator]() {
1592
+ for await (const event of response.result) {
1593
+ yield event;
1594
+ }
1595
+ }
1596
+ };
1597
+ return {
1598
+ headers: response.headers,
1599
+ result: iterable
1600
+ };
1601
+ }, `stream:${options.name}`);
1602
+ }
1182
1603
  /**
1183
1604
  * Create a span for tracing
1184
1605
  */
@@ -1347,6 +1768,189 @@ var mergeSchemaDefaults = (schema, value) => {
1347
1768
  return validateSchema(schema, value);
1348
1769
  };
1349
1770
 
1771
+ // src/utils/streaming.ts
1772
+ var STREAM_ROOT_PATH = "_root";
1773
+ var NUMERIC_TOKEN_PATTERN = /^\d+$/;
1774
+ var BRACKET_TOKEN_PATTERN = /\[(\d+)\]/g;
1775
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1776
+ var coercePrimitive = (value) => {
1777
+ const trimmed = value.trim();
1778
+ if (trimmed.length === 0) {
1779
+ return value;
1780
+ }
1781
+ const lower = trimmed.toLowerCase();
1782
+ if (lower === "true") {
1783
+ return true;
1784
+ }
1785
+ if (lower === "false") {
1786
+ return false;
1787
+ }
1788
+ if (lower === "null") {
1789
+ return null;
1790
+ }
1791
+ if (/^-?\d+$/.test(trimmed)) {
1792
+ const intValue = Number.parseInt(trimmed, 10);
1793
+ return Number.isNaN(intValue) ? value : intValue;
1794
+ }
1795
+ if (/^-?\d+\.\d+$/.test(trimmed)) {
1796
+ const floatValue = Number.parseFloat(trimmed);
1797
+ return Number.isNaN(floatValue) ? value : floatValue;
1798
+ }
1799
+ return value;
1800
+ };
1801
+ var toDisplayString = (value) => {
1802
+ if (value === null || value === void 0) {
1803
+ return "";
1804
+ }
1805
+ if (typeof value === "string") {
1806
+ return value;
1807
+ }
1808
+ return String(value);
1809
+ };
1810
+ var parsePathSegments = (path) => {
1811
+ return path.replace(BRACKET_TOKEN_PATTERN, (_, index) => `.${index}`).split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
1812
+ };
1813
+ var setNestedValue = (target, path, value) => {
1814
+ const segments = parsePathSegments(path);
1815
+ if (segments.length === 0) {
1816
+ return;
1817
+ }
1818
+ let current = target;
1819
+ for (let i = 0; i < segments.length - 1; i++) {
1820
+ const segment = segments[i];
1821
+ if (segment === void 0) {
1822
+ return;
1823
+ }
1824
+ const existing = current[segment];
1825
+ if (!isRecord(existing)) {
1826
+ const nextLevel = {};
1827
+ current[segment] = nextLevel;
1828
+ current = nextLevel;
1829
+ continue;
1830
+ }
1831
+ current = existing;
1832
+ }
1833
+ const lastSegment = segments[segments.length - 1];
1834
+ if (lastSegment === void 0) {
1835
+ return;
1836
+ }
1837
+ current[lastSegment] = value;
1838
+ };
1839
+ var normalizeIndexed = (value) => {
1840
+ if (Array.isArray(value)) {
1841
+ return value.map((item) => normalizeIndexed(item));
1842
+ }
1843
+ if (!isRecord(value)) {
1844
+ return value;
1845
+ }
1846
+ const normalizedEntries = {};
1847
+ for (const [key, entryValue] of Object.entries(value)) {
1848
+ normalizedEntries[key] = normalizeIndexed(entryValue);
1849
+ }
1850
+ const keys = Object.keys(normalizedEntries);
1851
+ if (keys.length > 0 && keys.every((key) => NUMERIC_TOKEN_PATTERN.test(key))) {
1852
+ const parsedIndices = keys.map((key) => Number.parseInt(key, 10)).filter((index) => Number.isFinite(index) && index >= 0);
1853
+ if (parsedIndices.length === 0) {
1854
+ return normalizedEntries;
1855
+ }
1856
+ const maxIndex = Math.max(...parsedIndices);
1857
+ const result = new Array(maxIndex + 1).fill(void 0);
1858
+ for (const [key, entryValue] of Object.entries(normalizedEntries)) {
1859
+ const index = Number.parseInt(key, 10);
1860
+ if (!Number.isFinite(index) || index < 0) {
1861
+ continue;
1862
+ }
1863
+ result[index] = entryValue;
1864
+ }
1865
+ return result.map(
1866
+ (item) => item === void 0 ? null : item
1867
+ );
1868
+ }
1869
+ return normalizedEntries;
1870
+ };
1871
+ var resolveFieldValue = (path, displayBuffers, valueBuffers) => {
1872
+ const values = valueBuffers.get(path) ?? [];
1873
+ if (values.length === 0) {
1874
+ return displayBuffers.get(path) ?? "";
1875
+ }
1876
+ const last = values[values.length - 1];
1877
+ if (typeof last === "number" || typeof last === "boolean") {
1878
+ return last;
1879
+ }
1880
+ if (last === null) {
1881
+ return null;
1882
+ }
1883
+ const joined = displayBuffers.get(path) ?? values.map((value) => toDisplayString(value)).join("");
1884
+ return coercePrimitive(joined);
1885
+ };
1886
+ var StreamAssembler = class {
1887
+ displayBuffers = /* @__PURE__ */ new Map();
1888
+ valueBuffers = /* @__PURE__ */ new Map();
1889
+ schema;
1890
+ constructor(options = {}) {
1891
+ this.schema = options.schema;
1892
+ }
1893
+ feed(chunk) {
1894
+ const { delta } = chunk;
1895
+ if (delta === null || delta === void 0) {
1896
+ return null;
1897
+ }
1898
+ const path = chunk.jsonPath === null || chunk.jsonPath === void 0 ? STREAM_ROOT_PATH : chunk.jsonPath;
1899
+ const existingDisplay = this.displayBuffers.get(path) ?? "";
1900
+ const nextDisplay = `${existingDisplay}${toDisplayString(delta)}`;
1901
+ this.displayBuffers.set(path, nextDisplay);
1902
+ const existingValues = this.valueBuffers.get(path) ?? [];
1903
+ existingValues.push(delta);
1904
+ this.valueBuffers.set(path, existingValues);
1905
+ return {
1906
+ path,
1907
+ accumulated: nextDisplay,
1908
+ snapshot: this.snapshot()
1909
+ };
1910
+ }
1911
+ snapshot() {
1912
+ return Object.fromEntries(this.displayBuffers.entries());
1913
+ }
1914
+ hasStructuredFields() {
1915
+ const keys = Array.from(this.displayBuffers.keys());
1916
+ return keys.some((key) => key !== STREAM_ROOT_PATH);
1917
+ }
1918
+ finalize() {
1919
+ if (this.displayBuffers.size === 0) {
1920
+ return { type: "empty" };
1921
+ }
1922
+ if (!this.hasStructuredFields()) {
1923
+ const root = this.displayBuffers.get(STREAM_ROOT_PATH) ?? "";
1924
+ return { type: "root", rootText: root };
1925
+ }
1926
+ const structured = this.reconstructStructured();
1927
+ let coerced = structured;
1928
+ if (this.schema !== void 0) {
1929
+ try {
1930
+ coerced = this.schema.parse(structured);
1931
+ } catch {
1932
+ coerced = structured;
1933
+ }
1934
+ }
1935
+ return { type: "structured", structured: coerced };
1936
+ }
1937
+ getFieldBuffers() {
1938
+ return new Map(this.displayBuffers);
1939
+ }
1940
+ reconstructStructured() {
1941
+ const result = {};
1942
+ for (const path of this.valueBuffers.keys()) {
1943
+ if (path === STREAM_ROOT_PATH) {
1944
+ continue;
1945
+ }
1946
+ const value = resolveFieldValue(path, this.displayBuffers, this.valueBuffers);
1947
+ setNestedValue(result, path, value);
1948
+ }
1949
+ return normalizeIndexed(result);
1950
+ }
1951
+ };
1952
+ var createStreamAssembler = (options) => new StreamAssembler(options);
1953
+
1350
1954
  // src/core/agent.ts
1351
1955
  var isToolSuccessResult = (value) => {
1352
1956
  if (typeof value !== "object" || value === null) {
@@ -1473,6 +2077,9 @@ var Agent = class extends BaseAgent {
1473
2077
  const spanName = "think";
1474
2078
  this.log("Think step", { iteration: context.iteration });
1475
2079
  await this.triggerHook(HookEvents.LlmCall, { context, callType: "think" });
2080
+ if (this.enableStreaming) {
2081
+ return this.thinkStreaming(input, context);
2082
+ }
1476
2083
  try {
1477
2084
  const instructions = this.buildThinkInstructions();
1478
2085
  const thinkContext = await this.buildThinkContext(input, context);
@@ -1515,6 +2122,132 @@ var Agent = class extends BaseAgent {
1515
2122
  );
1516
2123
  }
1517
2124
  }
2125
+ async thinkStreaming(input, context) {
2126
+ const spanName = "think";
2127
+ const instructions = this.buildThinkInstructions();
2128
+ const thinkContext = await this.buildThinkContext(input, context);
2129
+ const assembler = createStreamAssembler({
2130
+ schema: AgentDecisionSchema
2131
+ });
2132
+ let streamSpanId;
2133
+ await this.triggerHook(HookEvents.StreamStart, {
2134
+ context,
2135
+ callType: "think"
2136
+ });
2137
+ this.emitAgentEvent(HookEvents.StreamStart, {
2138
+ context,
2139
+ callType: "think"
2140
+ });
2141
+ try {
2142
+ const streamResponse = await this.opperClient.stream({
2143
+ name: spanName,
2144
+ instructions,
2145
+ input: thinkContext,
2146
+ outputSchema: AgentDecisionSchema,
2147
+ model: this.model,
2148
+ ...context.parentSpanId && { parentSpanId: context.parentSpanId }
2149
+ });
2150
+ for await (const event of streamResponse.result) {
2151
+ const data = event?.data;
2152
+ if (!data) {
2153
+ continue;
2154
+ }
2155
+ if (!streamSpanId && typeof data.spanId === "string" && data.spanId) {
2156
+ streamSpanId = data.spanId;
2157
+ }
2158
+ const feedResult = assembler.feed({
2159
+ delta: data.delta,
2160
+ jsonPath: data.jsonPath
2161
+ });
2162
+ if (!feedResult) {
2163
+ continue;
2164
+ }
2165
+ const chunkPayload = {
2166
+ context,
2167
+ callType: "think",
2168
+ chunkData: {
2169
+ delta: data.delta,
2170
+ jsonPath: data.jsonPath ?? null,
2171
+ chunkType: data.chunkType ?? null
2172
+ },
2173
+ accumulated: feedResult.accumulated,
2174
+ fieldBuffers: feedResult.snapshot
2175
+ };
2176
+ await this.triggerHook(HookEvents.StreamChunk, chunkPayload);
2177
+ this.emitAgentEvent(HookEvents.StreamChunk, chunkPayload);
2178
+ }
2179
+ const fieldBuffers = assembler.snapshot();
2180
+ const endPayload = {
2181
+ context,
2182
+ callType: "think",
2183
+ fieldBuffers
2184
+ };
2185
+ await this.triggerHook(HookEvents.StreamEnd, endPayload);
2186
+ this.emitAgentEvent(HookEvents.StreamEnd, endPayload);
2187
+ const finalize = assembler.finalize();
2188
+ let decision;
2189
+ if (finalize.type === "structured" && finalize.structured) {
2190
+ decision = AgentDecisionSchema.parse(
2191
+ finalize.structured
2192
+ );
2193
+ } else {
2194
+ decision = AgentDecisionSchema.parse({
2195
+ reasoning: finalize.type === "root" ? finalize.rootText ?? "" : ""
2196
+ });
2197
+ }
2198
+ const usageTracked = await this.trackStreamingUsageBySpan(
2199
+ context,
2200
+ streamSpanId
2201
+ );
2202
+ if (!usageTracked) {
2203
+ context.updateUsage({
2204
+ requests: 1,
2205
+ inputTokens: 0,
2206
+ outputTokens: 0,
2207
+ totalTokens: 0,
2208
+ cost: { generation: 0, platform: 0, total: 0 }
2209
+ });
2210
+ }
2211
+ await this.triggerHook(HookEvents.LlmResponse, {
2212
+ context,
2213
+ callType: "think",
2214
+ response: streamResponse,
2215
+ parsed: decision
2216
+ });
2217
+ await this.triggerHook(HookEvents.ThinkEnd, {
2218
+ context,
2219
+ thought: { reasoning: decision.reasoning }
2220
+ });
2221
+ this.log("Think result", {
2222
+ reasoning: decision.reasoning,
2223
+ toolCalls: decision.toolCalls.length,
2224
+ memoryReads: decision.memoryReads?.length ?? 0,
2225
+ memoryWrites: Object.keys(decision.memoryUpdates ?? {}).length
2226
+ });
2227
+ const resultPayload = {
2228
+ decision
2229
+ };
2230
+ if (streamSpanId) {
2231
+ resultPayload.spanId = streamSpanId;
2232
+ }
2233
+ return resultPayload;
2234
+ } catch (error) {
2235
+ await this.triggerHook(HookEvents.StreamError, {
2236
+ context,
2237
+ callType: "think",
2238
+ error
2239
+ });
2240
+ this.emitAgentEvent(HookEvents.StreamError, {
2241
+ context,
2242
+ callType: "think",
2243
+ error
2244
+ });
2245
+ this.logger.error("Think step failed", error);
2246
+ throw new Error(
2247
+ `Think step failed: ${error instanceof Error ? error.message : String(error)}`
2248
+ );
2249
+ }
2250
+ }
1518
2251
  /**
1519
2252
  * Build static instructions for the think step
1520
2253
  */
@@ -1828,6 +2561,13 @@ The memory you write persists across all process() calls on this agent.`;
1828
2561
  };
1829
2562
  const instructions = `Generate the final result based on the execution history.
1830
2563
  Follow any instructions provided for formatting and style.`;
2564
+ if (this.enableStreaming) {
2565
+ return this.generateFinalResultStreaming(
2566
+ context,
2567
+ finalContext,
2568
+ instructions
2569
+ );
2570
+ }
1831
2571
  try {
1832
2572
  const callOptions = {
1833
2573
  name: "generate_final_result",
@@ -1863,6 +2603,170 @@ Follow any instructions provided for formatting and style.`;
1863
2603
  );
1864
2604
  }
1865
2605
  }
2606
+ async generateFinalResultStreaming(context, finalContext, instructions) {
2607
+ const assembler = createStreamAssembler(
2608
+ this.outputSchema ? { schema: this.outputSchema } : void 0
2609
+ );
2610
+ let streamSpanId;
2611
+ await this.triggerHook(HookEvents.StreamStart, {
2612
+ context,
2613
+ callType: "final_result"
2614
+ });
2615
+ this.emitAgentEvent(HookEvents.StreamStart, {
2616
+ context,
2617
+ callType: "final_result"
2618
+ });
2619
+ try {
2620
+ const streamResponse = await this.opperClient.stream({
2621
+ name: "generate_final_result",
2622
+ instructions,
2623
+ input: finalContext,
2624
+ model: this.model,
2625
+ ...context.parentSpanId && { parentSpanId: context.parentSpanId },
2626
+ ...this.outputSchema && { outputSchema: this.outputSchema }
2627
+ });
2628
+ for await (const event of streamResponse.result) {
2629
+ const data = event?.data;
2630
+ if (!data) {
2631
+ continue;
2632
+ }
2633
+ if (!streamSpanId && typeof data.spanId === "string" && data.spanId) {
2634
+ streamSpanId = data.spanId;
2635
+ }
2636
+ const feedResult = assembler.feed({
2637
+ delta: data.delta,
2638
+ jsonPath: data.jsonPath
2639
+ });
2640
+ if (!feedResult) {
2641
+ continue;
2642
+ }
2643
+ const chunkPayload = {
2644
+ context,
2645
+ callType: "final_result",
2646
+ chunkData: {
2647
+ delta: data.delta,
2648
+ jsonPath: data.jsonPath ?? null,
2649
+ chunkType: data.chunkType ?? null
2650
+ },
2651
+ accumulated: feedResult.accumulated,
2652
+ fieldBuffers: feedResult.snapshot
2653
+ };
2654
+ await this.triggerHook(HookEvents.StreamChunk, chunkPayload);
2655
+ this.emitAgentEvent(HookEvents.StreamChunk, chunkPayload);
2656
+ }
2657
+ const fieldBuffers = assembler.snapshot();
2658
+ const endPayload = {
2659
+ context,
2660
+ callType: "final_result",
2661
+ fieldBuffers
2662
+ };
2663
+ await this.triggerHook(HookEvents.StreamEnd, endPayload);
2664
+ this.emitAgentEvent(HookEvents.StreamEnd, endPayload);
2665
+ const finalize = assembler.finalize();
2666
+ let result;
2667
+ if (this.outputSchema) {
2668
+ if (finalize.type !== "structured" || finalize.structured === void 0) {
2669
+ throw new Error(
2670
+ "Streaming response did not provide structured data for the configured output schema."
2671
+ );
2672
+ }
2673
+ result = this.outputSchema.parse(
2674
+ finalize.structured
2675
+ );
2676
+ } else if (finalize.type === "root") {
2677
+ result = finalize.rootText ?? "";
2678
+ } else if (finalize.type === "structured" && finalize.structured) {
2679
+ result = JSON.stringify(finalize.structured);
2680
+ } else {
2681
+ result = "";
2682
+ }
2683
+ const usageTracked = await this.trackStreamingUsageBySpan(
2684
+ context,
2685
+ streamSpanId
2686
+ );
2687
+ if (!usageTracked) {
2688
+ context.updateUsage({
2689
+ requests: 1,
2690
+ inputTokens: 0,
2691
+ outputTokens: 0,
2692
+ totalTokens: 0,
2693
+ cost: { generation: 0, platform: 0, total: 0 }
2694
+ });
2695
+ }
2696
+ await this.triggerHook(HookEvents.LlmResponse, {
2697
+ context,
2698
+ callType: "final_result",
2699
+ response: streamResponse,
2700
+ parsed: result
2701
+ });
2702
+ this.log(
2703
+ this.outputSchema ? "Final result generated (streaming, schema-validated)" : "Final result generated (streaming)"
2704
+ );
2705
+ return result;
2706
+ } catch (error) {
2707
+ await this.triggerHook(HookEvents.StreamError, {
2708
+ context,
2709
+ callType: "final_result",
2710
+ error
2711
+ });
2712
+ this.emitAgentEvent(HookEvents.StreamError, {
2713
+ context,
2714
+ callType: "final_result",
2715
+ error
2716
+ });
2717
+ this.logger.error("Failed to generate final result", error);
2718
+ throw new Error(
2719
+ `Failed to generate final result: ${error instanceof Error ? error.message : String(error)}`
2720
+ );
2721
+ }
2722
+ }
2723
+ async trackStreamingUsageBySpan(context, spanId) {
2724
+ if (!spanId) {
2725
+ return false;
2726
+ }
2727
+ try {
2728
+ const span = await this.opperClient.getClient().spans.get(spanId);
2729
+ const traceId = span?.traceId ?? span?.trace_id;
2730
+ if (!traceId) {
2731
+ return false;
2732
+ }
2733
+ const trace = await this.opperClient.getClient().traces.get(traceId);
2734
+ const spans = trace?.spans;
2735
+ if (!Array.isArray(spans)) {
2736
+ return false;
2737
+ }
2738
+ for (const entry of spans) {
2739
+ const entryId = entry?.id ?? entry["id"];
2740
+ if (entryId !== spanId) {
2741
+ continue;
2742
+ }
2743
+ const data = entry?.data;
2744
+ if (!data) {
2745
+ continue;
2746
+ }
2747
+ const record = data;
2748
+ const primaryTotal = record["totalTokens"];
2749
+ const fallbackTotal = record["total_tokens"];
2750
+ const totalTokensRaw = typeof primaryTotal === "number" && Number.isFinite(primaryTotal) ? primaryTotal : typeof fallbackTotal === "number" && Number.isFinite(fallbackTotal) ? fallbackTotal : void 0;
2751
+ if (totalTokensRaw !== void 0) {
2752
+ context.updateUsage({
2753
+ requests: 1,
2754
+ inputTokens: 0,
2755
+ outputTokens: 0,
2756
+ totalTokens: totalTokensRaw,
2757
+ cost: { generation: 0, platform: 0, total: 0 }
2758
+ });
2759
+ return true;
2760
+ }
2761
+ }
2762
+ } catch (error) {
2763
+ this.logger.warn("Could not fetch streaming usage", {
2764
+ spanId,
2765
+ error: error instanceof Error ? error.message : String(error)
2766
+ });
2767
+ }
2768
+ return false;
2769
+ }
1866
2770
  /**
1867
2771
  * Log helper
1868
2772
  */
@@ -2079,7 +2983,7 @@ var createMCPServerConfig = MCPconfig;
2079
2983
 
2080
2984
  // src/mcp/provider.ts
2081
2985
  init_tool();
2082
- var isRecord = (value) => typeof value === "object" && value !== null;
2986
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
2083
2987
  var MCPToolProvider = class {
2084
2988
  configs;
2085
2989
  namePrefix;
@@ -2170,7 +3074,7 @@ var MCPToolProvider = class {
2170
3074
  },
2171
3075
  execute: async (input, context) => {
2172
3076
  const startedAt = Date.now();
2173
- const args = isRecord(input) ? input : input === void 0 ? {} : { value: input };
3077
+ const args = isRecord2(input) ? input : input === void 0 ? {} : { value: input };
2174
3078
  try {
2175
3079
  const result = await client.callTool(mcpTool.name, args);
2176
3080
  return exports.ToolResultFactory.success(toolName, result, {
@@ -2630,6 +3534,8 @@ var ToolRunner = class {
2630
3534
 
2631
3535
  exports.Agent = Agent;
2632
3536
  exports.AgentDecisionSchema = AgentDecisionSchema;
3537
+ exports.AgentEventEmitter = AgentEventEmitter;
3538
+ exports.AgentEvents = AgentEvents;
2633
3539
  exports.BaseAgent = BaseAgent;
2634
3540
  exports.ConsoleLogger = ConsoleLogger;
2635
3541
  exports.DEFAULT_MODEL = DEFAULT_MODEL;
@@ -2646,8 +3552,10 @@ exports.MemoryEntryMetadataSchema = MemoryEntryMetadataSchema;
2646
3552
  exports.MemoryEntrySchema = MemoryEntrySchema;
2647
3553
  exports.MemoryUpdateSchema = MemoryUpdateSchema;
2648
3554
  exports.OpperClient = OpperClient;
3555
+ exports.STREAM_ROOT_PATH = STREAM_ROOT_PATH;
2649
3556
  exports.SchemaValidationError = SchemaValidationError;
2650
3557
  exports.SilentLogger = SilentLogger;
3558
+ exports.StreamAssembler = StreamAssembler;
2651
3559
  exports.ThoughtSchema = ThoughtSchema;
2652
3560
  exports.ToolCallSchema = ToolCallSchema;
2653
3561
  exports.ToolExecutionSummarySchema = ToolExecutionSummarySchema;
@@ -2657,7 +3565,9 @@ exports.createHookManager = createHookManager;
2657
3565
  exports.createInMemoryStore = createInMemoryStore;
2658
3566
  exports.createMCPServerConfig = createMCPServerConfig;
2659
3567
  exports.createOpperClient = createOpperClient;
3568
+ exports.createStreamAssembler = createStreamAssembler;
2660
3569
  exports.extractTools = extractTools;
3570
+ exports.generateAgentFlowDiagram = generateAgentFlowDiagram;
2661
3571
  exports.getDefaultLogger = getDefaultLogger;
2662
3572
  exports.getSchemaDefault = getSchemaDefault;
2663
3573
  exports.isSchemaValid = isSchemaValid;