@flue/sdk 0.4.0 → 0.5.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.
@@ -1,10 +1,33 @@
1
1
  import { a as buildSkillByPathPrompt, c as createTools, f as resolveSkillFilePath, i as buildSkillByNamePrompt, l as formatBashResult, n as buildPromptText, o as createResultTools, p as skillsDirIn, r as buildResultFollowUpPrompt, s as BUILTIN_TOOL_NAMES, t as ResultUnavailableError } from "./result-K1IRhWKM.mjs";
2
- import { i as getRegisteredApiKey, r as getProviderConfiguration } from "./providers-DeFRIwp0.mjs";
2
+ import { i as getRegisteredApiKey, l as generateOperationId, r as getProviderConfiguration } from "./providers-BjEEoKLy.mjs";
3
3
  import { n as createCallHandle, t as abortErrorFor } from "./abort-Bg3qsAkU.mjs";
4
4
  import { createFlueFs } from "./sandbox.mjs";
5
5
  import { completeSimple, isContextOverflow } from "@mariozechner/pi-ai";
6
6
  import { Agent } from "@mariozechner/pi-agent-core";
7
7
 
8
+ //#region src/roles.ts
9
+ function assertRoleExists(roles, roleName) {
10
+ if (!roleName) return;
11
+ if (roles[roleName]) return;
12
+ const available = Object.keys(roles);
13
+ const list = available.length > 0 ? available.join(", ") : "(none defined)";
14
+ throw new Error(`[flue] Role "${roleName}" not registered. Available roles: ${list}. Define roles as markdown files in \`roles/\` (or \`.flue/roles/\` if your root uses the .flue/ source layout).`);
15
+ }
16
+ function resolveEffectiveRole(options) {
17
+ const role = options.callRole ?? options.sessionRole ?? options.agentRole;
18
+ assertRoleExists(options.roles, role);
19
+ return role;
20
+ }
21
+ function resolveRoleModel(roles, roleName) {
22
+ assertRoleExists(roles, roleName);
23
+ return roleName ? roles[roleName]?.model : void 0;
24
+ }
25
+ function resolveRoleThinkingLevel(roles, roleName) {
26
+ assertRoleExists(roles, roleName);
27
+ return roleName ? roles[roleName]?.thinkingLevel : void 0;
28
+ }
29
+
30
+ //#endregion
8
31
  //#region src/usage.ts
9
32
  /** All-zero `PromptUsage`. Identity element for `addUsage`. */
10
33
  function emptyUsage() {
@@ -473,29 +496,6 @@ async function compact(preparation, model, apiKey, signal) {
473
496
  };
474
497
  }
475
498
 
476
- //#endregion
477
- //#region src/roles.ts
478
- function assertRoleExists(roles, roleName) {
479
- if (!roleName) return;
480
- if (roles[roleName]) return;
481
- const available = Object.keys(roles);
482
- const list = available.length > 0 ? available.join(", ") : "(none defined)";
483
- throw new Error(`[flue] Role "${roleName}" not registered. Available roles: ${list}. Define roles as markdown files in \`roles/\` (or \`.flue/roles/\` if your root uses the .flue/ source layout).`);
484
- }
485
- function resolveEffectiveRole(options) {
486
- const role = options.callRole ?? options.sessionRole ?? options.agentRole;
487
- assertRoleExists(options.roles, role);
488
- return role;
489
- }
490
- function resolveRoleModel(roles, roleName) {
491
- assertRoleExists(roles, roleName);
492
- return roleName ? roles[roleName]?.model : void 0;
493
- }
494
- function resolveRoleThinkingLevel(roles, roleName) {
495
- assertRoleExists(roles, roleName);
496
- return roleName ? roles[roleName]?.thinkingLevel : void 0;
497
- }
498
-
499
499
  //#endregion
500
500
  //#region src/session.ts
501
501
  const MAX_TASK_DEPTH = 4;
@@ -652,7 +652,7 @@ var SessionHistory = class SessionHistory {
652
652
  }
653
653
  toData(metadata, createdAt, updatedAt) {
654
654
  return {
655
- version: 2,
655
+ version: 3,
656
656
  entries: [...this.entries],
657
657
  leafId: this.leafId,
658
658
  metadata,
@@ -703,7 +703,7 @@ function generateEntryId(byId) {
703
703
  return crypto.randomUUID();
704
704
  }
705
705
  var Session = class {
706
- id;
706
+ name;
707
707
  fs;
708
708
  metadata;
709
709
  get role() {
@@ -723,13 +723,16 @@ var Session = class {
723
723
  agentTools;
724
724
  deleted = false;
725
725
  activeOperation;
726
+ activeOperationId;
727
+ toolStartTimes = /* @__PURE__ */ new Map();
728
+ turnStartTime;
726
729
  activeTasks = /* @__PURE__ */ new Set();
727
730
  sessionRole;
728
731
  taskDepth;
729
732
  createTaskSession;
730
733
  onDelete;
731
734
  constructor(options) {
732
- this.id = options.id;
735
+ this.name = options.name;
733
736
  this.storageKey = options.storageKey;
734
737
  this.config = options.config;
735
738
  this.env = options.env;
@@ -769,8 +772,9 @@ var Session = class {
769
772
  this.eventCallback = options.onAgentEvent;
770
773
  this.harness.subscribe(async (event) => {
771
774
  switch (event.type) {
772
- case "agent_start":
773
- this.emit({ type: "agent_start" });
775
+ case "agent_start": break;
776
+ case "turn_start":
777
+ this.turnStartTime = Date.now();
774
778
  break;
775
779
  case "message_update": {
776
780
  const aEvent = event.assistantMessageEvent;
@@ -790,6 +794,7 @@ var Session = class {
790
794
  break;
791
795
  }
792
796
  case "tool_execution_start":
797
+ this.toolStartTimes.set(event.toolCallId, Date.now());
793
798
  this.emit({
794
799
  type: "tool_start",
795
800
  toolName: event.toolName,
@@ -799,16 +804,30 @@ var Session = class {
799
804
  break;
800
805
  case "tool_execution_end":
801
806
  this.emit({
802
- type: "tool_end",
807
+ type: "tool_call",
803
808
  toolName: event.toolName,
804
809
  toolCallId: event.toolCallId,
805
810
  isError: event.isError,
806
- result: event.result
811
+ result: event.result,
812
+ durationMs: durationSince(this.toolStartTimes.get(event.toolCallId))
807
813
  });
814
+ this.toolStartTimes.delete(event.toolCallId);
808
815
  break;
809
- case "turn_end":
810
- this.emit({ type: "turn_end" });
816
+ case "turn_end": {
817
+ const message = event.message;
818
+ const assistant = message.role === "assistant" ? message : void 0;
819
+ this.emit({
820
+ type: "turn",
821
+ durationMs: durationSince(this.turnStartTime),
822
+ model: assistant?.model,
823
+ usage: fromProviderUsage(assistant?.usage),
824
+ stopReason: assistant?.stopReason,
825
+ isError: assistant?.stopReason === "error" || assistant?.stopReason === "aborted",
826
+ error: assistant?.errorMessage
827
+ });
828
+ this.turnStartTime = void 0;
811
829
  break;
830
+ }
812
831
  case "agent_end": break;
813
832
  }
814
833
  });
@@ -870,9 +889,10 @@ var Session = class {
870
889
  shell(command, options) {
871
890
  return createCallHandle(options?.signal, (signal) => this.runOperation("shell", signal, async () => {
872
891
  const toolCallId = crypto.randomUUID();
892
+ const toolStartMs = Date.now();
873
893
  const args = { command };
874
894
  if (options?.cwd !== void 0) args.cwd = options.cwd;
875
- if (options?.env !== void 0) args.env = options.env;
895
+ if (options?.env !== void 0) args.env = redactEnvValues(options.env);
876
896
  this.emit({
877
897
  type: "tool_start",
878
898
  toolName: "bash",
@@ -893,11 +913,12 @@ var Session = class {
893
913
  const toolResult = formatBashResult(shellResult, command);
894
914
  await this.appendShellTriple(toolCallId, args, toolResult, false);
895
915
  this.emit({
896
- type: "tool_end",
916
+ type: "tool_call",
897
917
  toolName: "bash",
898
918
  toolCallId,
899
919
  isError: false,
900
- result: toolResult
920
+ result: toolResult,
921
+ durationMs: durationSince(toolStartMs)
901
922
  });
902
923
  return shellResult;
903
924
  } catch (error) {
@@ -913,11 +934,12 @@ var Session = class {
913
934
  };
914
935
  await this.appendShellTriple(toolCallId, args, errResult, true);
915
936
  this.emit({
916
- type: "tool_end",
937
+ type: "tool_call",
917
938
  toolName: "bash",
918
939
  toolCallId,
919
940
  isError: true,
920
- result: errResult
941
+ result: errResult,
942
+ durationMs: durationSince(toolStartMs)
921
943
  });
922
944
  throw error;
923
945
  }
@@ -1070,7 +1092,7 @@ var Session = class {
1070
1092
  }],
1071
1093
  details: {
1072
1094
  taskId: result.taskId,
1073
- sessionId: result.sessionId,
1095
+ session: result.session,
1074
1096
  messageId: result.messageId,
1075
1097
  role: result.role,
1076
1098
  cwd: result.cwd
@@ -1078,6 +1100,46 @@ var Session = class {
1078
1100
  };
1079
1101
  }
1080
1102
  async runTask(text, options, signal) {
1103
+ return this.runExclusive("task", async () => {
1104
+ if (signal?.aborted) throw abortErrorFor(signal);
1105
+ this.activeOperationId = generateOperationId();
1106
+ const operationId = this.activeOperationId;
1107
+ const startedAt = Date.now();
1108
+ this.emit({
1109
+ type: "operation_start",
1110
+ operationId,
1111
+ operationKind: "task"
1112
+ });
1113
+ try {
1114
+ const result = await this.runTaskExclusive(text, options, signal);
1115
+ this.emit({
1116
+ type: "operation",
1117
+ operationId,
1118
+ operationKind: "task",
1119
+ durationMs: durationSince(startedAt),
1120
+ isError: false,
1121
+ result: result.output,
1122
+ usage: usageFromResult(result.output)
1123
+ });
1124
+ return result;
1125
+ } catch (error) {
1126
+ const surfaced = signal?.aborted ? abortErrorFor(signal) : error;
1127
+ this.emit({
1128
+ type: "operation",
1129
+ operationId,
1130
+ operationKind: "task",
1131
+ durationMs: durationSince(startedAt),
1132
+ isError: true,
1133
+ error: serializeError(surfaced)
1134
+ });
1135
+ throw surfaced;
1136
+ } finally {
1137
+ this.emit({ type: "idle" });
1138
+ this.activeOperationId = void 0;
1139
+ }
1140
+ });
1141
+ }
1142
+ async runTaskExclusive(text, options, signal) {
1081
1143
  this.assertActive();
1082
1144
  if (!this.createTaskSession) throw new Error("[flue] This session cannot create task sessions.");
1083
1145
  if (this.taskDepth >= MAX_TASK_DEPTH) throw new Error(`[flue] Maximum task depth (${MAX_TASK_DEPTH}) exceeded.`);
@@ -1092,19 +1154,20 @@ var Session = class {
1092
1154
  prompt: text,
1093
1155
  role: requestedRole,
1094
1156
  cwd: options?.cwd,
1095
- parentSessionId: this.id
1157
+ parentSession: this.name
1096
1158
  });
1159
+ const taskStartMs = Date.now();
1097
1160
  try {
1098
1161
  const role = this.resolveEffectiveRole(options?.role);
1099
1162
  child = await this.createTaskSession({
1100
- parentSessionId: this.id,
1163
+ parentSession: this.name,
1101
1164
  taskId,
1102
1165
  parentEnv: this.env,
1103
1166
  cwd: options?.cwd,
1104
1167
  role,
1105
1168
  depth: this.taskDepth + 1
1106
1169
  });
1107
- await this.recordTaskSession(child.id, child.storageKey, taskId);
1170
+ await this.recordTaskSession(child.name, child.storageKey, taskId);
1108
1171
  this.activeTasks.add(child);
1109
1172
  if (signal) {
1110
1173
  abortListener = () => child?.abort();
@@ -1126,26 +1189,28 @@ var Session = class {
1126
1189
  output,
1127
1190
  text: typeof output?.text === "string" ? output.text : child.getAssistantText(),
1128
1191
  taskId,
1129
- sessionId: child.id,
1192
+ session: child.name,
1130
1193
  messageId: child.getLatestAssistantMessageId(),
1131
1194
  role,
1132
1195
  cwd: options?.cwd
1133
1196
  };
1134
1197
  this.emit({
1135
- type: "task_end",
1198
+ type: "task",
1136
1199
  taskId,
1137
1200
  isError: false,
1138
1201
  result: taskResult.text,
1139
- parentSessionId: this.id
1202
+ durationMs: durationSince(taskStartMs),
1203
+ parentSession: this.name
1140
1204
  });
1141
1205
  return taskResult;
1142
1206
  } catch (error) {
1143
1207
  this.emit({
1144
- type: "task_end",
1208
+ type: "task",
1145
1209
  taskId,
1146
1210
  isError: true,
1147
1211
  result: getErrorMessage(error),
1148
- parentSessionId: this.id
1212
+ durationMs: durationSince(taskStartMs),
1213
+ parentSession: this.name
1149
1214
  });
1150
1215
  throw error;
1151
1216
  } finally {
@@ -1159,6 +1224,14 @@ var Session = class {
1159
1224
  async runOperation(operation, signal, fn) {
1160
1225
  return this.runExclusive(operation, async () => {
1161
1226
  if (signal?.aborted) throw abortErrorFor(signal);
1227
+ this.activeOperationId = generateOperationId();
1228
+ const operationId = this.activeOperationId;
1229
+ const startedAt = Date.now();
1230
+ this.emit({
1231
+ type: "operation_start",
1232
+ operationId,
1233
+ operationKind: operation
1234
+ });
1162
1235
  const onAbort = () => {
1163
1236
  this.harness.abort();
1164
1237
  this.compactionAbortController?.abort(signal?.reason);
@@ -1166,18 +1239,38 @@ var Session = class {
1166
1239
  };
1167
1240
  signal?.addEventListener("abort", onAbort, { once: true });
1168
1241
  try {
1169
- return await fn();
1242
+ const result = await fn();
1243
+ this.emit({
1244
+ type: "operation",
1245
+ operationId,
1246
+ operationKind: operation,
1247
+ durationMs: durationSince(startedAt),
1248
+ isError: false,
1249
+ result,
1250
+ usage: usageFromResult(result)
1251
+ });
1252
+ return result;
1170
1253
  } catch (error) {
1171
- throw signal?.aborted ? abortErrorFor(signal) : error;
1254
+ const surfaced = signal?.aborted ? abortErrorFor(signal) : error;
1255
+ this.emit({
1256
+ type: "operation",
1257
+ operationId,
1258
+ operationKind: operation,
1259
+ durationMs: durationSince(startedAt),
1260
+ isError: true,
1261
+ error: serializeError(surfaced)
1262
+ });
1263
+ throw surfaced;
1172
1264
  } finally {
1173
1265
  signal?.removeEventListener("abort", onAbort);
1174
1266
  this.emit({ type: "idle" });
1267
+ this.activeOperationId = void 0;
1175
1268
  }
1176
1269
  });
1177
1270
  }
1178
1271
  async runExclusive(operation, fn) {
1179
1272
  this.assertActive();
1180
- if (this.activeOperation) throw new Error(`[flue] Session "${this.id}" is already running ${this.activeOperation}. Start another session for parallel conversation branches.`);
1273
+ if (this.activeOperation) throw new Error(`[flue] Session "${this.name}" is already running ${this.activeOperation}. Start another session for parallel conversation branches.`);
1181
1274
  this.activeOperation = operation;
1182
1275
  try {
1183
1276
  return await fn();
@@ -1186,27 +1279,18 @@ var Session = class {
1186
1279
  }
1187
1280
  }
1188
1281
  emit(event) {
1189
- this.eventCallback?.({
1282
+ const decorated = {
1190
1283
  ...event,
1191
- sessionId: this.id
1192
- });
1284
+ session: event.session ?? this.name
1285
+ };
1286
+ const operationId = event.operationId ?? this.activeOperationId;
1287
+ if (operationId !== void 0) decorated.operationId = operationId;
1288
+ this.eventCallback?.(decorated);
1193
1289
  }
1194
1290
  assertActive() {
1195
- if (this.deleted) throw new Error(`[flue] Session "${this.id}" has been deleted.`);
1291
+ if (this.deleted) throw new Error(`[flue] Session "${this.name}" has been deleted.`);
1196
1292
  }
1197
- /**
1198
- * Append the three-message conversational triple that represents a
1199
- * `session.shell()` call in the message history:
1200
- *
1201
- * 1. user — out-of-band request to run the command
1202
- * 2. assistant — synthetic turn whose content is a single bash
1203
- * tool_use block (matching the shape pi-ai's
1204
- * providers produce when the LLM itself calls bash)
1205
- * 3. toolResult — the bash output, keyed to the same toolCallId
1206
- *
1207
- * This makes a session.shell() call indistinguishable from an
1208
- * LLM-issued bash tool call when later turns read the transcript.
1209
- */
1293
+ /** Append a `session.shell()` call as an LLM-shaped bash tool exchange. */
1210
1294
  async appendShellTriple(toolCallId, args, toolResult, isError) {
1211
1295
  const timestamp = Date.now();
1212
1296
  const userMessage = {
@@ -1271,11 +1355,11 @@ var Session = class {
1271
1355
  if (!this.createdAt) this.createdAt = now;
1272
1356
  await this.store.save(this.storageKey, data);
1273
1357
  }
1274
- async recordTaskSession(sessionId, storageKey, taskId) {
1358
+ async recordTaskSession(session, storageKey, taskId) {
1275
1359
  const taskSessions = Array.isArray(this.metadata.taskSessions) ? this.metadata.taskSessions : [];
1276
- if (!taskSessions.some((task) => task?.sessionId === sessionId)) {
1360
+ if (!taskSessions.some((task) => task?.session === session)) {
1277
1361
  taskSessions.push({
1278
- sessionId,
1362
+ session,
1279
1363
  taskId,
1280
1364
  storageKey
1281
1365
  });
@@ -1295,7 +1379,7 @@ var Session = class {
1295
1379
  if (isContextOverflow(assistantMessage, contextWindow)) {
1296
1380
  if (this.overflowRecoveryAttempted) return;
1297
1381
  this.overflowRecoveryAttempted = true;
1298
- console.error(`[flue:compaction] Overflow detected, compacting and retrying...`);
1382
+ this.internalLog("info", "[flue:compaction] Overflow detected, compacting and retrying...");
1299
1383
  const messages = this.harness.state.messages;
1300
1384
  const lastMsg = messages[messages.length - 1];
1301
1385
  if (lastMsg && lastMsg.role === "assistant") {
@@ -1313,7 +1397,7 @@ var Session = class {
1313
1397
  if (assistantMessage.stopReason === "error") return;
1314
1398
  const contextTokens = calculateContextTokens(assistantMessage.usage);
1315
1399
  if (shouldCompact(contextTokens, contextWindow, this.compactionSettings)) {
1316
- console.error(`[flue:compaction] Threshold reached — ${contextTokens} tokens used, window ${contextWindow}, reserve ${this.compactionSettings.reserveTokens}, triggering compaction`);
1400
+ this.internalLog("info", `[flue:compaction] Threshold reached — ${contextTokens} tokens used, window ${contextWindow}, reserve ${this.compactionSettings.reserveTokens}, triggering compaction`);
1317
1401
  await this.runCompaction("threshold", false);
1318
1402
  }
1319
1403
  }
@@ -1327,6 +1411,7 @@ var Session = class {
1327
1411
  async runCompaction(reason, willRetry) {
1328
1412
  this.compactionAbortController = new AbortController();
1329
1413
  const messagesBefore = this.harness.state.messages.length;
1414
+ const compactionStartMs = Date.now();
1330
1415
  try {
1331
1416
  const model = this.harness.state.model;
1332
1417
  const contextEntries = this.history.buildContextEntries();
@@ -1338,15 +1423,15 @@ var Session = class {
1338
1423
  details: latestCompaction.details
1339
1424
  } : void 0);
1340
1425
  if (!preparation) {
1341
- console.error(`[flue:compaction] Nothing to compact (no valid cut point found)`);
1426
+ this.internalLog("info", "[flue:compaction] Nothing to compact (no valid cut point found)");
1342
1427
  return;
1343
1428
  }
1344
1429
  const firstKeptEntry = contextEntries[preparation.firstKeptIndex]?.entry;
1345
1430
  if (!firstKeptEntry || firstKeptEntry.type !== "message") {
1346
- console.error(`[flue:compaction] Nothing to compact (first kept message has no entry)`);
1431
+ this.internalLog("info", "[flue:compaction] Nothing to compact (first kept message has no entry)");
1347
1432
  return;
1348
1433
  }
1349
- console.error(`[flue:compaction] Summarizing ${preparation.messagesToSummarize.length} messages` + (preparation.isSplitTurn ? ` (split turn: ${preparation.turnPrefixMessages.length} prefix messages)` : "") + `, keeping messages from index ${preparation.firstKeptIndex}`);
1434
+ this.internalLog("info", `[flue:compaction] Summarizing ${preparation.messagesToSummarize.length} messages` + (preparation.isSplitTurn ? ` (split turn: ${preparation.turnPrefixMessages.length} prefix messages)` : "") + `, keeping messages from index ${preparation.firstKeptIndex}`);
1350
1435
  const estimatedTokens = preparation.tokensBefore;
1351
1436
  this.emit({
1352
1437
  type: "compaction_start",
@@ -1364,18 +1449,20 @@ var Session = class {
1364
1449
  });
1365
1450
  this.harness.state.messages = this.history.buildContext();
1366
1451
  const messagesAfter = this.harness.state.messages.length;
1367
- console.error(`[flue:compaction] Complete — messages: ${messagesBefore} → ${messagesAfter}, tokens before: ${result.tokensBefore}`);
1452
+ this.internalLog("info", `[flue:compaction] Complete — messages: ${messagesBefore} → ${messagesAfter}, tokens before: ${result.tokensBefore}`);
1368
1453
  this.emit({
1369
- type: "compaction_end",
1454
+ type: "compaction",
1370
1455
  messagesBefore,
1371
- messagesAfter
1456
+ messagesAfter,
1457
+ durationMs: durationSince(compactionStartMs),
1458
+ usage: result.usage
1372
1459
  });
1373
1460
  await this.save();
1374
1461
  if (willRetry) {
1375
1462
  const msgs = this.harness.state.messages;
1376
1463
  const lastMsg = msgs[msgs.length - 1];
1377
1464
  if (lastMsg?.role === "assistant" && lastMsg.stopReason === "error") this.harness.state.messages = msgs.slice(0, -1);
1378
- console.error(`[flue:compaction] Retrying after overflow recovery...`);
1465
+ this.internalLog("info", "[flue:compaction] Retrying after overflow recovery...");
1379
1466
  const beforeRetry = this.harness.state.messages.length;
1380
1467
  await this.harness.continue();
1381
1468
  await this.harness.waitForIdle();
@@ -1383,11 +1470,20 @@ var Session = class {
1383
1470
  }
1384
1471
  } catch (error) {
1385
1472
  const errorMessage = error instanceof Error ? error.message : String(error);
1386
- console.error(`[flue:compaction] Failed: ${errorMessage}`);
1473
+ this.internalLog("error", `[flue:compaction] Failed: ${errorMessage}`, { error });
1387
1474
  } finally {
1388
1475
  this.compactionAbortController = void 0;
1389
1476
  }
1390
1477
  }
1478
+ internalLog(level, message, attributes) {
1479
+ console.error(message);
1480
+ this.emit({
1481
+ type: "log",
1482
+ level,
1483
+ message,
1484
+ attributes: normalizeLogAttributes(attributes)
1485
+ });
1486
+ }
1391
1487
  throwIfError(context) {
1392
1488
  const errorMsg = this.harness.state.errorMessage;
1393
1489
  if (errorMsg) throw new Error(`[flue] ${context} failed: ${errorMsg}`);
@@ -1530,6 +1626,35 @@ async function deleteSessionTree(store, storageKey, seen = /* @__PURE__ */ new S
1530
1626
  function getErrorMessage(error) {
1531
1627
  return error instanceof Error ? error.message : String(error);
1532
1628
  }
1629
+ function serializeError(error) {
1630
+ if (error instanceof Error) return {
1631
+ name: error.name,
1632
+ message: error.message
1633
+ };
1634
+ return error;
1635
+ }
1636
+ function normalizeLogAttributes(attributes) {
1637
+ if (!attributes) return void 0;
1638
+ if (!(attributes.error instanceof Error)) return attributes;
1639
+ return {
1640
+ ...attributes,
1641
+ error: serializeError(attributes.error)
1642
+ };
1643
+ }
1644
+ function durationSince(start) {
1645
+ return start === void 0 ? 0 : Date.now() - start;
1646
+ }
1647
+ function usageFromResult(result) {
1648
+ if (typeof result !== "object" || result === null) return void 0;
1649
+ const usage = result.usage;
1650
+ return isPromptUsage(usage) ? usage : void 0;
1651
+ }
1652
+ function isPromptUsage(value) {
1653
+ return typeof value === "object" && value !== null && typeof value.input === "number" && typeof value.output === "number" && typeof value.totalTokens === "number";
1654
+ }
1655
+ function redactEnvValues(env) {
1656
+ return Object.fromEntries(Object.keys(env).map((key) => [key, "<redacted>"]));
1657
+ }
1533
1658
 
1534
1659
  //#endregion
1535
1660
  export { assertRoleExists as a, normalizePath as i, Session as n, deleteSessionTree as r, InMemorySessionStore as t };