@plexor-dev/claude-code-plugin 0.1.0-beta.1 → 0.1.0-beta.10
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/commands/plexor-config.md +29 -30
- package/commands/plexor-enabled.md +46 -28
- package/commands/plexor-login.md +46 -57
- package/commands/plexor-logout.md +19 -27
- package/commands/plexor-mode.md +39 -17
- package/commands/plexor-provider.md +40 -18
- package/commands/plexor-settings.md +37 -72
- package/commands/plexor-status.js +154 -0
- package/commands/plexor-status.md +47 -34
- package/hooks/intercept.js +410 -0
- package/hooks/track-response.js +110 -0
- package/package.json +2 -1
- package/lib/constants.js +0 -40
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plexor Status Command
|
|
5
|
+
* Displays formatted status with usage statistics
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const https = require('https');
|
|
11
|
+
|
|
12
|
+
const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
// Read config
|
|
16
|
+
let config;
|
|
17
|
+
try {
|
|
18
|
+
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
19
|
+
config = JSON.parse(data);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.log('Not configured. Run /plexor-login first.');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const apiKey = config.auth?.api_key;
|
|
26
|
+
const enabled = config.settings?.enabled ?? false;
|
|
27
|
+
const mode = config.settings?.mode || 'balanced';
|
|
28
|
+
const provider = config.settings?.preferred_provider || 'auto';
|
|
29
|
+
const localCache = config.settings?.localCacheEnabled ?? false;
|
|
30
|
+
const apiUrl = config.settings?.apiUrl || 'https://api.plexor.dev';
|
|
31
|
+
|
|
32
|
+
if (!apiKey) {
|
|
33
|
+
console.log('Not authenticated. Run /plexor-login first.');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Fetch user info and stats
|
|
38
|
+
let user = { email: 'Unknown', tier: { name: 'Free', limits: {} } };
|
|
39
|
+
let stats = { period: {}, summary: {} };
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
[user, stats] = await Promise.all([
|
|
43
|
+
fetchJson(apiUrl, '/v1/user', apiKey),
|
|
44
|
+
fetchJson(apiUrl, '/v1/stats', apiKey)
|
|
45
|
+
]);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
// Continue with defaults if API fails
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Extract data
|
|
51
|
+
const email = user.email || 'Unknown';
|
|
52
|
+
const tierName = user.tier?.name || 'Free';
|
|
53
|
+
const monthlyOpts = user.tier?.limits?.monthly_optimizations || '∞';
|
|
54
|
+
const monthlyComps = user.tier?.limits?.monthly_completions || '∞';
|
|
55
|
+
|
|
56
|
+
const period = stats.period || {};
|
|
57
|
+
const summary = stats.summary || {};
|
|
58
|
+
|
|
59
|
+
const formatDate = (iso) => {
|
|
60
|
+
if (!iso) return '?';
|
|
61
|
+
const d = new Date(iso);
|
|
62
|
+
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
63
|
+
};
|
|
64
|
+
const weekRange = `${formatDate(period.start)} - ${formatDate(period.end)}`;
|
|
65
|
+
|
|
66
|
+
// Format numbers
|
|
67
|
+
const formatNum = (n) => (n || 0).toLocaleString();
|
|
68
|
+
const formatPct = (n) => (n || 0).toFixed(1);
|
|
69
|
+
const formatCost = (n) => (n || 0).toFixed(2);
|
|
70
|
+
|
|
71
|
+
const status = enabled ? '● Active' : '○ Inactive';
|
|
72
|
+
const optEnabled = enabled ? 'Enabled' : 'Disabled';
|
|
73
|
+
const cacheEnabled = localCache ? 'Enabled' : 'Disabled';
|
|
74
|
+
const cacheRate = formatPct((summary.cache_hit_rate || 0) * 100);
|
|
75
|
+
|
|
76
|
+
// Build dashboard URL
|
|
77
|
+
const dashboardUrl = apiUrl.replace('.api.', '.').replace('/api', '') + '/dashboard.html';
|
|
78
|
+
|
|
79
|
+
// Output formatted status - each line is exactly 43 chars inner width
|
|
80
|
+
const line = (content) => ` │ ${content.padEnd(43)}│`;
|
|
81
|
+
|
|
82
|
+
console.log(` ┌─────────────────────────────────────────────┐
|
|
83
|
+
${line('Plexor Status')}
|
|
84
|
+
├─────────────────────────────────────────────┤
|
|
85
|
+
${line(`Account: ${tierName}`)}
|
|
86
|
+
${line(`Email: ${email}`)}
|
|
87
|
+
${line(`Status: ${status}`)}
|
|
88
|
+
├─────────────────────────────────────────────┤
|
|
89
|
+
${line(`This Week (${weekRange})`)}
|
|
90
|
+
${line(`├── Requests: ${formatNum(summary.total_requests)}`)}
|
|
91
|
+
${line(`├── Original tokens: ${formatNum(summary.original_tokens)}`)}
|
|
92
|
+
${line(`├── Optimized tokens: ${formatNum(summary.optimized_tokens)}`)}
|
|
93
|
+
${line(`├── Tokens saved: ${formatNum(summary.tokens_saved)} (${formatPct(summary.tokens_saved_percent)}%)`)}
|
|
94
|
+
${line(`├── Baseline cost: $${formatCost(summary.baseline_cost)}`)}
|
|
95
|
+
${line(`├── Actual cost: $${formatCost(summary.total_cost)}`)}
|
|
96
|
+
${line(`└── Cost saved: $${formatCost(summary.cost_saved)} (${formatPct(summary.cost_saved_percent)}%)`)}
|
|
97
|
+
├─────────────────────────────────────────────┤
|
|
98
|
+
${line('Performance')}
|
|
99
|
+
${line(`└── Cache hit rate: ${cacheRate}%`)}
|
|
100
|
+
├─────────────────────────────────────────────┤
|
|
101
|
+
${line('Limits')}
|
|
102
|
+
${line(`├── Monthly optimizations: ${formatNum(monthlyOpts)}`)}
|
|
103
|
+
${line(`└── Monthly completions: ${formatNum(monthlyComps)}`)}
|
|
104
|
+
├─────────────────────────────────────────────┤
|
|
105
|
+
${line('Settings')}
|
|
106
|
+
${line(`├── Optimization: ${optEnabled}`)}
|
|
107
|
+
${line(`├── Local cache: ${cacheEnabled}`)}
|
|
108
|
+
${line(`├── Mode: ${mode}`)}
|
|
109
|
+
${line(`└── Provider routing: ${provider}`)}
|
|
110
|
+
└─────────────────────────────────────────────┘
|
|
111
|
+
|
|
112
|
+
Dashboard: ${dashboardUrl}
|
|
113
|
+
`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function fetchJson(apiUrl, endpoint, apiKey) {
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
const url = new URL(`${apiUrl}${endpoint}`);
|
|
119
|
+
|
|
120
|
+
const options = {
|
|
121
|
+
hostname: url.hostname,
|
|
122
|
+
port: 443,
|
|
123
|
+
path: url.pathname,
|
|
124
|
+
method: 'GET',
|
|
125
|
+
headers: {
|
|
126
|
+
'X-Plexor-Key': apiKey
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const req = https.request(options, (res) => {
|
|
131
|
+
let data = '';
|
|
132
|
+
res.on('data', chunk => data += chunk);
|
|
133
|
+
res.on('end', () => {
|
|
134
|
+
try {
|
|
135
|
+
resolve(JSON.parse(data));
|
|
136
|
+
} catch {
|
|
137
|
+
reject(new Error('Invalid response'));
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
req.on('error', reject);
|
|
143
|
+
req.setTimeout(5000, () => {
|
|
144
|
+
req.destroy();
|
|
145
|
+
reject(new Error('Timeout'));
|
|
146
|
+
});
|
|
147
|
+
req.end();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
main().catch(err => {
|
|
152
|
+
console.error('Error:', err.message);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
});
|
|
@@ -1,46 +1,59 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Show Plexor optimization statistics and savings
|
|
2
|
+
description: Show Plexor optimization statistics and savings (user)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Plexor Status
|
|
6
6
|
|
|
7
|
-
Display
|
|
7
|
+
Display Plexor optimization statistics in a clean, professional format.
|
|
8
8
|
|
|
9
9
|
## Instructions
|
|
10
10
|
|
|
11
|
-
1. Read
|
|
12
|
-
2.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- This session: requests optimized, tokens saved, estimated savings
|
|
17
|
-
- This week: total requests, total tokens saved, total savings
|
|
18
|
-
- Current settings: optimization enabled, local cache status
|
|
19
|
-
|
|
20
|
-
## Output Format
|
|
11
|
+
1. Read `~/.plexor/config.json` to get settings and API key
|
|
12
|
+
2. Call the Plexor APIs to get user info and stats:
|
|
13
|
+
- `GET {apiUrl}/v1/user` with header `X-Plexor-Key: {api_key}`
|
|
14
|
+
- `GET {apiUrl}/v1/stats` with header `X-Plexor-Key: {api_key}`
|
|
15
|
+
3. Output the formatted status box directly as text (not in a code block):
|
|
21
16
|
|
|
22
17
|
```
|
|
23
|
-
┌─────────────────────────────────────────────┐
|
|
24
|
-
│ Plexor Status │
|
|
25
|
-
├─────────────────────────────────────────────┤
|
|
26
|
-
│ Account:
|
|
27
|
-
│
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
│
|
|
31
|
-
│ ├──
|
|
32
|
-
│
|
|
33
|
-
|
|
34
|
-
│
|
|
35
|
-
│ ├──
|
|
36
|
-
│ ├──
|
|
37
|
-
│ └──
|
|
38
|
-
├─────────────────────────────────────────────┤
|
|
39
|
-
│
|
|
40
|
-
│
|
|
41
|
-
|
|
42
|
-
│
|
|
43
|
-
|
|
18
|
+
┌─────────────────────────────────────────────┐
|
|
19
|
+
│ Plexor Status │
|
|
20
|
+
├─────────────────────────────────────────────┤
|
|
21
|
+
│ Account: {tier.name} │
|
|
22
|
+
│ Email: {email} │
|
|
23
|
+
│ Status: ● Active │
|
|
24
|
+
├─────────────────────────────────────────────┤
|
|
25
|
+
│ This Week ({period.start} - {period.end}) │
|
|
26
|
+
│ ├── Requests: {total_requests} │
|
|
27
|
+
│ ├── Original tokens: {original_tokens} │
|
|
28
|
+
│ ├── Optimized tokens: {optimized_tokens} │
|
|
29
|
+
│ ├── Tokens saved: {tokens_saved} ({%}) │
|
|
30
|
+
│ ├── Baseline cost: ${baseline_cost} │
|
|
31
|
+
│ ├── Actual cost: ${total_cost} │
|
|
32
|
+
│ └── Cost saved: ${cost_saved} ({%}) │
|
|
33
|
+
├─────────────────────────────────────────────┤
|
|
34
|
+
│ Performance │
|
|
35
|
+
│ └── Cache hit rate: {cache_hit_rate}% │
|
|
36
|
+
├─────────────────────────────────────────────┤
|
|
37
|
+
│ Limits │
|
|
38
|
+
│ ├── Monthly optimizations: {limit} │
|
|
39
|
+
│ └── Monthly completions: {limit} │
|
|
40
|
+
├─────────────────────────────────────────────┤
|
|
41
|
+
│ Settings │
|
|
42
|
+
│ ├── Optimization: {Enabled/Disabled} │
|
|
43
|
+
│ ├── Local cache: {Enabled/Disabled} │
|
|
44
|
+
│ ├── Mode: {mode} │
|
|
45
|
+
│ └── Provider routing: {provider} │
|
|
46
|
+
└─────────────────────────────────────────────┘
|
|
44
47
|
|
|
45
|
-
Dashboard:
|
|
48
|
+
Dashboard: {dashboard_url}
|
|
46
49
|
```
|
|
50
|
+
|
|
51
|
+
IMPORTANT:
|
|
52
|
+
- Output ONLY the status box as plain text, exactly as shown above
|
|
53
|
+
- Do NOT wrap in markdown code blocks
|
|
54
|
+
- Do NOT add any commentary before or after
|
|
55
|
+
- Format numbers with commas (e.g., 50,000)
|
|
56
|
+
- Format costs with 2 decimals (e.g., $4.50)
|
|
57
|
+
- Format dates as "Mon D" (e.g., "Jan 7")
|
|
58
|
+
- Use "● Active" if enabled, "○ Inactive" if disabled
|
|
59
|
+
- Multiply cache_hit_rate by 100 for percentage
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plexor Interception Hook
|
|
5
|
+
*
|
|
6
|
+
* This script intercepts Claude Code prompts before they are sent to the LLM.
|
|
7
|
+
* It optimizes the prompt and optionally routes to a cheaper provider.
|
|
8
|
+
*
|
|
9
|
+
* Input: JSON object with messages, model, max_tokens, etc.
|
|
10
|
+
* Output: Modified JSON object with optimized messages
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const PlexorClient = require('../lib/plexor-client');
|
|
14
|
+
const ConfigManager = require('../lib/config');
|
|
15
|
+
const LocalCache = require('../lib/cache');
|
|
16
|
+
const Logger = require('../lib/logger');
|
|
17
|
+
|
|
18
|
+
const logger = new Logger('intercept');
|
|
19
|
+
const config = new ConfigManager();
|
|
20
|
+
const cache = new LocalCache();
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const input = await readStdin();
|
|
27
|
+
const request = JSON.parse(input);
|
|
28
|
+
|
|
29
|
+
// CRITICAL: Skip optimization for agentic/tool-using requests
|
|
30
|
+
// Modifying messages breaks the agent loop and causes infinite loops
|
|
31
|
+
if (isAgenticRequest(request)) {
|
|
32
|
+
logger.debug('Agentic request detected, passing through unchanged');
|
|
33
|
+
return output({
|
|
34
|
+
...request,
|
|
35
|
+
plexor_cwd: process.cwd(),
|
|
36
|
+
_plexor: {
|
|
37
|
+
source: 'passthrough_agentic',
|
|
38
|
+
reason: 'tool_use_detected',
|
|
39
|
+
cwd: process.cwd(),
|
|
40
|
+
latency_ms: Date.now() - startTime
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// CRITICAL: Skip optimization for slash commands (Issue #683)
|
|
46
|
+
// Slash commands like /plexor-status should pass through unchanged
|
|
47
|
+
if (isSlashCommand(request)) {
|
|
48
|
+
logger.debug('Slash command detected, passing through unchanged');
|
|
49
|
+
return output({
|
|
50
|
+
...request,
|
|
51
|
+
plexor_cwd: process.cwd(),
|
|
52
|
+
_plexor: {
|
|
53
|
+
source: 'passthrough_slash_command',
|
|
54
|
+
reason: 'slash_command_detected',
|
|
55
|
+
cwd: process.cwd(),
|
|
56
|
+
latency_ms: Date.now() - startTime
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// CRITICAL: Skip optimization for CLI commands requiring tool execution (Issue #683)
|
|
62
|
+
// Azure CLI, AWS CLI, kubectl, etc. need tools to be preserved
|
|
63
|
+
if (requiresToolExecution(request)) {
|
|
64
|
+
logger.debug('CLI tool execution detected, passing through unchanged');
|
|
65
|
+
return output({
|
|
66
|
+
...request,
|
|
67
|
+
plexor_cwd: process.cwd(),
|
|
68
|
+
_plexor: {
|
|
69
|
+
source: 'passthrough_cli',
|
|
70
|
+
reason: 'cli_tool_execution_detected',
|
|
71
|
+
cwd: process.cwd(),
|
|
72
|
+
latency_ms: Date.now() - startTime
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const settings = await config.load();
|
|
78
|
+
|
|
79
|
+
if (!settings.enabled) {
|
|
80
|
+
logger.debug('Plexor disabled, passing through');
|
|
81
|
+
return output(request);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!settings.apiKey) {
|
|
85
|
+
logger.info('Not authenticated. Run /plexor-login to enable optimization.');
|
|
86
|
+
return output(request);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const client = new PlexorClient({
|
|
90
|
+
apiKey: settings.apiKey,
|
|
91
|
+
baseUrl: settings.apiUrl || 'https://api.plexor.dev',
|
|
92
|
+
timeout: settings.timeout || 5000
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const messages = extractMessages(request);
|
|
96
|
+
const model = request.model || 'claude-sonnet-4-20250514';
|
|
97
|
+
const maxTokens = request.max_tokens || 4096;
|
|
98
|
+
|
|
99
|
+
const cacheKey = cache.generateKey(messages);
|
|
100
|
+
const cachedResponse = await cache.get(cacheKey);
|
|
101
|
+
|
|
102
|
+
if (cachedResponse && settings.localCacheEnabled) {
|
|
103
|
+
logger.info('[Plexor] Local cache hit');
|
|
104
|
+
return output({
|
|
105
|
+
...request,
|
|
106
|
+
_plexor: {
|
|
107
|
+
source: 'local_cache',
|
|
108
|
+
latency_ms: Date.now() - startTime
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
logger.debug('Calling Plexor API...');
|
|
114
|
+
|
|
115
|
+
const result = await client.optimize({
|
|
116
|
+
messages: messages,
|
|
117
|
+
model: model,
|
|
118
|
+
max_tokens: maxTokens,
|
|
119
|
+
task_hint: detectTaskType(messages),
|
|
120
|
+
context: {
|
|
121
|
+
session_id: request._session_id,
|
|
122
|
+
turn_number: request._turn_number,
|
|
123
|
+
cwd: process.cwd()
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const savingsPercent = ((result.original_tokens - result.optimized_tokens) / result.original_tokens * 100).toFixed(1);
|
|
128
|
+
|
|
129
|
+
logger.info(`[Plexor] Optimized: ${result.original_tokens} → ${result.optimized_tokens} tokens (${savingsPercent}% saved)`);
|
|
130
|
+
|
|
131
|
+
if (result.recommended_provider !== 'anthropic') {
|
|
132
|
+
logger.info(`[Plexor] Recommended: ${result.recommended_provider} (~$${result.estimated_cost.toFixed(4)})`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const optimizedRequest = {
|
|
136
|
+
...request,
|
|
137
|
+
messages: result.optimized_messages,
|
|
138
|
+
plexor_cwd: process.cwd(),
|
|
139
|
+
_plexor: {
|
|
140
|
+
request_id: result.request_id,
|
|
141
|
+
original_tokens: result.original_tokens,
|
|
142
|
+
optimized_tokens: result.optimized_tokens,
|
|
143
|
+
tokens_saved: result.tokens_saved,
|
|
144
|
+
savings_percent: parseFloat(savingsPercent),
|
|
145
|
+
recommended_provider: result.recommended_provider,
|
|
146
|
+
recommended_model: result.recommended_model,
|
|
147
|
+
estimated_cost: result.estimated_cost,
|
|
148
|
+
baseline_cost: result.baseline_cost,
|
|
149
|
+
latency_ms: Date.now() - startTime,
|
|
150
|
+
source: 'plexor_api',
|
|
151
|
+
cwd: process.cwd()
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
await cache.setMetadata(result.request_id, {
|
|
156
|
+
original_tokens: result.original_tokens,
|
|
157
|
+
optimized_tokens: result.optimized_tokens,
|
|
158
|
+
recommended_provider: result.recommended_provider,
|
|
159
|
+
timestamp: Date.now()
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return output(optimizedRequest);
|
|
163
|
+
|
|
164
|
+
} catch (error) {
|
|
165
|
+
logger.error(`[Plexor] Error: ${error.message}`);
|
|
166
|
+
logger.debug(error.stack);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const input = await readStdin();
|
|
170
|
+
const request = JSON.parse(input);
|
|
171
|
+
return output({
|
|
172
|
+
...request,
|
|
173
|
+
_plexor: {
|
|
174
|
+
error: error.message,
|
|
175
|
+
source: 'passthrough'
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
} catch {
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function readStdin() {
|
|
185
|
+
return new Promise((resolve, reject) => {
|
|
186
|
+
const chunks = [];
|
|
187
|
+
|
|
188
|
+
process.stdin.on('data', (chunk) => {
|
|
189
|
+
chunks.push(chunk);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
process.stdin.on('end', () => {
|
|
193
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
process.stdin.on('error', reject);
|
|
197
|
+
|
|
198
|
+
setTimeout(() => {
|
|
199
|
+
reject(new Error('Stdin read timeout'));
|
|
200
|
+
}, 5000);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function output(data) {
|
|
205
|
+
const json = JSON.stringify(data);
|
|
206
|
+
process.stdout.write(json);
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function extractMessages(request) {
|
|
211
|
+
if (Array.isArray(request.messages)) {
|
|
212
|
+
return request.messages;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (request.prompt) {
|
|
216
|
+
return [{ role: 'user', content: request.prompt }];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (request.system && request.user) {
|
|
220
|
+
return [
|
|
221
|
+
{ role: 'system', content: request.system },
|
|
222
|
+
{ role: 'user', content: request.user }
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function detectTaskType(messages) {
|
|
230
|
+
if (!messages || messages.length === 0) {
|
|
231
|
+
return 'general';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const lastUserMessage = [...messages]
|
|
235
|
+
.reverse()
|
|
236
|
+
.find(m => m.role === 'user');
|
|
237
|
+
|
|
238
|
+
if (!lastUserMessage) {
|
|
239
|
+
return 'general';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const content = lastUserMessage.content.toLowerCase();
|
|
243
|
+
|
|
244
|
+
if (/```|function|class|import|export|const |let |var |def |async |await/.test(content)) {
|
|
245
|
+
return 'code_generation';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (/test|spec|jest|pytest|unittest|describe\(|it\(|expect\(/.test(content)) {
|
|
249
|
+
return 'test_generation';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (/fix|bug|error|issue|debug|trace|exception|crash/.test(content)) {
|
|
253
|
+
return 'debugging';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (/refactor|improve|optimize|clean|restructure/.test(content)) {
|
|
257
|
+
return 'refactoring';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (/document|readme|comment|explain|docstring/.test(content)) {
|
|
261
|
+
return 'documentation';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (/review|check|audit|assess|evaluate/.test(content)) {
|
|
265
|
+
return 'code_review';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (/analyze|understand|what does|how does|explain/.test(content)) {
|
|
269
|
+
return 'analysis';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return 'general';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Detect if this is an agentic/tool-using request that should not be optimized.
|
|
277
|
+
* Modifying messages in agent loops breaks the loop detection and causes infinite loops.
|
|
278
|
+
*/
|
|
279
|
+
function isAgenticRequest(request) {
|
|
280
|
+
// Check if request has tools defined
|
|
281
|
+
if (request.tools && request.tools.length > 0) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Check if any message contains tool use or tool results
|
|
286
|
+
const messages = request.messages || [];
|
|
287
|
+
for (const msg of messages) {
|
|
288
|
+
// Tool use in content (Claude format)
|
|
289
|
+
if (msg.content && Array.isArray(msg.content)) {
|
|
290
|
+
for (const block of msg.content) {
|
|
291
|
+
if (block.type === 'tool_use' || block.type === 'tool_result') {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Tool role (OpenAI format)
|
|
298
|
+
if (msg.role === 'tool') {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Function call (OpenAI format)
|
|
303
|
+
if (msg.function_call || msg.tool_calls) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Check for assistant messages with tool indicators
|
|
309
|
+
for (const msg of messages) {
|
|
310
|
+
if (msg.role === 'assistant' && msg.content) {
|
|
311
|
+
const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
|
|
312
|
+
// Detect common tool use patterns in Claude Code
|
|
313
|
+
if (/\[Bash\]|\[Read\]|\[Write\]|\[Edit\]|\[Glob\]|\[Grep\]/.test(content)) {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Check for multi-turn conversations (likely agentic)
|
|
320
|
+
const assistantMessages = messages.filter(m => m.role === 'assistant');
|
|
321
|
+
if (assistantMessages.length > 2) {
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Detect if this is a slash command request that should not be optimized.
|
|
330
|
+
* Slash commands like /plexor-status need to pass through unchanged.
|
|
331
|
+
*/
|
|
332
|
+
function isSlashCommand(request) {
|
|
333
|
+
const messages = request.messages || [];
|
|
334
|
+
|
|
335
|
+
// Check the last user message for slash command patterns
|
|
336
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
337
|
+
const msg = messages[i];
|
|
338
|
+
if (msg.role === 'user') {
|
|
339
|
+
const content = typeof msg.content === 'string' ? msg.content : '';
|
|
340
|
+
// Detect slash commands at the start of user message
|
|
341
|
+
if (/^\/[a-z-]+/i.test(content.trim())) {
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
// Detect <command-name> tags (Claude Code skill invocation)
|
|
345
|
+
if (/<command-name>/.test(content)) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
// Detect plexor-related commands
|
|
349
|
+
if (/plexor-(?:status|login|logout|mode|provider|enabled|settings)/i.test(content)) {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
break; // Only check last user message
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check for system messages with skill instructions
|
|
357
|
+
for (const msg of messages) {
|
|
358
|
+
if (msg.role === 'system') {
|
|
359
|
+
const content = typeof msg.content === 'string' ? msg.content : '';
|
|
360
|
+
if (/# Plexor (?:Status|Login|Logout|Mode|Provider|Enabled|Settings)/i.test(content)) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Detect if this request involves CLI/shell commands that need tool execution.
|
|
371
|
+
* These should pass through to ensure proper tool calling behavior.
|
|
372
|
+
*/
|
|
373
|
+
function requiresToolExecution(request) {
|
|
374
|
+
const messages = request.messages || [];
|
|
375
|
+
|
|
376
|
+
// Check user messages for CLI command patterns
|
|
377
|
+
for (const msg of messages) {
|
|
378
|
+
if (msg.role === 'user') {
|
|
379
|
+
const content = typeof msg.content === 'string' ? msg.content : '';
|
|
380
|
+
const contentLower = content.toLowerCase();
|
|
381
|
+
|
|
382
|
+
// Azure CLI patterns
|
|
383
|
+
if (/\baz\s+(login|logout|group|account|vm|storage|webapp|aks|acr|keyvault|sql|cosmos|network)/i.test(content)) {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Common CLI execution requests
|
|
388
|
+
if (/\b(run|execute|show|list|create|delete|update)\b.*\b(az|aws|gcloud|kubectl|docker|npm|git)\b/i.test(content)) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Direct command patterns
|
|
393
|
+
if (/^(az|aws|gcloud|kubectl|docker|npm|yarn|pip|cargo|go)\s+/m.test(content)) {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Imperative CLI requests
|
|
398
|
+
if (/\b(list|show|get|describe)\s+(resource\s*groups?|rgs?|vms?|instances?|clusters?|pods?|containers?)/i.test(content)) {
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
main().catch((error) => {
|
|
408
|
+
console.error(`[Plexor] Fatal error: ${error.message}`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
});
|