illuma-agents 1.0.13 → 1.0.15

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.
@@ -209,6 +209,39 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
209
209
  }
210
210
  }
211
211
 
212
+ /**
213
+ * Estimates a human-friendly description of the conversation timeframe based on message count.
214
+ * Uses rough heuristics to provide context about how much history is available.
215
+ *
216
+ * @param messageCount - Number of messages in the remaining context
217
+ * @returns A friendly description like "the last few minutes", "the past hour", etc.
218
+ */
219
+ getContextTimeframeDescription(messageCount: number): string {
220
+ // Rough heuristics based on typical conversation patterns:
221
+ // - Very active chat: ~20-30 messages per hour
222
+ // - Normal chat: ~10-15 messages per hour
223
+ // - Slow/thoughtful chat: ~5-8 messages per hour
224
+ // We use a middle estimate of ~12 messages per hour
225
+
226
+ if (messageCount <= 5) {
227
+ return 'just the last few exchanges';
228
+ } else if (messageCount <= 15) {
229
+ return 'the last several minutes';
230
+ } else if (messageCount <= 30) {
231
+ return 'roughly the past hour';
232
+ } else if (messageCount <= 60) {
233
+ return 'the past couple of hours';
234
+ } else if (messageCount <= 150) {
235
+ return 'the past few hours';
236
+ } else if (messageCount <= 300) {
237
+ return 'roughly a day\'s worth';
238
+ } else if (messageCount <= 700) {
239
+ return 'the past few days';
240
+ } else {
241
+ return 'about a week or more';
242
+ }
243
+ }
244
+
212
245
  /* Run Step Processing */
213
246
 
214
247
  getRunStep(stepId: string): t.RunStep | undefined {
@@ -863,37 +896,153 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
863
896
  config
864
897
  );
865
898
  } catch (primaryError) {
866
- let lastError: unknown = primaryError;
867
- for (const fb of fallbacks) {
868
- try {
869
- let model = this.getNewModel({
870
- provider: fb.provider,
871
- clientOptions: fb.clientOptions,
872
- });
873
- const bindableTools = agentContext.tools;
874
- model = (
875
- !bindableTools || bindableTools.length === 0
876
- ? model
877
- : model.bindTools(bindableTools)
878
- ) as t.ChatModelInstance;
879
- result = await this.attemptInvoke(
880
- {
881
- currentModel: model,
882
- finalMessages,
883
- provider: fb.provider,
884
- tools: agentContext.tools,
885
- },
886
- config
899
+ // Check if this is a "input too long" error from Bedrock/Anthropic
900
+ const errorMessage = (primaryError as Error)?.message?.toLowerCase() ?? '';
901
+ const isInputTooLongError =
902
+ errorMessage.includes('too long') ||
903
+ errorMessage.includes('input is too long') ||
904
+ errorMessage.includes('context length') ||
905
+ errorMessage.includes('maximum context') ||
906
+ errorMessage.includes('validationexception') ||
907
+ errorMessage.includes('prompt is too long');
908
+
909
+ // If input too long and we have pruning capability, retry with progressively more aggressive pruning
910
+ if (isInputTooLongError && agentContext.pruneMessages && agentContext.maxContextTokens) {
911
+ // Progressive reduction: 50% -> 25% -> 10% of original context
912
+ const reductionLevels = [0.5, 0.25, 0.1];
913
+
914
+ for (const reductionFactor of reductionLevels) {
915
+ if (result) break; // Exit if we got a result
916
+
917
+ const reducedMaxTokens = Math.floor(agentContext.maxContextTokens * reductionFactor);
918
+ console.warn(
919
+ `[Graph] Input too long. Retrying with ${reductionFactor * 100}% context (${reducedMaxTokens} tokens)...`
887
920
  );
888
- lastError = undefined;
889
- break;
890
- } catch (e) {
891
- lastError = e;
892
- continue;
921
+
922
+ const emergencyPrune = createPruneMessages({
923
+ startIndex: this.startIndex,
924
+ provider: agentContext.provider,
925
+ tokenCounter: agentContext.tokenCounter!,
926
+ maxTokens: reducedMaxTokens,
927
+ thinkingEnabled: false, // Disable thinking for emergency prune
928
+ indexTokenCountMap: agentContext.indexTokenCountMap,
929
+ });
930
+
931
+ const { context: reducedMessages } = emergencyPrune({
932
+ messages,
933
+ usageMetadata: agentContext.currentUsage,
934
+ });
935
+
936
+ // Skip if we can't fit any messages
937
+ if (reducedMessages.length === 0) {
938
+ console.warn(`[Graph] Cannot fit any messages at ${reductionFactor * 100}% reduction, trying next level...`);
939
+ continue;
940
+ }
941
+
942
+ // Calculate how many messages were pruned and estimate context timeframe
943
+ const prunedCount = finalMessages.length - reducedMessages.length;
944
+ const remainingCount = reducedMessages.length;
945
+ const estimatedContextDescription = this.getContextTimeframeDescription(remainingCount);
946
+
947
+ // Inject a personalized context message to inform the agent about pruning
948
+ const pruneNoticeMessage = new HumanMessage({
949
+ content: `[CONTEXT NOTICE]
950
+ Our conversation has grown quite long, so I've focused on ${estimatedContextDescription} of our chat (${remainingCount} recent messages). ${prunedCount} earlier messages are no longer in my immediate memory.
951
+
952
+ If I seem to be missing something we discussed earlier, just give me a quick reminder and I'll pick right back up! I'm still fully engaged and ready to help with whatever you need.`,
953
+ });
954
+
955
+ // Insert the notice after the system message (if any) but before conversation
956
+ const hasSystemMessage = reducedMessages[0]?.getType() === 'system';
957
+ const insertIndex = hasSystemMessage ? 1 : 0;
958
+
959
+ // Create new array with the pruning notice
960
+ const messagesWithNotice = [
961
+ ...reducedMessages.slice(0, insertIndex),
962
+ pruneNoticeMessage,
963
+ ...reducedMessages.slice(insertIndex),
964
+ ];
965
+
966
+ let retryMessages = agentContext.useLegacyContent
967
+ ? formatContentStrings(messagesWithNotice)
968
+ : messagesWithNotice;
969
+
970
+ // Apply Bedrock cache control if needed
971
+ if (agentContext.provider === Providers.BEDROCK) {
972
+ const bedrockOptions = agentContext.clientOptions as
973
+ | t.BedrockAnthropicClientOptions
974
+ | undefined;
975
+ const modelId = bedrockOptions?.model?.toLowerCase() ?? '';
976
+ const supportsCaching = modelId.includes('claude') || modelId.includes('anthropic') || modelId.includes('nova');
977
+ if (bedrockOptions?.promptCache === true && supportsCaching) {
978
+ retryMessages = addBedrockCacheControl<BaseMessage>(retryMessages);
979
+ }
980
+ }
981
+
982
+ try {
983
+ result = await this.attemptInvoke(
984
+ {
985
+ currentModel: model,
986
+ finalMessages: retryMessages,
987
+ provider: agentContext.provider,
988
+ tools: agentContext.tools,
989
+ },
990
+ config
991
+ );
992
+ // Success with reduced context
993
+ console.info(`[Graph] ✅ Retry successful at ${reductionFactor * 100}% with ${reducedMessages.length} messages (reduced from ${finalMessages.length})`);
994
+ } catch (retryError) {
995
+ const retryErrorMsg = (retryError as Error)?.message?.toLowerCase() ?? '';
996
+ const stillTooLong =
997
+ retryErrorMsg.includes('too long') ||
998
+ retryErrorMsg.includes('context length') ||
999
+ retryErrorMsg.includes('validationexception');
1000
+
1001
+ if (stillTooLong && reductionFactor > 0.1) {
1002
+ console.warn(`[Graph] Still too long at ${reductionFactor * 100}%, trying more aggressive pruning...`);
1003
+ } else {
1004
+ console.error(`[Graph] Retry at ${reductionFactor * 100}% failed:`, (retryError as Error)?.message);
1005
+ }
1006
+ }
893
1007
  }
894
1008
  }
895
- if (lastError !== undefined) {
896
- throw lastError;
1009
+
1010
+ // If we got a result from retry, skip fallbacks
1011
+ if (result) {
1012
+ // result already set from retry
1013
+ } else {
1014
+ let lastError: unknown = primaryError;
1015
+ for (const fb of fallbacks) {
1016
+ try {
1017
+ let model = this.getNewModel({
1018
+ provider: fb.provider,
1019
+ clientOptions: fb.clientOptions,
1020
+ });
1021
+ const bindableTools = agentContext.tools;
1022
+ model = (
1023
+ !bindableTools || bindableTools.length === 0
1024
+ ? model
1025
+ : model.bindTools(bindableTools)
1026
+ ) as t.ChatModelInstance;
1027
+ result = await this.attemptInvoke(
1028
+ {
1029
+ currentModel: model,
1030
+ finalMessages,
1031
+ provider: fb.provider,
1032
+ tools: agentContext.tools,
1033
+ },
1034
+ config
1035
+ );
1036
+ lastError = undefined;
1037
+ break;
1038
+ } catch (e) {
1039
+ lastError = e;
1040
+ continue;
1041
+ }
1042
+ }
1043
+ if (lastError !== undefined) {
1044
+ throw lastError;
1045
+ }
897
1046
  }
898
1047
  }
899
1048
 
@@ -0,0 +1,355 @@
1
+ // src/specs/emergency-prune.test.ts
2
+ /**
3
+ * Tests for the emergency pruning feature that handles "input too long" errors
4
+ * by retrying with more aggressive message pruning and adding a context notice.
5
+ */
6
+ import {
7
+ HumanMessage,
8
+ AIMessage,
9
+ SystemMessage,
10
+ BaseMessage,
11
+ } from '@langchain/core/messages';
12
+ import type * as t from '@/types';
13
+ import { createPruneMessages } from '@/messages/prune';
14
+ import { Providers } from '@/common';
15
+
16
+ // Simple token counter for testing (1 character = 1 token)
17
+ const createTestTokenCounter = (): t.TokenCounter => {
18
+ return (message: BaseMessage): number => {
19
+ const content = message.content as string | Array<t.MessageContentComplex | string> | undefined;
20
+ if (typeof content === 'string') {
21
+ return content.length;
22
+ }
23
+ if (Array.isArray(content)) {
24
+ return content.reduce((total, item) => {
25
+ if (typeof item === 'string') return total + item.length;
26
+ if (typeof item === 'object' && 'text' in item && typeof item.text === 'string') {
27
+ return total + item.text.length;
28
+ }
29
+ return total;
30
+ }, 0);
31
+ }
32
+ return 0;
33
+ };
34
+ };
35
+
36
+ // Helper to create test messages
37
+ const createTestMessages = (count: number, tokensPer: number): BaseMessage[] => {
38
+ const messages: BaseMessage[] = [
39
+ new SystemMessage('You are a helpful assistant.'),
40
+ ];
41
+
42
+ for (let i = 0; i < count; i++) {
43
+ const content = 'x'.repeat(tokensPer);
44
+ if (i % 2 === 0) {
45
+ messages.push(new HumanMessage(content));
46
+ } else {
47
+ messages.push(new AIMessage(content));
48
+ }
49
+ }
50
+
51
+ return messages;
52
+ };
53
+
54
+ // Helper to build indexTokenCountMap
55
+ const buildIndexTokenCountMap = (
56
+ messages: BaseMessage[],
57
+ tokenCounter: t.TokenCounter
58
+ ): Record<string, number> => {
59
+ const map: Record<string, number> = {};
60
+ messages.forEach((msg, index) => {
61
+ map[index] = tokenCounter(msg);
62
+ });
63
+ return map;
64
+ };
65
+
66
+ /**
67
+ * Estimates a human-friendly description of the conversation timeframe based on message count.
68
+ * This mirrors the implementation in Graph.ts
69
+ */
70
+ const getContextTimeframeDescription = (messageCount: number): string => {
71
+ if (messageCount <= 5) {
72
+ return 'just the last few exchanges';
73
+ } else if (messageCount <= 15) {
74
+ return 'the last several minutes';
75
+ } else if (messageCount <= 30) {
76
+ return 'roughly the past hour';
77
+ } else if (messageCount <= 60) {
78
+ return 'the past couple of hours';
79
+ } else if (messageCount <= 150) {
80
+ return 'the past few hours';
81
+ } else if (messageCount <= 300) {
82
+ return "roughly a day's worth";
83
+ } else if (messageCount <= 700) {
84
+ return 'the past few days';
85
+ } else {
86
+ return 'about a week or more';
87
+ }
88
+ };
89
+
90
+ describe('Emergency Pruning Feature', () => {
91
+ const tokenCounter = createTestTokenCounter();
92
+
93
+ describe('Normal Pruning vs Emergency Pruning', () => {
94
+ it('should prune more aggressively with 50% reduced context', () => {
95
+ // Create 20 messages, each with 100 tokens = 2000 tokens total (excluding system)
96
+ const messages = createTestMessages(20, 100);
97
+ const indexTokenCountMap = buildIndexTokenCountMap(messages, tokenCounter);
98
+
99
+ // Normal prune with 1500 token limit
100
+ const normalMaxTokens = 1500;
101
+ const normalPrune = createPruneMessages({
102
+ startIndex: 0,
103
+ provider: Providers.BEDROCK,
104
+ tokenCounter,
105
+ maxTokens: normalMaxTokens,
106
+ thinkingEnabled: false,
107
+ indexTokenCountMap,
108
+ });
109
+
110
+ const { context: normalContext } = normalPrune({ messages });
111
+
112
+ // Emergency prune with 50% (750 tokens)
113
+ const emergencyMaxTokens = Math.floor(normalMaxTokens * 0.5);
114
+ const emergencyPrune = createPruneMessages({
115
+ startIndex: 0,
116
+ provider: Providers.BEDROCK,
117
+ tokenCounter,
118
+ maxTokens: emergencyMaxTokens,
119
+ thinkingEnabled: false,
120
+ indexTokenCountMap,
121
+ });
122
+
123
+ const { context: emergencyContext } = emergencyPrune({ messages });
124
+
125
+ // Emergency should have fewer messages
126
+ expect(emergencyContext.length).toBeLessThan(normalContext.length);
127
+ console.log(`Normal prune: ${normalContext.length} messages, Emergency prune: ${emergencyContext.length} messages`);
128
+ });
129
+
130
+ it('should preserve system message and latest user message after emergency prune', () => {
131
+ const messages = createTestMessages(10, 200);
132
+ const indexTokenCountMap = buildIndexTokenCountMap(messages, tokenCounter);
133
+
134
+ // Very aggressive prune - only 300 tokens
135
+ const emergencyPrune = createPruneMessages({
136
+ startIndex: 0,
137
+ provider: Providers.BEDROCK,
138
+ tokenCounter,
139
+ maxTokens: 300,
140
+ thinkingEnabled: false,
141
+ indexTokenCountMap,
142
+ });
143
+
144
+ const { context } = emergencyPrune({ messages });
145
+
146
+ // Should still have system message if it fits
147
+ if (context.length > 0) {
148
+ // Check that we have at least the most recent messages
149
+ const lastMessage = context[context.length - 1];
150
+ expect(lastMessage).toBeDefined();
151
+ }
152
+ });
153
+ });
154
+
155
+ describe('Pruning Notice Message Injection', () => {
156
+ it('should calculate correct number of pruned messages', () => {
157
+ const originalCount = 20;
158
+ const messages = createTestMessages(originalCount, 100);
159
+ const indexTokenCountMap = buildIndexTokenCountMap(messages, tokenCounter);
160
+
161
+ const emergencyPrune = createPruneMessages({
162
+ startIndex: 0,
163
+ provider: Providers.BEDROCK,
164
+ tokenCounter,
165
+ maxTokens: 500, // Very small to force aggressive pruning
166
+ thinkingEnabled: false,
167
+ indexTokenCountMap,
168
+ });
169
+
170
+ const { context: reducedMessages } = emergencyPrune({ messages });
171
+
172
+ // Calculate how many were pruned (this is what we inject in the notice)
173
+ const prunedCount = messages.length - reducedMessages.length;
174
+
175
+ expect(prunedCount).toBeGreaterThan(0);
176
+ console.log(`Original: ${messages.length}, After prune: ${reducedMessages.length}, Pruned: ${prunedCount}`);
177
+ });
178
+
179
+ it('should inject personalized notice message after system message', () => {
180
+ const messages = createTestMessages(10, 100);
181
+ const indexTokenCountMap = buildIndexTokenCountMap(messages, tokenCounter);
182
+
183
+ const emergencyPrune = createPruneMessages({
184
+ startIndex: 0,
185
+ provider: Providers.BEDROCK,
186
+ tokenCounter,
187
+ maxTokens: 800,
188
+ thinkingEnabled: false,
189
+ indexTokenCountMap,
190
+ });
191
+
192
+ const { context: reducedMessages } = emergencyPrune({ messages });
193
+
194
+ // Simulate the notice injection logic from Graph.ts
195
+ const prunedCount = messages.length - reducedMessages.length;
196
+ const remainingCount = reducedMessages.length;
197
+ const estimatedContextDescription = getContextTimeframeDescription(remainingCount);
198
+
199
+ const pruneNoticeMessage = new HumanMessage({
200
+ content: `[CONTEXT NOTICE]
201
+ Our conversation has grown quite long, so I've focused on ${estimatedContextDescription} of our chat (${remainingCount} recent messages). ${prunedCount} earlier messages are no longer in my immediate memory.
202
+
203
+ If I seem to be missing something we discussed earlier, just give me a quick reminder and I'll pick right back up! I'm still fully engaged and ready to help with whatever you need.`,
204
+ });
205
+
206
+ // Insert after system message
207
+ const hasSystemMessage = reducedMessages[0]?.getType() === 'system';
208
+ const insertIndex = hasSystemMessage ? 1 : 0;
209
+
210
+ const messagesWithNotice = [
211
+ ...reducedMessages.slice(0, insertIndex),
212
+ pruneNoticeMessage,
213
+ ...reducedMessages.slice(insertIndex),
214
+ ];
215
+
216
+ // Verify notice is in correct position
217
+ if (hasSystemMessage) {
218
+ expect(messagesWithNotice[0].getType()).toBe('system');
219
+ expect(messagesWithNotice[1].getType()).toBe('human');
220
+ expect((messagesWithNotice[1].content as string)).toContain('[CONTEXT NOTICE]');
221
+ expect((messagesWithNotice[1].content as string)).toContain('recent messages');
222
+ expect((messagesWithNotice[1].content as string)).toContain('quick reminder');
223
+ } else {
224
+ expect(messagesWithNotice[0].getType()).toBe('human');
225
+ expect((messagesWithNotice[0].content as string)).toContain('[CONTEXT NOTICE]');
226
+ }
227
+
228
+ // Total messages should be reduced + 1 notice
229
+ expect(messagesWithNotice.length).toBe(reducedMessages.length + 1);
230
+
231
+ console.log(`Notice preview:\n${(pruneNoticeMessage.content as string).substring(0, 200)}...`);
232
+ });
233
+ });
234
+
235
+ describe('Context Timeframe Description', () => {
236
+ it('should return appropriate descriptions for different message counts', () => {
237
+ expect(getContextTimeframeDescription(3)).toBe('just the last few exchanges');
238
+ expect(getContextTimeframeDescription(10)).toBe('the last several minutes');
239
+ expect(getContextTimeframeDescription(25)).toBe('roughly the past hour');
240
+ expect(getContextTimeframeDescription(45)).toBe('the past couple of hours');
241
+ expect(getContextTimeframeDescription(100)).toBe('the past few hours');
242
+ expect(getContextTimeframeDescription(200)).toBe("roughly a day's worth");
243
+ expect(getContextTimeframeDescription(500)).toBe('the past few days');
244
+ expect(getContextTimeframeDescription(1000)).toBe('about a week or more');
245
+ });
246
+ });
247
+
248
+ describe('Error Detection Patterns', () => {
249
+ const errorPatterns = [
250
+ 'Input is too long for the model',
251
+ 'context length exceeded',
252
+ 'maximum context length',
253
+ 'ValidationException: Input is too long',
254
+ 'prompt is too long for this model',
255
+ 'The input is too long',
256
+ ];
257
+
258
+ it('should detect various "input too long" error patterns', () => {
259
+ const isInputTooLongError = (errorMessage: string): boolean => {
260
+ const lowerMessage = errorMessage.toLowerCase();
261
+ return (
262
+ lowerMessage.includes('too long') ||
263
+ lowerMessage.includes('input is too long') ||
264
+ lowerMessage.includes('context length') ||
265
+ lowerMessage.includes('maximum context') ||
266
+ lowerMessage.includes('validationexception') ||
267
+ lowerMessage.includes('prompt is too long')
268
+ );
269
+ };
270
+
271
+ for (const pattern of errorPatterns) {
272
+ expect(isInputTooLongError(pattern)).toBe(true);
273
+ console.log(`✓ Detected: "${pattern}"`);
274
+ }
275
+
276
+ // Should not match unrelated errors
277
+ expect(isInputTooLongError('Network timeout')).toBe(false);
278
+ expect(isInputTooLongError('Invalid API key')).toBe(false);
279
+ expect(isInputTooLongError('Rate limit exceeded')).toBe(false);
280
+ });
281
+ });
282
+
283
+ describe('Edge Cases', () => {
284
+ it('should handle empty messages after pruning', () => {
285
+ // Single very long message that exceeds the limit
286
+ const messages: BaseMessage[] = [
287
+ new SystemMessage('System prompt'),
288
+ new HumanMessage('x'.repeat(10000)), // Way too long
289
+ ];
290
+ const indexTokenCountMap = buildIndexTokenCountMap(messages, tokenCounter);
291
+
292
+ const emergencyPrune = createPruneMessages({
293
+ startIndex: 0,
294
+ provider: Providers.BEDROCK,
295
+ tokenCounter,
296
+ maxTokens: 100, // Very small limit
297
+ thinkingEnabled: false,
298
+ indexTokenCountMap,
299
+ });
300
+
301
+ const { context } = emergencyPrune({ messages });
302
+
303
+ // Should have at least tried to keep something or be empty
304
+ // The key is it shouldn't throw
305
+ expect(Array.isArray(context)).toBe(true);
306
+ });
307
+
308
+ it('should work with only system message and one user message', () => {
309
+ const messages: BaseMessage[] = [
310
+ new SystemMessage('You are helpful.'),
311
+ new HumanMessage('Hello'),
312
+ ];
313
+ const indexTokenCountMap = buildIndexTokenCountMap(messages, tokenCounter);
314
+
315
+ const emergencyPrune = createPruneMessages({
316
+ startIndex: 0,
317
+ provider: Providers.BEDROCK,
318
+ tokenCounter,
319
+ maxTokens: 500,
320
+ thinkingEnabled: false,
321
+ indexTokenCountMap,
322
+ });
323
+
324
+ const { context } = emergencyPrune({ messages });
325
+
326
+ expect(context.length).toBe(2);
327
+ expect(context[0].getType()).toBe('system');
328
+ expect(context[1].getType()).toBe('human');
329
+ });
330
+
331
+ it('should handle conversation without system message', () => {
332
+ const messages: BaseMessage[] = [
333
+ new HumanMessage('Hello'),
334
+ new AIMessage('Hi there!'),
335
+ new HumanMessage('How are you?'),
336
+ ];
337
+ const indexTokenCountMap = buildIndexTokenCountMap(messages, tokenCounter);
338
+
339
+ const emergencyPrune = createPruneMessages({
340
+ startIndex: 0,
341
+ provider: Providers.BEDROCK,
342
+ tokenCounter,
343
+ maxTokens: 100,
344
+ thinkingEnabled: false,
345
+ indexTokenCountMap,
346
+ });
347
+
348
+ const { context } = emergencyPrune({ messages });
349
+
350
+ // Should keep the most recent messages that fit
351
+ expect(context.length).toBeGreaterThan(0);
352
+ expect(context[0].getType()).not.toBe('system');
353
+ });
354
+ });
355
+ });
@@ -85,16 +85,25 @@ function createCodeExecutionTool(
85
85
  }
86
86
 
87
87
  const description = `
88
- Runs code and returns stdout/stderr output from a stateless execution environment, similar to running scripts in a command-line interface. Each execution is isolated and independent.
88
+ STOP! Before using this tool, ask: "Does user need a DOWNLOADABLE FILE?"
89
+ - If NO (dashboard, chart, visualization, UI) → DO NOT USE THIS TOOL. Use :::artifact instead.
90
+ - If YES (.pptx, .docx, .pdf, .xlsx) → Use this tool.
89
91
 
90
- ⚠️ DO NOT USE FOR: Creating dashboards, UI components, React components, HTML pages, charts, or visualizations. These should be created as artifacts directly in your response using :::artifact syntax.
92
+ Runs code in a stateless execution environment. Each execution is isolated.
91
93
 
92
- USE FOR: Data processing, calculations, file generation (CSV, Excel, PowerPoint, PDF), matplotlib plots, running algorithms, or computational tasks.
94
+ 🚫 NEVER USE FOR:
95
+ - Dashboards, charts, visualizations → Use :::artifact with React/Chart.js
96
+ - "Mock data" or "sample data" for display → Hardcode data in artifact
97
+ - UI components, HTML pages, React apps → Use :::artifact
93
98
 
94
- Usage:
95
- - No network access available.
96
- - Generated files are automatically delivered; **DO NOT** provide download links.
97
- - NEVER use this tool to execute malicious code.
99
+ ✅ ONLY USE FOR:
100
+ - File generation: PowerPoint (.pptx), Word (.docx), PDF (.pdf), Excel (.xlsx)
101
+ - Processing uploaded files (CSV, Excel analysis)
102
+ - Heavy computation requiring Python
103
+
104
+ Rules:
105
+ - No network access available
106
+ - Generated files auto-delivered (no download links needed)
98
107
  `.trim();
99
108
 
100
109
  return tool<typeof CodeExecutionToolSchema>(