@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.
Files changed (2) hide show
  1. package/dist/index.js +164 -270
  2. 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
- You have tools to search markets and place trades. When showing market data, display ALL relevant information from the response including prices/probabilities.
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.config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
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 contextMessage = `[Working directory: ${this.workingDirectory}]
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(this.conversationHistory, {
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(this.conversationHistory);
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
- this.conversationHistory.push({
3810
+ currentTurnMessages.push({
3989
3811
  role: "assistant",
3990
3812
  content: responseContent
3991
3813
  });
3992
- this.conversationHistory.push({
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.config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
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
- const contextMessage = `[Working directory: ${this.workingDirectory}]
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: this.conversationHistory
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: this.conversationHistory
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
- this.conversationHistory.push({
4034
+ currentTurnMessages.push({
4220
4035
  role: "assistant",
4221
4036
  content: responseContent
4222
4037
  });
4223
- this.conversationHistory.push({
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantish/agent",
3
- "version": "0.1.50",
3
+ "version": "0.1.52",
4
4
  "description": "AI-powered agent for trading on Polymarket and Kalshi",
5
5
  "type": "module",
6
6
  "bin": {