@markusylisiurunen/tau 0.2.129 → 0.3.1
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 +111 -59
- package/dist/core/async/cli.js +12 -2
- package/dist/core/async/cli.js.map +1 -1
- package/dist/core/async/http_protocol.js +7 -1
- package/dist/core/async/http_protocol.js.map +1 -1
- package/dist/core/async/http_server.js +9 -1
- package/dist/core/async/http_server.js.map +1 -1
- package/dist/core/async/index.js.map +1 -1
- package/dist/core/async/session_manager.js +94 -51
- package/dist/core/async/session_manager.js.map +1 -1
- package/dist/core/cli.js +12 -15
- package/dist/core/cli.js.map +1 -1
- package/dist/core/commands/registry.js +9 -55
- package/dist/core/commands/registry.js.map +1 -1
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/runtime.js +0 -1
- package/dist/core/config/runtime.js.map +1 -1
- package/dist/core/config/schema.js +102 -17
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/debug.js +1 -20
- package/dist/core/debug.js.map +1 -1
- package/dist/core/diff_review/{session.js → bridge.js} +57 -114
- package/dist/core/diff_review/bridge.js.map +1 -0
- package/dist/core/diff_review/index.js +2 -3
- package/dist/core/diff_review/index.js.map +1 -1
- package/dist/core/diff_review/review_thread.js +48 -23
- package/dist/core/diff_review/review_thread.js.map +1 -1
- package/dist/core/diff_review/snapshot.js +40 -11
- package/dist/core/diff_review/snapshot.js.map +1 -1
- package/dist/core/events/parser.js +43 -12
- package/dist/core/events/parser.js.map +1 -1
- package/dist/core/index.js +0 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/modes/index.js +1 -2
- package/dist/core/modes/index.js.map +1 -1
- package/dist/core/modes/rpc_server.js +15 -321
- package/dist/core/modes/rpc_server.js.map +1 -1
- package/dist/core/modes/websocket_server.js +181 -0
- package/dist/core/modes/websocket_server.js.map +1 -0
- package/dist/core/personas.js +11 -1
- package/dist/core/personas.js.map +1 -1
- package/dist/core/runtime/chat_runtime.js +19 -12
- package/dist/core/runtime/chat_runtime.js.map +1 -1
- package/dist/core/runtime/conversation_turn_runtime.js +16 -1
- package/dist/core/runtime/conversation_turn_runtime.js.map +1 -1
- package/dist/core/runtime/runtime_bootstrap.js +326 -0
- package/dist/core/runtime/runtime_bootstrap.js.map +1 -1
- package/dist/core/session/compaction.js +13 -11
- package/dist/core/session/compaction.js.map +1 -1
- package/dist/core/session/core_session.js +9 -0
- package/dist/core/session/core_session.js.map +1 -1
- package/dist/core/session/direct_bash.js +34 -0
- package/dist/core/session/direct_bash.js.map +1 -0
- package/dist/core/session/pruning.js +595 -0
- package/dist/core/session/pruning.js.map +1 -0
- package/dist/core/session/runner.js +45 -5
- package/dist/core/session/runner.js.map +1 -1
- package/dist/core/session/session_engine.js +84 -10
- package/dist/core/session/session_engine.js.map +1 -1
- package/dist/core/static/prompts/diff-review-wrapper.md +1 -1
- package/dist/core/subagents/subagent_engine.js +1 -1
- package/dist/core/subagents/subagent_engine.js.map +1 -1
- package/dist/core/tools/catalog.js.map +1 -1
- package/dist/core/tools/diff_review.js +12 -12
- package/dist/core/tools/diff_review.js.map +1 -1
- package/dist/core/tools/execution_backend.js +9 -2
- package/dist/core/tools/execution_backend.js.map +1 -1
- package/dist/core/tools/registry.js.map +1 -1
- package/dist/core/usage/logs.js.map +1 -1
- package/dist/core/utils/context.js +1 -1
- package/dist/core/utils/context.js.map +1 -1
- package/dist/core/utils/context_builder.js +1 -18
- package/dist/core/utils/context_builder.js.map +1 -1
- package/dist/core/utils/gemini_speech.js +1 -1
- package/dist/core/utils/gemini_speech.js.map +1 -1
- package/dist/core/utils/project_files.js +11 -16
- package/dist/core/utils/project_files.js.map +1 -1
- package/dist/core/utils/spawn_capture.js +2 -2
- package/dist/core/utils/spawn_capture.js.map +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/execution/cloudflare_sandbox_execution_environment.js +400 -0
- package/dist/execution/cloudflare_sandbox_execution_environment.js.map +1 -0
- package/dist/execution/execution_environment.js +24 -0
- package/dist/execution/execution_environment.js.map +1 -0
- package/dist/execution/fly_sprite_execution_environment.js +559 -0
- package/dist/execution/fly_sprite_execution_environment.js.map +1 -0
- package/dist/execution/local_execution_environment.js +93 -0
- package/dist/execution/local_execution_environment.js.map +1 -0
- package/dist/execution/runtime_config_snapshot.js +209 -0
- package/dist/execution/runtime_config_snapshot.js.map +1 -0
- package/dist/execution/sandbox_tool_helpers.js +55 -0
- package/dist/execution/sandbox_tool_helpers.js.map +1 -0
- package/dist/execution/tool_backend_execution_environment.js +55 -0
- package/dist/execution/tool_backend_execution_environment.js.map +1 -0
- package/dist/host/hosted_ephemeral_agent_session.js +286 -0
- package/dist/host/hosted_ephemeral_agent_session.js.map +1 -0
- package/dist/host/index.js +3 -0
- package/dist/host/index.js.map +1 -0
- package/dist/host/local_session_host.js +1464 -0
- package/dist/host/local_session_host.js.map +1 -0
- package/dist/host/session_host.js +2 -0
- package/dist/host/session_host.js.map +1 -0
- package/dist/host/session_protocol_handler.js +873 -0
- package/dist/host/session_protocol_handler.js.map +1 -0
- package/dist/main.js +537 -77
- package/dist/main.js.map +1 -1
- package/dist/protocol/index.d.ts +2 -0
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/session_protocol.d.ts +691 -0
- package/dist/protocol/session_protocol.js +2117 -0
- package/dist/protocol/session_protocol.js.map +1 -0
- package/dist/sdk/client.d.ts +2 -1
- package/dist/sdk/client.js +136 -392
- package/dist/sdk/client.js.map +1 -1
- package/dist/sdk/index.d.ts +7 -4
- package/dist/sdk/index.js +4 -2
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/session.d.ts +5 -0
- package/dist/sdk/session.js +406 -0
- package/dist/sdk/session.js.map +1 -0
- package/dist/sdk/types.d.ts +97 -78
- package/dist/store/file_session_store.js +157 -0
- package/dist/store/file_session_store.js.map +1 -0
- package/dist/store/memory_session_store.js +24 -0
- package/dist/store/memory_session_store.js.map +1 -0
- package/dist/store/session_store.js +32 -0
- package/dist/store/session_store.js.map +1 -0
- package/dist/transport/errors.d.ts +29 -0
- package/dist/{sdk → transport}/errors.js +5 -5
- package/dist/transport/errors.js.map +1 -0
- package/dist/transport/in_process_session_transport.js +102 -0
- package/dist/transport/in_process_session_transport.js.map +1 -0
- package/dist/transport/index.d.ts +7 -0
- package/dist/transport/index.js +4 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/pending_session_protocol_requests.js +67 -0
- package/dist/transport/pending_session_protocol_requests.js.map +1 -0
- package/dist/transport/session_protocol_transport_helpers.js +98 -0
- package/dist/transport/session_protocol_transport_helpers.js.map +1 -0
- package/dist/transport/session_transport.d.ts +11 -0
- package/dist/transport/session_transport.js +2 -0
- package/dist/transport/session_transport.js.map +1 -0
- package/dist/transport/stdio_session_transport.d.ts +38 -0
- package/dist/transport/stdio_session_transport.js +243 -0
- package/dist/transport/stdio_session_transport.js.map +1 -0
- package/dist/transport/websocket_session_transport.d.ts +52 -0
- package/dist/transport/websocket_session_transport.js +235 -0
- package/dist/transport/websocket_session_transport.js.map +1 -0
- package/dist/tui/chat_controller/assistant_clipboard.js +39 -0
- package/dist/tui/chat_controller/assistant_clipboard.js.map +1 -0
- package/dist/tui/chat_controller/command_hints.js +43 -0
- package/dist/tui/chat_controller/command_hints.js.map +1 -0
- package/dist/tui/chat_controller/diff_review_service.js +9 -9
- package/dist/tui/chat_controller/diff_review_service.js.map +1 -1
- package/dist/tui/chat_controller/diff_review_user_message.js +17 -0
- package/dist/tui/chat_controller/diff_review_user_message.js.map +1 -0
- package/dist/tui/chat_controller/history_labels.js +8 -0
- package/dist/tui/chat_controller/history_labels.js.map +1 -0
- package/dist/tui/chat_controller/history_message_model.js +36 -0
- package/dist/tui/chat_controller/history_message_model.js.map +1 -0
- package/dist/tui/chat_controller/status_format.js +22 -0
- package/dist/tui/chat_controller/status_format.js.map +1 -0
- package/dist/tui/chat_view.js +0 -1
- package/dist/tui/chat_view.js.map +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/listen_capture.js +87 -0
- package/dist/tui/listen_capture.js.map +1 -0
- package/dist/tui/memory_mode.js +23 -0
- package/dist/tui/memory_mode.js.map +1 -0
- package/dist/tui/session_chat_app.js +227 -0
- package/dist/tui/session_chat_app.js.map +1 -0
- package/dist/tui/session_chat_controller.js +2176 -0
- package/dist/tui/session_chat_controller.js.map +1 -0
- package/dist/tui/session_tool_execution_backend.js +170 -0
- package/dist/tui/session_tool_execution_backend.js.map +1 -0
- package/dist/tui/speech_playback.js +107 -0
- package/dist/tui/speech_playback.js.map +1 -0
- package/dist/tui/ui/assistant_message.js +27 -21
- package/dist/tui/ui/assistant_message.js.map +1 -1
- package/dist/tui/ui/chat_container.js +4 -0
- package/dist/tui/ui/chat_container.js.map +1 -1
- package/dist/tui/ui/custom_editor.js +0 -5
- package/dist/tui/ui/custom_editor.js.map +1 -1
- package/dist/tui/ui/slash_autocomplete.js +14 -36
- package/dist/tui/ui/slash_autocomplete.js.map +1 -1
- package/dist/tui/ui/tool_ui_registry.js +21 -0
- package/dist/tui/ui/tool_ui_registry.js.map +1 -1
- package/package.json +6 -3
- package/dist/core/config/bash_commands.js +0 -44
- package/dist/core/config/bash_commands.js.map +0 -1
- package/dist/core/diff_review/session.js.map +0 -1
- package/dist/core/modes/rpc_adapter.js +0 -17
- package/dist/core/modes/rpc_adapter.js.map +0 -1
- package/dist/core/modes/rpc_protocol.js +0 -500
- package/dist/core/modes/rpc_protocol.js.map +0 -1
- package/dist/core/session/checkpoint.js +0 -34
- package/dist/core/session/checkpoint.js.map +0 -1
- package/dist/sdk/errors.d.ts +0 -33
- package/dist/sdk/errors.js.map +0 -1
- package/dist/tui/app.js +0 -64
- package/dist/tui/app.js.map +0 -1
- package/dist/tui/chat_controller/session_maintenance_service.js +0 -634
- package/dist/tui/chat_controller/session_maintenance_service.js.map +0 -1
- package/dist/tui/chat_controller.js +0 -2579
- package/dist/tui/chat_controller.js.map +0 -1
|
@@ -7,6 +7,9 @@ export class CoreSession {
|
|
|
7
7
|
reset() {
|
|
8
8
|
this.engine.reset();
|
|
9
9
|
}
|
|
10
|
+
restoreState(input) {
|
|
11
|
+
this.engine.restoreState(input);
|
|
12
|
+
}
|
|
10
13
|
dispose() {
|
|
11
14
|
this.engine.dispose();
|
|
12
15
|
}
|
|
@@ -25,6 +28,9 @@ export class CoreSession {
|
|
|
25
28
|
onEvent(handler) {
|
|
26
29
|
return this.engine.onEvent(handler);
|
|
27
30
|
}
|
|
31
|
+
onSubagentEvent(handler) {
|
|
32
|
+
return this.engine.onSubagentEvent(handler);
|
|
33
|
+
}
|
|
28
34
|
async terminateSubagent(id) {
|
|
29
35
|
return await this.engine.terminateSubagent(id);
|
|
30
36
|
}
|
|
@@ -64,6 +70,9 @@ export class CoreSession {
|
|
|
64
70
|
async compact(options) {
|
|
65
71
|
return await this.engine.compact(options);
|
|
66
72
|
}
|
|
73
|
+
async pruneToolResults(options) {
|
|
74
|
+
return await this.engine.pruneToolResults(options);
|
|
75
|
+
}
|
|
67
76
|
async *events(signal, options) {
|
|
68
77
|
return yield* this.engine.processTurn(signal, options);
|
|
69
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core_session.js","sourceRoot":"","sources":["../../../src/core/session/core_session.ts"],"names":[],"mappings":"AAMA,OAAO,EAOL,aAAa,
|
|
1
|
+
{"version":3,"file":"core_session.js","sourceRoot":"","sources":["../../../src/core/session/core_session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BASH_DEFAULT_TIMEOUT_MS, buildBashUiText, formatBashUserMessageText, getBashOutputPolicy, prepareBashOutput, } from "../tools/bash.js";
|
|
2
|
+
export async function runDirectBashCommand(options) {
|
|
3
|
+
const now = options.now ?? Date.now;
|
|
4
|
+
const startedAt = now();
|
|
5
|
+
const { output, exitCode, truncated: captureTruncated, } = await options.backend.runBash(options.command, {
|
|
6
|
+
signal: options.signal,
|
|
7
|
+
timeoutMs: BASH_DEFAULT_TIMEOUT_MS,
|
|
8
|
+
});
|
|
9
|
+
const durationMs = Math.max(0, now() - startedAt);
|
|
10
|
+
const truncationInfo = await prepareBashOutput(output, captureTruncated, getBashOutputPolicy({ mode: "user" }), options.backend);
|
|
11
|
+
const userMessageText = formatBashUserMessageText({
|
|
12
|
+
command: options.command,
|
|
13
|
+
truncationInfo,
|
|
14
|
+
});
|
|
15
|
+
const uiText = buildBashUiText({
|
|
16
|
+
truncationInfo,
|
|
17
|
+
exitCode,
|
|
18
|
+
durationMs,
|
|
19
|
+
previewLines: { head: 12, tail: 12 },
|
|
20
|
+
fullText: userMessageText,
|
|
21
|
+
});
|
|
22
|
+
const userHistoryEntryId = options.addToContext && options.addUserText
|
|
23
|
+
? await options.addUserText(userMessageText)
|
|
24
|
+
: undefined;
|
|
25
|
+
return {
|
|
26
|
+
command: options.command,
|
|
27
|
+
exitCode,
|
|
28
|
+
truncationInfo,
|
|
29
|
+
uiText,
|
|
30
|
+
durationMs,
|
|
31
|
+
...(userHistoryEntryId !== undefined ? { userHistoryEntryId } : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=direct_bash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"direct_bash.js","sourceRoot":"","sources":["../../../src/core/session/direct_bash.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EAEvB,eAAe,EACf,yBAAyB,EACzB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAsB1B,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAmC;IAEnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC;IACxB,MAAM,EACJ,MAAM,EACN,QAAQ,EACR,SAAS,EAAE,gBAAgB,GAC5B,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE;QACjD,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,SAAS,EAAE,uBAAuB;KACnC,CAAC,CAAC;IACH,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;IAClD,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAC5C,MAAM,EACN,gBAAgB,EAChB,mBAAmB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EACrC,OAAO,CAAC,OAAO,CAChB,CAAC;IACF,MAAM,eAAe,GAAG,yBAAyB,CAAC;QAChD,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,cAAc;KACf,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,eAAe,CAAC;QAC7B,cAAc;QACd,QAAQ;QACR,UAAU;QACV,YAAY,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACpC,QAAQ,EAAE,eAAe;KAC1B,CAAC,CAAC;IACH,MAAM,kBAAkB,GACtB,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,WAAW;QACzC,CAAC,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC;QAC5C,CAAC,CAAC,SAAS,CAAC;IAEhB,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,QAAQ;QACR,cAAc;QACd,MAAM;QACN,UAAU;QACV,GAAG,CAAC,kBAAkB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { TOOL_NAME_BASH, TOOL_NAME_EDIT } from "../tools/tool_names.js";
|
|
2
|
+
import { buildLineDiff, collapseLongUnchangedDiffRuns } from "../utils/line_diff.js";
|
|
3
|
+
import { extractAllFencedCodeBlocks } from "../utils/messages.js";
|
|
4
|
+
import { bytesToTokens, formatTokenEstimate, tokensToBytes } from "../utils/token.js";
|
|
5
|
+
import { truncateToBytesFromStart } from "../utils/truncate.js";
|
|
6
|
+
const DEFAULT_PRUNE_FRACTION = 0.25;
|
|
7
|
+
const PRUNED_TOOL_RESULT_PREFIX = "[Tool result pruned]";
|
|
8
|
+
const PRUNED_EDIT_RESULT_PREFIX = "[Tool result pruned] Edit diff";
|
|
9
|
+
const PRUNED_EDIT_ARGUMENT_MARKER = "[Content pruned]";
|
|
10
|
+
const PRUNE_EDIT_UNCHANGED_CONTEXT_LINES = 4;
|
|
11
|
+
const PRUNE_PREVIEW_MAX_TOKENS = 512;
|
|
12
|
+
const PRUNE_MAX_OVERAGE_RATIO = 0.1;
|
|
13
|
+
export function defaultSessionPruneFraction() {
|
|
14
|
+
return DEFAULT_PRUNE_FRACTION;
|
|
15
|
+
}
|
|
16
|
+
export function parseSessionPruneFraction(extra) {
|
|
17
|
+
if (!extra) {
|
|
18
|
+
return DEFAULT_PRUNE_FRACTION;
|
|
19
|
+
}
|
|
20
|
+
const parsed = Number(extra);
|
|
21
|
+
if (!Number.isFinite(parsed)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (parsed < 0 || parsed > 1) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
export function parseSessionPruneFractionAndGuidance(extra) {
|
|
30
|
+
if (!extra) {
|
|
31
|
+
return { fraction: DEFAULT_PRUNE_FRACTION };
|
|
32
|
+
}
|
|
33
|
+
const trimmed = extra.trim();
|
|
34
|
+
if (!trimmed) {
|
|
35
|
+
return { fraction: DEFAULT_PRUNE_FRACTION };
|
|
36
|
+
}
|
|
37
|
+
const [firstToken, ...rest] = trimmed.split(/\s+/);
|
|
38
|
+
const parsed = Number(firstToken);
|
|
39
|
+
if (Number.isFinite(parsed)) {
|
|
40
|
+
if (parsed < 0 || parsed > 1) {
|
|
41
|
+
return { fraction: null };
|
|
42
|
+
}
|
|
43
|
+
const guidance = rest.join(" ").trim();
|
|
44
|
+
return guidance ? { fraction: parsed, guidance } : { fraction: parsed };
|
|
45
|
+
}
|
|
46
|
+
return { fraction: DEFAULT_PRUNE_FRACTION, guidance: trimmed };
|
|
47
|
+
}
|
|
48
|
+
export function prepareSessionSmartPrunePrompt(args) {
|
|
49
|
+
validatePruneFraction(args.fraction);
|
|
50
|
+
if (args.fraction === 0 || args.historyEntries.length === 0) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const preparation = preparePruneCandidates(args.historyEntries, args.fraction);
|
|
54
|
+
if (preparation.candidates.length === 0 || preparation.totalTokens === 0) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
if (preparation.targetTokens <= 0) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
prompt: buildSmartPrunePrompt({
|
|
62
|
+
history: args.historyEntries.map((entry) => entry.message),
|
|
63
|
+
targetTokens: preparation.targetTokens,
|
|
64
|
+
guidance: args.guidance,
|
|
65
|
+
}),
|
|
66
|
+
targetTokens: preparation.targetTokens,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function buildSmartPruneSystemPrompt() {
|
|
70
|
+
return [
|
|
71
|
+
"You are a context pruning assistant.",
|
|
72
|
+
"Your task is to select which bash tool outputs should be pruned from the conversation history.",
|
|
73
|
+
"Analyze the conversation to understand what the user is working on and which tool outputs are most relevant.",
|
|
74
|
+
"Prioritize keeping outputs that contain important information, errors, or results that may be referenced later.",
|
|
75
|
+
"Prefer pruning outputs that are verbose, redundant, or contain routine information that can be regenerated if needed.",
|
|
76
|
+
"Follow the user's guidance carefully when provided.",
|
|
77
|
+
].join(" ");
|
|
78
|
+
}
|
|
79
|
+
export function clampPruneReasoning(reasoning) {
|
|
80
|
+
switch (reasoning) {
|
|
81
|
+
case undefined:
|
|
82
|
+
case "none":
|
|
83
|
+
return undefined;
|
|
84
|
+
case "minimal":
|
|
85
|
+
return "low";
|
|
86
|
+
case "low":
|
|
87
|
+
case "medium":
|
|
88
|
+
return reasoning;
|
|
89
|
+
default:
|
|
90
|
+
return "medium";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function parseSmartPruneResponse(raw) {
|
|
94
|
+
const trimmed = raw.trim();
|
|
95
|
+
if (!trimmed) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
const fenced = extractAllFencedCodeBlocks(trimmed);
|
|
99
|
+
const source = (fenced ?? trimmed).trim();
|
|
100
|
+
const start = source.indexOf("{");
|
|
101
|
+
const end = source.lastIndexOf("}");
|
|
102
|
+
if (start === -1 || end === -1 || end < start) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const jsonText = source.slice(start, end + 1);
|
|
106
|
+
let parsed;
|
|
107
|
+
try {
|
|
108
|
+
parsed = JSON.parse(jsonText);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const prune = parsed.prune;
|
|
117
|
+
if (!Array.isArray(prune) || prune.some((item) => typeof item !== "string")) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return prune;
|
|
121
|
+
}
|
|
122
|
+
export function pruneSessionHistory(args) {
|
|
123
|
+
validatePruneFraction(args.options.fraction);
|
|
124
|
+
if (args.options.fraction === 0) {
|
|
125
|
+
return emptyPruneResult("prune fraction is 0, nothing to prune.");
|
|
126
|
+
}
|
|
127
|
+
if (args.historyEntries.length === 0) {
|
|
128
|
+
return emptyPruneResult("no conversation to prune.");
|
|
129
|
+
}
|
|
130
|
+
const editSummary = pruneEditToolHistory(args.historyEntries, args.replaceMessageById);
|
|
131
|
+
const preparation = preparePruneCandidates(args.historyEntries, args.options.fraction);
|
|
132
|
+
const fallbackMessage = args.options.strategy === "smart" && args.options.smartSelection?.length === 0
|
|
133
|
+
? "model returned no prune candidates."
|
|
134
|
+
: "no bash tool results or edit tool calls to prune.";
|
|
135
|
+
if (preparation.candidates.length === 0 || preparation.totalTokens === 0) {
|
|
136
|
+
return buildNoopOrEditResult(editSummary, fallbackMessage);
|
|
137
|
+
}
|
|
138
|
+
if (preparation.targetTokens <= 0) {
|
|
139
|
+
return buildNoopOrEditResult(editSummary, "prune fraction is too small to remove anything.");
|
|
140
|
+
}
|
|
141
|
+
const toPrune = args.options.strategy === "smart"
|
|
142
|
+
? selectSmartPruneCandidates(args.options.smartSelection ?? [], preparation.candidates, preparation.targetTokens)
|
|
143
|
+
: selectPruneCandidatesByStrategy(preparation, args.options.strategy);
|
|
144
|
+
if (toPrune.length === 0) {
|
|
145
|
+
return buildNoopOrEditResult(editSummary, fallbackMessage);
|
|
146
|
+
}
|
|
147
|
+
const applied = applyBashPruneCandidates(toPrune, args.replaceMessageById);
|
|
148
|
+
const message = buildPruneSummaryMessage(editSummary, applied.resultsPruned, applied.bytesPruned);
|
|
149
|
+
return {
|
|
150
|
+
message,
|
|
151
|
+
noop: false,
|
|
152
|
+
bashResultsPruned: applied.resultsPruned,
|
|
153
|
+
editCallsPruned: editSummary.callsPruned,
|
|
154
|
+
editResultsPruned: editSummary.resultsPruned,
|
|
155
|
+
bytesPruned: applied.bytesPruned + editSummary.bytesRemoved,
|
|
156
|
+
prunedToolResults: [...editSummary.prunedToolResults, ...applied.prunedToolResults],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function validatePruneFraction(fraction) {
|
|
160
|
+
if (!Number.isFinite(fraction) || fraction < 0 || fraction > 1) {
|
|
161
|
+
throw new Error("invalid prune fraction. use a number between 0 and 1.");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function emptyPruneResult(message) {
|
|
165
|
+
return {
|
|
166
|
+
message,
|
|
167
|
+
noop: true,
|
|
168
|
+
bashResultsPruned: 0,
|
|
169
|
+
editCallsPruned: 0,
|
|
170
|
+
editResultsPruned: 0,
|
|
171
|
+
bytesPruned: 0,
|
|
172
|
+
prunedToolResults: [],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function buildNoopOrEditResult(editSummary, fallbackMessage) {
|
|
176
|
+
if (!hasAnyPrunedEditChanges(editSummary)) {
|
|
177
|
+
return emptyPruneResult(fallbackMessage);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
message: buildPruneSummaryMessage(editSummary),
|
|
181
|
+
noop: false,
|
|
182
|
+
bashResultsPruned: 0,
|
|
183
|
+
editCallsPruned: editSummary.callsPruned,
|
|
184
|
+
editResultsPruned: editSummary.resultsPruned,
|
|
185
|
+
bytesPruned: editSummary.bytesRemoved,
|
|
186
|
+
prunedToolResults: editSummary.prunedToolResults,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function preparePruneCandidates(historyEntries, fraction) {
|
|
190
|
+
const candidates = [];
|
|
191
|
+
let totalTokens = 0;
|
|
192
|
+
for (const entry of historyEntries) {
|
|
193
|
+
const message = entry.message;
|
|
194
|
+
if (message.role !== "toolResult") {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const toolResult = message;
|
|
198
|
+
if (toolResult.toolName !== TOOL_NAME_BASH) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const inspection = inspectToolResultContent(toolResult);
|
|
202
|
+
if (inspection.firstText?.startsWith(PRUNED_TOOL_RESULT_PREFIX) || inspection.hasImage) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const tokens = bytesToTokens(inspection.textBytes);
|
|
206
|
+
candidates.push({
|
|
207
|
+
historyEntryId: entry.id,
|
|
208
|
+
toolResult,
|
|
209
|
+
bytes: inspection.textBytes,
|
|
210
|
+
tokens,
|
|
211
|
+
order: candidates.length,
|
|
212
|
+
});
|
|
213
|
+
totalTokens += tokens;
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
candidates,
|
|
217
|
+
totalTokens,
|
|
218
|
+
targetTokens: Math.ceil(totalTokens * fraction),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function selectPruneCandidatesByStrategy(preparation, strategy) {
|
|
222
|
+
const ordered = strategy === "largest"
|
|
223
|
+
? [...preparation.candidates].sort((a, b) => b.tokens - a.tokens || b.bytes - a.bytes || a.order - b.order)
|
|
224
|
+
: preparation.candidates;
|
|
225
|
+
const selected = [];
|
|
226
|
+
let selectedTokens = 0;
|
|
227
|
+
for (const candidate of ordered) {
|
|
228
|
+
if (selectedTokens >= preparation.targetTokens) {
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
selected.push(candidate);
|
|
232
|
+
selectedTokens += candidate.tokens;
|
|
233
|
+
}
|
|
234
|
+
return selected;
|
|
235
|
+
}
|
|
236
|
+
function selectSmartPruneCandidates(ids, candidates, targetTokens) {
|
|
237
|
+
const candidatesById = new Map();
|
|
238
|
+
for (const candidate of candidates) {
|
|
239
|
+
candidatesById.set(candidate.toolResult.toolCallId, candidate);
|
|
240
|
+
}
|
|
241
|
+
const selected = [];
|
|
242
|
+
const seen = new Set();
|
|
243
|
+
let tokens = 0;
|
|
244
|
+
const maxTokens = Math.ceil(targetTokens * (1 + PRUNE_MAX_OVERAGE_RATIO));
|
|
245
|
+
for (const id of ids) {
|
|
246
|
+
if (seen.has(id))
|
|
247
|
+
continue;
|
|
248
|
+
const candidate = candidatesById.get(id);
|
|
249
|
+
if (!candidate)
|
|
250
|
+
continue;
|
|
251
|
+
if (tokens + candidate.tokens > maxTokens)
|
|
252
|
+
continue;
|
|
253
|
+
selected.push(candidate);
|
|
254
|
+
seen.add(id);
|
|
255
|
+
tokens += candidate.tokens;
|
|
256
|
+
if (tokens >= targetTokens) {
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return selected;
|
|
261
|
+
}
|
|
262
|
+
function applyBashPruneCandidates(candidates, replaceMessageById) {
|
|
263
|
+
let bytesPruned = 0;
|
|
264
|
+
const prunedToolResults = [];
|
|
265
|
+
for (const candidate of candidates) {
|
|
266
|
+
bytesPruned += candidate.bytes;
|
|
267
|
+
const noticeText = buildPrunedToolResultNotice(candidate.toolResult, candidate.bytes);
|
|
268
|
+
const prunedResult = {
|
|
269
|
+
...candidate.toolResult,
|
|
270
|
+
content: [{ type: "text", text: noticeText }],
|
|
271
|
+
};
|
|
272
|
+
replacePrunedMessageOrThrow(replaceMessageById, candidate.historyEntryId, prunedResult, "bash tool result");
|
|
273
|
+
prunedToolResults.push({ toolCallId: prunedResult.toolCallId, content: noticeText });
|
|
274
|
+
}
|
|
275
|
+
return { resultsPruned: candidates.length, bytesPruned, prunedToolResults };
|
|
276
|
+
}
|
|
277
|
+
function hasAnyPrunedEditChanges(summary) {
|
|
278
|
+
return summary.callsPruned > 0 || summary.resultsPruned > 0;
|
|
279
|
+
}
|
|
280
|
+
function buildPruneSummaryMessage(editSummary, bashResultsPruned = 0, bashBytesPruned = 0) {
|
|
281
|
+
const editSummaryText = formatPrunedEditSummary(editSummary);
|
|
282
|
+
if (bashResultsPruned <= 0) {
|
|
283
|
+
if (!editSummaryText) {
|
|
284
|
+
return "no bash tool results or edit tool calls to prune.";
|
|
285
|
+
}
|
|
286
|
+
return `pruned ${editSummaryText}.`;
|
|
287
|
+
}
|
|
288
|
+
const bashLabel = formatTokenEstimate(bashBytesPruned);
|
|
289
|
+
const bashNoun = bashResultsPruned === 1 ? "result" : "results";
|
|
290
|
+
if (!editSummaryText) {
|
|
291
|
+
return `pruned ${bashResultsPruned} bash tool ${bashNoun} (${bashLabel}).`;
|
|
292
|
+
}
|
|
293
|
+
return `pruned ${bashResultsPruned} bash tool ${bashNoun} (${bashLabel}) and ${editSummaryText}.`;
|
|
294
|
+
}
|
|
295
|
+
function formatPrunedEditSummary(summary) {
|
|
296
|
+
if (!hasAnyPrunedEditChanges(summary)) {
|
|
297
|
+
return "";
|
|
298
|
+
}
|
|
299
|
+
const parts = [];
|
|
300
|
+
if (summary.callsPruned > 0) {
|
|
301
|
+
const noun = summary.callsPruned === 1 ? "call" : "calls";
|
|
302
|
+
parts.push(`${summary.callsPruned} edit tool ${noun}`);
|
|
303
|
+
}
|
|
304
|
+
if (summary.resultsPruned > 0) {
|
|
305
|
+
const noun = summary.resultsPruned === 1 ? "result" : "results";
|
|
306
|
+
parts.push(`${summary.resultsPruned} edit tool ${noun}`);
|
|
307
|
+
}
|
|
308
|
+
const tokenEstimate = formatTokenEstimate(summary.bytesRemoved);
|
|
309
|
+
return `${parts.join(" and ")} (${tokenEstimate})`;
|
|
310
|
+
}
|
|
311
|
+
function pruneEditToolHistory(historyEntries, replaceMessageById) {
|
|
312
|
+
const summary = {
|
|
313
|
+
callsPruned: 0,
|
|
314
|
+
resultsPruned: 0,
|
|
315
|
+
bytesRemoved: 0,
|
|
316
|
+
prunedToolResults: [],
|
|
317
|
+
};
|
|
318
|
+
const editCallsById = new Map();
|
|
319
|
+
for (const entry of historyEntries) {
|
|
320
|
+
const message = entry.message;
|
|
321
|
+
if (message.role !== "assistant") {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const assistant = message;
|
|
325
|
+
const assistantContent = getAssistantContentOrThrow(assistant, "pruning edit tool calls");
|
|
326
|
+
let changed = false;
|
|
327
|
+
const nextContent = assistantContent.map((block) => {
|
|
328
|
+
if (block.type !== "toolCall") {
|
|
329
|
+
return block;
|
|
330
|
+
}
|
|
331
|
+
const toolCall = block;
|
|
332
|
+
if (toolCall.name !== TOOL_NAME_EDIT) {
|
|
333
|
+
return block;
|
|
334
|
+
}
|
|
335
|
+
const args = getToolCallArgumentsObject(toolCall);
|
|
336
|
+
const oldText = getRequiredEditArgument(args, "oldText", toolCall.id);
|
|
337
|
+
const newText = getRequiredEditArgument(args, "newText", toolCall.id);
|
|
338
|
+
if (oldText === PRUNED_EDIT_ARGUMENT_MARKER && newText === PRUNED_EDIT_ARGUMENT_MARKER) {
|
|
339
|
+
return block;
|
|
340
|
+
}
|
|
341
|
+
changed = true;
|
|
342
|
+
summary.callsPruned += 1;
|
|
343
|
+
const oldBytes = Buffer.byteLength(oldText, "utf8");
|
|
344
|
+
const newBytes = Buffer.byteLength(newText, "utf8");
|
|
345
|
+
const markerBytes = Buffer.byteLength(PRUNED_EDIT_ARGUMENT_MARKER, "utf8") * 2;
|
|
346
|
+
summary.bytesRemoved += Math.max(0, oldBytes + newBytes - markerBytes);
|
|
347
|
+
editCallsById.set(toolCall.id, { oldText, newText });
|
|
348
|
+
return {
|
|
349
|
+
...toolCall,
|
|
350
|
+
arguments: {
|
|
351
|
+
...args,
|
|
352
|
+
oldText: PRUNED_EDIT_ARGUMENT_MARKER,
|
|
353
|
+
newText: PRUNED_EDIT_ARGUMENT_MARKER,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
if (changed) {
|
|
358
|
+
replacePrunedMessageOrThrow(replaceMessageById, entry.id, { ...assistant, content: nextContent }, "edit tool call");
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (editCallsById.size === 0) {
|
|
362
|
+
return summary;
|
|
363
|
+
}
|
|
364
|
+
for (const entry of historyEntries) {
|
|
365
|
+
const message = entry.message;
|
|
366
|
+
if (message.role !== "toolResult") {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const toolResult = message;
|
|
370
|
+
if (toolResult.toolName !== TOOL_NAME_EDIT) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (toolResult.isError) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const callDiff = editCallsById.get(toolResult.toolCallId);
|
|
377
|
+
if (!callDiff) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
const inspection = inspectToolResultContent(toolResult);
|
|
381
|
+
if (inspection.firstText?.startsWith(PRUNED_EDIT_RESULT_PREFIX)) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const prunedText = buildPrunedEditToolResult(callDiff);
|
|
385
|
+
const prunedResult = {
|
|
386
|
+
...toolResult,
|
|
387
|
+
content: [{ type: "text", text: prunedText }],
|
|
388
|
+
};
|
|
389
|
+
replacePrunedMessageOrThrow(replaceMessageById, entry.id, prunedResult, "edit tool result");
|
|
390
|
+
summary.prunedToolResults.push({ toolCallId: prunedResult.toolCallId, content: prunedText });
|
|
391
|
+
summary.resultsPruned += 1;
|
|
392
|
+
}
|
|
393
|
+
return summary;
|
|
394
|
+
}
|
|
395
|
+
function replacePrunedMessageOrThrow(replaceMessageById, historyEntryId, message, label) {
|
|
396
|
+
if (!replaceMessageById(historyEntryId, message)) {
|
|
397
|
+
throw new Error(`failed to replace pruned ${label} '${historyEntryId}'`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function getAssistantContentOrThrow(assistant, context) {
|
|
401
|
+
if (!Array.isArray(assistant.content)) {
|
|
402
|
+
throw new Error(`invalid assistant message content while ${context}`);
|
|
403
|
+
}
|
|
404
|
+
return assistant.content;
|
|
405
|
+
}
|
|
406
|
+
function getToolCallArgumentsObject(toolCall) {
|
|
407
|
+
const { arguments: argumentsValue } = toolCall;
|
|
408
|
+
if (!argumentsValue || typeof argumentsValue !== "object" || Array.isArray(argumentsValue)) {
|
|
409
|
+
throw new Error(`invalid edit tool call arguments (tool_call_id=${toolCall.id})`);
|
|
410
|
+
}
|
|
411
|
+
return argumentsValue;
|
|
412
|
+
}
|
|
413
|
+
function getRequiredEditArgument(args, key, toolCallId) {
|
|
414
|
+
const value = args[key];
|
|
415
|
+
if (typeof value !== "string") {
|
|
416
|
+
throw new Error(`missing edit argument '${key}' (tool_call_id=${toolCallId})`);
|
|
417
|
+
}
|
|
418
|
+
return value;
|
|
419
|
+
}
|
|
420
|
+
function buildPrunedEditToolResult(diff) {
|
|
421
|
+
const diffText = buildPrunedEditDiff(diff.oldText, diff.newText);
|
|
422
|
+
return `${PRUNED_EDIT_RESULT_PREFIX}\n${diffText}`;
|
|
423
|
+
}
|
|
424
|
+
function buildPrunedEditDiff(oldText, newText) {
|
|
425
|
+
const diff = buildLineDiff(oldText, newText);
|
|
426
|
+
if (diff.added === 0 && diff.removed === 0) {
|
|
427
|
+
return "(No textual changes)";
|
|
428
|
+
}
|
|
429
|
+
const collapsed = collapseLongUnchangedDiffRuns({
|
|
430
|
+
diffLines: diff.lines,
|
|
431
|
+
maxUnchangedLines: PRUNE_EDIT_UNCHANGED_CONTEXT_LINES,
|
|
432
|
+
});
|
|
433
|
+
return collapsed.length > 0 ? collapsed.join("\n") : "(No textual changes)";
|
|
434
|
+
}
|
|
435
|
+
function buildPrunedToolResultNotice(toolResult, bytes) {
|
|
436
|
+
const tokenEstimate = formatTokenEstimate(bytes);
|
|
437
|
+
return `${PRUNED_TOOL_RESULT_PREFIX} ${toolResult.toolName} output removed (${tokenEstimate}). Re-run the command if needed.`;
|
|
438
|
+
}
|
|
439
|
+
function inspectToolResultContent(toolResult) {
|
|
440
|
+
const parts = [];
|
|
441
|
+
let textBytes = 0;
|
|
442
|
+
let hasImage = false;
|
|
443
|
+
let firstText;
|
|
444
|
+
for (const block of toolResult.content) {
|
|
445
|
+
if (block.type === "text") {
|
|
446
|
+
if (firstText === undefined) {
|
|
447
|
+
firstText = block.text;
|
|
448
|
+
}
|
|
449
|
+
parts.push(block.text);
|
|
450
|
+
textBytes += Buffer.byteLength(block.text, "utf8");
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (block.type === "image") {
|
|
454
|
+
hasImage = true;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
text: parts.join("\n").trimEnd(),
|
|
459
|
+
textBytes,
|
|
460
|
+
hasImage,
|
|
461
|
+
firstText,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function buildSmartPrunePrompt(args) {
|
|
465
|
+
const lines = [];
|
|
466
|
+
lines.push(`Target pruning token budget: ${args.targetTokens}`);
|
|
467
|
+
lines.push([
|
|
468
|
+
"The budget is the total tokens to prune.",
|
|
469
|
+
"It is better to slightly exceed it than to be significantly below it.",
|
|
470
|
+
].join(" "));
|
|
471
|
+
lines.push('Return only JSON: {"prune":[...]} with tool_call_id values from the conversation history.');
|
|
472
|
+
lines.push("Only include tool_call_id values for bash tool results.");
|
|
473
|
+
if (args.guidance?.trim()) {
|
|
474
|
+
lines.push(`Guidance (prioritize this): ${args.guidance.trim()}`);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
lines.push("Guidance: (none provided, use your best judgment)");
|
|
478
|
+
}
|
|
479
|
+
lines.push("");
|
|
480
|
+
lines.push("Conversation history:");
|
|
481
|
+
lines.push("<conversation>");
|
|
482
|
+
lines.push(...formatPruneConversationHistory(args.history));
|
|
483
|
+
lines.push("</conversation>");
|
|
484
|
+
return lines.join("\n");
|
|
485
|
+
}
|
|
486
|
+
function formatPruneConversationHistory(history) {
|
|
487
|
+
const lines = [];
|
|
488
|
+
for (const message of history) {
|
|
489
|
+
if (message.role === "user") {
|
|
490
|
+
lines.push("<user>");
|
|
491
|
+
appendPruneContentLines(lines, message.content);
|
|
492
|
+
lines.push("</user>");
|
|
493
|
+
lines.push("");
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (message.role === "assistant") {
|
|
497
|
+
lines.push("<assistant>");
|
|
498
|
+
const assistant = message;
|
|
499
|
+
const assistantContent = getAssistantContentOrThrow(assistant, "building smart prune prompt");
|
|
500
|
+
for (const block of assistantContent) {
|
|
501
|
+
if (block.type === "text") {
|
|
502
|
+
appendPruneText(lines, block.text);
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (block.type === "toolCall") {
|
|
506
|
+
const toolCall = block;
|
|
507
|
+
lines.push(buildPruneToolCallTag(toolCall));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
lines.push("</assistant>");
|
|
511
|
+
lines.push("");
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
if (message.role === "toolResult") {
|
|
515
|
+
const toolResult = message;
|
|
516
|
+
lines.push(...buildPruneToolResultLines(toolResult));
|
|
517
|
+
lines.push("");
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (lines[lines.length - 1] === "") {
|
|
521
|
+
lines.pop();
|
|
522
|
+
}
|
|
523
|
+
return lines;
|
|
524
|
+
}
|
|
525
|
+
function appendPruneContentLines(lines, content) {
|
|
526
|
+
if (typeof content === "string") {
|
|
527
|
+
appendPruneText(lines, content);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (!Array.isArray(content)) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
for (const block of content) {
|
|
534
|
+
if (block.type === "text") {
|
|
535
|
+
appendPruneText(lines, block.text);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function appendPruneText(lines, text) {
|
|
540
|
+
const escaped = escapeXml(text);
|
|
541
|
+
const parts = escaped.split(/\r?\n/);
|
|
542
|
+
for (const part of parts) {
|
|
543
|
+
lines.push(part);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function buildPruneToolCallTag(toolCall) {
|
|
547
|
+
const name = escapeXmlAttribute(toolCall.name);
|
|
548
|
+
const id = escapeXmlAttribute(toolCall.id);
|
|
549
|
+
const argsValue = JSON.stringify(toolCall.arguments);
|
|
550
|
+
const args = escapeXmlAttribute(argsValue);
|
|
551
|
+
return `<tool-call name="${name}" id="${id}" args="${args}" />`;
|
|
552
|
+
}
|
|
553
|
+
function buildPruneToolResultLines(toolResult) {
|
|
554
|
+
const lines = [];
|
|
555
|
+
const name = escapeXmlAttribute(toolResult.toolName);
|
|
556
|
+
const toolCallId = escapeXmlAttribute(toolResult.toolCallId);
|
|
557
|
+
const inspection = inspectToolResultContent(toolResult);
|
|
558
|
+
if (inspection.hasImage) {
|
|
559
|
+
lines.push(`<tool-result name="${name}" tool_call_id="${toolCallId}">`);
|
|
560
|
+
lines.push("<preview>[image omitted]</preview>");
|
|
561
|
+
lines.push("</tool-result>");
|
|
562
|
+
return lines;
|
|
563
|
+
}
|
|
564
|
+
const preview = buildPruneToolResultPreview(inspection.text);
|
|
565
|
+
lines.push(`<tool-result name="${name}" tool_call_id="${toolCallId}" total_tokens="${preview.totalTokens}">`);
|
|
566
|
+
lines.push(`<preview max_tokens="${PRUNE_PREVIEW_MAX_TOKENS}">`);
|
|
567
|
+
lines.push(...preview.lines);
|
|
568
|
+
lines.push("</preview>");
|
|
569
|
+
lines.push("</tool-result>");
|
|
570
|
+
return lines;
|
|
571
|
+
}
|
|
572
|
+
function buildPruneToolResultPreview(text) {
|
|
573
|
+
const normalized = text.trimEnd();
|
|
574
|
+
const totalBytes = Buffer.byteLength(normalized, "utf8");
|
|
575
|
+
const totalTokens = bytesToTokens(totalBytes);
|
|
576
|
+
if (totalTokens <= PRUNE_PREVIEW_MAX_TOKENS) {
|
|
577
|
+
const escaped = escapeXml(normalized);
|
|
578
|
+
const lines = escaped ? escaped.split(/\r?\n/) : [""];
|
|
579
|
+
return { lines, totalTokens };
|
|
580
|
+
}
|
|
581
|
+
const head = truncateToBytesFromStart(normalized, tokensToBytes(PRUNE_PREVIEW_MAX_TOKENS));
|
|
582
|
+
const remainingTokens = Math.max(0, totalTokens - PRUNE_PREVIEW_MAX_TOKENS);
|
|
583
|
+
const suffix = `...${remainingTokens} more tokens...`;
|
|
584
|
+
const previewText = head.endsWith("\n") || head === "" ? `${head}${suffix}` : `${head}\n${suffix}`;
|
|
585
|
+
const escaped = escapeXml(previewText);
|
|
586
|
+
const lines = escaped ? escaped.split(/\r?\n/) : [""];
|
|
587
|
+
return { lines, totalTokens };
|
|
588
|
+
}
|
|
589
|
+
function escapeXml(text) {
|
|
590
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
591
|
+
}
|
|
592
|
+
function escapeXmlAttribute(text) {
|
|
593
|
+
return escapeXml(text).replace(/"/g, """).replace(/'/g, "'");
|
|
594
|
+
}
|
|
595
|
+
//# sourceMappingURL=pruning.js.map
|