@quantish/agent 0.1.50 → 0.1.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +164 -270
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3579,195 +3579,22 @@ function createLLMProvider(config) {
|
|
|
3579
3579
|
}
|
|
3580
3580
|
|
|
3581
3581
|
// src/agent/loop.ts
|
|
3582
|
-
var MAX_TOOL_RESULT_CHARS = 8e3;
|
|
3583
|
-
function truncateToolResult(result, toolName) {
|
|
3584
|
-
const resultStr = JSON.stringify(result);
|
|
3585
|
-
if (resultStr.length <= MAX_TOOL_RESULT_CHARS) {
|
|
3586
|
-
return result;
|
|
3587
|
-
}
|
|
3588
|
-
if (typeof result === "object" && result !== null) {
|
|
3589
|
-
const obj = result;
|
|
3590
|
-
if (Array.isArray(obj.content) && obj.content.length > 0) {
|
|
3591
|
-
const firstContent = obj.content[0];
|
|
3592
|
-
if (firstContent?.type === "text" && typeof firstContent.text === "string") {
|
|
3593
|
-
try {
|
|
3594
|
-
const innerData = JSON.parse(firstContent.text);
|
|
3595
|
-
const truncatedInner = truncateDataObject(innerData);
|
|
3596
|
-
return {
|
|
3597
|
-
content: [{
|
|
3598
|
-
type: "text",
|
|
3599
|
-
text: JSON.stringify(truncatedInner)
|
|
3600
|
-
}]
|
|
3601
|
-
};
|
|
3602
|
-
} catch {
|
|
3603
|
-
const truncatedText = firstContent.text.length > MAX_TOOL_RESULT_CHARS ? firstContent.text.substring(0, MAX_TOOL_RESULT_CHARS) + "... [truncated]" : firstContent.text;
|
|
3604
|
-
return {
|
|
3605
|
-
content: [{
|
|
3606
|
-
type: "text",
|
|
3607
|
-
text: truncatedText
|
|
3608
|
-
}]
|
|
3609
|
-
};
|
|
3610
|
-
}
|
|
3611
|
-
}
|
|
3612
|
-
}
|
|
3613
|
-
}
|
|
3614
|
-
if (Array.isArray(result)) {
|
|
3615
|
-
return truncateArray(result);
|
|
3616
|
-
}
|
|
3617
|
-
if (typeof result === "object" && result !== null) {
|
|
3618
|
-
return truncateDataObject(result);
|
|
3619
|
-
}
|
|
3620
|
-
if (typeof result === "string" && result.length > MAX_TOOL_RESULT_CHARS) {
|
|
3621
|
-
return result.substring(0, MAX_TOOL_RESULT_CHARS) + "... [truncated]";
|
|
3622
|
-
}
|
|
3623
|
-
return result;
|
|
3624
|
-
}
|
|
3625
|
-
function truncateArray(arr) {
|
|
3626
|
-
const MAX_ITEMS = 5;
|
|
3627
|
-
const truncated = arr.slice(0, MAX_ITEMS).map(
|
|
3628
|
-
(item) => typeof item === "object" && item !== null ? truncateObject(item) : item
|
|
3629
|
-
);
|
|
3630
|
-
return {
|
|
3631
|
-
_truncated: arr.length > MAX_ITEMS,
|
|
3632
|
-
_originalCount: arr.length,
|
|
3633
|
-
_note: arr.length > MAX_ITEMS ? `Showing ${MAX_ITEMS} of ${arr.length} items.` : void 0,
|
|
3634
|
-
items: truncated
|
|
3635
|
-
};
|
|
3636
|
-
}
|
|
3637
|
-
function truncateDataObject(obj) {
|
|
3638
|
-
const truncated = {};
|
|
3639
|
-
const MAX_ARRAY_ITEMS = 5;
|
|
3640
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
3641
|
-
if (Array.isArray(value)) {
|
|
3642
|
-
if (value.length > MAX_ARRAY_ITEMS) {
|
|
3643
|
-
truncated[key] = value.slice(0, MAX_ARRAY_ITEMS).map(
|
|
3644
|
-
(item) => typeof item === "object" && item !== null ? truncateObject(item) : item
|
|
3645
|
-
);
|
|
3646
|
-
truncated[`_${key}Count`] = value.length;
|
|
3647
|
-
truncated["_truncated"] = true;
|
|
3648
|
-
} else {
|
|
3649
|
-
truncated[key] = value.map(
|
|
3650
|
-
(item) => typeof item === "object" && item !== null ? truncateObject(item) : item
|
|
3651
|
-
);
|
|
3652
|
-
}
|
|
3653
|
-
} else if (typeof value === "object" && value !== null) {
|
|
3654
|
-
truncated[key] = truncateObject(value);
|
|
3655
|
-
} else if (typeof value === "string" && value.length > 500) {
|
|
3656
|
-
truncated[key] = value.substring(0, 500) + "...";
|
|
3657
|
-
} else {
|
|
3658
|
-
truncated[key] = value;
|
|
3659
|
-
}
|
|
3660
|
-
}
|
|
3661
|
-
return truncated;
|
|
3662
|
-
}
|
|
3663
|
-
var ACTIONABLE_FIELDS = /* @__PURE__ */ new Set([
|
|
3664
|
-
// Market identifiers (required for trading)
|
|
3665
|
-
"conditionId",
|
|
3666
|
-
"tokenId",
|
|
3667
|
-
"marketId",
|
|
3668
|
-
"id",
|
|
3669
|
-
"ticker",
|
|
3670
|
-
// Token info (required for order placement)
|
|
3671
|
-
"token_id",
|
|
3672
|
-
"clobTokenIds",
|
|
3673
|
-
"tokens",
|
|
3674
|
-
// Pricing (required for trading decisions)
|
|
3675
|
-
"price",
|
|
3676
|
-
"probability",
|
|
3677
|
-
"outcomePrices",
|
|
3678
|
-
"bestBid",
|
|
3679
|
-
"bestAsk",
|
|
3680
|
-
// Market identity (for user understanding)
|
|
3681
|
-
"title",
|
|
3682
|
-
"question",
|
|
3683
|
-
"slug",
|
|
3684
|
-
"outcome",
|
|
3685
|
-
"name",
|
|
3686
|
-
// Status info (affects tradability)
|
|
3687
|
-
"active",
|
|
3688
|
-
"closed",
|
|
3689
|
-
"status",
|
|
3690
|
-
"endDate",
|
|
3691
|
-
// Platform (for multi-platform support)
|
|
3692
|
-
"platform",
|
|
3693
|
-
// Nested structures containing price data (Discovery MCP response)
|
|
3694
|
-
"markets",
|
|
3695
|
-
"outcomes"
|
|
3696
|
-
]);
|
|
3697
|
-
var SUMMARY_FIELDS = /* @__PURE__ */ new Set([
|
|
3698
|
-
"volume",
|
|
3699
|
-
"liquidity",
|
|
3700
|
-
"volume24hr"
|
|
3701
|
-
]);
|
|
3702
|
-
var DROP_FIELDS = /* @__PURE__ */ new Set([
|
|
3703
|
-
"description",
|
|
3704
|
-
"rules",
|
|
3705
|
-
"resolutionSource",
|
|
3706
|
-
"image",
|
|
3707
|
-
"icon",
|
|
3708
|
-
"createdAt",
|
|
3709
|
-
"updatedAt",
|
|
3710
|
-
"lastTradePrice",
|
|
3711
|
-
"spread",
|
|
3712
|
-
"acceptingOrders",
|
|
3713
|
-
"acceptingOrdersTimestamp",
|
|
3714
|
-
"minimum_tick_size",
|
|
3715
|
-
"minimum_order_size",
|
|
3716
|
-
"maker_base_fee",
|
|
3717
|
-
"taker_base_fee",
|
|
3718
|
-
"neg_risk",
|
|
3719
|
-
"neg_risk_market_id",
|
|
3720
|
-
"neg_risk_request_id",
|
|
3721
|
-
"notifications_enabled",
|
|
3722
|
-
"is_50_50_outcome",
|
|
3723
|
-
"game_start_time",
|
|
3724
|
-
"seconds_delay",
|
|
3725
|
-
"icon",
|
|
3726
|
-
"fpmm",
|
|
3727
|
-
"rewards",
|
|
3728
|
-
"competitive"
|
|
3729
|
-
]);
|
|
3730
|
-
function truncateObject(obj) {
|
|
3731
|
-
const truncated = {};
|
|
3732
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
3733
|
-
if (DROP_FIELDS.has(key)) continue;
|
|
3734
|
-
if (ACTIONABLE_FIELDS.has(key)) {
|
|
3735
|
-
if (typeof value === "string" && value.length > 150) {
|
|
3736
|
-
truncated[key] = value.substring(0, 150) + "...";
|
|
3737
|
-
} else if (Array.isArray(value)) {
|
|
3738
|
-
truncated[key] = value.slice(0, 10).map((item) => {
|
|
3739
|
-
if (typeof item === "object" && item !== null) {
|
|
3740
|
-
return extractTokenInfo(item);
|
|
3741
|
-
}
|
|
3742
|
-
return item;
|
|
3743
|
-
});
|
|
3744
|
-
} else {
|
|
3745
|
-
truncated[key] = value;
|
|
3746
|
-
}
|
|
3747
|
-
continue;
|
|
3748
|
-
}
|
|
3749
|
-
if (SUMMARY_FIELDS.has(key)) {
|
|
3750
|
-
if (typeof value === "number" || typeof value === "string") {
|
|
3751
|
-
truncated[key] = value;
|
|
3752
|
-
}
|
|
3753
|
-
continue;
|
|
3754
|
-
}
|
|
3755
|
-
if (typeof value !== "object" && JSON.stringify(truncated).length < 800) {
|
|
3756
|
-
truncated[key] = value;
|
|
3757
|
-
}
|
|
3758
|
-
}
|
|
3759
|
-
return truncated;
|
|
3760
|
-
}
|
|
3761
|
-
function extractTokenInfo(token) {
|
|
3762
|
-
return {
|
|
3763
|
-
token_id: token.token_id ?? token.tokenId,
|
|
3764
|
-
outcome: token.outcome ?? token.name,
|
|
3765
|
-
price: token.price ?? token.probability
|
|
3766
|
-
};
|
|
3767
|
-
}
|
|
3768
3582
|
var DEFAULT_SYSTEM_PROMPT = `You are Quantish, an AI trading agent for prediction markets (Polymarket, Kalshi).
|
|
3769
3583
|
|
|
3770
|
-
|
|
3584
|
+
## CRITICAL: Market Display Rules
|
|
3585
|
+
|
|
3586
|
+
When showing market search results, ALWAYS include:
|
|
3587
|
+
- Market title
|
|
3588
|
+
- Platform
|
|
3589
|
+
- **Price/Probability** (REQUIRED - never omit this)
|
|
3590
|
+
- Market ID
|
|
3591
|
+
|
|
3592
|
+
Format market tables like this:
|
|
3593
|
+
| Market | Platform | Price | ID |
|
|
3594
|
+
|--------|----------|-------|-----|
|
|
3595
|
+
| Example market | Polymarket | Yes 45\xA2 / No 55\xA2 | 12345 |
|
|
3596
|
+
|
|
3597
|
+
The price data is in the tool result - extract and display it.
|
|
3771
3598
|
|
|
3772
3599
|
## Building Applications
|
|
3773
3600
|
|
|
@@ -3808,6 +3635,12 @@ var Agent = class _Agent {
|
|
|
3808
3635
|
cost: { inputCost: 0, outputCost: 0, cacheWriteCost: 0, cacheReadCost: 0, totalCost: 0 },
|
|
3809
3636
|
sessionCost: 0
|
|
3810
3637
|
};
|
|
3638
|
+
// Sliding window context management
|
|
3639
|
+
conversationSummary = null;
|
|
3640
|
+
toolHistory = [];
|
|
3641
|
+
exchanges = [];
|
|
3642
|
+
static MAX_TOOL_HISTORY = 10;
|
|
3643
|
+
static MAX_EXCHANGES = 5;
|
|
3811
3644
|
constructor(config) {
|
|
3812
3645
|
this.config = {
|
|
3813
3646
|
enableLocalTools: true,
|
|
@@ -3869,11 +3702,8 @@ var Agent = class _Agent {
|
|
|
3869
3702
|
* Get or create the LLM provider instance
|
|
3870
3703
|
*/
|
|
3871
3704
|
async getOrCreateProvider() {
|
|
3872
|
-
if (this.llmProvider) {
|
|
3873
|
-
return this.llmProvider;
|
|
3874
|
-
}
|
|
3875
3705
|
const allTools = await this.getAllTools();
|
|
3876
|
-
const systemPrompt = this.
|
|
3706
|
+
const systemPrompt = this.buildSystemContext();
|
|
3877
3707
|
const defaultModel = this.config.provider === "openrouter" ? "z-ai/glm-4.7" : DEFAULT_MODEL;
|
|
3878
3708
|
const model = this.config.model ?? defaultModel;
|
|
3879
3709
|
const maxTokens = this.config.maxTokens ?? 8192;
|
|
@@ -3895,14 +3725,9 @@ var Agent = class _Agent {
|
|
|
3895
3725
|
const maxIterations = this.config.maxIterations ?? 200;
|
|
3896
3726
|
const useStreaming = this.config.streaming ?? true;
|
|
3897
3727
|
const provider = await this.getOrCreateProvider();
|
|
3898
|
-
const
|
|
3899
|
-
|
|
3900
|
-
${userMessage}`;
|
|
3901
|
-
this.conversationHistory.push({
|
|
3902
|
-
role: "user",
|
|
3903
|
-
content: contextMessage
|
|
3904
|
-
});
|
|
3728
|
+
const messages = this.buildSlimHistory(userMessage);
|
|
3905
3729
|
this.clearToolCallLoopTracking();
|
|
3730
|
+
let currentTurnMessages = [...messages];
|
|
3906
3731
|
const toolCalls = [];
|
|
3907
3732
|
let iterations = 0;
|
|
3908
3733
|
let finalText = "";
|
|
@@ -3916,7 +3741,7 @@ ${userMessage}`;
|
|
|
3916
3741
|
this.config.onStreamStart?.();
|
|
3917
3742
|
let response;
|
|
3918
3743
|
if (useStreaming) {
|
|
3919
|
-
response = await provider.streamChat(
|
|
3744
|
+
response = await provider.streamChat(currentTurnMessages, {
|
|
3920
3745
|
onText: (text) => {
|
|
3921
3746
|
finalText += text;
|
|
3922
3747
|
this.config.onText?.(text, false);
|
|
@@ -3932,7 +3757,7 @@ ${userMessage}`;
|
|
|
3932
3757
|
this.config.onText?.("", true);
|
|
3933
3758
|
}
|
|
3934
3759
|
} else {
|
|
3935
|
-
response = await provider.chat(
|
|
3760
|
+
response = await provider.chat(currentTurnMessages);
|
|
3936
3761
|
if (response.text) {
|
|
3937
3762
|
finalText += response.text;
|
|
3938
3763
|
this.config.onText?.(response.text, true);
|
|
@@ -3958,10 +3783,6 @@ ${userMessage}`;
|
|
|
3958
3783
|
});
|
|
3959
3784
|
}
|
|
3960
3785
|
if (response.toolCalls.length === 0) {
|
|
3961
|
-
this.conversationHistory.push({
|
|
3962
|
-
role: "assistant",
|
|
3963
|
-
content: responseContent
|
|
3964
|
-
});
|
|
3965
3786
|
break;
|
|
3966
3787
|
}
|
|
3967
3788
|
const toolResults = [];
|
|
@@ -3973,6 +3794,7 @@ ${userMessage}`;
|
|
|
3973
3794
|
);
|
|
3974
3795
|
const success2 = !(result && typeof result === "object" && "error" in result);
|
|
3975
3796
|
this.config.onToolResult?.(toolCall2.name, result, success2);
|
|
3797
|
+
this.addToolHistory(toolCall2.name, toolCall2.input, success2);
|
|
3976
3798
|
toolCalls.push({
|
|
3977
3799
|
name: toolCall2.name,
|
|
3978
3800
|
input: toolCall2.input,
|
|
@@ -3985,19 +3807,21 @@ ${userMessage}`;
|
|
|
3985
3807
|
content: JSON.stringify(result)
|
|
3986
3808
|
});
|
|
3987
3809
|
}
|
|
3988
|
-
|
|
3810
|
+
currentTurnMessages.push({
|
|
3989
3811
|
role: "assistant",
|
|
3990
3812
|
content: responseContent
|
|
3991
3813
|
});
|
|
3992
|
-
|
|
3814
|
+
currentTurnMessages.push({
|
|
3993
3815
|
role: "user",
|
|
3994
3816
|
content: toolResults
|
|
3995
3817
|
});
|
|
3996
|
-
this.truncateLastToolResults();
|
|
3997
3818
|
if (response.stopReason === "end_turn" && response.toolCalls.length === 0) {
|
|
3998
3819
|
break;
|
|
3999
3820
|
}
|
|
4000
3821
|
}
|
|
3822
|
+
if (finalText.trim()) {
|
|
3823
|
+
this.storeTextExchange(userMessage, finalText.trim());
|
|
3824
|
+
}
|
|
4001
3825
|
return {
|
|
4002
3826
|
text: finalText,
|
|
4003
3827
|
toolCalls,
|
|
@@ -4086,17 +3910,11 @@ ${userMessage}`;
|
|
|
4086
3910
|
const maxIterations = this.config.maxIterations ?? 15;
|
|
4087
3911
|
const model = this.config.model ?? "claude-sonnet-4-5-20250929";
|
|
4088
3912
|
const maxTokens = this.config.maxTokens ?? 8192;
|
|
4089
|
-
const systemPrompt = this.
|
|
3913
|
+
const systemPrompt = this.buildSystemContext();
|
|
4090
3914
|
const useStreaming = this.config.streaming ?? true;
|
|
4091
3915
|
const allTools = await this.getAllTools();
|
|
4092
3916
|
const contextManagement = this.config.contextEditing && this.config.contextEditing.length > 0 ? { edits: this.config.contextEditing } : void 0;
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
${userMessage}`;
|
|
4096
|
-
this.conversationHistory.push({
|
|
4097
|
-
role: "user",
|
|
4098
|
-
content: contextMessage
|
|
4099
|
-
});
|
|
3917
|
+
let currentTurnMessages = this.buildSlimHistory(userMessage);
|
|
4100
3918
|
this.clearToolCallLoopTracking();
|
|
4101
3919
|
const toolCalls = [];
|
|
4102
3920
|
let iterations = 0;
|
|
@@ -4126,7 +3944,7 @@ ${userMessage}`;
|
|
|
4126
3944
|
max_tokens: maxTokens,
|
|
4127
3945
|
system: systemWithCache,
|
|
4128
3946
|
tools: allTools,
|
|
4129
|
-
messages:
|
|
3947
|
+
messages: currentTurnMessages
|
|
4130
3948
|
};
|
|
4131
3949
|
if (contextManagement) {
|
|
4132
3950
|
streamOptions.context_management = contextManagement;
|
|
@@ -4168,7 +3986,7 @@ ${userMessage}`;
|
|
|
4168
3986
|
max_tokens: maxTokens,
|
|
4169
3987
|
system: systemWithCache,
|
|
4170
3988
|
tools: allTools,
|
|
4171
|
-
messages:
|
|
3989
|
+
messages: currentTurnMessages
|
|
4172
3990
|
};
|
|
4173
3991
|
if (contextManagement) {
|
|
4174
3992
|
createOptions.context_management = contextManagement;
|
|
@@ -4189,10 +4007,6 @@ ${userMessage}`;
|
|
|
4189
4007
|
}
|
|
4190
4008
|
this.config.onStreamEnd?.();
|
|
4191
4009
|
if (toolUses.length === 0) {
|
|
4192
|
-
this.conversationHistory.push({
|
|
4193
|
-
role: "assistant",
|
|
4194
|
-
content: responseContent
|
|
4195
|
-
});
|
|
4196
4010
|
break;
|
|
4197
4011
|
}
|
|
4198
4012
|
const toolResults = [];
|
|
@@ -4204,6 +4018,7 @@ ${userMessage}`;
|
|
|
4204
4018
|
);
|
|
4205
4019
|
const success2 = !(result && typeof result === "object" && "error" in result);
|
|
4206
4020
|
this.config.onToolResult?.(toolUse.name, result, success2);
|
|
4021
|
+
this.addToolHistory(toolUse.name, toolUse.input, success2);
|
|
4207
4022
|
toolCalls.push({
|
|
4208
4023
|
name: toolUse.name,
|
|
4209
4024
|
input: toolUse.input,
|
|
@@ -4216,19 +4031,21 @@ ${userMessage}`;
|
|
|
4216
4031
|
content: JSON.stringify(result)
|
|
4217
4032
|
});
|
|
4218
4033
|
}
|
|
4219
|
-
|
|
4034
|
+
currentTurnMessages.push({
|
|
4220
4035
|
role: "assistant",
|
|
4221
4036
|
content: responseContent
|
|
4222
4037
|
});
|
|
4223
|
-
|
|
4038
|
+
currentTurnMessages.push({
|
|
4224
4039
|
role: "user",
|
|
4225
4040
|
content: toolResults
|
|
4226
4041
|
});
|
|
4227
|
-
this.truncateLastToolResults();
|
|
4228
4042
|
if (response.stop_reason === "end_turn" && toolUses.length === 0) {
|
|
4229
4043
|
break;
|
|
4230
4044
|
}
|
|
4231
4045
|
}
|
|
4046
|
+
if (finalText.trim()) {
|
|
4047
|
+
this.storeTextExchange(userMessage, finalText.trim());
|
|
4048
|
+
}
|
|
4232
4049
|
return {
|
|
4233
4050
|
text: finalText,
|
|
4234
4051
|
toolCalls,
|
|
@@ -4241,6 +4058,9 @@ ${userMessage}`;
|
|
|
4241
4058
|
*/
|
|
4242
4059
|
clearHistory() {
|
|
4243
4060
|
this.conversationHistory = [];
|
|
4061
|
+
this.conversationSummary = null;
|
|
4062
|
+
this.toolHistory = [];
|
|
4063
|
+
this.exchanges = [];
|
|
4244
4064
|
}
|
|
4245
4065
|
/**
|
|
4246
4066
|
* Get current conversation history
|
|
@@ -4248,6 +4068,126 @@ ${userMessage}`;
|
|
|
4248
4068
|
getHistory() {
|
|
4249
4069
|
return [...this.conversationHistory];
|
|
4250
4070
|
}
|
|
4071
|
+
/**
|
|
4072
|
+
* Extract primary input from tool arguments for compact history.
|
|
4073
|
+
* Returns the most relevant parameter value, truncated if needed.
|
|
4074
|
+
*/
|
|
4075
|
+
extractPrimaryInput(input) {
|
|
4076
|
+
const primaryKeys = ["query", "path", "command", "marketId", "content", "url", "pattern", "ticker"];
|
|
4077
|
+
for (const key of primaryKeys) {
|
|
4078
|
+
if (input[key] && typeof input[key] === "string") {
|
|
4079
|
+
const val = input[key];
|
|
4080
|
+
return val.length > 40 ? val.slice(0, 40) + "..." : val;
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
for (const val of Object.values(input)) {
|
|
4084
|
+
if (typeof val === "string" && val.length > 0) {
|
|
4085
|
+
return val.length > 40 ? val.slice(0, 40) + "..." : val;
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
const firstKey = Object.keys(input)[0];
|
|
4089
|
+
if (firstKey) {
|
|
4090
|
+
const val = String(input[firstKey]);
|
|
4091
|
+
return val.length > 40 ? val.slice(0, 40) + "..." : val;
|
|
4092
|
+
}
|
|
4093
|
+
return "(no input)";
|
|
4094
|
+
}
|
|
4095
|
+
/**
|
|
4096
|
+
* Add a tool call to history after execution.
|
|
4097
|
+
* Keeps only the last 10 entries.
|
|
4098
|
+
*/
|
|
4099
|
+
addToolHistory(tool, input, success2) {
|
|
4100
|
+
this.toolHistory.push({
|
|
4101
|
+
tool,
|
|
4102
|
+
primaryInput: this.extractPrimaryInput(input),
|
|
4103
|
+
success: success2,
|
|
4104
|
+
timestamp: Date.now()
|
|
4105
|
+
});
|
|
4106
|
+
if (this.toolHistory.length > _Agent.MAX_TOOL_HISTORY) {
|
|
4107
|
+
this.toolHistory = this.toolHistory.slice(-_Agent.MAX_TOOL_HISTORY);
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
/**
|
|
4111
|
+
* Format tool history for context injection.
|
|
4112
|
+
* Simple, clean format without emojis.
|
|
4113
|
+
*/
|
|
4114
|
+
formatToolHistory() {
|
|
4115
|
+
if (this.toolHistory.length === 0) return "";
|
|
4116
|
+
const lines = this.toolHistory.map((t) => {
|
|
4117
|
+
const status = t.success ? "ok" : "failed";
|
|
4118
|
+
return `- ${t.tool}: "${t.primaryInput}" - ${status}`;
|
|
4119
|
+
});
|
|
4120
|
+
return "Recent actions:\n" + lines.join("\n");
|
|
4121
|
+
}
|
|
4122
|
+
/**
|
|
4123
|
+
* Add a user/model exchange to history.
|
|
4124
|
+
* If we exceed max exchanges, compact older ones first.
|
|
4125
|
+
* @deprecated Use storeTextExchange instead
|
|
4126
|
+
*/
|
|
4127
|
+
/**
|
|
4128
|
+
* Build the full system context including tool history and summary.
|
|
4129
|
+
*/
|
|
4130
|
+
buildSystemContext() {
|
|
4131
|
+
const basePrompt = this.config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
4132
|
+
const parts = [basePrompt];
|
|
4133
|
+
const toolHistoryStr = this.formatToolHistory();
|
|
4134
|
+
if (toolHistoryStr) {
|
|
4135
|
+
parts.push(toolHistoryStr);
|
|
4136
|
+
}
|
|
4137
|
+
if (this.conversationSummary) {
|
|
4138
|
+
parts.push(`Previous context:
|
|
4139
|
+
${this.conversationSummary}`);
|
|
4140
|
+
}
|
|
4141
|
+
return parts.join("\n\n");
|
|
4142
|
+
}
|
|
4143
|
+
/**
|
|
4144
|
+
* Build messages array from exchanges for API call.
|
|
4145
|
+
* Converts stored exchanges to MessageParam format.
|
|
4146
|
+
*/
|
|
4147
|
+
buildMessagesFromExchanges() {
|
|
4148
|
+
const messages = [];
|
|
4149
|
+
for (const exchange of this.exchanges) {
|
|
4150
|
+
messages.push({ role: "user", content: exchange.userMessage });
|
|
4151
|
+
messages.push({ role: "assistant", content: exchange.assistantResponse });
|
|
4152
|
+
}
|
|
4153
|
+
return messages;
|
|
4154
|
+
}
|
|
4155
|
+
/**
|
|
4156
|
+
* Build slim history for API call: last 2 text exchanges + current user message.
|
|
4157
|
+
* NO tool calls, NO tool results - just text.
|
|
4158
|
+
*/
|
|
4159
|
+
buildSlimHistory(currentUserMessage) {
|
|
4160
|
+
const messages = [];
|
|
4161
|
+
const recentExchanges = this.exchanges.slice(-2);
|
|
4162
|
+
for (const exchange of recentExchanges) {
|
|
4163
|
+
messages.push({ role: "user", content: exchange.userMessage });
|
|
4164
|
+
messages.push({ role: "assistant", content: exchange.assistantResponse });
|
|
4165
|
+
}
|
|
4166
|
+
messages.push({ role: "user", content: currentUserMessage });
|
|
4167
|
+
return messages;
|
|
4168
|
+
}
|
|
4169
|
+
/**
|
|
4170
|
+
* Store a text-only exchange (no tool calls).
|
|
4171
|
+
* Keeps only last 2 exchanges for context.
|
|
4172
|
+
*/
|
|
4173
|
+
storeTextExchange(userMessage, assistantResponse) {
|
|
4174
|
+
this.exchanges.push({
|
|
4175
|
+
userMessage,
|
|
4176
|
+
assistantResponse,
|
|
4177
|
+
timestamp: Date.now()
|
|
4178
|
+
});
|
|
4179
|
+
if (this.exchanges.length > 2) {
|
|
4180
|
+
this.exchanges = this.exchanges.slice(-2);
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
/**
|
|
4184
|
+
* Extract final text response from assistant content blocks.
|
|
4185
|
+
* Filters out tool_use blocks, returns only text.
|
|
4186
|
+
*/
|
|
4187
|
+
extractTextResponse(content) {
|
|
4188
|
+
const textBlocks = content.filter((block) => block.type === "text");
|
|
4189
|
+
return textBlocks.map((block) => block.text).join("\n").trim();
|
|
4190
|
+
}
|
|
4251
4191
|
/**
|
|
4252
4192
|
* Set working directory
|
|
4253
4193
|
*/
|
|
@@ -4260,52 +4200,6 @@ ${userMessage}`;
|
|
|
4260
4200
|
getWorkingDirectory() {
|
|
4261
4201
|
return this.workingDirectory;
|
|
4262
4202
|
}
|
|
4263
|
-
/**
|
|
4264
|
-
* Truncate tool results in the last message of conversation history.
|
|
4265
|
-
*
|
|
4266
|
-
* This is called AFTER Claude has seen the full tool results and responded.
|
|
4267
|
-
* We then replace the full results with truncated versions to save context
|
|
4268
|
-
* on future turns. This way:
|
|
4269
|
-
* - Current turn: Claude sees full data, can display everything to user
|
|
4270
|
-
* - Future turns: Only actionable data (IDs, prices) is in context
|
|
4271
|
-
*/
|
|
4272
|
-
truncateLastToolResults() {
|
|
4273
|
-
for (let i = this.conversationHistory.length - 1; i >= 0; i--) {
|
|
4274
|
-
const message = this.conversationHistory[i];
|
|
4275
|
-
if (message.role === "user" && Array.isArray(message.content)) {
|
|
4276
|
-
const toolResults = message.content.filter(
|
|
4277
|
-
(block) => block.type === "tool_result"
|
|
4278
|
-
);
|
|
4279
|
-
if (toolResults.length > 0) {
|
|
4280
|
-
const truncatedContent = message.content.map((block) => {
|
|
4281
|
-
if (block.type === "tool_result" && typeof block.content === "string") {
|
|
4282
|
-
try {
|
|
4283
|
-
const fullResult = JSON.parse(block.content);
|
|
4284
|
-
const truncatedResult = truncateToolResult(fullResult, "unknown");
|
|
4285
|
-
return {
|
|
4286
|
-
...block,
|
|
4287
|
-
content: JSON.stringify(truncatedResult)
|
|
4288
|
-
};
|
|
4289
|
-
} catch {
|
|
4290
|
-
if (block.content.length > MAX_TOOL_RESULT_CHARS) {
|
|
4291
|
-
return {
|
|
4292
|
-
...block,
|
|
4293
|
-
content: block.content.substring(0, MAX_TOOL_RESULT_CHARS) + "... [truncated for context]"
|
|
4294
|
-
};
|
|
4295
|
-
}
|
|
4296
|
-
}
|
|
4297
|
-
}
|
|
4298
|
-
return block;
|
|
4299
|
-
});
|
|
4300
|
-
this.conversationHistory[i] = {
|
|
4301
|
-
...message,
|
|
4302
|
-
content: truncatedContent
|
|
4303
|
-
};
|
|
4304
|
-
break;
|
|
4305
|
-
}
|
|
4306
|
-
}
|
|
4307
|
-
}
|
|
4308
|
-
}
|
|
4309
4203
|
/**
|
|
4310
4204
|
* Update cumulative token usage from API response
|
|
4311
4205
|
* @param usage - Token counts from the API response
|