@quantish/agent 0.1.51 → 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/LICENSE +2 -0
- package/dist/index.js +164 -50
- package/package.json +1 -1
package/LICENSE
CHANGED
package/dist/index.js
CHANGED
|
@@ -3581,7 +3581,20 @@ function createLLMProvider(config) {
|
|
|
3581
3581
|
// src/agent/loop.ts
|
|
3582
3582
|
var DEFAULT_SYSTEM_PROMPT = `You are Quantish, an AI trading agent for prediction markets (Polymarket, Kalshi).
|
|
3583
3583
|
|
|
3584
|
-
|
|
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.
|
|
3585
3598
|
|
|
3586
3599
|
## Building Applications
|
|
3587
3600
|
|
|
@@ -3622,6 +3635,12 @@ var Agent = class _Agent {
|
|
|
3622
3635
|
cost: { inputCost: 0, outputCost: 0, cacheWriteCost: 0, cacheReadCost: 0, totalCost: 0 },
|
|
3623
3636
|
sessionCost: 0
|
|
3624
3637
|
};
|
|
3638
|
+
// Sliding window context management
|
|
3639
|
+
conversationSummary = null;
|
|
3640
|
+
toolHistory = [];
|
|
3641
|
+
exchanges = [];
|
|
3642
|
+
static MAX_TOOL_HISTORY = 10;
|
|
3643
|
+
static MAX_EXCHANGES = 5;
|
|
3625
3644
|
constructor(config) {
|
|
3626
3645
|
this.config = {
|
|
3627
3646
|
enableLocalTools: true,
|
|
@@ -3683,11 +3702,8 @@ var Agent = class _Agent {
|
|
|
3683
3702
|
* Get or create the LLM provider instance
|
|
3684
3703
|
*/
|
|
3685
3704
|
async getOrCreateProvider() {
|
|
3686
|
-
if (this.llmProvider) {
|
|
3687
|
-
return this.llmProvider;
|
|
3688
|
-
}
|
|
3689
3705
|
const allTools = await this.getAllTools();
|
|
3690
|
-
const systemPrompt = this.
|
|
3706
|
+
const systemPrompt = this.buildSystemContext();
|
|
3691
3707
|
const defaultModel = this.config.provider === "openrouter" ? "z-ai/glm-4.7" : DEFAULT_MODEL;
|
|
3692
3708
|
const model = this.config.model ?? defaultModel;
|
|
3693
3709
|
const maxTokens = this.config.maxTokens ?? 8192;
|
|
@@ -3709,14 +3725,9 @@ var Agent = class _Agent {
|
|
|
3709
3725
|
const maxIterations = this.config.maxIterations ?? 200;
|
|
3710
3726
|
const useStreaming = this.config.streaming ?? true;
|
|
3711
3727
|
const provider = await this.getOrCreateProvider();
|
|
3712
|
-
const
|
|
3713
|
-
|
|
3714
|
-
${userMessage}`;
|
|
3715
|
-
this.conversationHistory.push({
|
|
3716
|
-
role: "user",
|
|
3717
|
-
content: contextMessage
|
|
3718
|
-
});
|
|
3728
|
+
const messages = this.buildSlimHistory(userMessage);
|
|
3719
3729
|
this.clearToolCallLoopTracking();
|
|
3730
|
+
let currentTurnMessages = [...messages];
|
|
3720
3731
|
const toolCalls = [];
|
|
3721
3732
|
let iterations = 0;
|
|
3722
3733
|
let finalText = "";
|
|
@@ -3730,7 +3741,7 @@ ${userMessage}`;
|
|
|
3730
3741
|
this.config.onStreamStart?.();
|
|
3731
3742
|
let response;
|
|
3732
3743
|
if (useStreaming) {
|
|
3733
|
-
response = await provider.streamChat(
|
|
3744
|
+
response = await provider.streamChat(currentTurnMessages, {
|
|
3734
3745
|
onText: (text) => {
|
|
3735
3746
|
finalText += text;
|
|
3736
3747
|
this.config.onText?.(text, false);
|
|
@@ -3746,7 +3757,7 @@ ${userMessage}`;
|
|
|
3746
3757
|
this.config.onText?.("", true);
|
|
3747
3758
|
}
|
|
3748
3759
|
} else {
|
|
3749
|
-
response = await provider.chat(
|
|
3760
|
+
response = await provider.chat(currentTurnMessages);
|
|
3750
3761
|
if (response.text) {
|
|
3751
3762
|
finalText += response.text;
|
|
3752
3763
|
this.config.onText?.(response.text, true);
|
|
@@ -3772,10 +3783,6 @@ ${userMessage}`;
|
|
|
3772
3783
|
});
|
|
3773
3784
|
}
|
|
3774
3785
|
if (response.toolCalls.length === 0) {
|
|
3775
|
-
this.conversationHistory.push({
|
|
3776
|
-
role: "assistant",
|
|
3777
|
-
content: responseContent
|
|
3778
|
-
});
|
|
3779
3786
|
break;
|
|
3780
3787
|
}
|
|
3781
3788
|
const toolResults = [];
|
|
@@ -3787,6 +3794,7 @@ ${userMessage}`;
|
|
|
3787
3794
|
);
|
|
3788
3795
|
const success2 = !(result && typeof result === "object" && "error" in result);
|
|
3789
3796
|
this.config.onToolResult?.(toolCall2.name, result, success2);
|
|
3797
|
+
this.addToolHistory(toolCall2.name, toolCall2.input, success2);
|
|
3790
3798
|
toolCalls.push({
|
|
3791
3799
|
name: toolCall2.name,
|
|
3792
3800
|
input: toolCall2.input,
|
|
@@ -3799,19 +3807,21 @@ ${userMessage}`;
|
|
|
3799
3807
|
content: JSON.stringify(result)
|
|
3800
3808
|
});
|
|
3801
3809
|
}
|
|
3802
|
-
|
|
3810
|
+
currentTurnMessages.push({
|
|
3803
3811
|
role: "assistant",
|
|
3804
3812
|
content: responseContent
|
|
3805
3813
|
});
|
|
3806
|
-
|
|
3814
|
+
currentTurnMessages.push({
|
|
3807
3815
|
role: "user",
|
|
3808
3816
|
content: toolResults
|
|
3809
3817
|
});
|
|
3810
|
-
this.truncateLastToolResults();
|
|
3811
3818
|
if (response.stopReason === "end_turn" && response.toolCalls.length === 0) {
|
|
3812
3819
|
break;
|
|
3813
3820
|
}
|
|
3814
3821
|
}
|
|
3822
|
+
if (finalText.trim()) {
|
|
3823
|
+
this.storeTextExchange(userMessage, finalText.trim());
|
|
3824
|
+
}
|
|
3815
3825
|
return {
|
|
3816
3826
|
text: finalText,
|
|
3817
3827
|
toolCalls,
|
|
@@ -3900,17 +3910,11 @@ ${userMessage}`;
|
|
|
3900
3910
|
const maxIterations = this.config.maxIterations ?? 15;
|
|
3901
3911
|
const model = this.config.model ?? "claude-sonnet-4-5-20250929";
|
|
3902
3912
|
const maxTokens = this.config.maxTokens ?? 8192;
|
|
3903
|
-
const systemPrompt = this.
|
|
3913
|
+
const systemPrompt = this.buildSystemContext();
|
|
3904
3914
|
const useStreaming = this.config.streaming ?? true;
|
|
3905
3915
|
const allTools = await this.getAllTools();
|
|
3906
3916
|
const contextManagement = this.config.contextEditing && this.config.contextEditing.length > 0 ? { edits: this.config.contextEditing } : void 0;
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
${userMessage}`;
|
|
3910
|
-
this.conversationHistory.push({
|
|
3911
|
-
role: "user",
|
|
3912
|
-
content: contextMessage
|
|
3913
|
-
});
|
|
3917
|
+
let currentTurnMessages = this.buildSlimHistory(userMessage);
|
|
3914
3918
|
this.clearToolCallLoopTracking();
|
|
3915
3919
|
const toolCalls = [];
|
|
3916
3920
|
let iterations = 0;
|
|
@@ -3940,7 +3944,7 @@ ${userMessage}`;
|
|
|
3940
3944
|
max_tokens: maxTokens,
|
|
3941
3945
|
system: systemWithCache,
|
|
3942
3946
|
tools: allTools,
|
|
3943
|
-
messages:
|
|
3947
|
+
messages: currentTurnMessages
|
|
3944
3948
|
};
|
|
3945
3949
|
if (contextManagement) {
|
|
3946
3950
|
streamOptions.context_management = contextManagement;
|
|
@@ -3982,7 +3986,7 @@ ${userMessage}`;
|
|
|
3982
3986
|
max_tokens: maxTokens,
|
|
3983
3987
|
system: systemWithCache,
|
|
3984
3988
|
tools: allTools,
|
|
3985
|
-
messages:
|
|
3989
|
+
messages: currentTurnMessages
|
|
3986
3990
|
};
|
|
3987
3991
|
if (contextManagement) {
|
|
3988
3992
|
createOptions.context_management = contextManagement;
|
|
@@ -4003,10 +4007,6 @@ ${userMessage}`;
|
|
|
4003
4007
|
}
|
|
4004
4008
|
this.config.onStreamEnd?.();
|
|
4005
4009
|
if (toolUses.length === 0) {
|
|
4006
|
-
this.conversationHistory.push({
|
|
4007
|
-
role: "assistant",
|
|
4008
|
-
content: responseContent
|
|
4009
|
-
});
|
|
4010
4010
|
break;
|
|
4011
4011
|
}
|
|
4012
4012
|
const toolResults = [];
|
|
@@ -4018,6 +4018,7 @@ ${userMessage}`;
|
|
|
4018
4018
|
);
|
|
4019
4019
|
const success2 = !(result && typeof result === "object" && "error" in result);
|
|
4020
4020
|
this.config.onToolResult?.(toolUse.name, result, success2);
|
|
4021
|
+
this.addToolHistory(toolUse.name, toolUse.input, success2);
|
|
4021
4022
|
toolCalls.push({
|
|
4022
4023
|
name: toolUse.name,
|
|
4023
4024
|
input: toolUse.input,
|
|
@@ -4030,19 +4031,21 @@ ${userMessage}`;
|
|
|
4030
4031
|
content: JSON.stringify(result)
|
|
4031
4032
|
});
|
|
4032
4033
|
}
|
|
4033
|
-
|
|
4034
|
+
currentTurnMessages.push({
|
|
4034
4035
|
role: "assistant",
|
|
4035
4036
|
content: responseContent
|
|
4036
4037
|
});
|
|
4037
|
-
|
|
4038
|
+
currentTurnMessages.push({
|
|
4038
4039
|
role: "user",
|
|
4039
4040
|
content: toolResults
|
|
4040
4041
|
});
|
|
4041
|
-
this.truncateLastToolResults();
|
|
4042
4042
|
if (response.stop_reason === "end_turn" && toolUses.length === 0) {
|
|
4043
4043
|
break;
|
|
4044
4044
|
}
|
|
4045
4045
|
}
|
|
4046
|
+
if (finalText.trim()) {
|
|
4047
|
+
this.storeTextExchange(userMessage, finalText.trim());
|
|
4048
|
+
}
|
|
4046
4049
|
return {
|
|
4047
4050
|
text: finalText,
|
|
4048
4051
|
toolCalls,
|
|
@@ -4055,6 +4058,9 @@ ${userMessage}`;
|
|
|
4055
4058
|
*/
|
|
4056
4059
|
clearHistory() {
|
|
4057
4060
|
this.conversationHistory = [];
|
|
4061
|
+
this.conversationSummary = null;
|
|
4062
|
+
this.toolHistory = [];
|
|
4063
|
+
this.exchanges = [];
|
|
4058
4064
|
}
|
|
4059
4065
|
/**
|
|
4060
4066
|
* Get current conversation history
|
|
@@ -4062,6 +4068,126 @@ ${userMessage}`;
|
|
|
4062
4068
|
getHistory() {
|
|
4063
4069
|
return [...this.conversationHistory];
|
|
4064
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
|
+
}
|
|
4065
4191
|
/**
|
|
4066
4192
|
* Set working directory
|
|
4067
4193
|
*/
|
|
@@ -4074,18 +4200,6 @@ ${userMessage}`;
|
|
|
4074
4200
|
getWorkingDirectory() {
|
|
4075
4201
|
return this.workingDirectory;
|
|
4076
4202
|
}
|
|
4077
|
-
/**
|
|
4078
|
-
* Truncate tool results in the last message of conversation history.
|
|
4079
|
-
*
|
|
4080
|
-
* This is called AFTER Claude has seen the full tool results and responded.
|
|
4081
|
-
* We then replace the full results with truncated versions to save context
|
|
4082
|
-
* on future turns. This way:
|
|
4083
|
-
* - Current turn: Claude sees full data, can display everything to user
|
|
4084
|
-
* - Future turns: Only actionable data (IDs, prices) is in context
|
|
4085
|
-
*/
|
|
4086
|
-
truncateLastToolResults() {
|
|
4087
|
-
return;
|
|
4088
|
-
}
|
|
4089
4203
|
/**
|
|
4090
4204
|
* Update cumulative token usage from API response
|
|
4091
4205
|
* @param usage - Token counts from the API response
|