@compilr-dev/agents 0.3.19 → 0.3.21

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/agent.d.ts CHANGED
@@ -1311,12 +1311,13 @@ export declare class Agent {
1311
1311
  */
1312
1312
  getContextStats(): ContextStats | undefined;
1313
1313
  /**
1314
- * Get observation masking statistics (tokens saved, observations masked).
1314
+ * Get observation masking statistics (tokens saved, observations masked, inputs compacted).
1315
1315
  */
1316
1316
  getObservationMaskStats(): {
1317
1317
  maskedCount: number;
1318
1318
  tokensSaved: number;
1319
1319
  activeStamps: number;
1320
+ inputsCompacted: number;
1320
1321
  } | undefined;
1321
1322
  /**
1322
1323
  * Get dead message pruning statistics (errors pruned, permissions pruned, tokens saved).
package/dist/agent.js CHANGED
@@ -835,7 +835,7 @@ export class Agent {
835
835
  return this.contextManager?.getStats(this.conversationHistory.length);
836
836
  }
837
837
  /**
838
- * Get observation masking statistics (tokens saved, observations masked).
838
+ * Get observation masking statistics (tokens saved, observations masked, inputs compacted).
839
839
  */
840
840
  getObservationMaskStats() {
841
841
  return this.observationMasker?.getStats();
@@ -21,7 +21,7 @@ export type { ToolResultDelegatorOptions } from './tool-result-delegator.js';
21
21
  export { DEFAULT_DELEGATION_CONFIG } from './delegation-types.js';
22
22
  export type { DelegationConfig, StoredResult, DelegationEvent } from './delegation-types.js';
23
23
  export { compactToolResult } from './result-compactor.js';
24
- export { ObservationMasker, DEFAULT_MASK_CONFIG, extractInputSummary, buildMaskText, isMasked, } from './observation-masker.js';
25
- export type { ObservationMaskConfig, MaskResult, ObservationMaskStats, } from './observation-masker.js';
24
+ export { ObservationMasker, DEFAULT_MASK_CONFIG, DEFAULT_INPUT_COMPACTION, extractInputSummary, buildMaskText, isMasked, } from './observation-masker.js';
25
+ export type { InputCompactionRule, ObservationMaskConfig, MaskResult, ObservationMaskStats, } from './observation-masker.js';
26
26
  export { DeadMessagePruner, DEFAULT_PRUNE_CONFIG, isPruned } from './dead-message-pruner.js';
27
27
  export type { PruneConfig, PruneResult, PruneStats } from './dead-message-pruner.js';
@@ -17,7 +17,7 @@ export { ToolResultDelegator, DELEGATION_SYSTEM_PROMPT } from './tool-result-del
17
17
  export { DEFAULT_DELEGATION_CONFIG } from './delegation-types.js';
18
18
  // Compact Tool Result Formatting (Phase 2 Token Optimization)
19
19
  export { compactToolResult } from './result-compactor.js';
20
- // Observation Masking (Phase 1 Token Optimization)
21
- export { ObservationMasker, DEFAULT_MASK_CONFIG, extractInputSummary, buildMaskText, isMasked, } from './observation-masker.js';
20
+ // Observation Masking (Phase 1 Token Optimization) + Tool Input Compaction (Phase 1b)
21
+ export { ObservationMasker, DEFAULT_MASK_CONFIG, DEFAULT_INPUT_COMPACTION, extractInputSummary, buildMaskText, isMasked, } from './observation-masker.js';
22
22
  // Dead Message Pruning (Phase 4 Token Optimization)
23
23
  export { DeadMessagePruner, DEFAULT_PRUNE_CONFIG, isPruned } from './dead-message-pruner.js';
@@ -9,6 +9,14 @@
9
9
  * The agent can re-read from the environment if needed (files, git, etc.).
10
10
  */
11
11
  import type { Message } from '../providers/types.js';
12
+ /**
13
+ * Defines which input fields to keep when compacting a tool_use input.
14
+ * All other fields are removed.
15
+ */
16
+ export interface InputCompactionRule {
17
+ /** Fields to preserve in the compacted input */
18
+ keepFields: string[];
19
+ }
12
20
  export interface ObservationMaskConfig {
13
21
  /** Turns after which tool results are masked (default: 6) */
14
22
  maskAfterTurns: number;
@@ -18,7 +26,15 @@ export interface ObservationMaskConfig {
18
26
  neverMask: string[];
19
27
  /** Tool names to mask after just 1 turn (large reads, bash output) */
20
28
  alwaysMaskEarly: string[];
29
+ /**
30
+ * Tool input compaction rules (Phase 1b). Keys are tool names.
31
+ * After the turn threshold, large input fields are removed, keeping only the fields listed.
32
+ * Set to false to disable input compaction entirely.
33
+ * Default: compact edit (keep filePath) and write_file (keep path, mode).
34
+ */
35
+ inputCompaction: Map<string, InputCompactionRule> | false;
21
36
  }
37
+ export declare const DEFAULT_INPUT_COMPACTION: Map<string, InputCompactionRule>;
22
38
  export declare const DEFAULT_MASK_CONFIG: ObservationMaskConfig;
23
39
  interface TurnStamp {
24
40
  turn: number;
@@ -39,6 +55,8 @@ export interface ObservationMaskStats {
39
55
  tokensSaved: number;
40
56
  /** Active stamps (pending masking) */
41
57
  activeStamps: number;
58
+ /** Total tool_use inputs compacted this session (Phase 1b) */
59
+ inputsCompacted: number;
42
60
  }
43
61
  export declare class ObservationMasker {
44
62
  private readonly stamps;
@@ -51,8 +69,9 @@ export declare class ObservationMasker {
51
69
  */
52
70
  stamp(toolUseId: string, toolName: string, input: Record<string, unknown>, contentLength: number, turn: number): void;
53
71
  /**
54
- * Mask old tool results in-place in the messages array.
55
- * Modifies ToolResultBlock.content directly.
72
+ * Mask old tool results and compact old tool_use inputs in-place.
73
+ * - tool_result: replaces content with compact mask text (Phase 1)
74
+ * - tool_use input: strips large fields, keeping only identifying fields (Phase 1b)
56
75
  */
57
76
  maskHistory(messages: Message[], currentTurn: number): MaskResult;
58
77
  getStats(): ObservationMaskStats;
@@ -64,6 +83,11 @@ export declare class ObservationMasker {
64
83
  * Get current configuration (for testing/inspection).
65
84
  */
66
85
  getConfig(): ObservationMaskConfig;
86
+ /**
87
+ * Compact a tool_use input in-place by stripping large fields.
88
+ * Returns estimated tokens saved (0 if no compaction performed).
89
+ */
90
+ private compactInput;
67
91
  }
68
92
  /**
69
93
  * Extract a short summary from tool input for the mask text.
@@ -8,11 +8,16 @@
8
8
  * Strategy: In-place masking of conversationHistory after N turns.
9
9
  * The agent can re-read from the environment if needed (files, git, etc.).
10
10
  */
11
+ export const DEFAULT_INPUT_COMPACTION = new Map([
12
+ ['edit', { keepFields: ['filePath'] }],
13
+ ['write_file', { keepFields: ['path', 'mode'] }],
14
+ ]);
11
15
  export const DEFAULT_MASK_CONFIG = {
12
16
  maskAfterTurns: 6,
13
17
  minCharsToMask: 400,
14
18
  neverMask: ['recall_full_result', 'recall_work'],
15
19
  alwaysMaskEarly: ['read_file', 'bash', 'bash_output', 'grep', 'glob'],
20
+ inputCompaction: new Map(DEFAULT_INPUT_COMPACTION),
16
21
  };
17
22
  // ============================================================
18
23
  // ObservationMasker
@@ -20,7 +25,7 @@ export const DEFAULT_MASK_CONFIG = {
20
25
  export class ObservationMasker {
21
26
  stamps = new Map();
22
27
  config;
23
- stats = { maskedCount: 0, tokensSaved: 0 };
28
+ stats = { maskedCount: 0, tokensSaved: 0, inputsCompacted: 0 };
24
29
  constructor(config) {
25
30
  this.config = { ...DEFAULT_MASK_CONFIG, ...config };
26
31
  }
@@ -43,43 +48,59 @@ export class ObservationMasker {
43
48
  // Masking — called after incrementTurn()
44
49
  // ----------------------------------------------------------
45
50
  /**
46
- * Mask old tool results in-place in the messages array.
47
- * Modifies ToolResultBlock.content directly.
51
+ * Mask old tool results and compact old tool_use inputs in-place.
52
+ * - tool_result: replaces content with compact mask text (Phase 1)
53
+ * - tool_use input: strips large fields, keeping only identifying fields (Phase 1b)
48
54
  */
49
55
  maskHistory(messages, currentTurn) {
50
56
  let tokensSaved = 0;
51
57
  let maskedCount = 0;
52
58
  for (const msg of messages) {
53
- if (msg.role !== 'user' || typeof msg.content === 'string')
59
+ if (typeof msg.content === 'string')
54
60
  continue;
55
- for (const block of msg.content) {
56
- if (block.type !== 'tool_result')
57
- continue;
58
- if (isMasked(block.content))
59
- continue;
60
- const stamp = this.stamps.get(block.toolUseId);
61
- if (!stamp)
62
- continue;
63
- if (this.config.neverMask.includes(stamp.toolName))
64
- continue;
65
- const age = currentTurn - stamp.turn;
66
- const threshold = this.config.alwaysMaskEarly.includes(stamp.toolName)
67
- ? 1
68
- : this.config.maskAfterTurns;
69
- if (age < threshold)
70
- continue;
71
- if (stamp.contentLength < this.config.minCharsToMask)
72
- continue;
73
- // Build mask and calculate savings
74
- const maskText = buildMaskText(stamp);
75
- const savedChars = stamp.contentLength - maskText.length;
76
- const savedTokens = Math.max(0, Math.ceil(savedChars / 4));
77
- // Mask in-place
78
- block.content = maskText;
79
- tokensSaved += savedTokens;
80
- maskedCount++;
81
- // Clean up stamp
82
- this.stamps.delete(block.toolUseId);
61
+ // Phase 1: Mask old tool_result content in user messages
62
+ if (msg.role === 'user') {
63
+ for (const block of msg.content) {
64
+ if (block.type !== 'tool_result')
65
+ continue;
66
+ if (isMasked(block.content))
67
+ continue;
68
+ const stamp = this.stamps.get(block.toolUseId);
69
+ if (!stamp)
70
+ continue;
71
+ if (this.config.neverMask.includes(stamp.toolName))
72
+ continue;
73
+ const age = currentTurn - stamp.turn;
74
+ const threshold = this.config.alwaysMaskEarly.includes(stamp.toolName)
75
+ ? 1
76
+ : this.config.maskAfterTurns;
77
+ if (age < threshold)
78
+ continue;
79
+ if (stamp.contentLength < this.config.minCharsToMask)
80
+ continue;
81
+ // Build mask and calculate savings
82
+ const maskText = buildMaskText(stamp);
83
+ const savedChars = stamp.contentLength - maskText.length;
84
+ const savedTokens = Math.max(0, Math.ceil(savedChars / 4));
85
+ // Mask in-place
86
+ block.content = maskText;
87
+ tokensSaved += savedTokens;
88
+ maskedCount++;
89
+ // Clean up stamp
90
+ this.stamps.delete(block.toolUseId);
91
+ }
92
+ }
93
+ // Phase 1b: Compact old tool_use inputs in assistant messages
94
+ if (msg.role === 'assistant') {
95
+ for (const block of msg.content) {
96
+ if (block.type !== 'tool_use')
97
+ continue;
98
+ const saved = this.compactInput(block, currentTurn);
99
+ if (saved > 0) {
100
+ tokensSaved += saved;
101
+ this.stats.inputsCompacted++;
102
+ }
103
+ }
83
104
  }
84
105
  }
85
106
  this.stats.maskedCount += maskedCount;
@@ -100,7 +121,7 @@ export class ObservationMasker {
100
121
  */
101
122
  reset() {
102
123
  this.stamps.clear();
103
- this.stats = { maskedCount: 0, tokensSaved: 0 };
124
+ this.stats = { maskedCount: 0, tokensSaved: 0, inputsCompacted: 0 };
104
125
  }
105
126
  /**
106
127
  * Get current configuration (for testing/inspection).
@@ -108,6 +129,47 @@ export class ObservationMasker {
108
129
  getConfig() {
109
130
  return { ...this.config };
110
131
  }
132
+ // ----------------------------------------------------------
133
+ // Phase 1b: Tool input compaction
134
+ // ----------------------------------------------------------
135
+ /**
136
+ * Compact a tool_use input in-place by stripping large fields.
137
+ * Returns estimated tokens saved (0 if no compaction performed).
138
+ */
139
+ compactInput(block, currentTurn) {
140
+ if (this.config.inputCompaction === false)
141
+ return 0;
142
+ const rule = this.config.inputCompaction.get(block.name);
143
+ if (!rule)
144
+ return 0;
145
+ const stamp = this.stamps.get(block.id);
146
+ if (!stamp)
147
+ return 0;
148
+ const age = currentTurn - stamp.turn;
149
+ const threshold = this.config.alwaysMaskEarly.includes(stamp.toolName)
150
+ ? 1
151
+ : this.config.maskAfterTurns;
152
+ if (age < threshold)
153
+ return 0;
154
+ // Check if there are any fields to remove
155
+ const fieldKeys = Object.keys(block.input);
156
+ const removableKeys = fieldKeys.filter((k) => !rule.keepFields.includes(k));
157
+ if (removableKeys.length === 0)
158
+ return 0;
159
+ // Estimate tokens before compaction
160
+ const beforeChars = JSON.stringify(block.input).length;
161
+ // Build compacted input with only kept fields
162
+ const compacted = {};
163
+ for (const field of rule.keepFields) {
164
+ if (field in block.input) {
165
+ compacted[field] = block.input[field];
166
+ }
167
+ }
168
+ block.input = compacted;
169
+ // Estimate tokens saved
170
+ const afterChars = JSON.stringify(compacted).length;
171
+ return Math.max(0, Math.ceil((beforeChars - afterChars) / 4));
172
+ }
111
173
  }
112
174
  // ============================================================
113
175
  // Pure functions (exported for testing)
@@ -116,9 +178,9 @@ export class ObservationMasker {
116
178
  * Extract a short summary from tool input for the mask text.
117
179
  */
118
180
  export function extractInputSummary(toolName, input) {
119
- // File operations — use path
181
+ // File operations — use path (supports path, file_path, and filePath conventions)
120
182
  if (toolName === 'read_file' || toolName === 'edit' || toolName === 'write_file') {
121
- const path = input.path ?? input.file_path;
183
+ const path = input.path ?? input.file_path ?? input.filePath;
122
184
  if (typeof path === 'string')
123
185
  return path;
124
186
  }
package/dist/index.d.ts CHANGED
@@ -39,8 +39,8 @@ export type { ToolPairingValidation } from './messages/index.js';
39
39
  export { generateId, sleep, retry, truncate, withRetryGenerator, calculateBackoffDelay, DEFAULT_RETRY_CONFIG, countTokens, countMessageTokens, } from './utils/index.js';
40
40
  export type { RetryConfig as LLMRetryConfig, WithRetryOptions } from './utils/index.js';
41
41
  export { AgentError, ProviderError, ToolError, ToolTimeoutError, ToolLoopError, ValidationError, MaxIterationsError, AbortError, ContextOverflowError, isAgentError, isProviderError, isToolError, isToolTimeoutError, isToolLoopError, isContextOverflowError, wrapError, } from './errors.js';
42
- export { ContextManager, DEFAULT_CONTEXT_CONFIG, FileAccessTracker, createFileTrackingHook, TRACKED_TOOLS, DelegatedResultStore, ToolResultDelegator, DELEGATION_SYSTEM_PROMPT, DEFAULT_DELEGATION_CONFIG, compactToolResult, ObservationMasker, DEFAULT_MASK_CONFIG, extractInputSummary, buildMaskText, isMasked, DeadMessagePruner, DEFAULT_PRUNE_CONFIG, isPruned, } from './context/index.js';
43
- export type { ContextManagerOptions, ContextCategory, BudgetAllocation, CategoryBudgetInfo, PreflightResult, VerbosityLevel, VerbosityConfig, ContextConfig, FilteringConfig, CompactionConfig, SummarizationConfig, CompactionResult, SummarizationResult, FilteringResult, ContextEvent, ContextEventHandler, ContextStats, FileAccessType, FileAccess, FileAccessTrackerOptions, FormatHintsOptions, FileAccessStats, RestorationHintMessage, DelegatedResultStoreStats, ToolResultDelegatorOptions, DelegationConfig, StoredResult, DelegationEvent, ObservationMaskConfig, MaskResult, ObservationMaskStats, PruneConfig, PruneResult, PruneStats, } from './context/index.js';
42
+ export { ContextManager, DEFAULT_CONTEXT_CONFIG, FileAccessTracker, createFileTrackingHook, TRACKED_TOOLS, DelegatedResultStore, ToolResultDelegator, DELEGATION_SYSTEM_PROMPT, DEFAULT_DELEGATION_CONFIG, compactToolResult, ObservationMasker, DEFAULT_MASK_CONFIG, DEFAULT_INPUT_COMPACTION, extractInputSummary, buildMaskText, isMasked, DeadMessagePruner, DEFAULT_PRUNE_CONFIG, isPruned, } from './context/index.js';
43
+ export type { ContextManagerOptions, ContextCategory, BudgetAllocation, CategoryBudgetInfo, PreflightResult, VerbosityLevel, VerbosityConfig, ContextConfig, FilteringConfig, CompactionConfig, SummarizationConfig, CompactionResult, SummarizationResult, FilteringResult, ContextEvent, ContextEventHandler, ContextStats, FileAccessType, FileAccess, FileAccessTrackerOptions, FormatHintsOptions, FileAccessStats, RestorationHintMessage, DelegatedResultStoreStats, ToolResultDelegatorOptions, DelegationConfig, StoredResult, DelegationEvent, InputCompactionRule, ObservationMaskConfig, MaskResult, ObservationMaskStats, PruneConfig, PruneResult, PruneStats, } from './context/index.js';
44
44
  export { SkillRegistry, defineSkill, createSkillRegistry, builtinSkills, getDefaultSkillRegistry, resetDefaultSkillRegistry, } from './skills/index.js';
45
45
  export type { Skill, SkillInvocationResult, SkillInvokeOptions } from './skills/index.js';
46
46
  export { JsonSerializer, CompactJsonSerializer, defaultSerializer, MemoryCheckpointer, FileCheckpointer, StateError, StateErrorCode, CURRENT_STATE_VERSION, } from './state/index.js';
package/dist/index.js CHANGED
@@ -50,8 +50,8 @@ export { ContextManager, DEFAULT_CONTEXT_CONFIG, FileAccessTracker, createFileTr
50
50
  DelegatedResultStore, ToolResultDelegator, DELEGATION_SYSTEM_PROMPT, DEFAULT_DELEGATION_CONFIG,
51
51
  // Compact tool result formatting (Phase 2 Token Optimization)
52
52
  compactToolResult,
53
- // Observation masking (Phase 1 Token Optimization)
54
- ObservationMasker, DEFAULT_MASK_CONFIG, extractInputSummary, buildMaskText, isMasked,
53
+ // Observation masking (Phase 1 Token Optimization) + Tool Input Compaction (Phase 1b)
54
+ ObservationMasker, DEFAULT_MASK_CONFIG, DEFAULT_INPUT_COMPACTION, extractInputSummary, buildMaskText, isMasked,
55
55
  // Dead message pruning (Phase 4 Token Optimization)
56
56
  DeadMessagePruner, DEFAULT_PRUNE_CONFIG, isPruned, } from './context/index.js';
57
57
  // Skills system
@@ -352,7 +352,7 @@ export class ClaudeProvider {
352
352
  return new ProviderError('Rate limit exceeded', 'claude', 429, error);
353
353
  }
354
354
  if (error instanceof Anthropic.AuthenticationError) {
355
- return new ProviderError('Authentication failed: Invalid API key', 'claude', 401, error);
355
+ return new ProviderError(`Authentication failed: Invalid API key. (${error.message})`, 'claude', 401, error);
356
356
  }
357
357
  if (error instanceof Error) {
358
358
  return new ProviderError(error.message, 'claude', undefined, error);
@@ -96,9 +96,9 @@ export class FireworksProvider extends OpenAICompatibleProvider {
96
96
  }
97
97
  switch (status) {
98
98
  case 401:
99
- return new ProviderError('Invalid Fireworks AI API key. Check your FIREWORKS_API_KEY.', 'fireworks', 401);
99
+ return new ProviderError(`Invalid Fireworks AI API key. Check your FIREWORKS_API_KEY. (${message})`, 'fireworks', 401);
100
100
  case 403:
101
- return new ProviderError('Access denied. Check your Fireworks AI API key permissions.', 'fireworks', 403);
101
+ return new ProviderError(`Access denied. Check your Fireworks AI API key permissions. (${message})`, 'fireworks', 403);
102
102
  case 429:
103
103
  return new ProviderError('Fireworks AI rate limit exceeded. Please wait and try again.', 'fireworks', 429);
104
104
  case 500:
@@ -93,9 +93,9 @@ export class GeminiProvider extends OpenAICompatibleProvider {
93
93
  case 400:
94
94
  return new ProviderError(`Invalid request to Gemini API: ${message}`, 'gemini', 400);
95
95
  case 401:
96
- return new ProviderError('Invalid Gemini API key. Check your GOOGLE_AI_API_KEY or GEMINI_API_KEY.', 'gemini', 401);
96
+ return new ProviderError(`Invalid Gemini API key. Check your GOOGLE_AI_API_KEY or GEMINI_API_KEY. (${message})`, 'gemini', 401);
97
97
  case 403:
98
- return new ProviderError('Access denied to Gemini API. Check your API key permissions.', 'gemini', 403);
98
+ return new ProviderError(`Access denied to Gemini API. Check your API key permissions. (${message})`, 'gemini', 403);
99
99
  case 429:
100
100
  return new ProviderError('Gemini rate limit exceeded. Please wait and try again.', 'gemini', 429);
101
101
  case 500:
@@ -81,9 +81,9 @@ export class GroqProvider extends OpenAICompatibleProvider {
81
81
  }
82
82
  switch (status) {
83
83
  case 401:
84
- return new ProviderError('Invalid Groq API key. Check your GROQ_API_KEY.', 'groq', 401);
84
+ return new ProviderError(`Invalid Groq API key. Check your GROQ_API_KEY. (${message})`, 'groq', 401);
85
85
  case 403:
86
- return new ProviderError('Access denied. Check your Groq API key permissions.', 'groq', 403);
86
+ return new ProviderError(`Access denied. Check your Groq API key permissions. (${message})`, 'groq', 403);
87
87
  case 429:
88
88
  return new ProviderError('Groq rate limit exceeded. Please wait and try again.', 'groq', 429);
89
89
  case 500:
@@ -87,9 +87,9 @@ export class OpenAIProvider extends OpenAICompatibleProvider {
87
87
  }
88
88
  switch (status) {
89
89
  case 401:
90
- return new ProviderError('Invalid OpenAI API key. Check your OPENAI_API_KEY.', 'openai', 401);
90
+ return new ProviderError(`Invalid OpenAI API key. Check your OPENAI_API_KEY. (${message})`, 'openai', 401);
91
91
  case 403:
92
- return new ProviderError('Access denied. Check your OpenAI API key permissions.', 'openai', 403);
92
+ return new ProviderError(`Access denied. Check your OpenAI API key permissions. (${message})`, 'openai', 403);
93
93
  case 429:
94
94
  return new ProviderError('OpenAI rate limit exceeded. Please wait and try again.', 'openai', 429);
95
95
  case 500:
@@ -94,9 +94,9 @@ export class OpenRouterProvider extends OpenAICompatibleProvider {
94
94
  }
95
95
  switch (status) {
96
96
  case 401:
97
- return new ProviderError('Invalid OpenRouter API key. Check your OPENROUTER_API_KEY.', 'openrouter', 401);
97
+ return new ProviderError(`Invalid OpenRouter API key. Check your OPENROUTER_API_KEY. (${message})`, 'openrouter', 401);
98
98
  case 403:
99
- return new ProviderError('Access denied. Check your OpenRouter API key permissions or credits.', 'openrouter', 403);
99
+ return new ProviderError(`Access denied. Check your OpenRouter API key permissions or credits. (${message})`, 'openrouter', 403);
100
100
  case 429:
101
101
  return new ProviderError('OpenRouter rate limit exceeded. Please wait and try again.', 'openrouter', 429);
102
102
  case 500:
@@ -81,9 +81,9 @@ export class PerplexityProvider extends OpenAICompatibleProvider {
81
81
  }
82
82
  switch (status) {
83
83
  case 401:
84
- return new ProviderError('Invalid Perplexity API key. Check your PERPLEXITY_API_KEY.', 'perplexity', 401);
84
+ return new ProviderError(`Invalid Perplexity API key. Check your PERPLEXITY_API_KEY. (${message})`, 'perplexity', 401);
85
85
  case 403:
86
- return new ProviderError('Access denied. Check your Perplexity API key permissions.', 'perplexity', 403);
86
+ return new ProviderError(`Access denied. Check your Perplexity API key permissions. (${message})`, 'perplexity', 403);
87
87
  case 429:
88
88
  return new ProviderError('Perplexity rate limit exceeded. Please wait and try again.', 'perplexity', 429);
89
89
  case 500:
@@ -81,9 +81,9 @@ export class TogetherProvider extends OpenAICompatibleProvider {
81
81
  }
82
82
  switch (status) {
83
83
  case 401:
84
- return new ProviderError('Invalid Together AI API key. Check your TOGETHER_API_KEY.', 'together', 401);
84
+ return new ProviderError(`Invalid Together AI API key. Check your TOGETHER_API_KEY. (${message})`, 'together', 401);
85
85
  case 403:
86
- return new ProviderError('Access denied. Check your Together AI API key permissions.', 'together', 403);
86
+ return new ProviderError(`Access denied. Check your Together AI API key permissions. (${message})`, 'together', 403);
87
87
  case 429:
88
88
  return new ProviderError('Together AI rate limit exceeded. Please wait and try again.', 'together', 429);
89
89
  case 500:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/agents",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
4
4
  "description": "Lightweight multi-LLM agent library for building CLI AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",