@opperai/agents 0.1.3 → 0.3.0-beta

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
@@ -394,7 +394,11 @@ var HookEvents = {
394
394
  ToolError: "tool:error",
395
395
  MemoryRead: "memory:read",
396
396
  MemoryWrite: "memory:write",
397
- MemoryError: "memory:error"
397
+ MemoryError: "memory:error",
398
+ StreamStart: "stream:start",
399
+ StreamChunk: "stream:chunk",
400
+ StreamEnd: "stream:end",
401
+ StreamError: "stream:error"
398
402
  };
399
403
  var HookManager = class {
400
404
  registry = /* @__PURE__ */ new Map();
@@ -466,6 +470,82 @@ var HookManager = class {
466
470
  };
467
471
  var createHookManager = (logger) => new HookManager(logger);
468
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
+
469
549
  // src/base/agent.ts
470
550
  init_tool();
471
551
  function sanitizeId(name) {
@@ -825,6 +905,10 @@ var BaseAgent = class {
825
905
  * Whether memory is enabled
826
906
  */
827
907
  enableMemory;
908
+ /**
909
+ * Whether streaming is enabled
910
+ */
911
+ enableStreaming;
828
912
  /**
829
913
  * Memory instance for persistent storage (null if disabled or initialization failed)
830
914
  */
@@ -837,6 +921,10 @@ var BaseAgent = class {
837
921
  * Hook manager for lifecycle events
838
922
  */
839
923
  hooks;
924
+ /**
925
+ * Event dispatcher for runtime events (notably streaming)
926
+ */
927
+ events;
840
928
  /**
841
929
  * Registry of available tools
842
930
  */
@@ -857,6 +945,28 @@ var BaseAgent = class {
857
945
  * Opper client configuration
858
946
  */
859
947
  opperConfig;
948
+ /**
949
+ * Creates a new BaseAgent instance
950
+ *
951
+ * @param config - Agent configuration object
952
+ * @param config.name - Unique name identifying this agent (required)
953
+ * @param config.description - Human-readable description of the agent's purpose
954
+ * @param config.instructions - System instructions guiding agent behavior
955
+ * @param config.tools - Array of tools or tool providers available to the agent
956
+ * @param config.maxIterations - Maximum iterations before terminating the agent loop (default: 25)
957
+ * @param config.model - Model identifier(s). Single model or array for fallback (default: "gcp/gemini-flash-latest")
958
+ * @param config.inputSchema - Zod schema for input validation
959
+ * @param config.outputSchema - Zod schema for output validation
960
+ * @param config.enableStreaming - Enable Opper streaming APIs for LLM calls (default: false)
961
+ * @param config.enableMemory - Enable memory subsystem (default: false)
962
+ * @param config.memory - Custom memory implementation (defaults to InMemoryStore if enableMemory is true)
963
+ * @param config.metadata - Additional metadata for the agent
964
+ * @param config.opperConfig - Opper API configuration containing apiKey and baseUrl
965
+ * @param config.onStreamStart - Handler invoked when a streaming call starts
966
+ * @param config.onStreamChunk - Handler invoked for each streaming chunk
967
+ * @param config.onStreamEnd - Handler invoked when a streaming call ends
968
+ * @param config.onStreamError - Handler invoked when streaming encounters an error
969
+ */
860
970
  constructor(config) {
861
971
  this.name = config.name;
862
972
  this.description = config.description;
@@ -866,12 +976,26 @@ var BaseAgent = class {
866
976
  this.inputSchema = config.inputSchema;
867
977
  this.outputSchema = config.outputSchema;
868
978
  this.enableMemory = config.enableMemory ?? false;
979
+ this.enableStreaming = config.enableStreaming ?? false;
869
980
  this.metadata = { ...config.metadata ?? {} };
870
981
  this.hooks = new HookManager();
982
+ this.events = new AgentEventEmitter();
871
983
  this.tools = /* @__PURE__ */ new Map();
872
984
  this.baseTools = /* @__PURE__ */ new Map();
873
985
  this.toolProviders = /* @__PURE__ */ new Set();
874
986
  this.providerToolRegistry = /* @__PURE__ */ new Map();
987
+ if (config.onStreamStart) {
988
+ this.on(HookEvents.StreamStart, config.onStreamStart);
989
+ }
990
+ if (config.onStreamChunk) {
991
+ this.on(HookEvents.StreamChunk, config.onStreamChunk);
992
+ }
993
+ if (config.onStreamEnd) {
994
+ this.on(HookEvents.StreamEnd, config.onStreamEnd);
995
+ }
996
+ if (config.onStreamError) {
997
+ this.on(HookEvents.StreamError, config.onStreamError);
998
+ }
875
999
  this.opperConfig = {
876
1000
  apiKey: config.opperConfig?.apiKey ?? process.env["OPPER_API_KEY"],
877
1001
  baseUrl: config.opperConfig?.baseUrl,
@@ -1053,6 +1177,35 @@ var BaseAgent = class {
1053
1177
  registerHook(event, handler) {
1054
1178
  return this.hooks.on(event, handler);
1055
1179
  }
1180
+ /**
1181
+ * Register an event listener.
1182
+ *
1183
+ * @param event - Event name
1184
+ * @param listener - Listener callback
1185
+ * @returns Cleanup function to unregister the listener
1186
+ */
1187
+ on(event, listener) {
1188
+ return this.events.on(event, listener);
1189
+ }
1190
+ /**
1191
+ * Register a one-time event listener that removes itself after the first call.
1192
+ *
1193
+ * @param event - Event name
1194
+ * @param listener - Listener callback
1195
+ * @returns Cleanup function (no-op once listener fires)
1196
+ */
1197
+ once(event, listener) {
1198
+ return this.events.once(event, listener);
1199
+ }
1200
+ /**
1201
+ * Remove a previously registered event listener.
1202
+ *
1203
+ * @param event - Event name
1204
+ * @param listener - Listener callback to remove
1205
+ */
1206
+ off(event, listener) {
1207
+ this.events.off(event, listener);
1208
+ }
1056
1209
  /**
1057
1210
  * Trigger a hook event with a payload.
1058
1211
  * Swallows errors to prevent hook failures from breaking agent execution.
@@ -1067,6 +1220,15 @@ var BaseAgent = class {
1067
1220
  console.warn(`Hook error for event ${event}:`, error);
1068
1221
  }
1069
1222
  }
1223
+ /**
1224
+ * Emit a runtime event to listeners.
1225
+ *
1226
+ * @param event - Event name
1227
+ * @param payload - Event payload
1228
+ */
1229
+ emitAgentEvent(event, payload) {
1230
+ this.events.emit(event, payload);
1231
+ }
1070
1232
  /**
1071
1233
  * Execute a tool with proper context, hooks, and error handling.
1072
1234
  *
@@ -1305,6 +1467,10 @@ var AgentDecisionSchema = zod.z.object({
1305
1467
  * Agent's internal reasoning
1306
1468
  */
1307
1469
  reasoning: zod.z.string(),
1470
+ /**
1471
+ * Status message for the user (e.g., "Searching for information...", "Processing results...")
1472
+ */
1473
+ userMessage: zod.z.string().default("Working on it..."),
1308
1474
  /**
1309
1475
  * Tool calls to execute (if any)
1310
1476
  * Empty array signals task completion
@@ -1317,8 +1483,34 @@ var AgentDecisionSchema = zod.z.object({
1317
1483
  /**
1318
1484
  * Memory entries to write/update (key -> payload)
1319
1485
  */
1320
- memoryUpdates: zod.z.record(MemoryUpdateSchema).default({})
1486
+ memoryUpdates: zod.z.record(MemoryUpdateSchema).default({}),
1487
+ /**
1488
+ * Whether the task is complete and finalResult is available
1489
+ * (single LLM call pattern)
1490
+ */
1491
+ isComplete: zod.z.boolean().default(false),
1492
+ /**
1493
+ * The final result when isComplete=true
1494
+ * Should match outputSchema if specified
1495
+ */
1496
+ finalResult: zod.z.unknown().optional()
1321
1497
  });
1498
+ function createAgentDecisionWithOutputSchema(outputSchema) {
1499
+ if (!outputSchema) {
1500
+ return AgentDecisionSchema;
1501
+ }
1502
+ const finalResultSchema = outputSchema.optional();
1503
+ const dynamicSchema = zod.z.object({
1504
+ reasoning: zod.z.string(),
1505
+ userMessage: zod.z.string().default("Working on it..."),
1506
+ toolCalls: zod.z.array(ToolCallSchema).default([]),
1507
+ memoryReads: zod.z.array(zod.z.string()).default([]),
1508
+ memoryUpdates: zod.z.record(MemoryUpdateSchema).default({}),
1509
+ isComplete: zod.z.boolean().default(false),
1510
+ finalResult: finalResultSchema
1511
+ });
1512
+ return dynamicSchema;
1513
+ }
1322
1514
  var ToolExecutionSummarySchema = zod.z.object({
1323
1515
  /**
1324
1516
  * Tool name
@@ -1340,7 +1532,7 @@ var ToolExecutionSummarySchema = zod.z.object({
1340
1532
 
1341
1533
  // package.json
1342
1534
  var package_default = {
1343
- version: "0.1.3"};
1535
+ version: "0.3.0-beta"};
1344
1536
 
1345
1537
  // src/utils/version.ts
1346
1538
  var SDK_NAME = "@opperai/agents";
@@ -1403,7 +1595,7 @@ var OpperClient = class {
1403
1595
  return this.withRetry(async () => {
1404
1596
  const inputSchema = this.toJsonSchema(options.inputSchema);
1405
1597
  const outputSchema = this.toJsonSchema(options.outputSchema);
1406
- const response = await this.client.call({
1598
+ const callPayload = {
1407
1599
  name: options.name,
1408
1600
  instructions: options.instructions,
1409
1601
  input: options.input,
@@ -1411,7 +1603,8 @@ var OpperClient = class {
1411
1603
  ...outputSchema && { outputSchema },
1412
1604
  ...options.model && { model: options.model },
1413
1605
  ...options.parentSpanId && { parentSpanId: options.parentSpanId }
1414
- });
1606
+ };
1607
+ const response = options.signal ? await this.client.call(callPayload, { signal: options.signal }) : await this.client.call(callPayload);
1415
1608
  const usagePayload = extractUsage(response);
1416
1609
  const costPayload = extractCost(response);
1417
1610
  const usage = {
@@ -1429,6 +1622,36 @@ var OpperClient = class {
1429
1622
  return result;
1430
1623
  }, options.name);
1431
1624
  }
1625
+ /**
1626
+ * Stream a call to Opper with retry logic
1627
+ */
1628
+ async stream(options) {
1629
+ return this.withRetry(async () => {
1630
+ const inputSchema = this.toJsonSchema(options.inputSchema);
1631
+ const outputSchema = this.toJsonSchema(options.outputSchema);
1632
+ const streamPayload = {
1633
+ name: options.name,
1634
+ instructions: options.instructions,
1635
+ input: options.input,
1636
+ ...inputSchema && { inputSchema },
1637
+ ...outputSchema && { outputSchema },
1638
+ ...options.model && { model: options.model },
1639
+ ...options.parentSpanId && { parentSpanId: options.parentSpanId }
1640
+ };
1641
+ const response = options.signal ? await this.client.stream(streamPayload, { signal: options.signal }) : await this.client.stream(streamPayload);
1642
+ const iterable = {
1643
+ async *[Symbol.asyncIterator]() {
1644
+ for await (const event of response.result) {
1645
+ yield event;
1646
+ }
1647
+ }
1648
+ };
1649
+ return {
1650
+ headers: response.headers,
1651
+ result: iterable
1652
+ };
1653
+ }, `stream:${options.name}`);
1654
+ }
1432
1655
  /**
1433
1656
  * Create a span for tracing
1434
1657
  */
@@ -1437,7 +1660,8 @@ var OpperClient = class {
1437
1660
  const span = await this.client.spans.create({
1438
1661
  name: options.name,
1439
1662
  ...options.input !== void 0 && { input: options.input },
1440
- ...options.parentSpanId && { parentId: options.parentSpanId }
1663
+ ...options.parentSpanId && { parentId: options.parentSpanId },
1664
+ ...options.type && { type: options.type }
1441
1665
  });
1442
1666
  return {
1443
1667
  id: span.id,
@@ -1454,7 +1678,11 @@ var OpperClient = class {
1454
1678
  const serializedOutput = output !== void 0 && output !== null ? typeof output === "object" ? JSON.stringify(output) : String(output) : void 0;
1455
1679
  await this.client.spans.update(spanId, {
1456
1680
  ...serializedOutput !== void 0 && { output: serializedOutput },
1457
- ...options?.error && { error: options.error }
1681
+ ...options?.error && { error: options.error },
1682
+ ...options?.startTime && { startTime: options.startTime },
1683
+ ...options?.endTime && { endTime: options.endTime },
1684
+ ...options?.meta && { meta: options.meta },
1685
+ ...options?.name && { name: options.name }
1458
1686
  });
1459
1687
  }, `update-span:${spanId}`);
1460
1688
  }
@@ -1597,6 +1825,193 @@ var mergeSchemaDefaults = (schema, value) => {
1597
1825
  return validateSchema(schema, value);
1598
1826
  };
1599
1827
 
1828
+ // src/utils/streaming.ts
1829
+ var STREAM_ROOT_PATH = "_root";
1830
+ var NUMERIC_TOKEN_PATTERN = /^\d+$/;
1831
+ var BRACKET_TOKEN_PATTERN = /\[(\d+)\]/g;
1832
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1833
+ var coercePrimitive = (value) => {
1834
+ const trimmed = value.trim();
1835
+ if (trimmed.length === 0) {
1836
+ return value;
1837
+ }
1838
+ const lower = trimmed.toLowerCase();
1839
+ if (lower === "true") {
1840
+ return true;
1841
+ }
1842
+ if (lower === "false") {
1843
+ return false;
1844
+ }
1845
+ if (lower === "null") {
1846
+ return null;
1847
+ }
1848
+ if (/^-?\d+$/.test(trimmed)) {
1849
+ const intValue = Number.parseInt(trimmed, 10);
1850
+ return Number.isNaN(intValue) ? value : intValue;
1851
+ }
1852
+ if (/^-?\d+\.\d+$/.test(trimmed)) {
1853
+ const floatValue = Number.parseFloat(trimmed);
1854
+ return Number.isNaN(floatValue) ? value : floatValue;
1855
+ }
1856
+ return value;
1857
+ };
1858
+ var toDisplayString = (value) => {
1859
+ if (value === null || value === void 0) {
1860
+ return "";
1861
+ }
1862
+ if (typeof value === "string") {
1863
+ return value;
1864
+ }
1865
+ return String(value);
1866
+ };
1867
+ var parsePathSegments = (path) => {
1868
+ return path.replace(BRACKET_TOKEN_PATTERN, (_, index) => `.${index}`).split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
1869
+ };
1870
+ var setNestedValue = (target, path, value) => {
1871
+ const segments = parsePathSegments(path);
1872
+ if (segments.length === 0) {
1873
+ return;
1874
+ }
1875
+ let current = target;
1876
+ for (let i = 0; i < segments.length - 1; i++) {
1877
+ const segment = segments[i];
1878
+ if (segment === void 0) {
1879
+ return;
1880
+ }
1881
+ const existing = current[segment];
1882
+ if (!isRecord(existing)) {
1883
+ const nextLevel = {};
1884
+ current[segment] = nextLevel;
1885
+ current = nextLevel;
1886
+ continue;
1887
+ }
1888
+ current = existing;
1889
+ }
1890
+ const lastSegment = segments[segments.length - 1];
1891
+ if (lastSegment === void 0) {
1892
+ return;
1893
+ }
1894
+ current[lastSegment] = value;
1895
+ };
1896
+ var normalizeIndexed = (value) => {
1897
+ if (Array.isArray(value)) {
1898
+ return value.map((item) => normalizeIndexed(item));
1899
+ }
1900
+ if (!isRecord(value)) {
1901
+ return value;
1902
+ }
1903
+ const normalizedEntries = {};
1904
+ for (const [key, entryValue] of Object.entries(value)) {
1905
+ normalizedEntries[key] = normalizeIndexed(entryValue);
1906
+ }
1907
+ const keys = Object.keys(normalizedEntries);
1908
+ if (keys.length > 0 && keys.every((key) => NUMERIC_TOKEN_PATTERN.test(key))) {
1909
+ const parsedIndices = keys.map((key) => Number.parseInt(key, 10)).filter((index) => Number.isFinite(index) && index >= 0);
1910
+ if (parsedIndices.length === 0) {
1911
+ return normalizedEntries;
1912
+ }
1913
+ const maxIndex = Math.max(...parsedIndices);
1914
+ const result = new Array(maxIndex + 1).fill(void 0);
1915
+ for (const [key, entryValue] of Object.entries(normalizedEntries)) {
1916
+ const index = Number.parseInt(key, 10);
1917
+ if (!Number.isFinite(index) || index < 0) {
1918
+ continue;
1919
+ }
1920
+ result[index] = entryValue;
1921
+ }
1922
+ return result.map(
1923
+ (item) => item === void 0 ? null : item
1924
+ );
1925
+ }
1926
+ return normalizedEntries;
1927
+ };
1928
+ var resolveFieldValue = (path, displayBuffers, valueBuffers) => {
1929
+ const values = valueBuffers.get(path) ?? [];
1930
+ if (values.length === 0) {
1931
+ return displayBuffers.get(path) ?? "";
1932
+ }
1933
+ const last = values[values.length - 1];
1934
+ if (typeof last === "number" || typeof last === "boolean") {
1935
+ return last;
1936
+ }
1937
+ if (last === null) {
1938
+ return null;
1939
+ }
1940
+ const joined = displayBuffers.get(path) ?? values.map((value) => toDisplayString(value)).join("");
1941
+ return coercePrimitive(joined);
1942
+ };
1943
+ var StreamAssembler = class {
1944
+ displayBuffers = /* @__PURE__ */ new Map();
1945
+ valueBuffers = /* @__PURE__ */ new Map();
1946
+ schema;
1947
+ constructor(options = {}) {
1948
+ this.schema = options.schema;
1949
+ }
1950
+ feed(chunk) {
1951
+ const { delta } = chunk;
1952
+ if (delta === null || delta === void 0) {
1953
+ return null;
1954
+ }
1955
+ const path = chunk.jsonPath === null || chunk.jsonPath === void 0 ? STREAM_ROOT_PATH : chunk.jsonPath;
1956
+ const existingDisplay = this.displayBuffers.get(path) ?? "";
1957
+ const nextDisplay = `${existingDisplay}${toDisplayString(delta)}`;
1958
+ this.displayBuffers.set(path, nextDisplay);
1959
+ const existingValues = this.valueBuffers.get(path) ?? [];
1960
+ existingValues.push(delta);
1961
+ this.valueBuffers.set(path, existingValues);
1962
+ return {
1963
+ path,
1964
+ accumulated: nextDisplay,
1965
+ snapshot: this.snapshot()
1966
+ };
1967
+ }
1968
+ snapshot() {
1969
+ return Object.fromEntries(this.displayBuffers.entries());
1970
+ }
1971
+ hasStructuredFields() {
1972
+ const keys = Array.from(this.displayBuffers.keys());
1973
+ return keys.some((key) => key !== STREAM_ROOT_PATH);
1974
+ }
1975
+ finalize() {
1976
+ if (this.displayBuffers.size === 0) {
1977
+ return { type: "empty" };
1978
+ }
1979
+ if (!this.hasStructuredFields()) {
1980
+ const root = this.displayBuffers.get(STREAM_ROOT_PATH) ?? "";
1981
+ return { type: "root", rootText: root };
1982
+ }
1983
+ const structured = this.reconstructStructured();
1984
+ let coerced = structured;
1985
+ if (this.schema !== void 0) {
1986
+ try {
1987
+ coerced = this.schema.parse(structured);
1988
+ } catch {
1989
+ coerced = structured;
1990
+ }
1991
+ }
1992
+ return { type: "structured", structured: coerced };
1993
+ }
1994
+ getFieldBuffers() {
1995
+ return new Map(this.displayBuffers);
1996
+ }
1997
+ reconstructStructured() {
1998
+ const result = {};
1999
+ for (const path of this.valueBuffers.keys()) {
2000
+ if (path === STREAM_ROOT_PATH) {
2001
+ continue;
2002
+ }
2003
+ const value = resolveFieldValue(
2004
+ path,
2005
+ this.displayBuffers,
2006
+ this.valueBuffers
2007
+ );
2008
+ setNestedValue(result, path, value);
2009
+ }
2010
+ return normalizeIndexed(result);
2011
+ }
2012
+ };
2013
+ var createStreamAssembler = (options) => new StreamAssembler(options);
2014
+
1600
2015
  // src/core/agent.ts
1601
2016
  var isToolSuccessResult = (value) => {
1602
2017
  if (typeof value !== "object" || value === null) {
@@ -1609,6 +2024,31 @@ var Agent = class extends BaseAgent {
1609
2024
  opperClient;
1610
2025
  logger;
1611
2026
  verbose;
2027
+ /**
2028
+ * Creates a new Agent instance
2029
+ *
2030
+ * @param config - Agent configuration object
2031
+ * @param config.name - Unique name identifying this agent (required)
2032
+ * @param config.description - Human-readable description of the agent's purpose
2033
+ * @param config.instructions - System instructions guiding agent behavior
2034
+ * @param config.tools - Array of tools or tool providers available to the agent
2035
+ * @param config.maxIterations - Maximum iterations before terminating (default: 25)
2036
+ * @param config.model - Model identifier(s) as string or array for fallback (default: "gcp/gemini-flash-latest")
2037
+ * @param config.inputSchema - Zod schema for input validation
2038
+ * @param config.outputSchema - Zod schema for output validation
2039
+ * @param config.enableStreaming - Enable streaming for LLM calls (default: false)
2040
+ * @param config.enableMemory - Enable memory subsystem (default: false)
2041
+ * @param config.memory - Custom memory implementation (defaults to InMemoryStore if enableMemory is true)
2042
+ * @param config.metadata - Additional metadata for the agent
2043
+ * @param config.opperConfig - Opper API configuration (apiKey, baseUrl)
2044
+ * @param config.opperClient - Custom Opper client instance (for testing or custom configuration)
2045
+ * @param config.logger - Logger instance for debugging
2046
+ * @param config.verbose - Enable verbose logging (default: false)
2047
+ * @param config.onStreamStart - Handler invoked when streaming starts
2048
+ * @param config.onStreamChunk - Handler invoked for each streaming chunk
2049
+ * @param config.onStreamEnd - Handler invoked when streaming ends
2050
+ * @param config.onStreamError - Handler invoked on streaming errors
2051
+ */
1612
2052
  constructor(config) {
1613
2053
  super(config);
1614
2054
  this.logger = config.logger ?? getDefaultLogger();
@@ -1644,6 +2084,7 @@ var Agent = class extends BaseAgent {
1644
2084
  maxIterations: this.maxIterations,
1645
2085
  tools: Array.from(this.tools.keys())
1646
2086
  });
2087
+ const executionStartTime = /* @__PURE__ */ new Date();
1647
2088
  const parentSpan = await this.opperClient.createSpan({
1648
2089
  name: `${this.name}_execution`,
1649
2090
  input: this.serializeInput(input),
@@ -1663,6 +2104,47 @@ var Agent = class extends BaseAgent {
1663
2104
  input,
1664
2105
  context
1665
2106
  );
2107
+ if (decision.isComplete && decision.finalResult !== void 0) {
2108
+ this.log("Task completed with final result in single call", {
2109
+ iteration: currentIteration
2110
+ });
2111
+ let finalResult;
2112
+ if (this.outputSchema) {
2113
+ const parseResult = this.outputSchema.safeParse(
2114
+ decision.finalResult
2115
+ );
2116
+ if (parseResult.success) {
2117
+ finalResult = parseResult.data;
2118
+ } else {
2119
+ this.logger.warn(
2120
+ "Final result validation against output schema failed, falling back to generate_final_result",
2121
+ { error: parseResult.error.message }
2122
+ );
2123
+ break;
2124
+ }
2125
+ } else {
2126
+ const rawResult = decision.finalResult;
2127
+ if (typeof rawResult === "string") {
2128
+ finalResult = rawResult;
2129
+ } else if (rawResult === null || rawResult === void 0) {
2130
+ finalResult = "";
2131
+ } else if (typeof rawResult === "object") {
2132
+ finalResult = JSON.stringify(rawResult);
2133
+ } else {
2134
+ finalResult = String(rawResult);
2135
+ }
2136
+ }
2137
+ const executionEndTime2 = /* @__PURE__ */ new Date();
2138
+ await this.opperClient.updateSpan(parentSpan.id, finalResult, {
2139
+ startTime: executionStartTime,
2140
+ endTime: executionEndTime2,
2141
+ meta: {
2142
+ durationMs: executionEndTime2.getTime() - executionStartTime.getTime()
2143
+ }
2144
+ });
2145
+ await this.triggerHook(HookEvents.LoopEnd, { context });
2146
+ return finalResult;
2147
+ }
1666
2148
  const memoryResults = await this.handleMemoryActions(
1667
2149
  decision,
1668
2150
  context,
@@ -1672,7 +2154,7 @@ var Agent = class extends BaseAgent {
1672
2154
  const toolResults = await this.executeToolCalls(
1673
2155
  decision,
1674
2156
  context,
1675
- thinkSpanId
2157
+ context.parentSpanId ?? void 0
1676
2158
  );
1677
2159
  const combinedResults = [...memoryResults, ...toolResults];
1678
2160
  const newToolCalls = context.toolCalls.slice(toolCallStartIndex);
@@ -1707,11 +2189,24 @@ var Agent = class extends BaseAgent {
1707
2189
  );
1708
2190
  }
1709
2191
  const result = await this.generateFinalResult(input, context);
1710
- await this.opperClient.updateSpan(parentSpan.id, result);
2192
+ const executionEndTime = /* @__PURE__ */ new Date();
2193
+ await this.opperClient.updateSpan(parentSpan.id, result, {
2194
+ startTime: executionStartTime,
2195
+ endTime: executionEndTime,
2196
+ meta: {
2197
+ durationMs: executionEndTime.getTime() - executionStartTime.getTime()
2198
+ }
2199
+ });
1711
2200
  return result;
1712
2201
  } catch (error) {
2202
+ const executionEndTime = /* @__PURE__ */ new Date();
1713
2203
  await this.opperClient.updateSpan(parentSpan.id, void 0, {
1714
- error: error instanceof Error ? error.message : String(error)
2204
+ error: error instanceof Error ? error.message : String(error),
2205
+ startTime: executionStartTime,
2206
+ endTime: executionEndTime,
2207
+ meta: {
2208
+ durationMs: executionEndTime.getTime() - executionStartTime.getTime()
2209
+ }
1715
2210
  });
1716
2211
  throw error;
1717
2212
  }
@@ -1720,9 +2215,16 @@ var Agent = class extends BaseAgent {
1720
2215
  * Think step: Call LLM to decide next action
1721
2216
  */
1722
2217
  async think(input, context) {
1723
- const spanName = "think";
2218
+ const sanitizedName = this.name.toLowerCase().replace(/[\s-]/g, "_");
2219
+ const spanName = `think_${sanitizedName}`;
1724
2220
  this.log("Think step", { iteration: context.iteration });
1725
2221
  await this.triggerHook(HookEvents.LlmCall, { context, callType: "think" });
2222
+ const decisionSchema = createAgentDecisionWithOutputSchema(
2223
+ this.outputSchema
2224
+ );
2225
+ if (this.enableStreaming) {
2226
+ return this.thinkStreaming(input, context, decisionSchema, spanName);
2227
+ }
1726
2228
  try {
1727
2229
  const instructions = this.buildThinkInstructions();
1728
2230
  const thinkContext = await this.buildThinkContext(input, context);
@@ -1730,7 +2232,7 @@ var Agent = class extends BaseAgent {
1730
2232
  name: spanName,
1731
2233
  instructions,
1732
2234
  input: thinkContext,
1733
- outputSchema: AgentDecisionSchema,
2235
+ outputSchema: decisionSchema,
1734
2236
  model: this.model,
1735
2237
  ...context.parentSpanId && { parentSpanId: context.parentSpanId }
1736
2238
  });
@@ -1741,15 +2243,25 @@ var Agent = class extends BaseAgent {
1741
2243
  totalTokens: response.usage.totalTokens,
1742
2244
  cost: response.usage.cost
1743
2245
  });
1744
- const decision = AgentDecisionSchema.parse(response.jsonPayload);
2246
+ const decision = decisionSchema.parse(
2247
+ response.jsonPayload
2248
+ );
1745
2249
  await this.triggerHook(HookEvents.LlmResponse, {
1746
2250
  context,
1747
2251
  callType: "think",
1748
2252
  response
1749
2253
  });
2254
+ if (response.spanId) {
2255
+ await this.opperClient.updateSpan(response.spanId, void 0, {
2256
+ name: "think"
2257
+ });
2258
+ }
1750
2259
  await this.triggerHook(HookEvents.ThinkEnd, {
1751
2260
  context,
1752
- thought: { reasoning: decision.reasoning }
2261
+ thought: {
2262
+ reasoning: decision.reasoning,
2263
+ userMessage: decision.userMessage
2264
+ }
1753
2265
  });
1754
2266
  this.log("Think result", {
1755
2267
  reasoning: decision.reasoning,
@@ -1765,6 +2277,139 @@ var Agent = class extends BaseAgent {
1765
2277
  );
1766
2278
  }
1767
2279
  }
2280
+ async thinkStreaming(input, context, decisionSchema, spanName) {
2281
+ const instructions = this.buildThinkInstructions();
2282
+ const thinkContext = await this.buildThinkContext(input, context);
2283
+ const assembler = createStreamAssembler({
2284
+ schema: decisionSchema
2285
+ });
2286
+ let streamSpanId;
2287
+ await this.triggerHook(HookEvents.StreamStart, {
2288
+ context,
2289
+ callType: "think"
2290
+ });
2291
+ this.emitAgentEvent(HookEvents.StreamStart, {
2292
+ context,
2293
+ callType: "think"
2294
+ });
2295
+ try {
2296
+ const streamResponse = await this.opperClient.stream({
2297
+ name: spanName,
2298
+ instructions,
2299
+ input: thinkContext,
2300
+ outputSchema: decisionSchema,
2301
+ model: this.model,
2302
+ ...context.parentSpanId && { parentSpanId: context.parentSpanId }
2303
+ });
2304
+ for await (const event of streamResponse.result) {
2305
+ const data = event?.data;
2306
+ if (!data) {
2307
+ continue;
2308
+ }
2309
+ if (!streamSpanId && typeof data.spanId === "string" && data.spanId) {
2310
+ streamSpanId = data.spanId;
2311
+ }
2312
+ const feedResult = assembler.feed({
2313
+ delta: data.delta,
2314
+ jsonPath: data.jsonPath
2315
+ });
2316
+ if (!feedResult) {
2317
+ continue;
2318
+ }
2319
+ const chunkPayload = {
2320
+ context,
2321
+ callType: "think",
2322
+ chunkData: {
2323
+ delta: data.delta,
2324
+ jsonPath: data.jsonPath ?? null,
2325
+ chunkType: data.chunkType ?? null
2326
+ },
2327
+ accumulated: feedResult.accumulated,
2328
+ fieldBuffers: feedResult.snapshot
2329
+ };
2330
+ await this.triggerHook(HookEvents.StreamChunk, chunkPayload);
2331
+ this.emitAgentEvent(HookEvents.StreamChunk, chunkPayload);
2332
+ }
2333
+ const fieldBuffers = assembler.snapshot();
2334
+ const endPayload = {
2335
+ context,
2336
+ callType: "think",
2337
+ fieldBuffers
2338
+ };
2339
+ await this.triggerHook(HookEvents.StreamEnd, endPayload);
2340
+ this.emitAgentEvent(HookEvents.StreamEnd, endPayload);
2341
+ const finalize = assembler.finalize();
2342
+ let decision;
2343
+ if (finalize.type === "structured" && finalize.structured) {
2344
+ decision = decisionSchema.parse(
2345
+ finalize.structured
2346
+ );
2347
+ } else {
2348
+ decision = decisionSchema.parse({
2349
+ reasoning: finalize.type === "root" ? finalize.rootText ?? "" : ""
2350
+ });
2351
+ }
2352
+ const usageTracked = await this.trackStreamingUsageBySpan(
2353
+ context,
2354
+ streamSpanId
2355
+ );
2356
+ if (!usageTracked) {
2357
+ context.updateUsage({
2358
+ requests: 1,
2359
+ inputTokens: 0,
2360
+ outputTokens: 0,
2361
+ totalTokens: 0,
2362
+ cost: { generation: 0, platform: 0, total: 0 }
2363
+ });
2364
+ }
2365
+ await this.triggerHook(HookEvents.LlmResponse, {
2366
+ context,
2367
+ callType: "think",
2368
+ response: streamResponse,
2369
+ parsed: decision
2370
+ });
2371
+ if (streamSpanId) {
2372
+ await this.opperClient.updateSpan(streamSpanId, void 0, {
2373
+ name: "think"
2374
+ });
2375
+ }
2376
+ await this.triggerHook(HookEvents.ThinkEnd, {
2377
+ context,
2378
+ thought: {
2379
+ reasoning: decision.reasoning,
2380
+ userMessage: decision.userMessage
2381
+ }
2382
+ });
2383
+ this.log("Think result", {
2384
+ reasoning: decision.reasoning,
2385
+ toolCalls: decision.toolCalls.length,
2386
+ memoryReads: decision.memoryReads?.length ?? 0,
2387
+ memoryWrites: Object.keys(decision.memoryUpdates ?? {}).length
2388
+ });
2389
+ const resultPayload = {
2390
+ decision
2391
+ };
2392
+ if (streamSpanId) {
2393
+ resultPayload.spanId = streamSpanId;
2394
+ }
2395
+ return resultPayload;
2396
+ } catch (error) {
2397
+ await this.triggerHook(HookEvents.StreamError, {
2398
+ context,
2399
+ callType: "think",
2400
+ error
2401
+ });
2402
+ this.emitAgentEvent(HookEvents.StreamError, {
2403
+ context,
2404
+ callType: "think",
2405
+ error
2406
+ });
2407
+ this.logger.error("Think step failed", error);
2408
+ throw new Error(
2409
+ `Think step failed: ${error instanceof Error ? error.message : String(error)}`
2410
+ );
2411
+ }
2412
+ }
1768
2413
  /**
1769
2414
  * Build static instructions for the think step
1770
2415
  */
@@ -1775,12 +2420,22 @@ YOUR TASK:
1775
2420
  1. Analyze the current situation
1776
2421
  2. Decide if the goal is complete or more actions are needed
1777
2422
  3. If more actions needed: specify tools to call
1778
- 4. If goal complete: return empty tool_calls list
2423
+ 4. If goal complete:
2424
+ - Set isComplete=true
2425
+ - Provide the complete answer/output in finalResult
2426
+ - Leave toolCalls empty
1779
2427
 
1780
2428
  IMPORTANT:
1781
- - Return empty toolCalls array when task is COMPLETE
2429
+ - When task is COMPLETE, you MUST set isComplete=true AND provide finalResult
2430
+ - The finalResult should be a complete, well-structured answer based on all work done
1782
2431
  - Only use available tools
1783
- - Provide clear reasoning for each decision`;
2432
+ - Provide clear reasoning for each decision
2433
+ - If an outputSchema was specified, ensure finalResult matches that schema
2434
+
2435
+ USER MESSAGE:
2436
+ - Always provide a brief, user-friendly status in userMessage
2437
+ - This message is shown to users to indicate progress (e.g., "Searching for weather data...", "Calculating results...", "Done!")
2438
+ - Keep it concise and informative`;
1784
2439
  if (this.enableMemory) {
1785
2440
  instructions += `
1786
2441
 
@@ -1864,9 +2519,14 @@ The memory you write persists across all process() calls on this agent.`;
1864
2519
  this.log(`Action: ${toolCall.toolName}`, {
1865
2520
  parameters: toolCall.arguments
1866
2521
  });
2522
+ const startTime = /* @__PURE__ */ new Date();
2523
+ const tool2 = this.tools.get(toolCall.toolName);
2524
+ const isAgentTool = tool2?.metadata?.["isAgent"] === true;
2525
+ const spanType = isAgentTool ? "\u{1F916} agent" : "\u{1F527} tool";
1867
2526
  const toolSpan = await this.opperClient.createSpan({
1868
2527
  name: `tool_${toolCall.toolName}`,
1869
2528
  input: toolCall.arguments,
2529
+ type: spanType,
1870
2530
  ...parentSpanId ? { parentSpanId } : context.parentSpanId ? { parentSpanId: context.parentSpanId } : {}
1871
2531
  });
1872
2532
  try {
@@ -1876,11 +2536,20 @@ The memory you write persists across all process() calls on this agent.`;
1876
2536
  context,
1877
2537
  { spanId: toolSpan.id }
1878
2538
  );
2539
+ const endTime = /* @__PURE__ */ new Date();
2540
+ const durationMs = endTime.getTime() - startTime.getTime();
1879
2541
  if (result.success) {
1880
- await this.opperClient.updateSpan(toolSpan.id, result.output);
2542
+ await this.opperClient.updateSpan(toolSpan.id, result.output, {
2543
+ startTime,
2544
+ endTime,
2545
+ meta: { durationMs }
2546
+ });
1881
2547
  } else {
1882
2548
  await this.opperClient.updateSpan(toolSpan.id, void 0, {
1883
- error: result.error instanceof Error ? result.error.message : String(result.error)
2549
+ error: result.error instanceof Error ? result.error.message : String(result.error),
2550
+ startTime,
2551
+ endTime,
2552
+ meta: { durationMs }
1884
2553
  });
1885
2554
  }
1886
2555
  const summary = {
@@ -1895,12 +2564,18 @@ The memory you write persists across all process() calls on this agent.`;
1895
2564
  this.log(
1896
2565
  `Tool ${toolCall.toolName} ${result.success ? "succeeded" : "failed"}`,
1897
2566
  {
1898
- success: result.success
2567
+ success: result.success,
2568
+ durationMs
1899
2569
  }
1900
2570
  );
1901
2571
  } catch (error) {
2572
+ const endTime = /* @__PURE__ */ new Date();
2573
+ const durationMs = endTime.getTime() - startTime.getTime();
1902
2574
  await this.opperClient.updateSpan(toolSpan.id, void 0, {
1903
- error: error instanceof Error ? error.message : String(error)
2575
+ error: error instanceof Error ? error.message : String(error),
2576
+ startTime,
2577
+ endTime,
2578
+ meta: { durationMs }
1904
2579
  });
1905
2580
  const summary = {
1906
2581
  toolName: toolCall.toolName,
@@ -1909,7 +2584,8 @@ The memory you write persists across all process() calls on this agent.`;
1909
2584
  };
1910
2585
  results.push(ToolExecutionSummarySchema.parse(summary));
1911
2586
  this.logger.warn(`Tool ${toolCall.toolName} threw error`, {
1912
- error: error instanceof Error ? error.message : String(error)
2587
+ error: error instanceof Error ? error.message : String(error),
2588
+ durationMs
1913
2589
  });
1914
2590
  }
1915
2591
  }
@@ -1938,6 +2614,7 @@ The memory you write persists across all process() calls on this agent.`;
1938
2614
  const spanParentId = parentSpanId ?? context.parentSpanId ?? void 0;
1939
2615
  const summaries = [];
1940
2616
  if (hasReads) {
2617
+ const startTime = /* @__PURE__ */ new Date();
1941
2618
  try {
1942
2619
  const keySet = new Set(
1943
2620
  decision.memoryReads.filter(
@@ -1949,10 +2626,17 @@ The memory you write persists across all process() calls on this agent.`;
1949
2626
  const memoryReadSpan = await this.opperClient.createSpan({
1950
2627
  name: "memory_read",
1951
2628
  input: keys,
2629
+ type: "\u{1F9E0} memory",
1952
2630
  ...spanParentId && { parentSpanId: spanParentId }
1953
2631
  });
1954
2632
  const memoryData = await this.memory.read(keys);
1955
- await this.opperClient.updateSpan(memoryReadSpan.id, memoryData);
2633
+ const endTime = /* @__PURE__ */ new Date();
2634
+ const durationMs = endTime.getTime() - startTime.getTime();
2635
+ await this.opperClient.updateSpan(memoryReadSpan.id, memoryData, {
2636
+ startTime,
2637
+ endTime,
2638
+ meta: { durationMs }
2639
+ });
1956
2640
  context.setMetadata("current_memory", memoryData);
1957
2641
  this.log(`Loaded ${Object.keys(memoryData).length} memory entries`, {
1958
2642
  keys
@@ -1991,10 +2675,12 @@ The memory you write persists across all process() calls on this agent.`;
1991
2675
  }
1992
2676
  }
1993
2677
  if (hasWrites) {
2678
+ const startTime = /* @__PURE__ */ new Date();
1994
2679
  try {
1995
2680
  const memoryWriteSpan = await this.opperClient.createSpan({
1996
2681
  name: "memory_write",
1997
2682
  input: updateEntries.map(([key]) => key),
2683
+ type: "\u{1F9E0} memory",
1998
2684
  ...spanParentId && { parentSpanId: spanParentId }
1999
2685
  });
2000
2686
  for (const [key, update] of updateEntries) {
@@ -2011,9 +2697,16 @@ The memory you write persists across all process() calls on this agent.`;
2011
2697
  value: castUpdate.value
2012
2698
  });
2013
2699
  }
2700
+ const endTime = /* @__PURE__ */ new Date();
2701
+ const durationMs = endTime.getTime() - startTime.getTime();
2014
2702
  await this.opperClient.updateSpan(
2015
2703
  memoryWriteSpan.id,
2016
- `Successfully wrote ${updateEntries.length} keys`
2704
+ `Successfully wrote ${updateEntries.length} keys`,
2705
+ {
2706
+ startTime,
2707
+ endTime,
2708
+ meta: { durationMs }
2709
+ }
2017
2710
  );
2018
2711
  this.log(`Wrote ${updateEntries.length} memory entries`);
2019
2712
  summaries.push(
@@ -2078,9 +2771,18 @@ The memory you write persists across all process() calls on this agent.`;
2078
2771
  };
2079
2772
  const instructions = `Generate the final result based on the execution history.
2080
2773
  Follow any instructions provided for formatting and style.`;
2774
+ if (this.enableStreaming) {
2775
+ return this.generateFinalResultStreaming(
2776
+ context,
2777
+ finalContext,
2778
+ instructions
2779
+ );
2780
+ }
2081
2781
  try {
2782
+ const sanitizedName = this.name.toLowerCase().replace(/[\s-]/g, "_");
2783
+ const functionName = `generate_final_result_${sanitizedName}`;
2082
2784
  const callOptions = {
2083
- name: "generate_final_result",
2785
+ name: functionName,
2084
2786
  instructions,
2085
2787
  input: finalContext,
2086
2788
  model: this.model
@@ -2113,6 +2815,172 @@ Follow any instructions provided for formatting and style.`;
2113
2815
  );
2114
2816
  }
2115
2817
  }
2818
+ async generateFinalResultStreaming(context, finalContext, instructions) {
2819
+ const assembler = createStreamAssembler(
2820
+ this.outputSchema ? { schema: this.outputSchema } : void 0
2821
+ );
2822
+ let streamSpanId;
2823
+ await this.triggerHook(HookEvents.StreamStart, {
2824
+ context,
2825
+ callType: "final_result"
2826
+ });
2827
+ this.emitAgentEvent(HookEvents.StreamStart, {
2828
+ context,
2829
+ callType: "final_result"
2830
+ });
2831
+ try {
2832
+ const sanitizedName = this.name.toLowerCase().replace(/[\s-]/g, "_");
2833
+ const functionName = `generate_final_result_${sanitizedName}`;
2834
+ const streamResponse = await this.opperClient.stream({
2835
+ name: functionName,
2836
+ instructions,
2837
+ input: finalContext,
2838
+ model: this.model,
2839
+ ...context.parentSpanId && { parentSpanId: context.parentSpanId },
2840
+ ...this.outputSchema && { outputSchema: this.outputSchema }
2841
+ });
2842
+ for await (const event of streamResponse.result) {
2843
+ const data = event?.data;
2844
+ if (!data) {
2845
+ continue;
2846
+ }
2847
+ if (!streamSpanId && typeof data.spanId === "string" && data.spanId) {
2848
+ streamSpanId = data.spanId;
2849
+ }
2850
+ const feedResult = assembler.feed({
2851
+ delta: data.delta,
2852
+ jsonPath: data.jsonPath
2853
+ });
2854
+ if (!feedResult) {
2855
+ continue;
2856
+ }
2857
+ const chunkPayload = {
2858
+ context,
2859
+ callType: "final_result",
2860
+ chunkData: {
2861
+ delta: data.delta,
2862
+ jsonPath: data.jsonPath ?? null,
2863
+ chunkType: data.chunkType ?? null
2864
+ },
2865
+ accumulated: feedResult.accumulated,
2866
+ fieldBuffers: feedResult.snapshot
2867
+ };
2868
+ await this.triggerHook(HookEvents.StreamChunk, chunkPayload);
2869
+ this.emitAgentEvent(HookEvents.StreamChunk, chunkPayload);
2870
+ }
2871
+ const fieldBuffers = assembler.snapshot();
2872
+ const endPayload = {
2873
+ context,
2874
+ callType: "final_result",
2875
+ fieldBuffers
2876
+ };
2877
+ await this.triggerHook(HookEvents.StreamEnd, endPayload);
2878
+ this.emitAgentEvent(HookEvents.StreamEnd, endPayload);
2879
+ const finalize = assembler.finalize();
2880
+ let result;
2881
+ if (this.outputSchema) {
2882
+ if (finalize.type !== "structured" || finalize.structured === void 0) {
2883
+ throw new Error(
2884
+ "Streaming response did not provide structured data for the configured output schema."
2885
+ );
2886
+ }
2887
+ result = this.outputSchema.parse(
2888
+ finalize.structured
2889
+ );
2890
+ } else if (finalize.type === "root") {
2891
+ result = finalize.rootText ?? "";
2892
+ } else if (finalize.type === "structured" && finalize.structured) {
2893
+ result = JSON.stringify(finalize.structured);
2894
+ } else {
2895
+ result = "";
2896
+ }
2897
+ const usageTracked = await this.trackStreamingUsageBySpan(
2898
+ context,
2899
+ streamSpanId
2900
+ );
2901
+ if (!usageTracked) {
2902
+ context.updateUsage({
2903
+ requests: 1,
2904
+ inputTokens: 0,
2905
+ outputTokens: 0,
2906
+ totalTokens: 0,
2907
+ cost: { generation: 0, platform: 0, total: 0 }
2908
+ });
2909
+ }
2910
+ await this.triggerHook(HookEvents.LlmResponse, {
2911
+ context,
2912
+ callType: "final_result",
2913
+ response: streamResponse,
2914
+ parsed: result
2915
+ });
2916
+ this.log(
2917
+ this.outputSchema ? "Final result generated (streaming, schema-validated)" : "Final result generated (streaming)"
2918
+ );
2919
+ return result;
2920
+ } catch (error) {
2921
+ await this.triggerHook(HookEvents.StreamError, {
2922
+ context,
2923
+ callType: "final_result",
2924
+ error
2925
+ });
2926
+ this.emitAgentEvent(HookEvents.StreamError, {
2927
+ context,
2928
+ callType: "final_result",
2929
+ error
2930
+ });
2931
+ this.logger.error("Failed to generate final result", error);
2932
+ throw new Error(
2933
+ `Failed to generate final result: ${error instanceof Error ? error.message : String(error)}`
2934
+ );
2935
+ }
2936
+ }
2937
+ async trackStreamingUsageBySpan(context, spanId) {
2938
+ if (!spanId) {
2939
+ return false;
2940
+ }
2941
+ try {
2942
+ const span = await this.opperClient.getClient().spans.get(spanId);
2943
+ const traceId = span?.traceId ?? span?.trace_id;
2944
+ if (!traceId) {
2945
+ return false;
2946
+ }
2947
+ const trace = await this.opperClient.getClient().traces.get(traceId);
2948
+ const spans = trace?.spans;
2949
+ if (!Array.isArray(spans)) {
2950
+ return false;
2951
+ }
2952
+ for (const entry of spans) {
2953
+ const entryId = entry?.id ?? entry["id"];
2954
+ if (entryId !== spanId) {
2955
+ continue;
2956
+ }
2957
+ const data = entry?.data;
2958
+ if (!data) {
2959
+ continue;
2960
+ }
2961
+ const record = data;
2962
+ const primaryTotal = record["totalTokens"];
2963
+ const fallbackTotal = record["total_tokens"];
2964
+ const totalTokensRaw = typeof primaryTotal === "number" && Number.isFinite(primaryTotal) ? primaryTotal : typeof fallbackTotal === "number" && Number.isFinite(fallbackTotal) ? fallbackTotal : void 0;
2965
+ if (totalTokensRaw !== void 0) {
2966
+ context.updateUsage({
2967
+ requests: 1,
2968
+ inputTokens: 0,
2969
+ outputTokens: 0,
2970
+ totalTokens: totalTokensRaw,
2971
+ cost: { generation: 0, platform: 0, total: 0 }
2972
+ });
2973
+ return true;
2974
+ }
2975
+ }
2976
+ } catch (error) {
2977
+ this.logger.warn("Could not fetch streaming usage", {
2978
+ spanId,
2979
+ error: error instanceof Error ? error.message : String(error)
2980
+ });
2981
+ }
2982
+ return false;
2983
+ }
2116
2984
  /**
2117
2985
  * Log helper
2118
2986
  */
@@ -2329,7 +3197,7 @@ var createMCPServerConfig = MCPconfig;
2329
3197
 
2330
3198
  // src/mcp/provider.ts
2331
3199
  init_tool();
2332
- var isRecord = (value) => typeof value === "object" && value !== null;
3200
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
2333
3201
  var MCPToolProvider = class {
2334
3202
  configs;
2335
3203
  namePrefix;
@@ -2420,7 +3288,7 @@ var MCPToolProvider = class {
2420
3288
  },
2421
3289
  execute: async (input, context) => {
2422
3290
  const startedAt = Date.now();
2423
- const args = isRecord(input) ? input : input === void 0 ? {} : { value: input };
3291
+ const args = isRecord2(input) ? input : input === void 0 ? {} : { value: input };
2424
3292
  try {
2425
3293
  const result = await client.callTool(mcpTool.name, args);
2426
3294
  return exports.ToolResultFactory.success(toolName, result, {
@@ -2880,6 +3748,8 @@ var ToolRunner = class {
2880
3748
 
2881
3749
  exports.Agent = Agent;
2882
3750
  exports.AgentDecisionSchema = AgentDecisionSchema;
3751
+ exports.AgentEventEmitter = AgentEventEmitter;
3752
+ exports.AgentEvents = AgentEvents;
2883
3753
  exports.BaseAgent = BaseAgent;
2884
3754
  exports.ConsoleLogger = ConsoleLogger;
2885
3755
  exports.DEFAULT_MODEL = DEFAULT_MODEL;
@@ -2896,17 +3766,21 @@ exports.MemoryEntryMetadataSchema = MemoryEntryMetadataSchema;
2896
3766
  exports.MemoryEntrySchema = MemoryEntrySchema;
2897
3767
  exports.MemoryUpdateSchema = MemoryUpdateSchema;
2898
3768
  exports.OpperClient = OpperClient;
3769
+ exports.STREAM_ROOT_PATH = STREAM_ROOT_PATH;
2899
3770
  exports.SchemaValidationError = SchemaValidationError;
2900
3771
  exports.SilentLogger = SilentLogger;
3772
+ exports.StreamAssembler = StreamAssembler;
2901
3773
  exports.ThoughtSchema = ThoughtSchema;
2902
3774
  exports.ToolCallSchema = ToolCallSchema;
2903
3775
  exports.ToolExecutionSummarySchema = ToolExecutionSummarySchema;
2904
3776
  exports.ToolRunner = ToolRunner;
3777
+ exports.createAgentDecisionWithOutputSchema = createAgentDecisionWithOutputSchema;
2905
3778
  exports.createFunctionTool = createFunctionTool;
2906
3779
  exports.createHookManager = createHookManager;
2907
3780
  exports.createInMemoryStore = createInMemoryStore;
2908
3781
  exports.createMCPServerConfig = createMCPServerConfig;
2909
3782
  exports.createOpperClient = createOpperClient;
3783
+ exports.createStreamAssembler = createStreamAssembler;
2910
3784
  exports.extractTools = extractTools;
2911
3785
  exports.generateAgentFlowDiagram = generateAgentFlowDiagram;
2912
3786
  exports.getDefaultLogger = getDefaultLogger;