@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.
- package/README.md +44 -42
- package/dist/app.d.mts +2 -2
- package/dist/app.mjs +2 -2
- package/dist/client.d.mts +7 -3
- package/dist/client.mjs +86 -14
- package/dist/cloudflare/index.d.mts +1 -1
- package/dist/cloudflare/index.mjs +2 -2
- package/dist/{flue-app-CG8i4wNG.d.mts → flue-app-O4_iqLkn.d.mts} +69 -4
- package/dist/{flue-app-DeTOZjPs.mjs → flue-app-SjL4I83Y.mjs} +520 -112
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +56 -6
- package/dist/internal.d.mts +40 -3
- package/dist/internal.mjs +284 -5
- package/dist/{mcp-C3UBXVkR.d.mts → mcp-BfcWmA-A.d.mts} +1 -1
- package/dist/{mcp-2SW_tpox.mjs → mcp-DwLSoSxp.mjs} +52 -38
- package/dist/node/index.d.mts +1 -1
- package/dist/{providers-DeFRIwp0.mjs → providers-BjEEoKLy.mjs} +11 -1
- package/dist/sandbox.d.mts +1 -1
- package/dist/sandbox.mjs +2 -2
- package/dist/{session-CO_uGVOk.mjs → session-CRFfAJDq.mjs} +205 -80
- package/dist/{types-BAmV4f3Q.d.mts → types-Cdcq_ET2.d.mts} +87 -32
- package/package.json +2 -1
|
@@ -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-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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: "
|
|
1198
|
+
type: "task",
|
|
1136
1199
|
taskId,
|
|
1137
1200
|
isError: false,
|
|
1138
1201
|
result: taskResult.text,
|
|
1139
|
-
|
|
1202
|
+
durationMs: durationSince(taskStartMs),
|
|
1203
|
+
parentSession: this.name
|
|
1140
1204
|
});
|
|
1141
1205
|
return taskResult;
|
|
1142
1206
|
} catch (error) {
|
|
1143
1207
|
this.emit({
|
|
1144
|
-
type: "
|
|
1208
|
+
type: "task",
|
|
1145
1209
|
taskId,
|
|
1146
1210
|
isError: true,
|
|
1147
1211
|
result: getErrorMessage(error),
|
|
1148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
1282
|
+
const decorated = {
|
|
1190
1283
|
...event,
|
|
1191
|
-
|
|
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.
|
|
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(
|
|
1358
|
+
async recordTaskSession(session, storageKey, taskId) {
|
|
1275
1359
|
const taskSessions = Array.isArray(this.metadata.taskSessions) ? this.metadata.taskSessions : [];
|
|
1276
|
-
if (!taskSessions.some((task) => task?.
|
|
1360
|
+
if (!taskSessions.some((task) => task?.session === session)) {
|
|
1277
1361
|
taskSessions.push({
|
|
1278
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1431
|
+
this.internalLog("info", "[flue:compaction] Nothing to compact (first kept message has no entry)");
|
|
1347
1432
|
return;
|
|
1348
1433
|
}
|
|
1349
|
-
|
|
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
|
-
|
|
1452
|
+
this.internalLog("info", `[flue:compaction] Complete — messages: ${messagesBefore} → ${messagesAfter}, tokens before: ${result.tokensBefore}`);
|
|
1368
1453
|
this.emit({
|
|
1369
|
-
type: "
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|