@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/README.md +32 -0
- package/dist/index.cjs +917 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +241 -9
- package/dist/index.d.ts +241 -9
- package/dist/index.js +912 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
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 =
|
|
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;
|