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