@relayplane/proxy 0.1.9 → 0.2.0
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/README.md +113 -247
- package/__tests__/server.test.ts +512 -0
- package/__tests__/telemetry.test.ts +126 -0
- package/dist/cli.d.ts +35 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +262 -3024
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +80 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +208 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +25 -1130
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -3005
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +209 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1089 -0
- package/dist/server.js.map +1 -0
- package/dist/streaming.d.ts +80 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +271 -0
- package/dist/streaming.js.map +1 -0
- package/dist/telemetry.d.ts +111 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +315 -0
- package/dist/telemetry.js.map +1 -0
- package/package.json +21 -46
- package/src/cli.ts +341 -0
- package/src/config.ts +206 -0
- package/src/index.ts +82 -0
- package/src/server.ts +1328 -0
- package/src/streaming.ts +331 -0
- package/src/telemetry.ts +343 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +21 -0
- package/LICENSE +0 -21
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -3043
- package/dist/cli.mjs.map +0 -1
- package/dist/index.d.mts +0 -1141
- package/dist/index.mjs +0 -2948
- package/dist/index.mjs.map +0 -1
package/src/cli.ts
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* RelayPlane Proxy CLI
|
|
4
|
+
*
|
|
5
|
+
* Intelligent AI model routing proxy server.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx @relayplane/proxy [command] [options]
|
|
9
|
+
* relayplane-proxy [command] [options]
|
|
10
|
+
*
|
|
11
|
+
* Commands:
|
|
12
|
+
* (default) Start the proxy server
|
|
13
|
+
* telemetry [on|off|status] Manage telemetry settings
|
|
14
|
+
* stats Show usage statistics
|
|
15
|
+
* config Show configuration
|
|
16
|
+
*
|
|
17
|
+
* Options:
|
|
18
|
+
* --port <number> Port to listen on (default: 3001)
|
|
19
|
+
* --host <string> Host to bind to (default: 127.0.0.1)
|
|
20
|
+
* --offline Disable all network calls except LLM endpoints
|
|
21
|
+
* --audit Show telemetry payloads before sending
|
|
22
|
+
* -v, --verbose Enable verbose logging
|
|
23
|
+
* -h, --help Show this help message
|
|
24
|
+
* --version Show version
|
|
25
|
+
*
|
|
26
|
+
* Environment Variables:
|
|
27
|
+
* ANTHROPIC_API_KEY Anthropic API key
|
|
28
|
+
* OPENAI_API_KEY OpenAI API key
|
|
29
|
+
* GEMINI_API_KEY Google Gemini API key
|
|
30
|
+
* XAI_API_KEY xAI/Grok API key
|
|
31
|
+
* MOONSHOT_API_KEY Moonshot API key
|
|
32
|
+
*
|
|
33
|
+
* @packageDocumentation
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { startProxy } from '@relayplane/openclaw';
|
|
37
|
+
import {
|
|
38
|
+
loadConfig,
|
|
39
|
+
isFirstRun,
|
|
40
|
+
markFirstRunComplete,
|
|
41
|
+
isTelemetryEnabled,
|
|
42
|
+
enableTelemetry,
|
|
43
|
+
disableTelemetry,
|
|
44
|
+
getConfigPath,
|
|
45
|
+
setApiKey,
|
|
46
|
+
} from './config.js';
|
|
47
|
+
import {
|
|
48
|
+
printTelemetryDisclosure,
|
|
49
|
+
setAuditMode,
|
|
50
|
+
setOfflineMode,
|
|
51
|
+
getTelemetryStats,
|
|
52
|
+
getTelemetryPath,
|
|
53
|
+
} from './telemetry.js';
|
|
54
|
+
|
|
55
|
+
const VERSION = '0.2.0';
|
|
56
|
+
|
|
57
|
+
function printHelp(): void {
|
|
58
|
+
console.log(`
|
|
59
|
+
RelayPlane Proxy - Intelligent AI Model Routing
|
|
60
|
+
|
|
61
|
+
Usage:
|
|
62
|
+
npx @relayplane/proxy [command] [options]
|
|
63
|
+
relayplane-proxy [command] [options]
|
|
64
|
+
|
|
65
|
+
Commands:
|
|
66
|
+
(default) Start the proxy server
|
|
67
|
+
telemetry [on|off|status] Manage telemetry settings
|
|
68
|
+
stats Show usage statistics
|
|
69
|
+
config Show configuration
|
|
70
|
+
|
|
71
|
+
Options:
|
|
72
|
+
--port <number> Port to listen on (default: 3001)
|
|
73
|
+
--host <string> Host to bind to (default: 127.0.0.1)
|
|
74
|
+
--offline Disable all network calls except LLM endpoints
|
|
75
|
+
--audit Show telemetry payloads before sending
|
|
76
|
+
-v, --verbose Enable verbose logging
|
|
77
|
+
-h, --help Show this help message
|
|
78
|
+
--version Show version
|
|
79
|
+
|
|
80
|
+
Environment Variables:
|
|
81
|
+
ANTHROPIC_API_KEY Anthropic API key
|
|
82
|
+
OPENAI_API_KEY OpenAI API key
|
|
83
|
+
GEMINI_API_KEY Google Gemini API key (optional)
|
|
84
|
+
XAI_API_KEY xAI/Grok API key (optional)
|
|
85
|
+
MOONSHOT_API_KEY Moonshot API key (optional)
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
# Start proxy on default port
|
|
89
|
+
npx @relayplane/proxy
|
|
90
|
+
|
|
91
|
+
# Start with audit mode (see telemetry before it's sent)
|
|
92
|
+
npx @relayplane/proxy --audit
|
|
93
|
+
|
|
94
|
+
# Start in offline mode (no telemetry transmission)
|
|
95
|
+
npx @relayplane/proxy --offline
|
|
96
|
+
|
|
97
|
+
# Disable telemetry completely
|
|
98
|
+
npx @relayplane/proxy telemetry off
|
|
99
|
+
|
|
100
|
+
# Then point your SDKs to the proxy
|
|
101
|
+
export ANTHROPIC_BASE_URL=http://localhost:3001
|
|
102
|
+
export OPENAI_BASE_URL=http://localhost:3001
|
|
103
|
+
|
|
104
|
+
Learn more: https://relayplane.com/docs
|
|
105
|
+
`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function printVersion(): void {
|
|
109
|
+
console.log(`RelayPlane Proxy v${VERSION}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function handleTelemetryCommand(args: string[]): void {
|
|
113
|
+
const subcommand = args[0];
|
|
114
|
+
|
|
115
|
+
switch (subcommand) {
|
|
116
|
+
case 'on':
|
|
117
|
+
enableTelemetry();
|
|
118
|
+
console.log('✅ Telemetry enabled');
|
|
119
|
+
console.log(' Anonymous usage data will be collected to improve routing.');
|
|
120
|
+
console.log(' Run with --audit to see exactly what\'s collected.');
|
|
121
|
+
break;
|
|
122
|
+
|
|
123
|
+
case 'off':
|
|
124
|
+
disableTelemetry();
|
|
125
|
+
console.log('✅ Telemetry disabled');
|
|
126
|
+
console.log(' No usage data will be collected.');
|
|
127
|
+
console.log(' The proxy will continue to work normally.');
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'status':
|
|
131
|
+
default:
|
|
132
|
+
const enabled = isTelemetryEnabled();
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log('📊 Telemetry Status');
|
|
135
|
+
console.log('───────────────────');
|
|
136
|
+
console.log(` Enabled: ${enabled ? '✅ Yes' : '❌ No'}`);
|
|
137
|
+
console.log(` Data file: ${getTelemetryPath()}`);
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log(' To enable: relayplane-proxy telemetry on');
|
|
140
|
+
console.log(' To disable: relayplane-proxy telemetry off');
|
|
141
|
+
console.log(' To audit: relayplane-proxy --audit');
|
|
142
|
+
console.log('');
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function handleStatsCommand(): void {
|
|
148
|
+
const stats = getTelemetryStats();
|
|
149
|
+
|
|
150
|
+
console.log('');
|
|
151
|
+
console.log('📊 Usage Statistics');
|
|
152
|
+
console.log('═══════════════════');
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log(` Total requests: ${stats.totalEvents}`);
|
|
155
|
+
console.log(` Total cost: $${stats.totalCost.toFixed(2)}`);
|
|
156
|
+
console.log(` Success rate: ${(stats.successRate * 100).toFixed(1)}%`);
|
|
157
|
+
console.log('');
|
|
158
|
+
|
|
159
|
+
if (Object.keys(stats.byModel).length > 0) {
|
|
160
|
+
console.log(' By Model:');
|
|
161
|
+
for (const [model, data] of Object.entries(stats.byModel)) {
|
|
162
|
+
console.log(` ${model}: ${data.count} requests, $${data.cost.toFixed(2)}`);
|
|
163
|
+
}
|
|
164
|
+
console.log('');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (Object.keys(stats.byTaskType).length > 0) {
|
|
168
|
+
console.log(' By Task Type:');
|
|
169
|
+
for (const [taskType, data] of Object.entries(stats.byTaskType)) {
|
|
170
|
+
console.log(` ${taskType}: ${data.count} requests, $${data.cost.toFixed(2)}`);
|
|
171
|
+
}
|
|
172
|
+
console.log('');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (stats.totalEvents === 0) {
|
|
176
|
+
console.log(' No data yet. Start using the proxy to collect statistics.');
|
|
177
|
+
console.log('');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function handleConfigCommand(args: string[]): void {
|
|
182
|
+
const subcommand = args[0];
|
|
183
|
+
|
|
184
|
+
if (subcommand === 'set-key' && args[1]) {
|
|
185
|
+
setApiKey(args[1]);
|
|
186
|
+
console.log('✅ API key saved');
|
|
187
|
+
console.log(' Pro features will be enabled on next proxy start.');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const config = loadConfig();
|
|
192
|
+
|
|
193
|
+
console.log('');
|
|
194
|
+
console.log('⚙️ Configuration');
|
|
195
|
+
console.log('═════════════════');
|
|
196
|
+
console.log('');
|
|
197
|
+
console.log(` Config file: ${getConfigPath()}`);
|
|
198
|
+
console.log(` Device ID: ${config.device_id}`);
|
|
199
|
+
console.log(` Telemetry: ${config.telemetry_enabled ? '✅ Enabled' : '❌ Disabled'}`);
|
|
200
|
+
console.log(` API Key: ${config.api_key ? '••••' + config.api_key.slice(-4) : 'Not set'}`);
|
|
201
|
+
console.log(` Created: ${config.created_at}`);
|
|
202
|
+
console.log('');
|
|
203
|
+
console.log(' To set API key: relayplane-proxy config set-key <your-key>');
|
|
204
|
+
console.log('');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function main(): Promise<void> {
|
|
208
|
+
const args = process.argv.slice(2);
|
|
209
|
+
|
|
210
|
+
// Check for help
|
|
211
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
212
|
+
printHelp();
|
|
213
|
+
process.exit(0);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Check for version
|
|
217
|
+
if (args.includes('--version')) {
|
|
218
|
+
printVersion();
|
|
219
|
+
process.exit(0);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Handle commands
|
|
223
|
+
const command = args[0];
|
|
224
|
+
|
|
225
|
+
if (command === 'telemetry') {
|
|
226
|
+
handleTelemetryCommand(args.slice(1));
|
|
227
|
+
process.exit(0);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (command === 'stats') {
|
|
231
|
+
handleStatsCommand();
|
|
232
|
+
process.exit(0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (command === 'config') {
|
|
236
|
+
handleConfigCommand(args.slice(1));
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Parse server options
|
|
241
|
+
let port = 3001;
|
|
242
|
+
let host = '127.0.0.1';
|
|
243
|
+
let verbose = false;
|
|
244
|
+
let audit = false;
|
|
245
|
+
let offline = false;
|
|
246
|
+
|
|
247
|
+
for (let i = 0; i < args.length; i++) {
|
|
248
|
+
const arg = args[i];
|
|
249
|
+
|
|
250
|
+
if (arg === '--port' && args[i + 1]) {
|
|
251
|
+
port = parseInt(args[i + 1]!, 10);
|
|
252
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
253
|
+
console.error('Error: Invalid port number');
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
i++;
|
|
257
|
+
} else if (arg === '--host' && args[i + 1]) {
|
|
258
|
+
host = args[i + 1]!;
|
|
259
|
+
i++;
|
|
260
|
+
} else if (arg === '-v' || arg === '--verbose') {
|
|
261
|
+
verbose = true;
|
|
262
|
+
} else if (arg === '--audit') {
|
|
263
|
+
audit = true;
|
|
264
|
+
} else if (arg === '--offline') {
|
|
265
|
+
offline = true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Set modes
|
|
270
|
+
setAuditMode(audit);
|
|
271
|
+
setOfflineMode(offline);
|
|
272
|
+
|
|
273
|
+
// First run disclosure
|
|
274
|
+
if (isFirstRun()) {
|
|
275
|
+
printTelemetryDisclosure();
|
|
276
|
+
markFirstRunComplete();
|
|
277
|
+
|
|
278
|
+
// Wait for user to read (brief pause)
|
|
279
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Check for at least one API key
|
|
283
|
+
const hasAnthropicKey = !!process.env['ANTHROPIC_API_KEY'];
|
|
284
|
+
const hasOpenAIKey = !!process.env['OPENAI_API_KEY'];
|
|
285
|
+
const hasGeminiKey = !!process.env['GEMINI_API_KEY'];
|
|
286
|
+
const hasXAIKey = !!process.env['XAI_API_KEY'];
|
|
287
|
+
const hasMoonshotKey = !!process.env['MOONSHOT_API_KEY'];
|
|
288
|
+
|
|
289
|
+
if (!hasAnthropicKey && !hasOpenAIKey && !hasGeminiKey && !hasXAIKey && !hasMoonshotKey) {
|
|
290
|
+
console.error('Error: No API keys found. Set at least one of:');
|
|
291
|
+
console.error(' ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, XAI_API_KEY, MOONSHOT_API_KEY');
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Print startup info
|
|
296
|
+
console.log('');
|
|
297
|
+
console.log(' ╭─────────────────────────────────────────╮');
|
|
298
|
+
console.log(` │ RelayPlane Proxy v${VERSION} │`);
|
|
299
|
+
console.log(' │ Intelligent AI Model Routing │');
|
|
300
|
+
console.log(' ╰─────────────────────────────────────────╯');
|
|
301
|
+
console.log('');
|
|
302
|
+
|
|
303
|
+
// Show modes
|
|
304
|
+
const telemetryEnabled = isTelemetryEnabled();
|
|
305
|
+
console.log(' Mode:');
|
|
306
|
+
if (offline) {
|
|
307
|
+
console.log(' 🔒 Offline (no telemetry transmission)');
|
|
308
|
+
} else if (audit) {
|
|
309
|
+
console.log(' 🔍 Audit (showing telemetry payloads)');
|
|
310
|
+
} else if (telemetryEnabled) {
|
|
311
|
+
console.log(' 📊 Telemetry enabled (--audit to inspect, telemetry off to disable)');
|
|
312
|
+
} else {
|
|
313
|
+
console.log(' 📴 Telemetry disabled');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
console.log('');
|
|
317
|
+
console.log(' Providers:');
|
|
318
|
+
if (hasAnthropicKey) console.log(' ✓ Anthropic');
|
|
319
|
+
if (hasOpenAIKey) console.log(' ✓ OpenAI');
|
|
320
|
+
if (hasGeminiKey) console.log(' ✓ Google Gemini');
|
|
321
|
+
if (hasXAIKey) console.log(' ✓ xAI (Grok)');
|
|
322
|
+
if (hasMoonshotKey) console.log(' ✓ Moonshot');
|
|
323
|
+
console.log('');
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
await startProxy({ port, host, verbose });
|
|
327
|
+
|
|
328
|
+
console.log('');
|
|
329
|
+
console.log(' To use, set these environment variables:');
|
|
330
|
+
console.log(` export ANTHROPIC_BASE_URL=http://${host}:${port}`);
|
|
331
|
+
console.log(` export OPENAI_BASE_URL=http://${host}:${port}`);
|
|
332
|
+
console.log('');
|
|
333
|
+
console.log(' Then run your agent (OpenClaw, Cursor, Aider, etc.)');
|
|
334
|
+
console.log('');
|
|
335
|
+
} catch (err) {
|
|
336
|
+
console.error('Failed to start proxy:', err);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
main();
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RelayPlane Proxy Configuration
|
|
3
|
+
*
|
|
4
|
+
* Handles configuration persistence, telemetry settings, and device identity.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import * as os from 'os';
|
|
12
|
+
import * as crypto from 'crypto';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Configuration schema for RelayPlane proxy
|
|
16
|
+
*/
|
|
17
|
+
export interface ProxyConfig {
|
|
18
|
+
/** Anonymous device ID (generated on first run) */
|
|
19
|
+
device_id: string;
|
|
20
|
+
|
|
21
|
+
/** Telemetry enabled state */
|
|
22
|
+
telemetry_enabled: boolean;
|
|
23
|
+
|
|
24
|
+
/** Whether first run disclosure has been shown */
|
|
25
|
+
first_run_complete: boolean;
|
|
26
|
+
|
|
27
|
+
/** RelayPlane API key (for Pro features) */
|
|
28
|
+
api_key?: string;
|
|
29
|
+
|
|
30
|
+
/** Schema version for migrations */
|
|
31
|
+
config_version: number;
|
|
32
|
+
|
|
33
|
+
/** Timestamp of config creation */
|
|
34
|
+
created_at: string;
|
|
35
|
+
|
|
36
|
+
/** Timestamp of last update */
|
|
37
|
+
updated_at: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const CONFIG_VERSION = 1;
|
|
41
|
+
const CONFIG_DIR = path.join(os.homedir(), '.relayplane');
|
|
42
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generate an anonymous device ID
|
|
46
|
+
* Uses a random hash that cannot be traced back to the device
|
|
47
|
+
*/
|
|
48
|
+
function generateDeviceId(): string {
|
|
49
|
+
const randomBytes = crypto.randomBytes(16);
|
|
50
|
+
const hash = crypto.createHash('sha256').update(randomBytes).digest('hex');
|
|
51
|
+
return `anon_${hash.slice(0, 16)}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Ensure config directory exists
|
|
56
|
+
*/
|
|
57
|
+
function ensureConfigDir(): void {
|
|
58
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
59
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create default configuration
|
|
65
|
+
*/
|
|
66
|
+
function createDefaultConfig(): ProxyConfig {
|
|
67
|
+
const now = new Date().toISOString();
|
|
68
|
+
return {
|
|
69
|
+
device_id: generateDeviceId(),
|
|
70
|
+
telemetry_enabled: true, // On by default, opt-out available
|
|
71
|
+
first_run_complete: false,
|
|
72
|
+
config_version: CONFIG_VERSION,
|
|
73
|
+
created_at: now,
|
|
74
|
+
updated_at: now,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Load configuration from disk
|
|
80
|
+
* Creates default config if none exists
|
|
81
|
+
*/
|
|
82
|
+
export function loadConfig(): ProxyConfig {
|
|
83
|
+
ensureConfigDir();
|
|
84
|
+
|
|
85
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
86
|
+
const config = createDefaultConfig();
|
|
87
|
+
saveConfig(config);
|
|
88
|
+
return config;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const data = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
93
|
+
const config = JSON.parse(data) as ProxyConfig;
|
|
94
|
+
|
|
95
|
+
// Ensure required fields exist (for migrations)
|
|
96
|
+
if (!config.device_id) {
|
|
97
|
+
config.device_id = generateDeviceId();
|
|
98
|
+
}
|
|
99
|
+
if (config.telemetry_enabled === undefined) {
|
|
100
|
+
config.telemetry_enabled = true;
|
|
101
|
+
}
|
|
102
|
+
if (!config.config_version) {
|
|
103
|
+
config.config_version = CONFIG_VERSION;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return config;
|
|
107
|
+
} catch (err) {
|
|
108
|
+
// If config is corrupted, create new one
|
|
109
|
+
const config = createDefaultConfig();
|
|
110
|
+
saveConfig(config);
|
|
111
|
+
return config;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Save configuration to disk
|
|
117
|
+
*/
|
|
118
|
+
export function saveConfig(config: ProxyConfig): void {
|
|
119
|
+
ensureConfigDir();
|
|
120
|
+
config.updated_at = new Date().toISOString();
|
|
121
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Update specific config fields
|
|
126
|
+
*/
|
|
127
|
+
export function updateConfig(updates: Partial<ProxyConfig>): ProxyConfig {
|
|
128
|
+
const config = loadConfig();
|
|
129
|
+
Object.assign(config, updates);
|
|
130
|
+
saveConfig(config);
|
|
131
|
+
return config;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if this is the first run (disclosure not shown yet)
|
|
136
|
+
*/
|
|
137
|
+
export function isFirstRun(): boolean {
|
|
138
|
+
const config = loadConfig();
|
|
139
|
+
return !config.first_run_complete;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Mark first run as complete
|
|
144
|
+
*/
|
|
145
|
+
export function markFirstRunComplete(): void {
|
|
146
|
+
updateConfig({ first_run_complete: true });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check if telemetry is enabled
|
|
151
|
+
*/
|
|
152
|
+
export function isTelemetryEnabled(): boolean {
|
|
153
|
+
const config = loadConfig();
|
|
154
|
+
return config.telemetry_enabled;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Enable telemetry
|
|
159
|
+
*/
|
|
160
|
+
export function enableTelemetry(): void {
|
|
161
|
+
updateConfig({ telemetry_enabled: true });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Disable telemetry
|
|
166
|
+
*/
|
|
167
|
+
export function disableTelemetry(): void {
|
|
168
|
+
updateConfig({ telemetry_enabled: false });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get device ID for telemetry
|
|
173
|
+
*/
|
|
174
|
+
export function getDeviceId(): string {
|
|
175
|
+
const config = loadConfig();
|
|
176
|
+
return config.device_id;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Set API key for Pro features
|
|
181
|
+
*/
|
|
182
|
+
export function setApiKey(key: string): void {
|
|
183
|
+
updateConfig({ api_key: key });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get API key
|
|
188
|
+
*/
|
|
189
|
+
export function getApiKey(): string | undefined {
|
|
190
|
+
const config = loadConfig();
|
|
191
|
+
return config.api_key;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get config directory path
|
|
196
|
+
*/
|
|
197
|
+
export function getConfigDir(): string {
|
|
198
|
+
return CONFIG_DIR;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get config file path
|
|
203
|
+
*/
|
|
204
|
+
export function getConfigPath(): string {
|
|
205
|
+
return CONFIG_FILE;
|
|
206
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @relayplane/proxy
|
|
3
|
+
*
|
|
4
|
+
* RelayPlane Agent Ops Proxy Server
|
|
5
|
+
*
|
|
6
|
+
* Intelligent AI model routing with integrated observability via
|
|
7
|
+
* the Learning Ledger and auth enforcement via Auth Gate.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createProxyServer } from '@relayplane/proxy';
|
|
12
|
+
*
|
|
13
|
+
* const server = createProxyServer({
|
|
14
|
+
* port: 3001,
|
|
15
|
+
* providers: {
|
|
16
|
+
* anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! },
|
|
17
|
+
* openai: { apiKey: process.env.OPENAI_API_KEY! },
|
|
18
|
+
* },
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* await server.start();
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
// New Agent Ops proxy server (Phase 1)
|
|
28
|
+
export { ProxyServer, createProxyServer } from './server.js';
|
|
29
|
+
export type { ProxyServerConfig } from './server.js';
|
|
30
|
+
|
|
31
|
+
// Streaming support (Phase 8)
|
|
32
|
+
export {
|
|
33
|
+
SSEWriter,
|
|
34
|
+
createSSEWriter,
|
|
35
|
+
streamProviderResponse,
|
|
36
|
+
aggregateStreamingResponse,
|
|
37
|
+
startKeepAlive,
|
|
38
|
+
} from './streaming.js';
|
|
39
|
+
export type { SSEMessage } from './streaming.js';
|
|
40
|
+
|
|
41
|
+
// Legacy proxy functionality from openclaw
|
|
42
|
+
export { startProxy, type ProxyConfig } from '@relayplane/openclaw';
|
|
43
|
+
|
|
44
|
+
// Configuration
|
|
45
|
+
export {
|
|
46
|
+
loadConfig,
|
|
47
|
+
saveConfig,
|
|
48
|
+
updateConfig,
|
|
49
|
+
isFirstRun,
|
|
50
|
+
markFirstRunComplete,
|
|
51
|
+
isTelemetryEnabled,
|
|
52
|
+
enableTelemetry,
|
|
53
|
+
disableTelemetry,
|
|
54
|
+
getDeviceId,
|
|
55
|
+
setApiKey,
|
|
56
|
+
getApiKey,
|
|
57
|
+
getConfigDir,
|
|
58
|
+
getConfigPath,
|
|
59
|
+
} from './config.js';
|
|
60
|
+
export type { ProxyConfig as ProxyLocalConfig } from './config.js';
|
|
61
|
+
|
|
62
|
+
// Telemetry
|
|
63
|
+
export {
|
|
64
|
+
recordTelemetry,
|
|
65
|
+
inferTaskType,
|
|
66
|
+
estimateCost,
|
|
67
|
+
setAuditMode,
|
|
68
|
+
isAuditMode,
|
|
69
|
+
setOfflineMode,
|
|
70
|
+
isOfflineMode,
|
|
71
|
+
getAuditBuffer,
|
|
72
|
+
clearAuditBuffer,
|
|
73
|
+
getLocalTelemetry,
|
|
74
|
+
getTelemetryStats,
|
|
75
|
+
clearTelemetry,
|
|
76
|
+
getTelemetryPath,
|
|
77
|
+
printTelemetryDisclosure,
|
|
78
|
+
} from './telemetry.js';
|
|
79
|
+
export type { TelemetryEvent } from './telemetry.js';
|
|
80
|
+
|
|
81
|
+
// Re-export core types
|
|
82
|
+
export type { Provider, TaskType } from '@relayplane/core';
|