@iinm/plain-agent 1.13.0 → 1.14.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 +13 -2
- package/config/config.predefined.json +150 -42
- package/package.json +1 -1
- package/src/agent.d.ts +4 -0
- package/src/agent.mjs +4 -0
- package/src/agentLoop.mjs +49 -1
- package/src/cli/commands.mjs +8 -9
- package/src/cli/interactive.mjs +1 -1
- package/src/config.d.ts +4 -0
- package/src/config.mjs +28 -0
- package/src/main.mjs +9 -1
- package/src/modelDefinition.d.ts +6 -0
- package/src/prompt.mjs +39 -0
- package/src/tokenUsage.mjs +28 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Plain Agent
|
|
2
2
|
|
|
3
3
|
[](https://github.com/iinm/plain-agent/actions/workflows/github-code-scanning/codeql)
|
|
4
|
-
[](https://socket.dev/npm/package/@iinm/plain-agent)
|
|
5
5
|
[](https://packagephobia.com/result?p=@iinm/plain-agent)
|
|
6
6
|
|
|
7
7
|
A lightweight terminal-based coding agent focused on safety and low token cost
|
|
@@ -229,7 +229,7 @@ A few design choices keep token usage low:
|
|
|
229
229
|
|
|
230
230
|
- Minimal system prompt — the [system prompt](https://github.com/iinm/plain-agent/blob/main/src/prompt.mjs) contains only what the agent needs to function.
|
|
231
231
|
- Output truncation — when a command or MCP tool produces large output, it is truncated and saved to a file. The agent can then read only the relevant parts.
|
|
232
|
-
- Context compaction —
|
|
232
|
+
- Context compaction — run `/compact` to discard old messages and reload task state from a memory file. This also happens automatically when input tokens exceed a configurable soft limit.
|
|
233
233
|
- MCP tool filtering — MCP servers often expose many tools. Use `enabledTools` in the server config to enable only the ones you need, which reduces the number of tool definitions sent to the model.
|
|
234
234
|
|
|
235
235
|
### Claude Code Compatibility
|
|
@@ -745,6 +745,17 @@ Files are loaded in the following order. Settings in later files override earlie
|
|
|
745
745
|
}
|
|
746
746
|
},
|
|
747
747
|
|
|
748
|
+
// Auto-compact: when input tokens exceed the soft limit after a tool execution,
|
|
749
|
+
// the agent is prompted to update the memory file and call compact_context.
|
|
750
|
+
// Reduces noise and token costs before hitting the model's hard limit.
|
|
751
|
+
"autoCompact": {
|
|
752
|
+
"softLimit": 120000,
|
|
753
|
+
// Optional: override per model (prefix match on name+variant)
|
|
754
|
+
"softLimitPerModelPrefix": {
|
|
755
|
+
"gemini-2.5-pro": 500000
|
|
756
|
+
}
|
|
757
|
+
},
|
|
758
|
+
|
|
748
759
|
// Override the default notification command
|
|
749
760
|
"notifyCmd": { "command": "plain-notify-desktop", "args": [] }
|
|
750
761
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"autoCompact": { "softLimit": 120000 },
|
|
2
3
|
"autoApproval": {
|
|
3
4
|
"defaultAction": "ask",
|
|
4
5
|
"maxApprovals": 50,
|
|
@@ -154,12 +155,18 @@
|
|
|
154
155
|
"tests": [
|
|
155
156
|
{
|
|
156
157
|
"desc": "find should be denied",
|
|
157
|
-
"toolUse": {
|
|
158
|
+
"toolUse": {
|
|
159
|
+
"toolName": "exec_command",
|
|
160
|
+
"input": { "command": "find" }
|
|
161
|
+
},
|
|
158
162
|
"expectedAction": "deny"
|
|
159
163
|
},
|
|
160
164
|
{
|
|
161
165
|
"desc": "grep should be denied",
|
|
162
|
-
"toolUse": {
|
|
166
|
+
"toolUse": {
|
|
167
|
+
"toolName": "exec_command",
|
|
168
|
+
"input": { "command": "grep" }
|
|
169
|
+
},
|
|
163
170
|
"expectedAction": "deny"
|
|
164
171
|
},
|
|
165
172
|
{
|
|
@@ -362,7 +369,12 @@
|
|
|
362
369
|
"toolName": "exec_command",
|
|
363
370
|
"input": {
|
|
364
371
|
"command": "gh",
|
|
365
|
-
"args": [
|
|
372
|
+
"args": [
|
|
373
|
+
"api",
|
|
374
|
+
"--method",
|
|
375
|
+
"GET",
|
|
376
|
+
"repos/owner/repo/pulls/123/comments"
|
|
377
|
+
]
|
|
366
378
|
}
|
|
367
379
|
},
|
|
368
380
|
"expectedAction": "allow"
|
|
@@ -371,7 +383,10 @@
|
|
|
371
383
|
"desc": "gh api without --method should be denied",
|
|
372
384
|
"toolUse": {
|
|
373
385
|
"toolName": "exec_command",
|
|
374
|
-
"input": {
|
|
386
|
+
"input": {
|
|
387
|
+
"command": "gh",
|
|
388
|
+
"args": ["api", "repos/owner/repo/pulls"]
|
|
389
|
+
}
|
|
375
390
|
},
|
|
376
391
|
"expectedAction": "deny"
|
|
377
392
|
},
|
|
@@ -398,7 +413,10 @@
|
|
|
398
413
|
"desc": "bash -c with pipe should not match deny pattern",
|
|
399
414
|
"toolUse": {
|
|
400
415
|
"toolName": "exec_command",
|
|
401
|
-
"input": {
|
|
416
|
+
"input": {
|
|
417
|
+
"command": "bash",
|
|
418
|
+
"args": ["-c", "echo hello | grep hello"]
|
|
419
|
+
}
|
|
402
420
|
},
|
|
403
421
|
"expectedAction": null
|
|
404
422
|
},
|
|
@@ -406,7 +424,10 @@
|
|
|
406
424
|
"desc": "bash -c with redirect should not match deny pattern",
|
|
407
425
|
"toolUse": {
|
|
408
426
|
"toolName": "exec_command",
|
|
409
|
-
"input": {
|
|
427
|
+
"input": {
|
|
428
|
+
"command": "bash",
|
|
429
|
+
"args": ["-c", "echo hello > file.txt"]
|
|
430
|
+
}
|
|
410
431
|
},
|
|
411
432
|
"expectedAction": null
|
|
412
433
|
},
|
|
@@ -422,7 +443,10 @@
|
|
|
422
443
|
"desc": "bash -c with semicolon should not match deny pattern",
|
|
423
444
|
"toolUse": {
|
|
424
445
|
"toolName": "exec_command",
|
|
425
|
-
"input": {
|
|
446
|
+
"input": {
|
|
447
|
+
"command": "bash",
|
|
448
|
+
"args": ["-c", "for i in *.txt; do echo $i; done"]
|
|
449
|
+
}
|
|
426
450
|
},
|
|
427
451
|
"expectedAction": null
|
|
428
452
|
},
|
|
@@ -470,6 +494,9 @@
|
|
|
470
494
|
"cache_read_input_tokens": 0.1,
|
|
471
495
|
"cache_creation_input_tokens": 1.25
|
|
472
496
|
}
|
|
497
|
+
},
|
|
498
|
+
"autoCompact": {
|
|
499
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
473
500
|
}
|
|
474
501
|
},
|
|
475
502
|
{
|
|
@@ -497,6 +524,9 @@
|
|
|
497
524
|
"cache_read_input_tokens": 0.1,
|
|
498
525
|
"cache_creation_input_tokens": 1.25
|
|
499
526
|
}
|
|
527
|
+
},
|
|
528
|
+
"autoCompact": {
|
|
529
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
500
530
|
}
|
|
501
531
|
},
|
|
502
532
|
{
|
|
@@ -525,6 +555,9 @@
|
|
|
525
555
|
"cache_read_input_tokens": 0.3,
|
|
526
556
|
"cache_creation_input_tokens": 3.75
|
|
527
557
|
}
|
|
558
|
+
},
|
|
559
|
+
"autoCompact": {
|
|
560
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
528
561
|
}
|
|
529
562
|
},
|
|
530
563
|
{
|
|
@@ -553,6 +586,9 @@
|
|
|
553
586
|
"cache_read_input_tokens": 0.3,
|
|
554
587
|
"cache_creation_input_tokens": 3.75
|
|
555
588
|
}
|
|
589
|
+
},
|
|
590
|
+
"autoCompact": {
|
|
591
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
556
592
|
}
|
|
557
593
|
},
|
|
558
594
|
{
|
|
@@ -581,6 +617,9 @@
|
|
|
581
617
|
"cache_read_input_tokens": 0.5,
|
|
582
618
|
"cache_creation_input_tokens": 6.25
|
|
583
619
|
}
|
|
620
|
+
},
|
|
621
|
+
"autoCompact": {
|
|
622
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
584
623
|
}
|
|
585
624
|
},
|
|
586
625
|
{
|
|
@@ -609,6 +648,9 @@
|
|
|
609
648
|
"cache_read_input_tokens": 0.5,
|
|
610
649
|
"cache_creation_input_tokens": 6.25
|
|
611
650
|
}
|
|
651
|
+
},
|
|
652
|
+
"autoCompact": {
|
|
653
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
612
654
|
}
|
|
613
655
|
},
|
|
614
656
|
{
|
|
@@ -635,8 +677,11 @@
|
|
|
635
677
|
"input_tokens": 10,
|
|
636
678
|
"output_tokens": 50,
|
|
637
679
|
"cache_read_input_tokens": 1,
|
|
638
|
-
"cache_creation_input_tokens": 12.
|
|
680
|
+
"cache_creation_input_tokens": 12.5
|
|
639
681
|
}
|
|
682
|
+
},
|
|
683
|
+
"autoCompact": {
|
|
684
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
640
685
|
}
|
|
641
686
|
},
|
|
642
687
|
{
|
|
@@ -663,8 +708,11 @@
|
|
|
663
708
|
"input_tokens": 10,
|
|
664
709
|
"output_tokens": 50,
|
|
665
710
|
"cache_read_input_tokens": 1,
|
|
666
|
-
"cache_creation_input_tokens": 12.
|
|
711
|
+
"cache_creation_input_tokens": 12.5
|
|
667
712
|
}
|
|
713
|
+
},
|
|
714
|
+
"autoCompact": {
|
|
715
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
668
716
|
}
|
|
669
717
|
},
|
|
670
718
|
|
|
@@ -692,6 +740,9 @@
|
|
|
692
740
|
"cache_read_input_tokens": 0.1,
|
|
693
741
|
"cache_creation_input_tokens": 1.25
|
|
694
742
|
}
|
|
743
|
+
},
|
|
744
|
+
"autoCompact": {
|
|
745
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
695
746
|
}
|
|
696
747
|
},
|
|
697
748
|
{
|
|
@@ -718,6 +769,9 @@
|
|
|
718
769
|
"cache_read_input_tokens": 0.1,
|
|
719
770
|
"cache_creation_input_tokens": 1.25
|
|
720
771
|
}
|
|
772
|
+
},
|
|
773
|
+
"autoCompact": {
|
|
774
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
721
775
|
}
|
|
722
776
|
},
|
|
723
777
|
{
|
|
@@ -745,6 +799,9 @@
|
|
|
745
799
|
"cache_read_input_tokens": 0.3,
|
|
746
800
|
"cache_creation_input_tokens": 3.75
|
|
747
801
|
}
|
|
802
|
+
},
|
|
803
|
+
"autoCompact": {
|
|
804
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
748
805
|
}
|
|
749
806
|
},
|
|
750
807
|
{
|
|
@@ -772,6 +829,9 @@
|
|
|
772
829
|
"cache_read_input_tokens": 0.3,
|
|
773
830
|
"cache_creation_input_tokens": 3.75
|
|
774
831
|
}
|
|
832
|
+
},
|
|
833
|
+
"autoCompact": {
|
|
834
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
775
835
|
}
|
|
776
836
|
},
|
|
777
837
|
{
|
|
@@ -799,6 +859,9 @@
|
|
|
799
859
|
"cache_read_input_tokens": 0.5,
|
|
800
860
|
"cache_creation_input_tokens": 6.25
|
|
801
861
|
}
|
|
862
|
+
},
|
|
863
|
+
"autoCompact": {
|
|
864
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
802
865
|
}
|
|
803
866
|
},
|
|
804
867
|
{
|
|
@@ -826,6 +889,9 @@
|
|
|
826
889
|
"cache_read_input_tokens": 0.5,
|
|
827
890
|
"cache_creation_input_tokens": 6.25
|
|
828
891
|
}
|
|
892
|
+
},
|
|
893
|
+
"autoCompact": {
|
|
894
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
829
895
|
}
|
|
830
896
|
},
|
|
831
897
|
{
|
|
@@ -851,8 +917,11 @@
|
|
|
851
917
|
"input_tokens": 10,
|
|
852
918
|
"output_tokens": 50,
|
|
853
919
|
"cache_read_input_tokens": 1,
|
|
854
|
-
"cache_creation_input_tokens": 12.
|
|
920
|
+
"cache_creation_input_tokens": 12.5
|
|
855
921
|
}
|
|
922
|
+
},
|
|
923
|
+
"autoCompact": {
|
|
924
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
856
925
|
}
|
|
857
926
|
},
|
|
858
927
|
{
|
|
@@ -878,8 +947,11 @@
|
|
|
878
947
|
"input_tokens": 10,
|
|
879
948
|
"output_tokens": 50,
|
|
880
949
|
"cache_read_input_tokens": 1,
|
|
881
|
-
"cache_creation_input_tokens": 12.
|
|
950
|
+
"cache_creation_input_tokens": 12.5
|
|
882
951
|
}
|
|
952
|
+
},
|
|
953
|
+
"autoCompact": {
|
|
954
|
+
"inputTokensKeys": ["input_tokens", "cache_read_input_tokens"]
|
|
883
955
|
}
|
|
884
956
|
},
|
|
885
957
|
|
|
@@ -912,7 +984,8 @@
|
|
|
912
984
|
"cachedContentTokenCount": -1.35,
|
|
913
985
|
"candidatesTokenCount": 9
|
|
914
986
|
}
|
|
915
|
-
}
|
|
987
|
+
},
|
|
988
|
+
"autoCompact": { "inputTokensKeys": ["promptTokenCount"] }
|
|
916
989
|
},
|
|
917
990
|
{
|
|
918
991
|
"name": "gemini-3.5-flash",
|
|
@@ -943,7 +1016,8 @@
|
|
|
943
1016
|
"cachedContentTokenCount": -1.35,
|
|
944
1017
|
"candidatesTokenCount": 9
|
|
945
1018
|
}
|
|
946
|
-
}
|
|
1019
|
+
},
|
|
1020
|
+
"autoCompact": { "inputTokensKeys": ["promptTokenCount"] }
|
|
947
1021
|
},
|
|
948
1022
|
{
|
|
949
1023
|
"name": "gemini-3.1-pro-preview",
|
|
@@ -974,7 +1048,8 @@
|
|
|
974
1048
|
"cachedContentTokenCount": -1.8,
|
|
975
1049
|
"candidatesTokenCount": 12
|
|
976
1050
|
}
|
|
977
|
-
}
|
|
1051
|
+
},
|
|
1052
|
+
"autoCompact": { "inputTokensKeys": ["promptTokenCount"] }
|
|
978
1053
|
},
|
|
979
1054
|
{
|
|
980
1055
|
"name": "gemini-3.1-pro-preview",
|
|
@@ -1005,7 +1080,8 @@
|
|
|
1005
1080
|
"cachedContentTokenCount": -1.8,
|
|
1006
1081
|
"candidatesTokenCount": 12
|
|
1007
1082
|
}
|
|
1008
|
-
}
|
|
1083
|
+
},
|
|
1084
|
+
"autoCompact": { "inputTokensKeys": ["promptTokenCount"] }
|
|
1009
1085
|
},
|
|
1010
1086
|
|
|
1011
1087
|
{
|
|
@@ -1036,7 +1112,8 @@
|
|
|
1036
1112
|
"cachedContentTokenCount": -1.35,
|
|
1037
1113
|
"candidatesTokenCount": 9
|
|
1038
1114
|
}
|
|
1039
|
-
}
|
|
1115
|
+
},
|
|
1116
|
+
"autoCompact": { "inputTokensKeys": ["promptTokenCount"] }
|
|
1040
1117
|
},
|
|
1041
1118
|
{
|
|
1042
1119
|
"name": "gemini-3.5-flash",
|
|
@@ -1066,7 +1143,8 @@
|
|
|
1066
1143
|
"cachedContentTokenCount": -1.35,
|
|
1067
1144
|
"candidatesTokenCount": 9
|
|
1068
1145
|
}
|
|
1069
|
-
}
|
|
1146
|
+
},
|
|
1147
|
+
"autoCompact": { "inputTokensKeys": ["promptTokenCount"] }
|
|
1070
1148
|
},
|
|
1071
1149
|
{
|
|
1072
1150
|
"name": "gemini-3.1-pro-preview",
|
|
@@ -1096,7 +1174,8 @@
|
|
|
1096
1174
|
"cachedContentTokenCount": -1.8,
|
|
1097
1175
|
"candidatesTokenCount": 12
|
|
1098
1176
|
}
|
|
1099
|
-
}
|
|
1177
|
+
},
|
|
1178
|
+
"autoCompact": { "inputTokensKeys": ["promptTokenCount"] }
|
|
1100
1179
|
},
|
|
1101
1180
|
{
|
|
1102
1181
|
"name": "gemini-3.1-pro-preview",
|
|
@@ -1126,7 +1205,8 @@
|
|
|
1126
1205
|
"cachedContentTokenCount": -1.8,
|
|
1127
1206
|
"candidatesTokenCount": 12
|
|
1128
1207
|
}
|
|
1129
|
-
}
|
|
1208
|
+
},
|
|
1209
|
+
"autoCompact": { "inputTokensKeys": ["promptTokenCount"] }
|
|
1130
1210
|
},
|
|
1131
1211
|
|
|
1132
1212
|
{
|
|
@@ -1154,7 +1234,8 @@
|
|
|
1154
1234
|
"input_tokens_details.cached_tokens": -0.675,
|
|
1155
1235
|
"output_tokens": 4.5
|
|
1156
1236
|
}
|
|
1157
|
-
}
|
|
1237
|
+
},
|
|
1238
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1158
1239
|
},
|
|
1159
1240
|
{
|
|
1160
1241
|
"name": "gpt-5.4-mini",
|
|
@@ -1181,7 +1262,8 @@
|
|
|
1181
1262
|
"input_tokens_details.cached_tokens": -0.675,
|
|
1182
1263
|
"output_tokens": 4.5
|
|
1183
1264
|
}
|
|
1184
|
-
}
|
|
1265
|
+
},
|
|
1266
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1185
1267
|
},
|
|
1186
1268
|
{
|
|
1187
1269
|
"name": "gpt-5.4-mini",
|
|
@@ -1208,7 +1290,8 @@
|
|
|
1208
1290
|
"input_tokens_details.cached_tokens": -0.675,
|
|
1209
1291
|
"output_tokens": 4.5
|
|
1210
1292
|
}
|
|
1211
|
-
}
|
|
1293
|
+
},
|
|
1294
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1212
1295
|
},
|
|
1213
1296
|
{
|
|
1214
1297
|
"name": "gpt-5.5",
|
|
@@ -1235,7 +1318,8 @@
|
|
|
1235
1318
|
"input_tokens_details.cached_tokens": -4.5,
|
|
1236
1319
|
"output_tokens": 30
|
|
1237
1320
|
}
|
|
1238
|
-
}
|
|
1321
|
+
},
|
|
1322
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1239
1323
|
},
|
|
1240
1324
|
{
|
|
1241
1325
|
"name": "gpt-5.5",
|
|
@@ -1262,7 +1346,8 @@
|
|
|
1262
1346
|
"input_tokens_details.cached_tokens": -4.5,
|
|
1263
1347
|
"output_tokens": 30
|
|
1264
1348
|
}
|
|
1265
|
-
}
|
|
1349
|
+
},
|
|
1350
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1266
1351
|
},
|
|
1267
1352
|
{
|
|
1268
1353
|
"name": "gpt-5.5",
|
|
@@ -1289,7 +1374,8 @@
|
|
|
1289
1374
|
"input_tokens_details.cached_tokens": -4.5,
|
|
1290
1375
|
"output_tokens": 30
|
|
1291
1376
|
}
|
|
1292
|
-
}
|
|
1377
|
+
},
|
|
1378
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1293
1379
|
},
|
|
1294
1380
|
|
|
1295
1381
|
{
|
|
@@ -1316,7 +1402,8 @@
|
|
|
1316
1402
|
"input_tokens_details.cached_tokens": -0.675,
|
|
1317
1403
|
"output_tokens": 4.5
|
|
1318
1404
|
}
|
|
1319
|
-
}
|
|
1405
|
+
},
|
|
1406
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1320
1407
|
},
|
|
1321
1408
|
{
|
|
1322
1409
|
"name": "gpt-5.4-mini",
|
|
@@ -1342,7 +1429,8 @@
|
|
|
1342
1429
|
"input_tokens_details.cached_tokens": -0.675,
|
|
1343
1430
|
"output_tokens": 4.5
|
|
1344
1431
|
}
|
|
1345
|
-
}
|
|
1432
|
+
},
|
|
1433
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1346
1434
|
},
|
|
1347
1435
|
{
|
|
1348
1436
|
"name": "gpt-5.4-mini",
|
|
@@ -1368,7 +1456,8 @@
|
|
|
1368
1456
|
"input_tokens_details.cached_tokens": -0.675,
|
|
1369
1457
|
"output_tokens": 4.5
|
|
1370
1458
|
}
|
|
1371
|
-
}
|
|
1459
|
+
},
|
|
1460
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1372
1461
|
},
|
|
1373
1462
|
{
|
|
1374
1463
|
"name": "gpt-5.5",
|
|
@@ -1394,7 +1483,8 @@
|
|
|
1394
1483
|
"input_tokens_details.cached_tokens": -4.5,
|
|
1395
1484
|
"output_tokens": 30
|
|
1396
1485
|
}
|
|
1397
|
-
}
|
|
1486
|
+
},
|
|
1487
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1398
1488
|
},
|
|
1399
1489
|
{
|
|
1400
1490
|
"name": "gpt-5.5",
|
|
@@ -1420,7 +1510,8 @@
|
|
|
1420
1510
|
"input_tokens_details.cached_tokens": -4.5,
|
|
1421
1511
|
"output_tokens": 30
|
|
1422
1512
|
}
|
|
1423
|
-
}
|
|
1513
|
+
},
|
|
1514
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1424
1515
|
},
|
|
1425
1516
|
{
|
|
1426
1517
|
"name": "gpt-5.5",
|
|
@@ -1446,7 +1537,8 @@
|
|
|
1446
1537
|
"input_tokens_details.cached_tokens": -4.5,
|
|
1447
1538
|
"output_tokens": 30
|
|
1448
1539
|
}
|
|
1449
|
-
}
|
|
1540
|
+
},
|
|
1541
|
+
"autoCompact": { "inputTokensKeys": ["input_tokens"] }
|
|
1450
1542
|
},
|
|
1451
1543
|
|
|
1452
1544
|
{
|
|
@@ -1468,9 +1560,10 @@
|
|
|
1468
1560
|
"prices": {
|
|
1469
1561
|
"prompt_tokens": 0.15,
|
|
1470
1562
|
"prompt_tokens_details.cached_tokens": -0.14,
|
|
1471
|
-
"completion_tokens": 0.6
|
|
1563
|
+
"completion_tokens": 0.6
|
|
1472
1564
|
}
|
|
1473
|
-
}
|
|
1565
|
+
},
|
|
1566
|
+
"autoCompact": { "inputTokensKeys": ["prompt_tokens"] }
|
|
1474
1567
|
},
|
|
1475
1568
|
|
|
1476
1569
|
{
|
|
@@ -1494,7 +1587,8 @@
|
|
|
1494
1587
|
"completion_tokens": 3.2,
|
|
1495
1588
|
"prompt_tokens_details.cached_tokens": -0.9
|
|
1496
1589
|
}
|
|
1497
|
-
}
|
|
1590
|
+
},
|
|
1591
|
+
"autoCompact": { "inputTokensKeys": ["prompt_tokens"] }
|
|
1498
1592
|
},
|
|
1499
1593
|
{
|
|
1500
1594
|
"name": "glm-5",
|
|
@@ -1517,7 +1611,8 @@
|
|
|
1517
1611
|
"prompt_tokens": 1,
|
|
1518
1612
|
"completion_tokens": 3.2
|
|
1519
1613
|
}
|
|
1520
|
-
}
|
|
1614
|
+
},
|
|
1615
|
+
"autoCompact": { "inputTokensKeys": ["prompt_tokens"] }
|
|
1521
1616
|
},
|
|
1522
1617
|
|
|
1523
1618
|
{
|
|
@@ -1541,7 +1636,8 @@
|
|
|
1541
1636
|
"prompt_tokens_details.cached_tokens": -1.14,
|
|
1542
1637
|
"completion_tokens": 4.4
|
|
1543
1638
|
}
|
|
1544
|
-
}
|
|
1639
|
+
},
|
|
1640
|
+
"autoCompact": { "inputTokensKeys": ["prompt_tokens"] }
|
|
1545
1641
|
},
|
|
1546
1642
|
|
|
1547
1643
|
{
|
|
@@ -1557,7 +1653,8 @@
|
|
|
1557
1653
|
"model": "Kimi-K2.6",
|
|
1558
1654
|
"reasoning_effort": "high"
|
|
1559
1655
|
}
|
|
1560
|
-
}
|
|
1656
|
+
},
|
|
1657
|
+
"autoCompact": { "inputTokensKeys": ["prompt_tokens"] }
|
|
1561
1658
|
},
|
|
1562
1659
|
{
|
|
1563
1660
|
"name": "kimi-k2.7-code",
|
|
@@ -1580,7 +1677,8 @@
|
|
|
1580
1677
|
"prompt_tokens_details.cached_tokens": -0.76,
|
|
1581
1678
|
"completion_tokens": 4
|
|
1582
1679
|
}
|
|
1583
|
-
}
|
|
1680
|
+
},
|
|
1681
|
+
"autoCompact": { "inputTokensKeys": ["prompt_tokens"] }
|
|
1584
1682
|
},
|
|
1585
1683
|
|
|
1586
1684
|
{
|
|
@@ -1604,7 +1702,8 @@
|
|
|
1604
1702
|
"prompt_tokens_details.cached_tokens": -1.6,
|
|
1605
1703
|
"completion_tokens": 3.48
|
|
1606
1704
|
}
|
|
1607
|
-
}
|
|
1705
|
+
},
|
|
1706
|
+
"autoCompact": { "inputTokensKeys": ["prompt_tokens"] }
|
|
1608
1707
|
},
|
|
1609
1708
|
{
|
|
1610
1709
|
"name": "deepseek-v4-pro",
|
|
@@ -1619,7 +1718,8 @@
|
|
|
1619
1718
|
"model": "DeepSeek-V4-Pro",
|
|
1620
1719
|
"reasoning_effort": "high"
|
|
1621
1720
|
}
|
|
1622
|
-
}
|
|
1721
|
+
},
|
|
1722
|
+
"autoCompact": { "inputTokensKeys": ["prompt_tokens"] }
|
|
1623
1723
|
},
|
|
1624
1724
|
|
|
1625
1725
|
{
|
|
@@ -1643,7 +1743,8 @@
|
|
|
1643
1743
|
"prompt_tokens_details.cached_tokens": -0.24,
|
|
1644
1744
|
"completion_tokens": 1.2
|
|
1645
1745
|
}
|
|
1646
|
-
}
|
|
1746
|
+
},
|
|
1747
|
+
"autoCompact": { "inputTokensKeys": ["prompt_tokens"] }
|
|
1647
1748
|
},
|
|
1648
1749
|
|
|
1649
1750
|
{
|
|
@@ -1667,7 +1768,8 @@
|
|
|
1667
1768
|
"prompt_tokens_details.cached_tokens": -0.32,
|
|
1668
1769
|
"completion_tokens": 1.6
|
|
1669
1770
|
}
|
|
1670
|
-
}
|
|
1771
|
+
},
|
|
1772
|
+
"autoCompact": { "inputTokensKeys": ["prompt_tokens"] }
|
|
1671
1773
|
},
|
|
1672
1774
|
|
|
1673
1775
|
{
|
|
@@ -1689,6 +1791,9 @@
|
|
|
1689
1791
|
}
|
|
1690
1792
|
}
|
|
1691
1793
|
}
|
|
1794
|
+
},
|
|
1795
|
+
"autoCompact": {
|
|
1796
|
+
"inputTokensKeys": ["inputTokens", "cacheReadInputTokens"]
|
|
1692
1797
|
}
|
|
1693
1798
|
},
|
|
1694
1799
|
{
|
|
@@ -1713,6 +1818,9 @@
|
|
|
1713
1818
|
}
|
|
1714
1819
|
}
|
|
1715
1820
|
}
|
|
1821
|
+
},
|
|
1822
|
+
"autoCompact": {
|
|
1823
|
+
"inputTokensKeys": ["inputTokens", "cacheReadInputTokens"]
|
|
1716
1824
|
}
|
|
1717
1825
|
}
|
|
1718
1826
|
]
|
package/package.json
CHANGED
package/src/agent.d.ts
CHANGED
|
@@ -61,4 +61,8 @@ export type AgentConfig = {
|
|
|
61
61
|
};
|
|
62
62
|
/** When provided, the agent restores its state from this snapshot. */
|
|
63
63
|
initialState?: SessionState | null;
|
|
64
|
+
/** Soft limit on input tokens; triggers auto-compact prompt when exceeded. */
|
|
65
|
+
contextSoftLimit?: number;
|
|
66
|
+
/** Keys in providerTokenUsage to sum for input token count. */
|
|
67
|
+
inputTokensKeys?: string[];
|
|
64
68
|
};
|
package/src/agent.mjs
CHANGED
|
@@ -34,6 +34,8 @@ export function createAgent({
|
|
|
34
34
|
modelCostConfig,
|
|
35
35
|
sessionMetadata,
|
|
36
36
|
initialState,
|
|
37
|
+
contextSoftLimit,
|
|
38
|
+
inputTokensKeys,
|
|
37
39
|
}) {
|
|
38
40
|
/** @type {UserEventEmitter} */
|
|
39
41
|
const userEventEmitter = new EventEmitter();
|
|
@@ -195,6 +197,8 @@ export function createAgent({
|
|
|
195
197
|
toolUseApprover,
|
|
196
198
|
subagentManager,
|
|
197
199
|
pauseSignal,
|
|
200
|
+
contextSoftLimit,
|
|
201
|
+
inputTokensKeys,
|
|
198
202
|
});
|
|
199
203
|
|
|
200
204
|
userEventEmitter.on("userInput", agentLoop.handleUserInput);
|
package/src/agentLoop.mjs
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { styleText } from "node:util";
|
|
10
|
+
import { buildCompactPrompt } from "./prompt.mjs";
|
|
11
|
+
import { extractInputTokenCount } from "./tokenUsage.mjs";
|
|
10
12
|
import { compactContextToolName } from "./tools/compactContext.mjs";
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -55,6 +57,8 @@ function applyCompactContextIfCalled(stateManager, toolUseParts, toolResults) {
|
|
|
55
57
|
* @property {ToolUseApprover} toolUseApprover - Tool use approval checker
|
|
56
58
|
* @property {SubagentManager} subagentManager - Subagent manager instance
|
|
57
59
|
* @property {PauseSignal} pauseSignal - Signal to pause auto-approve after current tool completes
|
|
60
|
+
* @property {number} [contextSoftLimit] - Soft limit on input tokens for auto-compact
|
|
61
|
+
* @property {string[]} [inputTokensKeys] - Keys in providerTokenUsage to sum for input token count
|
|
58
62
|
*/
|
|
59
63
|
|
|
60
64
|
/**
|
|
@@ -74,6 +78,8 @@ export function createAgentLoop({
|
|
|
74
78
|
toolUseApprover,
|
|
75
79
|
subagentManager,
|
|
76
80
|
pauseSignal,
|
|
81
|
+
contextSoftLimit,
|
|
82
|
+
inputTokensKeys,
|
|
77
83
|
}) {
|
|
78
84
|
const inputHandler = createInputHandler({
|
|
79
85
|
stateManager,
|
|
@@ -102,7 +108,7 @@ export function createAgentLoop({
|
|
|
102
108
|
async function runTurnLoop() {
|
|
103
109
|
let thinkingLoops = 0;
|
|
104
110
|
const maxThinkingLoops = 5;
|
|
105
|
-
|
|
111
|
+
let turnsAfterCompactPrompt = -1;
|
|
106
112
|
while (true) {
|
|
107
113
|
// Check if auto-approve was paused by Ctrl-C during tool execution
|
|
108
114
|
if (pauseSignal.isPaused()) {
|
|
@@ -206,6 +212,13 @@ export function createAgentLoop({
|
|
|
206
212
|
break;
|
|
207
213
|
}
|
|
208
214
|
|
|
215
|
+
// Ctrl-C during model call: skip execution and ask for approval
|
|
216
|
+
if (pauseSignal.isPaused()) {
|
|
217
|
+
pauseSignal.reset();
|
|
218
|
+
agentEventEmitter.emit("toolUseRequest", toolUseParts.length);
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
|
|
209
222
|
const executionResult = await toolExecutor.executeBatch(toolUseParts);
|
|
210
223
|
|
|
211
224
|
if (!executionResult.success) {
|
|
@@ -233,6 +246,7 @@ export function createAgentLoop({
|
|
|
233
246
|
if (
|
|
234
247
|
applyCompactContextIfCalled(stateManager, toolUseParts, toolResults)
|
|
235
248
|
) {
|
|
249
|
+
turnsAfterCompactPrompt = -1;
|
|
236
250
|
continue;
|
|
237
251
|
}
|
|
238
252
|
|
|
@@ -247,6 +261,40 @@ export function createAgentLoop({
|
|
|
247
261
|
} else {
|
|
248
262
|
stateManager.appendMessages([{ role: "user", content: toolResults }]);
|
|
249
263
|
}
|
|
264
|
+
|
|
265
|
+
// Auto-compact: insert prompt if context exceeds soft limit
|
|
266
|
+
if (contextSoftLimit && inputTokensKeys && providerTokenUsage) {
|
|
267
|
+
const inputTokens = extractInputTokenCount(
|
|
268
|
+
providerTokenUsage,
|
|
269
|
+
inputTokensKeys,
|
|
270
|
+
);
|
|
271
|
+
if (inputTokens !== undefined && inputTokens > contextSoftLimit) {
|
|
272
|
+
if (0 <= turnsAfterCompactPrompt && turnsAfterCompactPrompt < 3) {
|
|
273
|
+
turnsAfterCompactPrompt += 1;
|
|
274
|
+
} else {
|
|
275
|
+
stateManager.appendMessages([
|
|
276
|
+
{
|
|
277
|
+
role: "user",
|
|
278
|
+
content: [
|
|
279
|
+
{
|
|
280
|
+
type: "text",
|
|
281
|
+
text: buildCompactPrompt({
|
|
282
|
+
isSubagent: subagentManager.isSubagentActive(),
|
|
283
|
+
}),
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
]);
|
|
288
|
+
turnsAfterCompactPrompt = 0;
|
|
289
|
+
console.error(
|
|
290
|
+
styleText(
|
|
291
|
+
"yellow",
|
|
292
|
+
`\nContext exceeded soft limit (${inputTokens.toLocaleString()} / ${contextSoftLimit.toLocaleString()} tokens). Auto-compact prompt inserted.`,
|
|
293
|
+
),
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
250
298
|
}
|
|
251
299
|
}
|
|
252
300
|
|
package/src/cli/commands.mjs
CHANGED
|
@@ -8,7 +8,10 @@ import { styleText } from "node:util";
|
|
|
8
8
|
import { loadAgentRoles } from "../context/loadAgentRoles.mjs";
|
|
9
9
|
import { loadPrompts } from "../context/loadPrompts.mjs";
|
|
10
10
|
import { loadUserMessageContext } from "../context/loadUserMessageContext.mjs";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
buildCompactPrompt,
|
|
13
|
+
CLAUDE_CODE_COMPATIBILITY_NOTES,
|
|
14
|
+
} from "../prompt.mjs";
|
|
12
15
|
import { parseFileRange } from "../utils/parseFileRange.mjs";
|
|
13
16
|
import { readFileRange } from "../utils/readFileRange.mjs";
|
|
14
17
|
import { toOneLine } from "../utils/toOneLine.mjs";
|
|
@@ -140,14 +143,10 @@ export function createCommandHandler({
|
|
|
140
143
|
|
|
141
144
|
// /compact [reason]
|
|
142
145
|
if (/^\/compact( |$)/i.test(inputTrimmed)) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"Compact the conversation context:",
|
|
148
|
-
"1. Update the memory file for the current task so it fully captures the task overview, progress, decisions, and next steps in a self-contained way.",
|
|
149
|
-
'2. Then call the "compact_context" tool alone with that memory file path and a brief reason.',
|
|
150
|
-
].join("\n");
|
|
146
|
+
const message = buildCompactPrompt({
|
|
147
|
+
invocation: inputTrimmed,
|
|
148
|
+
isSubagent: agentCommands.getActiveSubagent() !== null,
|
|
149
|
+
});
|
|
151
150
|
userEventEmitter.emit("userInput", [{ type: "text", text: message }]);
|
|
152
151
|
return "continue";
|
|
153
152
|
}
|
package/src/cli/interactive.mjs
CHANGED
|
@@ -188,7 +188,7 @@ export function startInteractiveSession({
|
|
|
188
188
|
console.error(
|
|
189
189
|
styleText(
|
|
190
190
|
"yellow",
|
|
191
|
-
"\n\n⚠️ Ctrl-C: Auto-approve paused
|
|
191
|
+
"\n\n⚠️ Ctrl-C: Auto-approve paused.\nPress Ctrl-D twice to exit.\n",
|
|
192
192
|
),
|
|
193
193
|
);
|
|
194
194
|
return;
|
package/src/config.d.ts
CHANGED
|
@@ -87,6 +87,10 @@ export type AppConfig = {
|
|
|
87
87
|
};
|
|
88
88
|
mcpServers?: Record<string, MCPServerConfig>;
|
|
89
89
|
notifyCmd?: { command: string; args?: string[] };
|
|
90
|
+
autoCompact?: {
|
|
91
|
+
softLimit?: number;
|
|
92
|
+
softLimitPerModelPrefix?: Record<string, number>;
|
|
93
|
+
};
|
|
90
94
|
claudeCodePlugins?: ClaudeCodePluginRepo[];
|
|
91
95
|
};
|
|
92
96
|
|
package/src/config.mjs
CHANGED
|
@@ -124,6 +124,16 @@ export async function loadAppConfig(options = {}) {
|
|
|
124
124
|
...(merged.mcpServers ?? {}),
|
|
125
125
|
...(config.mcpServers ?? {}),
|
|
126
126
|
},
|
|
127
|
+
autoCompact: config.autoCompact
|
|
128
|
+
? {
|
|
129
|
+
softLimit:
|
|
130
|
+
config.autoCompact.softLimit ?? merged.autoCompact?.softLimit,
|
|
131
|
+
softLimitPerModelPrefix: {
|
|
132
|
+
...(merged.autoCompact?.softLimitPerModelPrefix ?? {}),
|
|
133
|
+
...(config.autoCompact.softLimitPerModelPrefix ?? {}),
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
: merged.autoCompact,
|
|
127
137
|
notifyCmd: config.notifyCmd ?? merged.notifyCmd,
|
|
128
138
|
claudeCodePlugins: [
|
|
129
139
|
...(merged.claudeCodePlugins ?? []),
|
|
@@ -223,3 +233,21 @@ async function trustConfigHash(hash) {
|
|
|
223
233
|
await fs.mkdir(TRUSTED_CONFIG_HASHES_DIR, { recursive: true });
|
|
224
234
|
await fs.writeFile(path.join(TRUSTED_CONFIG_HASHES_DIR, hash), "");
|
|
225
235
|
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Resolve the effective context soft limit for the given model.
|
|
239
|
+
*
|
|
240
|
+
* @param {AppConfig["autoCompact"]} autoCompact
|
|
241
|
+
* @param {string} modelNameWithVariant - e.g. "claude-sonnet-4-6+thinking-high"
|
|
242
|
+
* @returns {number | undefined}
|
|
243
|
+
*/
|
|
244
|
+
export function resolveContextSoftLimit(autoCompact, modelNameWithVariant) {
|
|
245
|
+
if (!autoCompact) return undefined;
|
|
246
|
+
const perPrefix = autoCompact.softLimitPerModelPrefix;
|
|
247
|
+
const matched = perPrefix
|
|
248
|
+
? Object.entries(perPrefix).find(([prefix]) =>
|
|
249
|
+
modelNameWithVariant.startsWith(prefix),
|
|
250
|
+
)?.[1]
|
|
251
|
+
: undefined;
|
|
252
|
+
return matched ?? autoCompact.softLimit ?? undefined;
|
|
253
|
+
}
|
package/src/main.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { startBatchSession } from "./cli/batch.mjs";
|
|
|
16
16
|
import { runCostCommand } from "./cli/cost.mjs";
|
|
17
17
|
import { startInteractiveSession } from "./cli/interactive.mjs";
|
|
18
18
|
import { runTestApprovalCommand } from "./cli/testApproval.mjs";
|
|
19
|
-
import { loadAppConfig } from "./config.mjs";
|
|
19
|
+
import { loadAppConfig, resolveContextSoftLimit } from "./config.mjs";
|
|
20
20
|
import { loadAgentRoles } from "./context/loadAgentRoles.mjs";
|
|
21
21
|
import { loadPrompts } from "./context/loadPrompts.mjs";
|
|
22
22
|
import { AGENT_PROJECT_METADATA_DIR, USER_NAME } from "./env.mjs";
|
|
@@ -428,6 +428,12 @@ export async function main(argv = process.argv) {
|
|
|
428
428
|
},
|
|
429
429
|
});
|
|
430
430
|
|
|
431
|
+
// Resolve context soft limit from autoCompact config
|
|
432
|
+
const contextSoftLimit = resolveContextSoftLimit(
|
|
433
|
+
appConfig.autoCompact,
|
|
434
|
+
modelNameWithVariant,
|
|
435
|
+
);
|
|
436
|
+
|
|
431
437
|
const { userEventEmitter, agentEventEmitter, agentCommands } = createAgent({
|
|
432
438
|
callModel: agentCallModel,
|
|
433
439
|
prompt,
|
|
@@ -442,6 +448,8 @@ export async function main(argv = process.argv) {
|
|
|
442
448
|
startTime,
|
|
443
449
|
},
|
|
444
450
|
initialState: resumedState,
|
|
451
|
+
contextSoftLimit,
|
|
452
|
+
inputTokensKeys: modelDef.autoCompact?.inputTokensKeys,
|
|
445
453
|
});
|
|
446
454
|
|
|
447
455
|
const sessionOptions = {
|
package/src/modelDefinition.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export type ModelDefinition = {
|
|
|
10
10
|
platform: PlatformConfig;
|
|
11
11
|
model: ModelConfig;
|
|
12
12
|
cost?: CostConfig;
|
|
13
|
+
autoCompact?: ModelAutoCompactConfig;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type ModelAutoCompactConfig = {
|
|
17
|
+
/** Keys in providerTokenUsage whose values are summed to get the input token count. */
|
|
18
|
+
inputTokensKeys: string[];
|
|
13
19
|
};
|
|
14
20
|
|
|
15
21
|
export type PlatformConfig =
|
package/src/prompt.mjs
CHANGED
|
@@ -105,3 +105,42 @@ export const CLAUDE_CODE_COMPATIBILITY_NOTES = `# Environment Constraints
|
|
|
105
105
|
- Subagents cannot run in parallel. Switch to them one at a time.
|
|
106
106
|
- Use AGENTS.md instead when CLAUDE.md is absent.
|
|
107
107
|
- If instructed to use "haiku agent", "sonnet agent", or "opus agent", use "worker" instead.`;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Build the compact context prompt message text.
|
|
111
|
+
*
|
|
112
|
+
* Shared between the interactive `/compact` command and the automatic
|
|
113
|
+
* soft-limit prompt insertion in the agent loop.
|
|
114
|
+
*
|
|
115
|
+
* @param {object} [options]
|
|
116
|
+
* @param {string} [options.invocation] - The invocation string (e.g., "/compact reason").
|
|
117
|
+
* When omitted, a generic auto-compact preamble is used instead.
|
|
118
|
+
* @param {boolean} [options.isSubagent] - When true, instructs the model to
|
|
119
|
+
* return to the main agent via `switch_to_main_agent` before compacting.
|
|
120
|
+
* @returns {string}
|
|
121
|
+
*/
|
|
122
|
+
export function buildCompactPrompt(options) {
|
|
123
|
+
const { invocation, isSubagent } = options ?? {};
|
|
124
|
+
|
|
125
|
+
const preamble = invocation
|
|
126
|
+
? `System: This prompt was invoked as "${invocation}".`
|
|
127
|
+
: "System: Context is approaching the soft limit.";
|
|
128
|
+
|
|
129
|
+
if (isSubagent) {
|
|
130
|
+
return [
|
|
131
|
+
preamble,
|
|
132
|
+
"",
|
|
133
|
+
"The context is growing large. Wrap up your current work:",
|
|
134
|
+
"1. Summarize your progress and findings so far in the memory file.",
|
|
135
|
+
'2. Call "switch_to_main_agent" to hand back control to the main agent.',
|
|
136
|
+
].join("\n");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return [
|
|
140
|
+
preamble,
|
|
141
|
+
"",
|
|
142
|
+
"Compact the conversation context:",
|
|
143
|
+
"1. Update the memory file for the current task so it fully captures the task overview, progress, decisions, and next steps in a self-contained way.",
|
|
144
|
+
'2. Then call the "compact_context" tool alone with that memory file path and a brief reason.',
|
|
145
|
+
].join("\n");
|
|
146
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { ProviderTokenUsage } from "./model"
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract the input (prompt/context) token count from a single turn's
|
|
7
|
+
* provider token usage object by summing the values of the specified keys.
|
|
8
|
+
*
|
|
9
|
+
* Returns `undefined` when no specified key yields a positive number.
|
|
10
|
+
*
|
|
11
|
+
* @param {ProviderTokenUsage} usage
|
|
12
|
+
* @param {string[]} inputTokensKeys - Keys whose numeric values are summed.
|
|
13
|
+
* @returns {number | undefined}
|
|
14
|
+
*/
|
|
15
|
+
export function extractInputTokenCount(usage, inputTokensKeys) {
|
|
16
|
+
let total = 0;
|
|
17
|
+
let found = false;
|
|
18
|
+
|
|
19
|
+
for (const key of inputTokensKeys) {
|
|
20
|
+
const value = usage[key];
|
|
21
|
+
if (typeof value === "number" && value > 0) {
|
|
22
|
+
total += value;
|
|
23
|
+
found = true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return found ? total : undefined;
|
|
28
|
+
}
|