@exagent/agent 0.3.5 → 0.3.6

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.
@@ -1,100 +1,8 @@
1
- /**
2
- * LLM provider and model registry.
3
- * Keep in sync with apps/web/src/lib/llm-providers.ts.
4
- */
5
-
6
- export interface LlmModel {
7
- id: string;
8
- label: string;
9
- }
10
-
11
- export interface LlmProvider {
12
- id: string;
13
- label: string;
14
- models: LlmModel[];
15
- }
16
-
17
- export const LLM_PROVIDERS: LlmProvider[] = [
18
- {
19
- id: 'openai',
20
- label: 'OpenAI',
21
- models: [
22
- { id: 'gpt-5.2', label: 'GPT-5.2' },
23
- { id: 'gpt-5.2-pro', label: 'GPT-5.2 Pro' },
24
- { id: 'gpt-5-mini', label: 'GPT-5 Mini' },
25
- { id: 'gpt-5-nano', label: 'GPT-5 Nano' },
26
- { id: 'gpt-4o', label: 'GPT-4o' },
27
- { id: 'gpt-4o-mini', label: 'GPT-4o Mini' },
28
- ],
29
- },
30
- {
31
- id: 'anthropic',
32
- label: 'Anthropic',
33
- models: [
34
- { id: 'claude-opus-4-6', label: 'Claude Opus 4.6' },
35
- { id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' },
36
- { id: 'claude-haiku-4-5', label: 'Claude Haiku 4.5' },
37
- ],
38
- },
39
- {
40
- id: 'google',
41
- label: 'Google',
42
- models: [
43
- { id: 'gemini-3-pro', label: 'Gemini 3 Pro' },
44
- { id: 'gemini-3-flash', label: 'Gemini 3 Flash' },
45
- { id: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
46
- { id: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
47
- { id: 'gemini-2.5-flash-lite', label: 'Gemini 2.5 Flash Lite' },
48
- ],
49
- },
50
- {
51
- id: 'deepseek',
52
- label: 'DeepSeek',
53
- models: [
54
- { id: 'deepseek-chat', label: 'DeepSeek Chat' },
55
- { id: 'deepseek-reasoner', label: 'DeepSeek Reasoner' },
56
- ],
57
- },
58
- {
59
- id: 'mistral',
60
- label: 'Mistral',
61
- models: [
62
- { id: 'mistral-large-latest', label: 'Mistral Large' },
63
- { id: 'mistral-small-latest', label: 'Mistral Small' },
64
- ],
65
- },
66
- {
67
- id: 'groq',
68
- label: 'Groq',
69
- models: [
70
- { id: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70B' },
71
- { id: 'llama-3.1-8b-instant', label: 'Llama 3.1 8B' },
72
- { id: 'mixtral-8x7b-32768', label: 'Mixtral 8x7B' },
73
- ],
74
- },
75
- {
76
- id: 'together',
77
- label: 'Together',
78
- models: [
79
- { id: 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo', label: 'Llama 3.1 70B' },
80
- { id: 'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo', label: 'Llama 3.1 8B' },
81
- ],
82
- },
83
- {
84
- id: 'ollama',
85
- label: 'Ollama (local)',
86
- models: [
87
- { id: 'llama3.1', label: 'Llama 3.1' },
88
- { id: 'mistral', label: 'Mistral' },
89
- { id: 'custom', label: 'Custom (type model name)' },
90
- ],
91
- },
92
- ];
93
-
94
- export function getProvider(id: string): LlmProvider | undefined {
95
- return LLM_PROVIDERS.find((p) => p.id === id);
96
- }
97
-
98
- export function getProviderIds(): string[] {
99
- return LLM_PROVIDERS.map((p) => p.id);
100
- }
1
+ export {
2
+ LLM_PROVIDERS,
3
+ getDefaultModel,
4
+ getProvider,
5
+ getProviderIds,
6
+ providerRequiresApiKey,
7
+ } from '@exagent/sdk';
8
+ export type { LlmModel, LlmProvider } from '@exagent/sdk';
@@ -9,7 +9,9 @@
9
9
  */
10
10
 
11
11
  import { ClobClient, Side } from '@polymarket/clob-client';
12
- import { Wallet } from 'ethers';
12
+ import { createWalletClient, http, type WalletClient } from 'viem';
13
+ import { privateKeyToAccount } from 'viem/accounts';
14
+ import { polygon } from 'viem/chains';
13
15
  import type {
14
16
  PredictionConfig,
15
17
  PredictionMarket,
@@ -32,7 +34,7 @@ export class PolymarketClient {
32
34
  private readonly config: PredictionConfig;
33
35
  private clobClient: ClobClient | null = null;
34
36
  private apiCreds: ApiKeyCreds | null = null;
35
- private readonly signer: Wallet;
37
+ private readonly signer: WalletClient;
36
38
  private readonly walletAddress: string;
37
39
 
38
40
  private marketCache: Map<string, { market: PredictionMarket; cachedAt: number }> = new Map();
@@ -40,8 +42,13 @@ export class PolymarketClient {
40
42
 
41
43
  constructor(privateKey: string, config?: Partial<PredictionConfig>) {
42
44
  this.config = { ...DEFAULT_PREDICTION_CONFIG, ...config } as PredictionConfig;
43
- this.signer = new Wallet(privateKey);
44
- this.walletAddress = this.signer.address;
45
+ const account = privateKeyToAccount(privateKey as `0x${string}`);
46
+ this.signer = createWalletClient({
47
+ account,
48
+ chain: polygon,
49
+ transport: http(),
50
+ });
51
+ this.walletAddress = account.address;
45
52
  }
46
53
 
47
54
  // ── INITIALIZATION ─────────────────────────────────────────
package/src/runtime.ts CHANGED
@@ -52,7 +52,7 @@ import type { BridgeConfig } from './bridge/types.js';
52
52
 
53
53
  import { createRequire } from 'module';
54
54
  const _require = createRequire(import.meta.url);
55
- let SDK_VERSION = '0.3.0';
55
+ let SDK_VERSION = '0.3.5';
56
56
  try { SDK_VERSION = _require('../package.json').version; } catch {}
57
57
 
58
58
  /** Number of consecutive cycle failures before switching to idle */
@@ -427,7 +427,7 @@ export class AgentRuntime {
427
427
  : 'Strategy description: follow the owner-configured strategy exactly and do not improvise outside it.',
428
428
  `Allowed venues: ${venues}.`,
429
429
  'Stay inside the owner-configured scope. If the setup is unclear or the trade does not fit, return no trades.',
430
- 'Return ONLY a JSON array of trade signals.',
430
+ 'Return ONLY a JSON array of trade intents.',
431
431
  ].filter((line): line is string => Boolean(line)).join('\n');
432
432
  }
433
433
 
@@ -446,7 +446,7 @@ export class AgentRuntime {
446
446
  }
447
447
 
448
448
  if (typeof rawStrategy.code === 'string' && rawStrategy.code.trim()) {
449
- return { code: rawStrategy.code, venues };
449
+ throw new Error('Raw JavaScript strategy code is disabled. Use a prompt-backed strategy or template instead.');
450
450
  }
451
451
 
452
452
  if (typeof rawStrategy.systemPrompt === 'string' && rawStrategy.systemPrompt.trim()) {
package/src/setup.ts CHANGED
@@ -146,11 +146,11 @@ async function setupWallet(config: RuntimeConfigFile): Promise<string> {
146
146
  // Step 3: LLM
147
147
  // ---------------------------------------------------------------------------
148
148
 
149
- import { LLM_PROVIDERS, getProvider } from './llm-providers.js';
149
+ import { LLM_PROVIDERS, getDefaultModel, getProvider, providerRequiresApiKey } from './llm-providers.js';
150
150
 
151
151
  async function setupLlm(
152
152
  config: RuntimeConfigFile,
153
- ): Promise<{ provider: string; model: string; apiKey: string }> {
153
+ ): Promise<{ provider: string; model: string; apiKey?: string }> {
154
154
  // LLM config is always entered locally — never pulled from bootstrap.
155
155
  // Config file may have provider/model as defaults from the deploy wizard.
156
156
 
@@ -161,7 +161,9 @@ async function setupLlm(
161
161
  const apiKey = process.env.EXAGENT_LLM_KEY;
162
162
  if (!provider) throw new Error('EXAGENT_LLM_PROVIDER required in non-interactive mode');
163
163
  if (!model) throw new Error('EXAGENT_LLM_MODEL required in non-interactive mode');
164
- if (!apiKey) throw new Error('EXAGENT_LLM_KEY required in non-interactive mode');
164
+ if (providerRequiresApiKey(provider) && !apiKey) {
165
+ throw new Error('EXAGENT_LLM_KEY required in non-interactive mode');
166
+ }
165
167
  printDone('LLM configured');
166
168
  return { provider, model, apiKey };
167
169
  }
@@ -182,7 +184,7 @@ async function setupLlm(
182
184
  const providerInfo = getProvider(provider);
183
185
  const modelOptions = providerInfo
184
186
  ? providerInfo.models.map(m => ({ value: m.id, label: m.label }))
185
- : [{ value: defaultModel || 'gpt-4o', label: defaultModel || 'gpt-4o' }];
187
+ : [{ value: defaultModel || getDefaultModel('openai'), label: defaultModel || getDefaultModel('openai') }];
186
188
  const selectedModel = await clack.select({
187
189
  message: 'LLM model:',
188
190
  options: modelOptions,
@@ -191,12 +193,18 @@ async function setupLlm(
191
193
  if (clack.isCancel(selectedModel)) cancelled();
192
194
  const model = selectedModel;
193
195
 
194
- // API Key always prompt, never from bootstrap
195
- const apiKey = await clack.password({
196
- message: 'LLM API key:',
197
- validate: (val) => validateLlmKeyFormat(provider, val),
198
- });
199
- if (clack.isCancel(apiKey)) cancelled();
196
+ let apiKey: string | undefined;
197
+ if (providerRequiresApiKey(provider)) {
198
+ // API Key — always prompt, never from bootstrap
199
+ const enteredApiKey = await clack.password({
200
+ message: 'LLM API key:',
201
+ validate: (val) => validateLlmKeyFormat(provider, val),
202
+ });
203
+ if (clack.isCancel(enteredApiKey)) cancelled();
204
+ apiKey = enteredApiKey;
205
+ } else {
206
+ printInfo('Ollama uses your local server; no API key needed.');
207
+ }
200
208
 
201
209
  printDone('LLM configured');
202
210
  return { provider, model, apiKey };
@@ -1,27 +1,36 @@
1
- import { existsSync } from 'node:fs';
2
- import { resolve } from 'node:path';
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { extname, resolve } from 'node:path';
3
3
  import { z } from 'zod';
4
4
  import type { StrategyFunction, StrategyContext, TradeSignal } from '@exagent/sdk';
5
5
  import { getTemplate } from './templates.js';
6
6
  import { scrubSecrets } from '../scrub-secrets.js';
7
7
 
8
+ const MAX_PROMPT_SIGNALS = 10;
9
+ const SUPPORTED_FILE_EXPORTS = new Set(['name', 'description', 'category', 'venues', 'systemPrompt', 'template']);
10
+
8
11
  const promptSignalSchema = z.object({
9
- symbol: z.string().min(1),
12
+ symbol: z.string().min(1).max(80),
10
13
  side: z.enum(['buy', 'sell', 'long', 'short']),
11
14
  confidence: z.number().min(0).max(1).optional(),
12
- reasoning: z.string().optional(),
13
- venue: z.string().optional(),
14
- chain: z.string().optional(),
15
+ reasoning: z.string().max(1000).optional(),
16
+ venue: z.string().min(1).max(80).optional(),
17
+ chain: z.string().min(1).max(80).optional(),
15
18
  size: z.number().positive().optional(),
16
19
  price: z.number().positive().optional(),
17
- fee: z.number().min(0).optional(),
18
- venueFillId: z.string().optional(),
19
- venueTimestamp: z.string().optional(),
20
- leverage: z.number().positive().optional(),
21
- orderType: z.string().optional(),
22
- });
20
+ leverage: z.number().positive().max(100).optional(),
21
+ orderType: z.enum(['market', 'limit', 'yes', 'no']).optional(),
22
+ }).strict();
23
+
24
+ const promptSignalArraySchema = z.array(promptSignalSchema).max(MAX_PROMPT_SIGNALS);
23
25
 
24
- const promptSignalArraySchema = z.array(promptSignalSchema);
26
+ const strategyMetadataSchema = z.object({
27
+ name: z.string().min(1).max(120).optional(),
28
+ description: z.string().max(1000).optional(),
29
+ category: z.string().max(80).optional(),
30
+ venues: z.array(z.string().min(1).max(80)).max(20).optional(),
31
+ systemPrompt: z.string().min(1).max(12000).optional(),
32
+ template: z.string().min(1).max(80).optional(),
33
+ }).strict();
25
34
 
26
35
  export async function loadStrategy(config: {
27
36
  file?: string;
@@ -33,12 +42,12 @@ export async function loadStrategy(config: {
33
42
  venues?: string[];
34
43
  };
35
44
  }): Promise<StrategyFunction> {
36
- if (config.file) {
37
- return loadFromFile(config.file);
45
+ if (config.code?.trim()) {
46
+ throw new Error('Raw JavaScript strategy code is disabled. Use a template or prompt-backed strategy instead.');
38
47
  }
39
48
 
40
- if (config.code) {
41
- return loadFromCode(config.code);
49
+ if (config.file) {
50
+ return loadFromFile(config.file);
42
51
  }
43
52
 
44
53
  if (config.prompt) {
@@ -50,7 +59,14 @@ export async function loadStrategy(config: {
50
59
  if (!template) {
51
60
  throw new Error(`Unknown strategy template: ${config.template}. Available: momentum, value, arbitrage, hold`);
52
61
  }
53
- return loadFromCode(template.code);
62
+ if (!template.systemPrompt.trim()) {
63
+ return holdStrategy;
64
+ }
65
+ return loadFromPrompt({
66
+ name: template.name,
67
+ systemPrompt: template.systemPrompt,
68
+ venues: template.venues,
69
+ });
54
70
  }
55
71
 
56
72
  // Default: hold strategy (no trades)
@@ -64,51 +80,40 @@ async function loadFromFile(filePath: string): Promise<StrategyFunction> {
64
80
  }
65
81
 
66
82
  try {
67
- const mod = await import(resolved);
68
- const fn = mod.default || mod.strategy;
83
+ const source = readFileSync(resolved, 'utf8');
84
+ const metadata = extname(resolved) === '.json'
85
+ ? strategyMetadataSchema.parse(JSON.parse(source))
86
+ : parseStrategyMetadataModule(source, resolved);
69
87
 
70
- if (typeof fn !== 'function') {
71
- if (typeof mod.code === 'string' && mod.code.trim()) {
72
- return loadFromCode(mod.code);
73
- }
88
+ if (metadata.systemPrompt?.trim()) {
89
+ return loadFromPrompt({
90
+ name: metadata.name,
91
+ systemPrompt: metadata.systemPrompt,
92
+ venues: metadata.venues,
93
+ });
94
+ }
74
95
 
75
- if (typeof mod.systemPrompt === 'string' && mod.systemPrompt.trim()) {
76
- const venues = Array.isArray(mod.venues)
77
- ? mod.venues.filter((venue: unknown): venue is string => typeof venue === 'string')
78
- : undefined;
79
- const name = typeof mod.name === 'string' ? mod.name : undefined;
80
- return loadFromPrompt({
81
- name,
82
- systemPrompt: mod.systemPrompt,
83
- venues,
84
- });
96
+ if (metadata.template?.trim()) {
97
+ const template = getTemplate(metadata.template);
98
+ if (!template) {
99
+ throw new Error(`Unknown strategy template: ${metadata.template}. Available: momentum, value, arbitrage, hold`);
85
100
  }
86
-
87
- if (typeof mod.template === 'string' && mod.template.trim()) {
88
- const template = getTemplate(mod.template);
89
- if (!template) {
90
- throw new Error(`Unknown strategy template: ${mod.template}. Available: momentum, value, arbitrage, hold`);
91
- }
92
- return loadFromCode(template.code);
101
+ if (!template.systemPrompt.trim()) {
102
+ return holdStrategy;
93
103
  }
94
-
95
- throw new Error(`Strategy file must export a default function, 'strategy' function, 'code' string, 'systemPrompt' string, or 'template' string`);
104
+ return loadFromPrompt({
105
+ name: template.name,
106
+ systemPrompt: template.systemPrompt,
107
+ venues: metadata.venues?.length ? metadata.venues : template.venues,
108
+ });
96
109
  }
97
110
 
98
- return fn as StrategyFunction;
111
+ throw new Error('Strategy file must define a systemPrompt or template. Executable strategy files are disabled.');
99
112
  } catch (err) {
100
113
  throw new Error(`Failed to load strategy from ${resolved}: ${(err as Error).message}`);
101
114
  }
102
115
  }
103
116
 
104
- async function loadFromCode(code: string): Promise<StrategyFunction> {
105
- // Templates return a factory function as a string
106
- // We wrap it in a module and evaluate
107
- const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
108
- const fn = new AsyncFunction('context', code) as StrategyFunction;
109
- return fn;
110
- }
111
-
112
117
  function loadFromPrompt(config: {
113
118
  name?: string;
114
119
  systemPrompt: string;
@@ -124,17 +129,18 @@ function loadFromPrompt(config: {
124
129
  chain: position.chain,
125
130
  }));
126
131
 
132
+ const allowedVenues = config.venues?.filter(Boolean) ?? [];
127
133
  const response = await context.llm.chat([
128
134
  { role: 'system', content: config.systemPrompt },
129
135
  {
130
136
  role: 'user',
131
137
  content: [
132
138
  `Strategy: ${config.name || 'Prompt Strategy'}`,
133
- `Allowed venues: ${(config.venues || []).join(', ') || 'any'}`,
139
+ `Allowed venues: ${allowedVenues.join(', ') || 'none configured'}`,
134
140
  `Current prices: ${JSON.stringify(prices)}`,
135
141
  `Open positions: ${JSON.stringify(positions)}`,
136
142
  `Risk config: ${JSON.stringify(context.config)}`,
137
- 'Return ONLY a JSON array of trade signals.',
143
+ 'Return ONLY a JSON array of trade intents. Do not include executable code. Do not invent fill IDs.',
138
144
  ].join('\n'),
139
145
  },
140
146
  ]);
@@ -142,16 +148,26 @@ function loadFromPrompt(config: {
142
148
  // Defense-in-depth: scrub any secrets the LLM might echo back before parsing
143
149
  const scrubbedContent = scrubSecrets(response.content);
144
150
 
145
- const match = scrubbedContent.match(/\[[\s\S]*\]/);
146
- if (!match) {
151
+ const jsonPayload = extractJsonArray(scrubbedContent);
152
+ if (!jsonPayload) {
147
153
  context.log('Prompt strategy returned a non-JSON response; no signals emitted.');
148
154
  return [];
149
155
  }
150
156
 
151
157
  try {
152
- const parsed = promptSignalArraySchema.parse(JSON.parse(match[0]));
158
+ const parsed = promptSignalArraySchema.parse(JSON.parse(jsonPayload));
153
159
  const signals: TradeSignal[] = [];
154
160
  for (const signal of parsed) {
161
+ const venue = signal.venue || allowedVenues[0];
162
+ if (!venue) {
163
+ context.log(`Prompt strategy skipped ${signal.symbol}: no allowed venue configured.`);
164
+ continue;
165
+ }
166
+ if (allowedVenues.length > 0 && !allowedVenues.includes(venue)) {
167
+ context.log(`Prompt strategy skipped ${signal.symbol}: venue ${venue} is not allowed.`);
168
+ continue;
169
+ }
170
+
155
171
  const price = signal.price ?? prices[signal.symbol.toUpperCase()];
156
172
  if (!price || price <= 0) {
157
173
  context.log(`Prompt strategy skipped ${signal.symbol}: no usable price in response or market cache.`);
@@ -163,13 +179,13 @@ function loadFromPrompt(config: {
163
179
  side: signal.side,
164
180
  confidence: signal.confidence ?? 0.5,
165
181
  reasoning: signal.reasoning,
166
- venue: signal.venue || config.venues?.[0] || 'manual',
182
+ venue,
167
183
  chain: signal.chain,
168
- size: signal.size ?? 1,
184
+ size: signal.size ?? 0,
169
185
  price,
170
- fee: signal.fee ?? 0,
171
- venueFillId: signal.venueFillId ?? '',
172
- venueTimestamp: signal.venueTimestamp ?? new Date().toISOString(),
186
+ fee: 0,
187
+ venueFillId: '',
188
+ venueTimestamp: '',
173
189
  leverage: signal.leverage,
174
190
  orderType: signal.orderType,
175
191
  });
@@ -182,6 +198,64 @@ function loadFromPrompt(config: {
182
198
  };
183
199
  }
184
200
 
201
+ function extractJsonArray(content: string): string | null {
202
+ const trimmed = content.trim();
203
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
204
+ return trimmed;
205
+ }
206
+
207
+ const start = trimmed.indexOf('[');
208
+ const end = trimmed.lastIndexOf(']');
209
+ if (start === -1 || end === -1 || end <= start) {
210
+ return null;
211
+ }
212
+ return trimmed.slice(start, end + 1);
213
+ }
214
+
215
+ function parseStrategyMetadataModule(source: string, filePath: string): z.infer<typeof strategyMetadataSchema> {
216
+ const values: Record<string, unknown> = {};
217
+ let statement = '';
218
+
219
+ for (const rawLine of source.replace(/\r\n/g, '\n').split('\n')) {
220
+ const line = rawLine.trim();
221
+ if (!line || line.startsWith('//')) {
222
+ continue;
223
+ }
224
+
225
+ statement = statement ? `${statement}\n${rawLine}` : rawLine;
226
+ if (!line.endsWith(';')) {
227
+ continue;
228
+ }
229
+
230
+ const match = statement.match(/^\s*export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*([\s\S]*);\s*$/);
231
+ if (!match) {
232
+ throw new Error(`Unsupported statement in ${filePath}. Strategy files may only export JSON constants.`);
233
+ }
234
+
235
+ const [, name, rawValue] = match;
236
+ if (name === 'code') {
237
+ throw new Error('Raw JavaScript strategy code is disabled. Replace code with systemPrompt.');
238
+ }
239
+ if (!SUPPORTED_FILE_EXPORTS.has(name)) {
240
+ throw new Error(`Unsupported strategy export "${name}".`);
241
+ }
242
+
243
+ try {
244
+ values[name] = JSON.parse(rawValue.trim());
245
+ } catch {
246
+ throw new Error(`Strategy export "${name}" must be a JSON literal.`);
247
+ }
248
+
249
+ statement = '';
250
+ }
251
+
252
+ if (statement.trim()) {
253
+ throw new Error(`Unterminated export in ${filePath}.`);
254
+ }
255
+
256
+ return strategyMetadataSchema.parse(values);
257
+ }
258
+
185
259
  const holdStrategy: StrategyFunction = async (_context: StrategyContext): Promise<TradeSignal[]> => {
186
260
  return []; // No trades — hold position
187
261
  };
@@ -18,23 +18,6 @@ Rules:
18
18
  - Consider volume as confirmation of momentum
19
19
 
20
20
  Return a JSON array of trade signals.`,
21
- code: `
22
- const prices = context.market.getPrices();
23
- const positions = context.position.openPositions;
24
-
25
- const messages = [
26
- { role: 'system', content: 'You are a momentum trading agent. Analyze market data and return trade signals as a JSON array. Each signal: { symbol, side: "buy"|"sell", confidence: 0-1, reasoning }. Return [] if no opportunities.' },
27
- { role: 'user', content: 'Current prices: ' + JSON.stringify(prices) + '\\nOpen positions: ' + JSON.stringify(positions.map(p => p.token)) + '\\nAnalyze for momentum opportunities.' }
28
- ];
29
-
30
- const response = await context.llm.chat(messages);
31
- try {
32
- const signals = JSON.parse(response.content);
33
- return Array.isArray(signals) ? signals : [];
34
- } catch {
35
- return [];
36
- }
37
- `,
38
21
  },
39
22
  {
40
23
  id: 'value',
@@ -53,23 +36,6 @@ Rules:
53
36
  - Diversify across sectors
54
37
 
55
38
  Return a JSON array of trade signals.`,
56
- code: `
57
- const prices = context.market.getPrices();
58
- const positions = context.position.openPositions;
59
-
60
- const messages = [
61
- { role: 'system', content: 'You are a value investing agent. Identify undervalued tokens. Return trade signals as JSON array: { symbol, side: "buy"|"sell", confidence: 0-1, reasoning }. Return [] if nothing is compelling.' },
62
- { role: 'user', content: 'Current prices: ' + JSON.stringify(prices) + '\\nPositions: ' + JSON.stringify(positions.map(p => ({ token: p.token, entry: p.costBasisPerUnit }))) + '\\nLook for value opportunities.' }
63
- ];
64
-
65
- const response = await context.llm.chat(messages);
66
- try {
67
- const signals = JSON.parse(response.content);
68
- return Array.isArray(signals) ? signals : [];
69
- } catch {
70
- return [];
71
- }
72
- `,
73
39
  },
74
40
  {
75
41
  id: 'arbitrage',
@@ -87,22 +53,6 @@ Rules:
87
53
  - Monitor cross-chain opportunities (CEX vs DEX spreads)
88
54
 
89
55
  Return a JSON array of trade signals.`,
90
- code: `
91
- const prices = context.market.getPrices();
92
-
93
- const messages = [
94
- { role: 'system', content: 'You are an arbitrage agent. Find price discrepancies. Return trade signals as JSON array: { symbol, side: "buy"|"sell", venue, confidence: 0-1, reasoning }. Return [] if no arb opportunities.' },
95
- { role: 'user', content: 'Market prices: ' + JSON.stringify(prices) + '\\nScan for cross-venue arbitrage opportunities.' }
96
- ];
97
-
98
- const response = await context.llm.chat(messages);
99
- try {
100
- const signals = JSON.parse(response.content);
101
- return Array.isArray(signals) ? signals : [];
102
- } catch {
103
- return [];
104
- }
105
- `,
106
56
  },
107
57
  {
108
58
  id: 'hold',
@@ -112,7 +62,6 @@ try {
112
62
  venues: [],
113
63
  riskLevel: 'conservative',
114
64
  systemPrompt: '',
115
- code: `return [];`,
116
65
  },
117
66
  ];
118
67